この記事では主に webpack 組織モジュールの原理を紹介し、参考にさせていただきます。
現在では、Webpack を使用してフロントエンドで JS やその他のファイルをパッケージ化することが主流になり、Node の普及と相まって、フロントエンドのエンジニアリング手法はますますバックエンドに似てきています。すべてはモジュール化され、最終的にはまとめてコンパイルされます。 Webpack のバージョンは常に更新され、さまざまな複雑な設定オプションがあるため、使用中に不可解なエラーが発生し、人々が混乱することがよくあります。したがって、Webpack がコンパイルされたモジュールをどのように編成し、生成されたコードがどのように実行されるかを理解することは非常に有益です。そうでないと、常にブラック ボックスになります。もちろん私はフロントエンドの初心者で、最近 Webpack の原理を勉強し始めたばかりなので、ここでいくつかメモしておきます。
コンパイルされたモジュール
「コンパイル」という言葉は非常にハイテクに聞こえ、生成されたコードは理解できないものが入り乱れていることが多いため、人々を怖がらせてしまうことがよくありますが、実際には、内部の中心原則は「コンパイルとは何か」ではありません。難しい。 Webpack のいわゆるコンパイルは、実際には、Webpack がソース コードを分析し、それに特定の変更を加えた後、すべてのソース コードを 1 つのファイルに編成するだけです。最後に、大きなバンドル JS ファイルが生成され、ブラウザまたは他の Javascript エンジンによって実行され、結果が返されます。
ここでは、Webpack パッケージ化モジュールの原理を説明するための簡単なケースを示します。例えば、mA.js
var aa = 1; function getDate() { return new Date(); } module.exports = { aa: aa, getDate: getDate }
というモジュールがあるので、変数aaと関数getDateを適当に定義して、CommonJSを使った書き方を紹介します。
次に、CommonJS スタイルのまま、app.js をメイン ファイルとして定義します。
var mA = require('./mA.js'); console.log('mA.aa =' + mA.aa); mA.getDate();
これで、Webpack を使用してパッケージ化された 2 つのモジュールができました。エントリ ファイルは app.js で、モジュール mA.js に依存します。 , Webpack やるべきことがいくつかあります:
エントリーモジュールapp.jsから開始し、すべてのモジュールの依存関係を分析し、使用されているすべてのモジュールを読み込みます。
各モジュールのソースコードは、すぐに実行される関数にまとめられます。
モジュールコード内のrequireとexportに関連する構文と、それらに対応する参照変数を書き換えます。
最終的に生成されたバンドル ファイル内にモジュール管理システムを確立します。これにより、使用されるモジュールを実行時に動的にロードできます。
Webpack パッケージ化の結果である上記の例を見てみましょう。最終的なバンドル ファイルは通常、すぐに実行される大きな関数であり、組織レベルは比較的複雑で、多くの名前が比較的わかりにくいため、ここでいくつかの書き直しと変更を加えて、できるだけシンプルで理解しやすいものにしました。可能。
まず、使用されているすべてのモジュールをリストし、そのファイル名 (通常はフルパス) を ID として使用してテーブルを作成します:
var modules = { './mA.js': generated_mA, './app.js': generated_app }
重要なのは、上記の generated_xxx とは何かということです。各モジュールのソースコードを内部にラップしてローカルスコープにし、内部変数が露出しないようにし、実際に各モジュールを実行関数にする関数です。その定義は一般に次のようになります:
function generated_module(module, exports, webpack_require) { // 模块的具体代码。 // ... }
ここでのモジュールの特定のコードは、Webpack が生成コードと呼ぶ、生成されたコードを指します。たとえば、mA を書き換えると、結果は次のようになります。
function generated_mA(module, exports, webpack_require) { var aa = 1; function getDate() { return new Date(); } module.exports = { aa: aa, getDate: getDate } }
一見すると、ソースコードとまったく同じように見えます。確かに、mA は他のモジュールを必要としたりインポートしたりする必要がなく、エクスポートでも従来の CommonJS スタイルを使用するため、生成されたコードに変更はありません。ただし、最後の module.exports = ... であることは注目に値します。ここでのモジュールは外部から渡されるパラメーター module であり、実際には、この関数が実行されるとモジュール mA のソース コードが実行されることがわかります。そして最後に、エクスポートする必要のあるコンテンツが外部に保存されます。これで mA のロードが完了しました。外部のものは、実際には後で説明するモジュール管理システムです。
次に、app.js の生成されたコードを見てみましょう:
function generated_app(module, exports, webpack_require) { var mA_imported_module = webpack_require('./mA.js'); console.log('mA.aa =' + mA_imported_module['aa']); mA_imported_module['getDate'](); }
インポートされたモジュール mA に関して app.js のソース コードが変更されていることがわかります。これは、それが require/exports であるか、ES6 スタイルのインポートであるかに関係なく、 /export は、JavaScript インタプリタによって直接実行することはできません。これらの抽象キーワードを具体化するには、モジュール管理システムに依存する必要があります。つまり、webpack_require は require の特定の実装であり、モジュール mA を動的にロードして結果をアプリに返すことができます。
この時点で、モジュール管理システムのアイデアが徐々に頭の中に構築されているかもしれません。webpack_require の実装を見てみましょう:
// 加载完毕的所有模块。 var installedModules = {}; function webpack_require(moduleId) { // 如果模块已经加载过了,直接从Cache中读取。 if (installedModules[moduleId]) { return installedModules[moduleId].exports; } // 创建新模块并添加到installedModules。 var module = installedModules[moduleId] = { id: moduleId, exports: {} }; // 加载模块,即运行模块的生成代码, modules[moduleId].call( module.exports, module, module.exports, webpack_require); return module.exports; }
最後から 2 番目の文のモジュールがすべてのモジュールに対して生成されることに注意してください。以前に定義したコード:
var modules = { './mA.js': generated_mA, './app.js': generated_app }
webpack_require のロジックは非常に明確に記述されています。まず、モジュールがロードされているかどうかを確認し、ロードされている場合は、モジュールのエクスポート結果をキャッシュから直接返します。新しいモジュールの場合は、対応するデータ構造モジュールを作成し、このモジュールの生成されたコードを実行します。この関数が渡すのは、作成したモジュール オブジェクトとそのエクスポート フィールドです。これは、実際には CommonJS のエクスポート フィールドとエクスポート フィールドです。 . モジュールの起源。この関数を実行すると、モジュールがロードされ、エクスポートする必要がある結果がモジュール オブジェクトに保存されます。
所以我们看到所谓的模块管理系统,原理其实非常简单,只要耐心将它们抽丝剥茧理清楚了,根本没有什么深奥的东西,就是由这三个部分组成:
// 所有模块的生成代码 var modules; // 所有已经加载的模块,作为缓存表 var installedModules; // 加载模块的函数 function webpack_require(moduleId);
当然以上一切代码,在整个编译后的bundle文件中,都被包在一个大的立即执行的匿名函数中,最后返回的就是这么一句话:
return webpack_require(‘./app.js');
即加载入口模块app.js,后面所有的依赖都会动态地、递归地在runtime加载。当然Webpack真正生成的代码略有不同,它在结构上大致是这样:
(function(modules) { var installedModules = {}; function webpack_require(moduleId) { // ... } return webpack_require('./app.js'); }) ({ './mA.js': generated_mA, './app.js': generated_app });
可以看到它是直接把modules作为立即执行函数的参数传进去的而不是另外定义的,当然这和上面的写法没什么本质不同,我做这样的改写是为了解释起来更清楚。
ES6的import和export
以上的例子里都是用传统的CommonJS的写法,现在更通用的ES6风格是用import和export关键词,在使用上也略有一些不同。不过对于Webpack或者其它模块管理系统而言,这些新特性应该只被视为语法糖,它们本质上还是和require/exports一样的,例如export:
export aa // 等价于: module.exports['aa'] = aa export default bb // 等价于: module.exports['default'] = bb
而对于import:
import {aa} from './mA.js' // 等价于 var aa = require('./mA.js')['aa']
比较特殊的是这样的:
import m from './m.js'
情况会稍微复杂一点,它需要载入模块m的default export,而模块m可能并非是由ES6的export来写的,也可能根本没有export default,所以Webpack在为模块生成generated code的时候,会判断它是不是ES6风格的export,例如我们定义模块mB.js:
let x = 3; let printX = () => { console.log('x = ' + x); } export {printX} export default x
它使用了ES6的export,那么Webpack在mB的generated code就会加上一句话:
function generated_mB(module, exports, webpack_require) { Object.defineProperty(module.exports, '__esModule', {value: true}); // mB的具体代码 // .... }
也就是说,它给mB的export标注了一个__esModule,说明它是ES6风格的export。这样在其它模块中,当一个依赖模块以类似import m from './m.js'这样的方式加载时,会首先判断得到的是不是一个ES6 export出来的模块。如果是,则返回它的default,如果不是,则返回整个export对象。例如上面的mA是传统CommonJS的,mB是ES6风格的:
// mA is CommonJS module import mA from './mA.js' console.log(mA); // mB is ES6 module import mB from './mB.js' console.log(mB);
我们定义get_export_default函数:
function get_export_default(module) { return module && module.__esModule? module['default'] : module; }
这样generated code运行后在mA和mB上会得到不同的结果:
var mA_imported_module = webpack_require('./mA.js'); // 打印完整的 mA_imported_module console.log(get_export_default(mA_imported_module)); var mB_imported_module = webpack_require('./mB.js'); // 打印 mB_imported_module['default'] console.log(get_export_default(mB_imported_module));
这就是在ES6的import上,Webpack需要做一些特殊处理的地方。不过总体而言,ES6的import/export在本质上和CommonJS没有区别,而且Webpack最后生成的generated code也还是基于CommonJS的module/exports这一套机制来实现模块的加载的。
模块管理系统
以上就是Webpack如何打包组织模块,实现runtime模块加载的解读,其实它的原理并不难,核心的思想就是建立模块的管理系统,而这样的做法也是具有普遍性的,如果你读过Node.js的Module部分的源代码,就会发现其实用的是类似的方法。这里有一篇文章可以参考。
上面是我整理给大家的,希望今后会对大家有帮助。
相关文章:
以上がWebpack モジュールの基本原理の詳細な説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。