准备一个简单的项目
webpack.config.js
:
const path = require('path')
module.exports = {
mode: 'development',
entry: './src/home/index.js',
output: {
filename: 'js/[name].js',
path: path.resolve(__dirname, `./dist`),
},
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}
}
package.json
"scripts": {
"build": "webpack --progress"
},
src/home/index.js
import initialNum from './a.js'
export default function add(a, b) {
return a + b + initialNum()
}
src/home/a.js
export default function initialNum() {
return 1
}
执行npm run build
, 看看打包生成的文件
(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;
}
//...此处省略其余代码
return __webpack_require__(__webpack_require__.s = "./src/home/index.js");
})
({
"./src/home/a.js":(function(module, __webpack_exports__, __webpack_require__) {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return initialNum; });\n/**\n * Created by dengxuelian on 2020/3/7\n */\nfunction initialNum() {\n return 1;\n}\n\n//# sourceURL=webpack:///./src/home/a.js?");
}),
"./src/home/index.js":(function(module, __webpack_exports__, __webpack_require__) {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return add; });\n/* harmony import */ var _a_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./a.js */ \"./src/home/a.js\");\n/**\n * Created by dengxuelian on 2020/2/29\n */\n\nfunction add(a, b) {\n return a + b + Object(_a_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])();\n}\n\n//# sourceURL=webpack:///./src/home/index.js?");
})
});
手把手分析代码结构
- 整体结构:一个立即执行函数,接收一个参数
(function (modules) {
//函数体
})(/*实参*/)
- 函数参数
modules
:一个对象,key
是文件相对路径,value
是一个函数
(function (modules) {
//函数体
})({
"./src/home/a.js": function(){}
"./src/home/index.js": function () {},
})
想必你能猜到modules
对象是什么了,它就是你项目中所有被引用的文件,准确地说:modules
对象是从入口文件(webpack.entry)开始,所有被引用的文件列表,而webpack
如何获取到这些文件列表,我们暂时不关注。
现在我们有了一份js清单,接下来我们要做的就是加载并执行这些js文件。可以猜测,在立即执行函数体内,一定会调用参数modules
传入的函数,而其唯一的标识是key
,其调用会像这样:modules[moduleId]()
- 立即执行函数里调用
modules
对象上的某个key
上的某个方法
(function (modules) {
function __webpack_require__(moduleId) {
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); //moduleId就是modules的key,也就是文件的相对路径
}
})({
"./src/home/a.js": function(){}
"./src/home/index.js": function () {},
})
我们接着看函数__webpack_require__
的调用地方
- 函数
__webpack_require__
的执行时机
(function (modules) {
function __webpack_require__(moduleId) {
modules[moduleId].call(); //moduleId就是modules的key,也就是文件的相对路径
}
return __webpack_require__("./src/home/index.js");
})({
"./src/home/a.js": function(){}
"./src/home/index.js": function () {},
})
这个立即函数又执行了__webpack_require__()
,那么就等于执行了modules
对象传进来的某个键上的方法,具体是哪个键(文件)呢?这里是./src/home/index.js
,也就是webpack.entry
配置的入口文件。
- 现在看看
"./src/home/index.js": function () {}
这个函数里面写了什么
{
"./src/home/index.js": (function(module, __webpack_exports__, __webpack_require__) {
eval(/*我把这段字符格式化成js写在下面了*/);
__webpack_require__.r(__webpack_exports__)
__webpack_require__.d(__webpack_exports__, "default", function() { return add; });
var _a_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/home/a.js")
function add(a, b) {return a + b + Object(_a_js__WEBPACK_IMPORTED_MODULE_0__["default"])();}
})
}
这个函数内部是一个eval()
方法加上一堆字符串,我把字符串格式调整了一下,方便分析,首先关注的是这个函数确实加载了./src/home/index.js
这个文件,其次是注意import initialNum from './a.js'
变成了
var _a_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/home/a.js")
__webpack_require__("./src/home/a.js")
等价于执行"./src/home/a.js": function(){}
。假如./src/home/a.js
里面又引用了其它文件,那么可以猜测该函数内部也会有形如var XXX = __webpack_require__(XXX)
这种定义,以此循环,直到到达从入口文件开始的所有js文件调用树的结尾。
__webpack_require__
上还挂载了一些辅助方法或值,比如判断该模块是否加载过,定义exports
属性等等。这里不再展开。
多个entry
entry
是webpack
分析文件依赖关系的入口,多个entry
就对应多个打包文件。我们可以看看多个entry
的打包结果
webpack.config.js
:
const path = require('path')
const pages = ['home', 'login'];
let entries = {}
pages.forEach((page) => {
entries[page] = path.resolve(__dirname, `./src/${page}/index.js`);
})
module.exports = {
mode: 'development',
entry: entries,
output: {
filename: 'js/[name].js',
path: path.resolve(__dirname, `./dist`),
},
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}
}
./src/login/index.js
import initialNum from '../home/a.js'
export default function desc(value) {
return value - initialNum()
}
打包生成的文件:
//src/login.js
(function(modules) { // webpackBootstrap
return __webpack_require__(__webpack_require__.s = "./src/login/index.js");
})
({
"./src/home/a.js": (function(module, __webpack_exports__, __webpack_require__) {}),
"./src/login/index.js": (function(module, __webpack_exports__, __webpack_require__) {})
});
//src/home.js
(function(modules) { // webpackBootstrap
return __webpack_require__(__webpack_require__.s = "./src/home/index.js");
})
({
"./src/home/a.js": (function(module, __webpack_exports__, __webpack_require__) {}),
"./src/home/index.js": (function(module, __webpack_exports__, __webpack_require__) {})
});
细心的同学发现,两个入口文件都引用了同一个js,打包后的代码里也有一份相同的代码,在没有增加任何额外的配置之前,webpack
只是老实本分地完成了自己的本职工作--找依赖、打包文件,拆包这种事,你不“下命令”,它可是不会做的。
当然,拆包是很简单的,加一个splitChunks
配置就可以解决,不过webpack
的世界里配置实在太多,我们一步一步来吧。
下期预告
webpack
构建依赖图(dependency graph
)
网友评论