简单分析下webpack5的模块加载的原理。
测试文件如下:
login.js 提供方法
// login.js
export function login() {
const ele = document.createElement('h1')
ele.className = 'name example iconfont icon-linggan'
ele.innerHTML = '这是测试内容啊啥的sodas啊实打实的'
return ele
}
export function funB() {
console.log('funB')
}
export const a = 'a'
index.js 调用login.js的方法
import login from './js/login.js'
document.body.appendChild(login())
tips: webpack不能同时支持 ES模块 和 CommonJS 模块
经过测试同时存在会报错:
Uncaught Error: ES Modules may not assign module.exports or exports.*, Use ESM export syntax, instead: ./src/js/login.js
所以这里只考虑ES模块加载规则。
webpack配置:
const path = require('path')
const webpack = require('webpack')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
entry: './src/index',
output: {
filename: 'index.js',
path: path.resolve(__dirname, 'dist')
},
mode: 'development',
devtool: false,
target: 'web',
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: ['babel-loader']
},
]
},
optimization: {
minimizer: [
// 在 webpack@5 中,你可以使用 `...` 语法来扩展现有的 minimizer(即 `terser-webpack-plugin`),将下一行取消注释
// `...`,
new CssMinimizerPlugin(),
new TerserPlugin(),
],
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[hash:8].min.css',
}),
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: '插件使用',
template: './index.html'
}),
new webpack.DefinePlugin({
BASE_URL: '"./"'
}),
]
}
webpack的配置主要关注 mode: 'development',devtool: false 这2个配置 ,其他都是辅助能解析基本文件的可以忽略。
打包出来的删除注释后的js文件
tips: 里面的注释是为了增加理解手动所写
(() => {
"use strict";
// 声明一个全局存储的变量: id --> () => {}
var __webpack_modules__ = ({
"./src/js/login.js":
((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, {
"login": () => (/* binding */ login),
"funB": () => (/* binding */ funB),
"a": () => (/* binding */ a)
});
function login() {
var ele = document.createElement('h1');
ele.className = 'name example iconfont icon-linggan';
ele.innerHTML = '这是测试内容啊啥的sodas啊实打实的';
return ele;
}
function funB() {
console.log('funB');
}
var a = 'a';
})
});
// 声明一个缓存变量
var __webpack_module_cache__ = {};
// 根据moduleId获取和存储 exports变量
function __webpack_require__(moduleId) {
var cachedModule = __webpack_module_cache__[moduleId];
if (cachedModule !== undefined) {
return cachedModule.exports;
}
var module = __webpack_module_cache__[moduleId] = {
exports: {}
};
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
return module.exports;
}
// 三个自执行函数 在__webpack_require__函数上增加 _d, _o, _r 方法
(() => {
__webpack_require__.d = (exports, definition) => {
for(var key in definition) {
if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
}
}
};
})();
(() => {
__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
})();
(() => {
__webpack_require__.r = (exports) => {
if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}
Object.defineProperty(exports, '__esModule', { value: true });
};
})();
// 自执行结束--
// 获取并执行 moduleId = "./src/js/login.js" 的模块
var __webpack_exports__ = {};
(() => {
__webpack_require__.r(__webpack_exports__);
var _js_login_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./js/login.js */ "./src/js/login.js");
document.body.appendChild((0,_js_login_js__WEBPACK_IMPORTED_MODULE_0__["default"])());
})();
})()
;
根据文件总结步骤如下:
(moduleId: ./src/js/login.js)
- webpack_modules 存储全局模块数量,存储以后的形式如下:
{
moduleId: (_, exports, require) => {
__webpack_require__.r(exports)
__webpack_require__.d(exports)
// 见上面的源码
...
}
}
- webpack_module_cache 缓存加载过的模块,缓存形式如下:
{
moduleId: { exports: { a: 'a', login: function login() {}, ...} }
}
- function webpack_require 根据moduleId 获取导出的内容。如果没有则加入到__webpack_module_cache__中,存储形式如下:
// __webpack_modules__存储的内容。
// 进过 __webpack_modules__[moduleId](module, module.exports, __webpack_require__)后
// __webpack_module_cache__ 中存储了 moduleId 的 exports
{
moduleId: {
exports : {
// 该文件导出的内容...
}
}
}
tips: 所以在__webpack_require__的时候根据根据moduleId 确定 是谁的 exports, require。
-
webpack_require.o 判断是自己的属性并且不在原型上
-
webpack_require.r 标记 exports.__esModule = true 即为es模块
-
webpack_require._d 会把内容绑定到 exports属性上,经过处理以后得到:
exports: {
login: function login() {},
funb: ...
}
-
webpack_require('./src/js/login.js') 找到并存储moduleId 为 './src/js/login.js'的 exports
-
执行 exports的 default 函数
最后总结下执行顺序:
_webpack_require_ ----> webpack_modules[moduleId] ---> _webpack_require_.r / _webpack_require._d
_webpack_require__传入moduleId以后就会获取webpack_modules中的箭头函数, 箭头函数主要就是调用__webpack_require_.d获取exports内容,并存储在cache中,下次直接获取cache中的内容。获取到exports内容后就可以调用啦。