相关推荐recommended
webpack 打包原理及流程解析,超详细!
作者:mmseoamin日期:2023-12-02

webpack 打包原理及流程解析

    • 1. 什么是webpack?
    • 2. 关键术语解析
    • 3. webpack核心概念
    • 4. webpack 构建流程
    • 5. webpack应用案例
    • 6. 打包分析

      1. 什么是webpack?

      友情提示:

      a.前面会稍微有些枯燥,文字居多(建议还是过一遍),后面就劲爆了!!!

      b.本文干货满满,非常详细,整理资料到发布文章耗时5个小时+,请大家耐心看

      webpack 打包原理及流程解析,超详细!,在这里插入图片描述,第1张

      • 本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。
      • webpack 就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果。 这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。
      • 插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。
      • webpack 通过 Tapable 来组织这条复杂的生产线。 webpack 在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。 webpack 的事件流机制保证了插件的有序性,使得整个系统扩展性很好。

        webpack 打包原理及流程解析,超详细!,在这里插入图片描述,第2张

      • 官方的说法看不太懂的,🙈🙈可以看这个:
      • 在目前的项目中,我们会有很多依赖包,webpack负责将浏览器不能识别的文件类型、语法等转化为可识别的前端三剑客(html,css,js),并在这个过程中充当组织者与优化者的角色。

        2. 关键术语解析

        2.1 bundle

        • Bundle(捆绑包)是指将所有相关的模块和资源打包在一起形成的单个文件。它是应用程序的最终输出,可以在浏览器中加载和执行。
        • 捆绑包通常由Webpack根据入口点(entry)和它们的依赖关系自动创建。当你运行Webpack构建时,它会根据配置将所有模块和资源打包成一个或多个捆绑包。

          2.2 Chunk

          • Chunk(代码块)是Webpack在打包过程中生成的中间文件,它代表着一个模块的集合。

          • Webpack 根据代码的拓扑结构和配置将模块组织成不同的代码块。每个代码块可以是一个独立的文件,也可以与其他代码块组合成一个捆绑包。

          • Webpack使用代码分割(code splitting)技术将应用程序代码拆分成更小的代码块,以便在需要时进行按需加载。这有助于减小初始加载的文件大小,提高应用程序的性能。

          • 在Webpack中,捆绑包和代码块之间存在一对多的关系。一个捆绑包可以包含多个代码块,而一个代码块也可以属于多个不同的捆绑包。这取决于Webpack配置中的拆分点(split points)和代码块的依赖关系。

          • 总结起来,bundle 是Webpack打包过程的最终输出文件,而chunk是Webpack在打包过程中生成的中间文件,用于组织和按需加载模块。

            3. webpack核心概念

            3.1 Entry

            • 入口起点(entry point)指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。
            • 进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。
            • 每个依赖项随即被处理,最后输出到称之为 bundles 的文件中。

              3.2 Output

              • output 属性告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist。
              • 基本上,整个应用程序结构,都会被编译到你指定的输出路径的文件夹中。

                3.3 Module

                • 模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。

                  3.4 Chunk

                  • 代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。

                    3.5 Loader

                    • loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。
                    • loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。
                    • 本质上,webpack loader 将所有类型的文件,转换为应用程序的依赖图(和最终的 bundle)可以直接引用的模块。

                      3.6 Plugin

                      • loader 被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。
                      • 插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务。

                        4. webpack 构建流程

                        Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程

                        webpack 打包原理及流程解析,超详细!,在这里插入图片描述,第3张

                        • 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数。
                        • 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译。
                        • 确定入口:根据配置中的 entry 找出所有的入口文件。
                        • 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理。
                        • 完成模块编译:在经过第 4 步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系。
                        • 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会。
                        • 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
                        • 在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果。

                          5. webpack应用案例

                          5.1 前置条件

                          • 首先检查node版本,建议使用 16.16.x 及以上版本(因为玩的是webpack 4.0+ 的版本)
                          • 全局安装 => npm install webpack -g
                          • 当然,也可以带版本号,如 webpack@4.4.0
                          • 这里使用的是最新的(不带版本号默认安装最新版本)
                          • 当然,也可以局部安装(全局可能会影响你的其它项目),5.2 步骤将会做的是局部安装

                            5.2 初始化项目

                            • 整个空文件夹,执行npm init,然后一路回车,把回车摁烂!!!
                            • 当然你也可以搞一些个性化配置,文件名,版本,描述,入口文件等等。

                              webpack 打包原理及流程解析,超详细!,在这里插入图片描述,第4张

                            • 局部安装webpack:npm i webpack webpack-cli -D
                            • 然后你会看到 package.json 这个鸟样:

                              webpack 打包原理及流程解析,超详细!,在这里插入图片描述,第5张

                              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:表示项目所在目录的根路径。

                                5.4 src/index.js 与 test.js

                                • 新建入口文件src/index.js,随便整点代码:
                                  const test = require('./test');
                                  const a = 12
                                  const b = 12
                                  function 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;
                                    

                                    5.5 测试打包

                                    • package.json 下的 scripts 中添加打包命令:
                                      "build": "webpack --mode development"
                                      
                                      • 这里说明一下,不加 –mode development,默认打包是生产环境,打包出来的代码会默认压缩,看不得,辣眼睛
                                      • 运行 npm run build,进行打包。
                                      • 如果不出意外的话,控制台就成功的打包了两个js文件,同时目录中会生成 dist 文件夹(出意外的话看下上面的步骤)

                                        webpack 打包原理及流程解析,超详细!,在这里插入图片描述,第6张

                                        6. 打包分析

                                        • 刚刚的步骤是一个非常简单的打包示例,打包后你会发现两个js文件变成了一个
                                        • 试运行一下,也是没问题的,如下

                                          webpack 打包原理及流程解析,超详细!,在这里插入图片描述,第7张

                                          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)

                                                    webpack 打包原理及流程解析,超详细!,在这里插入图片描述,第8张