webpack 打包后文件分析
webpack 用于编译 javascript 模块, 可以把文件格式编译成我们想要的静态文件格式, 但是处理的过程并不是全部由 webpack 本身完成, webpack 只是提供了一个打包机制, 对于各类文件的打包处理需要使用相对应的 预处理模块 loader 来处理, 作为一种机制 webpack 会帮助各种 loader 提供识别入口目录、入口文件、 输出目录, 输出文件。
首先我们试着打包一个只包含 console.log('hello world') 的 js 文件。
初始化文件和安装 webpack 环境
# 新建 demo 目录
mkdir webpack-demo cd webpack-demo
# 初始化目录
npm init -y
# 本地安装 webpack 工具
npm install webpack webpacl-cli --save-dev
# webpack 默认的入口文件是 .src/index.js 创建 src 目录和 index.js 文件
mkdir src
echo “console.log('hello world')” > src/index.js
# 执行 webpack 命令 需要查看打包后文件, 这里使用 development 模式
npx webpack --mode development
简化打包后文件
由于打包后的文件比较繁琐, 这里我们简化一下打包后的文件
(function(modules) {
var installedModules = {}
function __webpack_require__(moduleIid) {
}
return __webpack_require__(__webpack_require__.s = "./src/index.js")
})({
"./src/index.js": (function(module, exports) {
eval("console.log('test webpack entry')");
})
})
打包后的文件含有大量的注释和
webpack本身的变量, 为了方便分析可以把这些注释和 类似__webpack_require__.s的复制语句全部删掉
从上面的代码可以看到,
- 经过
webpack打包后的代码通过一个IIFE自执行函数, 这个函数接收一个对象参数, 这个对象的key为入口文件的目录,value是一个执行入口文件里面代码的函数 - 这个对象作为参数
modules传递给IIFE函数 IIFE函数里面声明了一个变量installedModules用来存放缓存, 一个函数__webpack_require__, 用来转化入口文件里面的代码IIFE最终把modules里面的的key传递给__webpack_require__函数并返回。
我们进一步看 __webpack_require__ 函数都做了什么。
__webpack_require__ 分析
function (modules) {
var installedModules = {}
function __webpack_require__ (moduleId) {
if(installedModules[moduleId]) {
return installedModules[moduleId].exports
}
var module = installedModules[moduleId] = {
i: moduleId,
l:false,
exports: {}
}
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__)
module.l = true
return module.exports
}
}
__webpack_require__函数接收./src/index.js- 首先检查缓存
installedModules中是否包含key为./src/index.js的对象, 如果存在直接返回这个对象中的exports - 当缓存中不存在入口模块的时候, 在缓存中生成一个对象并放到缓存中, 这个对象包括三个值:
ilexports - 使用
modules[moduleId].call调用IIFE参数的value函数, 并把value对应的函数中的this指向赋值给了module.exports, 后面的call方法的后面三个参数为value对应函数的参数 - 最后返回了
module.exports, 这里的module.exports在第四步的时候已赋值为IIFE参数对象中的value对应的函数。
所以可以看出来。 函数 __webpack_require__ 实际返回的就是 IIFE 参数对象中的 value 对应的函数, 也就是 eval("\nconsole.log('test webpack entry')\n\n\n//# sourceURL=webpack:///./src/index.js?"),
当我们运行 webpack 打包后的文件的时候执行的是 "console.log('test webpack entry')"
eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码。
入口文件引用其他模块时的打包过程
上面讲的打包过程入口文件中并没有引用其他的代码模块, 当入口文件中引用其他的模块的时候, webpack 的打包过程也和上述过程相似。
在 ./src/ 下新建 main.js
module.exports = () => {
console.log('main module')
}
在 ./src/index.js 中引入 main.js
const main = reuiqre('./main.js')
console.log('webpack index entry')
main()
运行 npx webpack 打包后的文件
IIFE 参数的变化
(function (mudoles) {
})({
'./src/index.js': (function(module,exports, __webpack_require__) {
eval("const main = __webpack_require__(/*! ./main.js */ \"./src/main.js\")\r\nconsole.log('test webpack entry')\r\n\n\n//# sourceURL=webpack:///./src/index.js?");
}),
'./src/main.js': (function(module, exports) {
eval("console.log('main module')\n\n//# sourceURL=webpack:///./src/main.js?");
})
})
如果入口文件中引用了其他模块的文件,将会把这些模块添加到 IIFE 的参数对象中, key 为模块的路径, value 执行该模块代码的函数。
IIFE 函数执行逻辑的变化
function (modules) {
var installedModules = {}
function __webpack_require__ (moduleId) {
if (installedModules[moduleId]) {
return installedMOdules[mudoleId].exports
}
var module = installedModuled[moduleId] = {
i: moduleId,
l: false,
exports: {}
}
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__)
return module.exports
}
return __webpack_require__(__webpack_require__.s = "./src/index.js")
}
上面的代码中依旧返回了以 ./src/index.js 为参数的函数。 但是函数里面的逻辑发生了改变。
- 声明一个
installedModules变量存放缓存 - 将
./src/index.js传入__webpack_require__函数 ./src/index.js不在缓存中, 往下执行- 声明一个
module并在缓存中存放一以./src/index.js为key的对象 - 调用
modules[moduleId]函数,并指明 作用域和参数 也就是function(module,exports, __webpack_require__) { eval("const main = __webpack_require__(/*! ./main.js */ \"./src/main.js\")\r\nconsole.log('test webpack entry')\r\n\n\n//# sourceURL=webpack:///./src/index.js?")} - 返回
module.exports, 由于在第五步调用modules['./src/index.js']函数的时候, 已经把module.exports作为了函数的this作用域, 所以这时module.exports实际就是modules['./src/index.js']执行的函数。 - 在上面的函数中,
eval代码中使用了一个函数__webpack_require__, 这个函数就是在第五步modules[moduleId].call(module.exports, module, module.exports, __webpack_require__)的最后一个参数__webpack_require__, 这时继续调用__webpack__require__函数并传入./src/main.js ./src/main.js传入__webpack_require__中, 依旧不在缓存, 再次声明一个变量module并在缓存中新增一个key为./src/main.js的对象。modules[moduleId].call这时调用IIFE参数对象中key为./src/main.js的函数:(function(module, exports) { eval("console.log('main module')\n\n//# sourceURL=webpack:///./src/main.js?"); })- 返回
module.export, 同第6步相似这时的module.exports就是modules[./src/main.js]对应的函数。 - 返回的最终结果就是(去除了
eval和注释):
const main = console.log('main module')
console.log(test webpack entry)
转载自:https://juejin.cn/post/6844903829536784391