webpack打包出的文件解析
打包出的结果
我们看下上一节打包出的结果,build\bundle.js
,把没用的代码和注释删一删,然后开始捋一捋代码
看代码注释的时候,从001开始,一直到038结束,顺序是代码执行的顺序
js
// 001 其实就是一个匿名自执行函数
// 002 折叠一下就看出来了
(function (modules) { // 007 webpack的启动函数 webpackBootstrap
// 008 先定义了一个缓存 The module cache
// 009 缓存主要放什么呢?就是这个模块加载完了,我不需要再次加载模块,可以直接从缓存中拿
var installedModules = {};
// 010 自己实现了一个require方法 The require function
// 因为require是没办法在浏览器中运行的,所以自己实现了一个
function __webpack_require__(moduleId) { // 014 这个入口文件的名字 就会跑到这里./src/index.js
// 015 检查这个模块是否在缓存中,刚加载肯定不在缓存中 Check if module is in cache
if (installedModules[moduleId]) { // 016 不在缓存中
return installedModules[moduleId].exports;
}
// 017 因为不在缓存中,所以会安装这个模块 Create a new module (and put it into the cache)
// 018 是怎么安装呢,就是用了那个缓存对象
// 019 在缓存中的对象 使用文件名为key ./src/index.js
var module = installedModules[moduleId] = {
i: moduleId, // 020 i属性是 id,现在用不到
l: false, // 021 l属性是false,现在也用不到
exports: {} // 022 主要是这个 exports 对象
};
// Execute the module function
// 023 modules是在 006 中传入的对象,所以这就相当于拿到了参数中对应的函数
// 024 使用call 执行这个函数,
// 025 传入的参数 module.exports:当前模块的 exports 空对象(在 022 步骤中的)
// 026 传入的参数 module,当前模块
// 027 传入的参数 module.exports 当前模块的 exports 空对象(在 022 步骤中的)
// 028 传入的参数 __webpack_require__ (010 中 自己实现的require方法)
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// 031 新需要加载的模块 ./src/a.js,对应的函数被执行了
// 033 当前模块的方法执行完毕了,把表示为改为true,表示当前模块已经被加载过了 Flag the module as loaded
// 037 当前模块的方法执行完毕了,把表示为改为true,表示当前模块已经被加载过了
module.l = true;
// 034 把当前模块的 exports 结果返回 Return the exports of the module
// 038 把当前模块的 exports 结果返回 程序执行完毕
return module.exports;
}
// 013 入口模块 Load entry module and return exports
// 011 在此处调用了 内部自己实现的require方法
// 012 这个方法回去找 我们的入口文件 ./src/index.js
return __webpack_require__(__webpack_require__.s = "./src/index.js");
})
({ // 003 这就是匿名函数的参数,就是一个对象
// 006 把这个对象传递给了 modules
"./src/a.js": // 004 key,key是当前模块的路径
(function (module, exports) { // 005 value,就是一个函数
// 032 module.exports = 'xzz' 就把当前模块的 exports 空对象,变成了字符串 'xzz'
eval("module.exports = 'xzz'\n\n//# sourceURL=webpack:///./src/a.js?");
}),
"./src/index.js":
(function (module, exports, __webpack_require__) {
// 029 这个函数被执行了
// 030 let str = __webpack_require__( \"./src/a.js\") 发现代码里有需要加载的模块,然后就会跳转到 010 去加载这个模块
eval("let str = __webpack_require__( \"./src/a.js\")\r\n\r\nconsole.log('hello cheny');\r\n\r\nconsole.log(str);\n\n//# sourceURL=webpack:///./src/index.js?");
// 035 所以此时的 str 就是 034 中返回的字符串 'xzz'
// 036 最后执行完毕,打印出结果 cheny xzz
})
});
小结一下
- 因为浏览器不会解析commonjs的模块,所以所有require的模块代码,浏览器都是看不懂的
- 所以webpack就自己实现了一个require方法,
__webpack_require__
- webpack打包后的文件,是一个自执行函数,自执行函数的参数是一个对象,这个对象的key是当前模块的路径(相对路径),value是一个函数,待会加载这个模块的时候会执行这个函数。函数体是使用
eval
方法包裹的代码片段,原先我们使用require
的写法,都被改写为了let xxx = __webpack_require__('模块路径')
的形式,所以再次执行的执行,都会调用webpack自己写的require方法。 __webpack_require__
,在匿名自执行函数里声明的,在自执行函数的最开始,有一对象installedModules
,用来放已经加载过的模块,如果没有加载的模块,第一次加载的时候都会塞到这个对象上,塞的时候每个模块也是一个对象,对象的key就是模块的路径,value也是一个对象,里面主要操作export空对象,所有的返回值都会挂载到这个对象上。- 所以第一次执行
__webpack_require__
方法时,会从入口文件开始,如果入口文件里需要加载别的模块,就递归执行__webpack_require__
方法,去找对应的模块,执行完成后,对应的返回值都会挂载到exports对象上,然后回溯到上一级,如此循环往复,直到代码执行完毕。 - 所以webpack的核心原理就是这个自己实现的require方法
__webpack_require__
。
如何在打包的时候使用指定名称的配置文件
在使用npx webpack
打包时,会默认找webpack.config.js
文件(上一节的知识点)
假如我们想换个别的名称,怎么办?
在根目录下建一个新的配置文件,webpack.config.my.js
js
// webpack-dev-1\webpack.config.my.js
// webpack是node写出来的,所以使用node的语法
let path = require('path')
module.exports = {
mode: 'development', // 模式,默认两种模式,production 和 development
entry: './src/index.js', // 入口
output: {
filename: 'bundle.js', // 打包后的文件名
path: path.resolve(__dirname, 'build2'), // 打包后的路径,必须是一个绝对路径
}
}
我们可以使用带参数的npx webpack
来使用指定配置文件打包
shell
npx webpack --config webpack.config.my.js
或者你嫌指令太长记不住,可以在package.json,自定义一个脚本来执行,这样也是可以的(npx就不用带了)
json
{
"name": "webpack-dev-1",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"build": "webpack --config webpack.config.my.js"
},
"devDependencies": {
"webpack": "4.32.2",
"webpack-cli": "3.3.2"
}
}
然后使用npm指令来打包
shell
npm run build
另外,假如你在package.json里不知道这个文件的名字,你在打包的时候才知道,所以package.json就变成了这样
json
{
"name": "webpack-dev-1",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"build": "webpack"
},
"devDependencies": {
"webpack": "4.32.2",
"webpack-cli": "3.3.2"
}
}
如果是这样的话,在使用build命令时,可以直接把名字带上,但是记得多加个--
shell
npm run build -- --config webpack.config.my.js
效果是一样的。多加的--
后面的就会别识别为字符串了。
总结
webpack的原理
- 因为浏览器不会解析commonjs的模块,所以所有require的模块代码,浏览器都是看不懂的
- 所以webpack就自己实现了一个require方法,
__webpack_require__
- webpack打包后的文件,是一个自执行函数,自执行函数的参数是一个对象,这个对象的key是当前模块的路径(相对路径),value是一个函数,待会加载这个模块的时候会执行这个函数。函数体是使用
eval
方法包裹的代码片段,原先我们使用require
的写法,都被改写为了let xxx = __webpack_require__('模块路径')
的形式,所以再次执行的执行,都会调用webpack自己写的require方法。 __webpack_require__
,在匿名自执行函数里声明的,在自执行函数的最开始,有一对象installedModules
,用来放已经加载过的模块,如果没有加载的模块,第一次加载的时候都会塞到这个对象上,塞的时候每个模块也是一个对象,对象的key就是模块的路径,value也是一个对象,里面主要操作export空对象,所有的返回值都会挂载到这个对象上。- 所以第一次执行
__webpack_require__
方法时,会从入口文件开始,如果入口文件里需要加载别的模块,就递归执行__webpack_require__
方法,去找对应的模块,执行完成后,对应的返回值都会挂载到exports对象上,然后回溯到上一级,如此循环往复,直到代码执行完毕。 - 所以webpack的核心原理就是这个自己实现的require方法
__webpack_require__
。
使用指定配置文件运行webpack
npx webpack --config webpack.config.my.js
- 或者在
package.json
里配置脚本,"build": "webpack --config webpack.config.my.js"
- 如果使用脚本传参时,需要多加两个杠,
npm run build -- --config webpack.config.my.js
,多加的--
后面的就会别识别为字符串了。