webpack 打包编译好的文件发现可以通过 import 或者 require 去引用,很好奇webpack 是如何做到的?webpack 是如何实现模块化的?
webpack 模块化原理
webpack hack 了 commonjs,维护了自己的一套模块化系统,通过自定义的 exports 和 require 实现模块化,兼容了前端前面的模块化规范。
新建文件 a.js 和 b.js,在 a.js 中通过 require 和 import 两种方式引入 b.js, 并使用 webpack4 打包生成 bundle.js。
console.log('b.js');
export default 'b.js';
// a.js
import './b.js';
require('./b.js');
export default 'a.js';
// webpack.config.js
const webpack = require('webpack');
const path = require('path');
module.exports = {
entry: './a.js',
mode: 'development',
output: { path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
}
}
生成的 bundle.js 文件
(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;
}
// Load entry module and return exports
return __webpack_require__(__webpack_require__.s = "./a.js"); })
({
"./a.js":
(function(module, __webpack_exports__, __webpack_require__) {"use strict";eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _b_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./b.js */ \"./b.js\");\n\r\n__webpack_require__(/*! ./b.js */ \"./b.js\");\r\n\r\n/* harmony default export */ __webpack_exports__[\"default\"] = ('a.js');\r\n\n\n//# sourceURL=webpack:///./a.js?"); }),
"./b.js":
(function(module, __webpack_exports__, __webpack_require__) {"use strict";eval("__webpack_require__.r(__webpack_exports__);\nconsole.log('b.js');\r\n\r\n/* harmony default export */ __webpack_exports__[\"default\"] = ('b.js');\r\n\n\n//# sourceURL=webpack:///./b.js?");
})
});
bundle.js 本质上是自执行函数,自执行函数的入参是包含所有模块的对象,以模块名称作为 key 值,value 为包裹在函数内的模块,自执行函数体内是处理模块化的逻辑,installedModules 缓存模块,关键在于 __webpack_require__ 函数,函数内部维护着 modules 对象
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
__webpack_require__ 函数内执行了自执行函数传入的模块,并将 module、module.exports、__webpack_require_ 传入
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);return module.exports;
再来看下 a.js 模块函数的执行, 发现 import 和 require 都被 __webpack_require__ 替换了
(function(module, __webpack_exports__, __webpack_require__) {"use strict";eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _b_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./b.js */ \"./b.js\");\n\r\n__webpack_require__(/*! ./b.js */ \"./b.js\");\r\n\r\n/* harmony default export */ __webpack_exports__[\"default\"] = ('a.js');\r\n\n\n//# sourceURL=webpack:///./a.js?"); })
所以 webapck 其实就是在内部实现了 require 和 exports,然后自动加载入口模块,根据依赖加载其他模块,并且控制模块缓存。