webpack 打包原理及流程解析,超详细!
欧阳呀 2024-06-19 11:03:02 阅读 92
webpack 打包原理及流程解析
1. 什么是webpack?2. 关键术语解析3. webpack核心概念4. webpack 构建流程5. webpack应用案例6. 打包分析
1. 什么是webpack?
友情提示:
a.前面会稍微有些枯燥,文字居多(建议还是过一遍),后面就劲爆了!!!
b.本文干货满满,非常详细,整理资料到发布文章耗时5个小时+,请大家耐心看
本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。webpack 就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果。 这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。webpack 通过 Tapable 来组织这条复杂的生产线。 webpack 在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。 webpack 的事件流机制保证了插件的有序性,使得整个系统扩展性很好。
官方的说法看不太懂的,🙈🙈可以看这个:在目前的项目中,我们会有很多依赖包,webpack负责将浏览器不能识别的文件类型、语法等转化为可识别的前端三剑客(html,css,js),并在这个过程中充当组织者与优化者的角色。
2. 关键术语解析
Bundle(捆绑包)是指将所有相关的模块和资源打包在一起形成的单个文件。它是应用程序的最终输出,可以在浏览器中加载和执行。捆绑包通常由Webpack根据入口点(entry)和它们的依赖关系自动创建。当你运行Webpack构建时,它会根据配置将所有模块和资源打包成一个或多个捆绑包。2.1 bundle
2.2 Chunk
Chunk(代码块)是Webpack在打包过程中生成的中间文件,它代表着一个模块的集合。
Webpack 根据代码的拓扑结构和配置将模块组织成不同的代码块。每个代码块可以是一个独立的文件,也可以与其他代码块组合成一个捆绑包。
Webpack使用代码分割(code splitting)技术将应用程序代码拆分成更小的代码块,以便在需要时进行按需加载。这有助于减小初始加载的文件大小,提高应用程序的性能。
在Webpack中,捆绑包和代码块之间存在一对多的关系。一个捆绑包可以包含多个代码块,而一个代码块也可以属于多个不同的捆绑包。这取决于Webpack配置中的拆分点(split points)和代码块的依赖关系。
总结起来,bundle 是Webpack打包过程的最终输出文件,而chunk是Webpack在打包过程中生成的中间文件,用于组织和按需加载模块。
3. webpack核心概念
入口起点(entry point)指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。每个依赖项随即被处理,最后输出到称之为 bundles 的文件中。3.1 Entry
output 属性告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist。基本上,整个应用程序结构,都会被编译到你指定的输出路径的文件夹中。3.2 Output
模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。3.3 Module
代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。3.4 Chunk
loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。本质上,webpack loader 将所有类型的文件,转换为应用程序的依赖图(和最终的 bundle)可以直接引用的模块。3.5 Loader
loader 被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务。3.6 Plugin
4. webpack 构建流程
初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数。开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译。确定入口:根据配置中的 entry 找出所有的入口文件。编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理。完成模块编译:在经过第 4 步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系。输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会。输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果。Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程
5. webpack应用案例
首先检查node版本,建议使用 16.16.x 及以上版本(因为玩的是webpack 4.0+ 的版本)全局安装 => npm install webpack -g当然,也可以带版本号,如 webpack@4.4.0这里使用的是最新的(不带版本号默认安装最新版本)当然,也可以局部安装(全局可能会影响你的其它项目),5.2 步骤将会做的是局部安装5.1 前置条件
整个空文件夹,执行npm init,然后一路回车,把回车摁烂!!!当然你也可以搞一些个性化配置,文件名,版本,描述,入口文件等等。5.2 初始化项目
局部安装webpack:npm i webpack webpack-cli -D然后你会看到 package.json 这个鸟样:
5.3 新建 webpack.config.js 配置文件(根目录)
const path = require('path');module.exports = { // 入口 entry: path.resolve(__dirname, 'src/index.js'), // 出口 output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js', publicPath: './' }}
publicPath:指定基础路径,开发环境一般是项目的根路径,上线之后一般是CDN的路径。__dirname:表示项目所在目录的根路径。
新建入口文件src/index.js,随便整点代码:5.4 src/index.js 与 test.js
const test = require('./test');const a = 12const b = 12function add(x, y) { return x + y}const c = add(a,b)console.log(c)test();
src/test.js,非主入口文件用来测试打包的
function test(){ console.log(2);}module.exports = test;
package.json 下的 scripts 中添加打包命令:5.5 测试打包
"build": "webpack --mode development"
这里说明一下,不加 –mode development,默认打包是生产环境,打包出来的代码会默认压缩,看不得,辣眼睛运行 npm run build,进行打包。如果不出意外的话,控制台就成功的打包了两个js文件,同时目录中会生成 dist 文件夹(出意外的话看下上面的步骤)
6. 打包分析
刚刚的步骤是一个非常简单的打包示例,打包后你会发现两个js文件变成了一个试运行一下,也是没问题的,如下
源码实际上很好懂,代码量也不多,不妨来解读一下:6.1 源码分析
/* * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). * This devtool is neither made for production nor for readable output files. * It uses "eval()" calls to create a separate source file in the browser devtools. * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) * or disable the default devtool with "devtool: false". * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). *//******/ (() => { // webpackBootstrap/******/ var __webpack_modules__ = ({ /***/ "./src/index.js":/*!**********************!*\ !*** ./src/index.js ***! \**********************//***/ ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => { eval("const test = __webpack_require__(/*! ./test */ \"./src/test.js\");\r\nconst a = 12\r\nconst b = 12\r\nfunction add(x, y) { \r\n return x + y\r\n}\r\nconst c = add(a,b)\r\nconsole.log(c)\r\ntest();\n\n//# sourceURL=webpack://blog/./src/index.js?");/***/ }),/***/ "./src/test.js":/*!*********************!*\ !*** ./src/test.js ***! \*********************//***/ ((module) => { eval("function test(){\r\n console.log(2);\r\n}\r\n\r\nmodule.exports = test;\r\n\n\n//# sourceURL=webpack://blog/./src/test.js?");/***/ })/******/ });/************************************************************************//******/ // The module cache/******/ var __webpack_module_cache__ = { };/******/ /******/ // The require function/******/ function __webpack_require__(moduleId) { /******/ // Check if module is in cache/******/ var cachedModule = __webpack_module_cache__[moduleId];/******/ if (cachedModule !== undefined) { /******/ return cachedModule.exports;/******/ }/******/ // Create a new module (and put it into the cache)/******/ var module = __webpack_module_cache__[moduleId] = { /******/ // no module.id needed/******/ // no module.loaded needed/******/ exports: { }/******/ };/******/ /******/ // Execute the module function/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);/******/ /******/ // Return the exports of the module/******/ return module.exports;/******/ }/******/ /************************************************************************//******/ /******/ // startup/******/ // Load entry module and return exports/******/ // This entry module can't be inlined because the eval devtool is used./******/ var __webpack_exports__ = __webpack_require__("./src/index.js");/******/ /******/ })();
可以看到最简单的场景下 webpack 实现的模块加载系统非常简洁,仅仅只有60多行代码打包后的代码其实是一个立即执行函数,在Webpack打包过程中,每个模块都会被转换为一个独立的函数,并通过__webpack_modules__对象进行注册和管理。这个对象以文件路径为 key,以文件内容为 value,它包含了所有打包后的模块。当模块被引用或加载时,Webpack会使用__webpack_modules__来查找和执行相应的模块函数。通过使用__webpack_modules__,Webpack可以管理模块之间的依赖关系,并在需要时按需加载和执行模块。接着定义了一个模块加载函数 webpack_require()它接收的参数是 moduleId,其实就是文件路径。它的执行过程如下: 判断模块是否有缓存,如果有则返回缓存模块的 export 对象,即 module.exports。新建一个模块 module,并放入缓存。执行文件路径对应的模块函数。执行完模块后,返回该模块的 exports 对象。 其中 module、module.exports 的作用和 CommonJS 中的 module、module.exports 的作用是一样的,而 webpack_require 相当于 CommonJS 中的 require。在立即函数的最后,使用了 webpack_require() 加载入口模块。并传入了入口模块的路径 ./src/index.js。我们再来分析一下入口模块的内容:
((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => { eval("const test = __webpack_require__(/*! ./test */ \"./src/test.js\");\r\nconst a = 12\r\nconst b = 12\r\nfunction add(x, y) { \r\n return x + y\r\n}\r\nconst c = add(a,b)\r\nconsole.log(c)\r\ntest();\n\n//# sourceURL=webpack://blog/./src/index.js?");})
入口模块函数的参数正好是刚才所说的那三个参数(module、module.exports 、require),而 eval 函数的内容美化一下后和下面内容一样:
const test = __webpack_require__(/*! ./test */ "./src/test.js");function test(){ console.log(2);}test();
将打包后的模块代码和原模块的代码进行对比,可以发现仅有一个地方发生了变化,那就是 require 变成了 webpack_require。从刚才的分析可知,webpack_require() 加载模块后,会先执行模块对应的函数,然后返回该模块的 exports 对象。而 test.js 的导出对象 module.exports 就是 test() 函数。所以入口模块能通过 webpack_require() 引入 test() 函数并执行到目前为止可以发现 webpack 自定义的模块规范完美适配 CommonJS 规范。
1. 希望本文能对大家有所帮助,如有错误,敬请指出
2. 原创不易,还请各位客官动动发财的小手支持一波(关注、评论、点赞、收藏)
3. 拜谢各位!后续将继续奉献优质好文
4. 如果存在疑问,可以私信我(主页有V)
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。