我们都晓得,webpack作为一个构建东西,处理了前端代码缺乏模块化才能的题目。我们写的代码,经由webpack构建和包装以后,能够在浏览器以模块化的体式格局运转。这些才能,都是由于webpack对我们的代码举行了一层包装,本文就以webpack天生的代码入手,剖析webpack是怎样完成模块化的。
PS: webpack的模块不仅指js,包括css、图片等资本都能够以模块对待,但本文只关注js。
预备
起首我们建立一个简朴进口模块index.js和一个依靠模块bar.js:
//index.js
'use strict';
var bar = require('./bar');
function foo() {
return bar.bar();
}
//bar.js
'use strict';
exports.bar = function () {
return 1;
}
webpack设置以下:
var path = require("path");
module.exports = {
entry: path.join(__dirname, 'index.js'),
output: {
path: path.join(__dirname, 'outs'),
filename: 'index.js'
},
};
这是一个最简朴的设置,只指定了模块进口和输出途径,但已满足了我们的请求。
在根目录下实行webpack
,获得经由webpack打包的代码以下(去掉了不必要的解释):
(function(modules) { // webpackBootstrap
// The module cache
var installedModules = {};
// The require function
function __webpack_require__(moduleId) {
// Check if module is in cache
if(installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// Create a new module (and put it into the cache)
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
// Execute the module function
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// Flag the module as loaded
module.l = true;
// Return the exports of the module
return module.exports;
}
// expose the modules object (__webpack_modules__)
__webpack_require__.m = modules;
// expose the module cache
__webpack_require__.c = installedModules;
// define getter function for harmony exports
__webpack_require__.d = function(exports, name, getter) {
if(!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, {
configurable: false,
enumerable: true,
get: getter
});
}
};
// getDefaultExport function for compatibility with non-harmony modules
__webpack_require__.n = function(module) {
var getter = module && module.__esModule ?
function getDefault() { return module['default']; } :
function getModuleExports() { return module; };
__webpack_require__.d(getter, 'a', getter);
return getter;
};
// Object.prototype.hasOwnProperty.call
__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
// __webpack_public_path__
__webpack_require__.p = "";
// Load entry module and return exports
return __webpack_require__(__webpack_require__.s = 0);
})
/************************************************************************/
([
/* 0 */
(function(module, exports, __webpack_require__) {
"use strict";
var bar = __webpack_require__(1);
bar.bar();
}),
/* 1 */
(function(module, exports, __webpack_require__) {
"use strict";
exports.bar = function () {
return 1;
}
})
]);
剖析
上面webpack打包的代码,团体能够简化成下面的构造:
(function (modules) {/* 省略函数内容 */})
([
function (module, exports, __webpack_require__) {
/* 模块index.js的代码 */
},
function (module, exports, __webpack_require__) {
/* 模块bar.js的代码 */
}
]);
能够看到,全部打包天生的代码是一个IIFE(马上实行函数),函数内容我们待会看,我们先来剖析函数的参数。
函数参数是我们写的各个模块构成的数组,只不过我们的代码,被webpack包装在了一个函数的内部,也就是说我们的模块,在这里就是一个函数。为何要如许做,是由于浏览器自身不支持模块化,那末webpack就用函数作用域来hack模块化的结果。
假如你debug过node代码,你会发明一样的hack体式格局,node中的模块也是函数,跟模块相干的参数exports
、require
,或许其他参数__filename
和__dirname
等都是经由历程函数传值作为模块中的变量,模块与外部模块的接见就是经由历程这些参数举行的,固然这对开发者来说是通明的。
一样的体式格局,webpack也掌握了模块的module
、exports
和require
,那末我们就看看webpack是怎样完成这些功用的。
下面是摘取的函数内容,并添加了一些解释:
// 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);
模块数组作为参数传入IIFE函数后,IIFE做了一些初始化事情:
- IIFE起首定义了
installedModules
,这个变量被用来缓存已加载的模块。 - 定义了
__webpack_require__
这个函数,函数参数为模块的id。这个函数用来完成模块的require。 -
__webpack_require__
函数起首会搜检是不是缓存了已加载的模块,假如有则直接返回缓存模块的exports
。 - 假如没有缓存,也就是第一次加载,则起首初始化模块,并将模块举行缓存。
- 然后挪用模块函数,也就是前面webpack对我们的模块的包装函数,将
module
、module.exports
和__webpack_require__
作为参数传入。注重这里做了一个动态绑定,将模块函数的挪用对象绑定为module.exports
,这是为了保证在模块中的this指向当前模块。 - 挪用完成后,模块标记为已加载。
- 返回模块
exports
的内容。 - 应用前面定义的
__webpack_require__
函数,require第0个模块,也就是进口模块。
require进口模块时,进口模块会收到收到三个参数,下面是进口模块代码:
function(module, exports, __webpack_require__) {
"use strict";
var bar = __webpack_require__(1);
bar.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代码。
道理照样很简朴的,实在就是完成exports
和require
,然后自动加载进口模块,掌握缓存模块,that’s all。
仔细的你一定会发明,文章到这里只引见了webpack对commonjs的完成,那末ES6 module是怎样完成的呢?
迎接浏览本系列第二篇《webpack模块化道理-ES6 module》。