1. 什么是webpack?
友情提示:
a.前面会稍微有些枯燥,文字居多(建议还是过一遍),后面就劲爆了!!!
b.本文干货满满,非常详细,整理资料到发布文章耗时5个小时+,请大家耐心看
- 本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。
- webpack 就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果。 这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。
- 插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。
- webpack 通过 Tapable 来组织这条复杂的生产线。 webpack 在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。 webpack 的事件流机制保证了插件的有序性,使得整个系统扩展性很好。
- 官方的说法看不太懂的,🙈🙈可以看这个:
- 在目前的项目中,我们会有很多依赖包,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 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程
- 初始化参数:从配置文件和 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: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:表示项目所在目录的根路径。
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 文件夹(出意外的话看下上面的步骤)
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)文章来源:https://www.toymoban.com/news/detail-646676.html
文章来源地址https://www.toymoban.com/news/detail-646676.html
到了这里,关于webpack 打包原理及流程解析,超详细!的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!