wepack的模块化原理及配置方法

我们都知道,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;第二个参数exportsmodule.exports的引用,这也符合commonjs的规范;第三个__webpack_require__ 则是require的实现。

在我们的模块中,就可以对外使用module.exportsexports进行导出,使用__webpack_require__导入需要的模块,代码跟commonjs完全一样。

这样,就完成了对第一个模块的require,然后第一个模块会根据自己对其他模块的require,依次加载其他模块,最终形成一个依赖网状结构。webpack管理着这些模块的缓存,如果一个模块被require多次,那么只会有一次加载过程,而返回的是缓存的内容,这也是commonjs的规范。

 

这样就完成了webpack hack commonjs的过程。

posted @ 2018-01-28 16:46  cheer4chai  阅读(697)  评论(0编辑  收藏  举报