準備一個簡單的項目
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
)