我们都知道,webpack的特点之一就在于其的模块化,将各个文件都使用loader功能转换为js文件,并将其模块化,那么其模块化的原理是什么呢?
首先我们需要了解CommonJS规范,以及AMD、CMD、UMD规范都是什么及其原理。
首先,CommonJS是在服务器端用于模块化的规范,因为是同步的,所以只能在服务器端实现,而浏览器端因为缺少node的四个字段:
- module
- exports
- require
- global
// foobar.js //私有变量 var test = 123; //公有方法 function foobar () { this.foo = function () { // do someing ... } this.bar = function () { //do someing ... } } //exports对象上的方法和变量是公有的 var foobar = new foobar(); exports.foobar = foobar; //require方法默认读取js文件,所以可以省略js后缀 var test = require(‘./boobar‘).foobar; test.bar();
因为没办法在浏览器使用这个规范,所有就用了AMD规范和CMD规范。
AMD规范是RequireJS 在推广过程中对模块定义的规范化产出的,异步使用回调来实现模块化:
通过数组引入依赖 ,回调函数通过形参传入依赖 define([‘someModule1‘, ‘someModule2’], function (someModule1, someModule2) { function foo () { /// someing someModule1.test(); } return {foo: foo} }); AMD规范允许输出模块兼容CommonJS规范,这时define方法如下: define(function (require, exports, module) { var reqModule = require("./someModule"); requModule.test(); exports.asplode = function () { //someing } });
而CMD是SeaJS 在推广过程中对模块定义的规范化产出的
CMD和AMD的区别有以下几点:
1.对于依赖的模块AMD是提前执行,CMD是延迟执行。不过RequireJS从2.0开始,也改成可以延迟执行。
2.CMD推崇依赖就近,AMD推崇依赖前置。
//AMD define([‘./a‘,‘./b‘], function (a, b) { //依赖一开始就写好 a.test(); b.test(); }); //CMD define(function (requie, exports, module) { //依赖可以就近书写 var a = require(‘./a‘); a.test(); ... //软依赖 if (status) { var b = requie(‘./b‘); b.test(); } });
那么我们接下来再看下webpack的模块化是如何去实现的:
当我将一个依赖于main.js的entry.js打包的时候,整理其打包后的文件可以看到其结构如下:
(function (modules) {/* 省略函数内容 */}) ([ function (module, exports, __webpack_require__) { /* 模块index.js的代码 */ }, function (module, exports, __webpack_require__) { /* 模块bar.js的代码 */ } ]);
其中函数内容:
// 1、模块缓存对象 var installedModules = {}; // 2、webpack实现的require function __webpack_require__(moduleId) { // 3、判断是否已缓存模块 if(installedModules[moduleId]) { return installedModules[moduleId].exports; } // 4、缓存模块 var module = installedModules[moduleId] = { i: moduleId, l: false, exports: {} }; // 5、调用模块函数 modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); // 6、标记模块为已加载 module.l = true; // 7、返回module.exports return module.exports; } // 8、require第一个模块 return __webpack_require__(__webpack_require__.s = 0);
我们可以看出:
1.首先webpack声明了一个installedModules对象,用于缓存模块
2.声明了__webpack_require__内部函数,用于实现require功能,首先检查缓存中是否存在要引用的模块,如有的话return缓存中的该模块,否则初始化该模块,再返回该模块
3.用完成后,模块标记为已加载。
require入口模块时,入口模块会收到收到三个参数,下面是入口模块代码:
function(module, exports, __webpack_require__) { "use strict"; var bar = __webpack_require__(1); bar(); }
webpack传入的第一个参数module
是当前缓存的模块,包含当前模块的信息和exports
;第二个参数exports
是module.exports
的引用,这也符合commonjs的规范;第三个__webpack_require__
则是require
的实现。
在我们的模块中,就可以对外使用module.exports
或exports
进行导出,使用__webpack_require__
导入需要的模块,代码跟commonjs完全一样。
这样,就完成了对第一个模块的require,然后第一个模块会根据自己对其他模块的require,依次加载其他模块,最终形成一个依赖网状结构。webpack管理着这些模块的缓存,如果一个模块被require多次,那么只会有一次加载过程,而返回的是缓存的内容,这也是commonjs的规范。
这样就完成了webpack hack commonjs的过程。