复盘Webpack相关概念和原理

11 阅读9分钟

通过一个在线文档编辑器的开发与运行过程,来形象化理解 Webpack 及其相关概念的原理和作用。

完整流程:文档编辑器的开发与打包

  1. 代码撰写:开发文档编辑器,使用 ES6+ 语法和不同的资源类型(JS、CSS、Markdown 等)。
  2. 模块打包:使用 Webpack 通过 entry 构建模块依赖图,结合 Loader 翻译资源。
  3. 代码优化:通过 Tree Shaking 移除无用代码,使用 Babel 转译为兼容版本。
  4. 按需加载:通过 Code Splitting,拆分成多个文件(普通功能和高级功能)。
  5. 热更新:HMR 提供开发体验,实时刷新浏览器。
  6. 代理请求:通过 Proxy 转发 API 请求。
  7. 最终上线:Nginx 配置静态资源托管和后端 API 的反向代理。

Webpack的构建流程主要包括以下几个步骤:

  1. 初始化参数。解析Webpack配置参数,合并Shell传入和webpack.config.js文件配置的参数,形成最终的配 置结果。
  2. 开始编译。使用上一次得到的参数初始化compiler对象,注册所有配置的插件,插件监听Webpack构建生命周期的事件节点,做出相应的反应,执行对象的run方法开始执行编译。
  3. 确定入口。从配置的entry入口,开始解析文件构建AST语法树,找出依赖,递归下去。
  4. 编译模块。递归中根据文件类型和loader配置,调用所有配置的loader对文件进行转换,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理。
  5. 完成模块编译。在经过第四步使用Loader翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系。
  6. 输出资源。根据入口和模块之间的依赖关系,组装成一个个包含多个模块的Chunk,再把每个Chunk转换成单独的文件加入到输出列表,这步是可以修改输出内容的最后机会。
  7. 输出完成。在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。

1. Webpack

角色:Webpack 是一个“模块打包器”,类似于在线文档的“打包工具”,可以把散落的代码、样式、图片等资源模块整合到一起,最终生成一个可以直接在浏览器中运行的文件。

例子

  • 你写了很多代码文件:index.jsstyles.csseditor.js 等。

  • Webpack 会通过 入口文件(如 index.js),分析模块依赖,构建一个“模块依赖图”,然后把所有的模块打包成最终的 bundle.js 文件。

  • Webpack的配置主要包括以下几个部分:

    • entry。指定Webpack打包的入口文件,可以是单个或多个JavaScript文件。这个配置决定了Webpack从哪个模块开始生成依赖关系图。
    • output。设置Webpack打包后输出的目录和文件名称,包括path、filename和publicPath等。235
    • module。配置了不同的loaders来处理不同的模块,例如,对于CSS文件,可以使用css-loader和style-loader。
    • resolve设置Webpack如何解析模块依赖,包括别名、扩展名等。
    • plugins使用不同的插件可以增强Webpack的功能,例如,使用html-webpack-plugin可以将打包后的js文件自动引用到HTML文件中。
    • devServer提供了一个简单的web服务器和实时重载功能,可以通过devServer.contentBase、devServer.port、devServer.proxy等进行配置。
    • optimization可以使用optimization.splitChunks和optimization.runtimeChunk配置代码拆分和运行时代码提取等优化策略。
    • externals用于配置排除打包的模块,例如,可以将jQuery作为外置扩展,避免将其打包到应用程序中。
    • devtool。配置source-map类型。

2. Loader 本质是一个函数,它是一个转换器

角色:Loader 是 Webpack 的“翻译官”,能把不同类型的资源翻译成 Webpack 能理解的内容(如 JS 或 JSON)。

例子

  • 你的文档编辑器需要加载 .css 文件,Webpack 本身不懂 CSS,所以用 css-loader 翻译 CSS 文件;
  • 你还需要加载 .md 文件(Markdown 格式的文档),可以使用 markdown-loader
  • 常用的Loader:
    • babel-loader:将ES6+的代码转换成ES5的代码。
    • css-loader:解析CSS文件,并处理CSS中的依赖关系。
    • style-loader:将CSS代码注入到HTML文档中。
    • file-loader:解析文件路径,将文件赋值到输出目录,并返回文件路径。
    • url-loader:类似于file-loader,但是可以将小于指定大小的文件转成base64编码的Data URL格式
    • sass-loader:将Sass文件编译成CSS文件。
    • less-loader:将Less文件编译成CSS文件。
    • postcss-loader:自动添加CSS前缀,优化CSS代码等。
    • vue-loader:将Vue单文件组件编译成JavaScript代码。

3. Plugin它是一个插件,用于增强webpack功能

角色:Plugin 是 Webpack 的“插件”,可以扩展 Webpack 的功能,比如优化代码、生成 HTML 文件等。

例子

  • 开发文档编辑器时,你需要自动生成一个 index.html 并插入打包后的 bundle.js,可以使用 HtmlWebpackPlugin
  • 还需要优化文件大小,移除无用代码,使用 TerserPlugin
  • 常用的Plugin:
    • HtmlWebpackPlugin:生成HTML文件,并自动将打包后的javaScript和CSS文件引入到HTML文件中。
    • HotModuleReplacementPlugin:热模块替换,用于在开发环境下实现热更新。
    • MiniCssExtractPlugin:与ExtractTextWebpackPlugin类似,将CSS代码提取到单独的CSS文件中。
    • BundleAnalyzerPlugin:分析打包后的文件大小和依赖关系。

4.热更新(Hot Module Replacement, HMR)

角色:HMR 是 Webpack 的“热更新工具”,在代码改动后,自动刷新浏览器。

例子

  • 你修改了文档编辑器的代码,比如按钮样式,无需重新加载整个页面,HMR 可以局部刷新。

原理

  • HRM的原理实际上是 webpack-dev-server(WDS)和浏览器之间维护了一个websocket服务。当本地资源发生变化后,webpack会先将打包生成新的模块代码放入内存中,然后WDS向浏览器推送更新,并附带上构建时的hash,让客户端和上一次资源进行对比.(浏览器和 Webpack 开发服务器通过 WebSocket 通信,当代码发生变化时,Webpack 发送更新通知,浏览器局部替换模块。 )

5. Tree Shaking

角色:Tree Shaking 是 Webpack 的“优化员”,移除代码中未使用的部分。

例子

  • 你引入了一个大模块 lodash,但只用了其中的 merge 函数,Tree Shaking 会移除未使用的代码,减少打包体积。
  • 其工作原理在于:
  1. 当Webpack分析代码时,它会标记出所有的import语句和export语句。
  2. 然后,当Webpack确定某个模块没有被导入时,它会在生成的bundle中排除这个模块的代码。
  3. 同时,Webpack还会进行递归的标记清理,以确保所有未使用的依赖项都不会出现在最终的bundle中。

6. Code Splitting

角色:Code Splitting 是 Webpack 的“拆包员”,把代码分成多个文件,按需加载,提升性能。

例子

  • 文档编辑器有一个高级功能,比如“协作模式”,但普通用户不用,Code Splitting 可以把相关代码分成 collaboration.js,等用户点击时再加载。
  • Code Splitting代码分割,是一种优化技术。它允许将一个大的chunk拆分成多个小的chunk,从而实现按需加载,减少初始加载时间,并提高应用程序的性能 。在Webpack中通过optimization.splitChunks配置项来开启代码分割

7. Proxy

角色:Proxy 是 Webpack 开发服务器的“代理员”,可以转发请求到后端服务器。

例子

  • 文档编辑器需要调用后端的 API,比如保存文档,开发时你可以通过 Webpack DevServer 的 Proxy,把前端请求转发到后端。

8. Babel

角色:Babel 是 Webpack 的“语言翻译器”,将现代 JavaScript 代码转译为旧浏览器支持的版本。

例子

  • 你的文档编辑器使用了 ES6+ 语法,比如 async/await,但一些老旧浏览器不支持,需要 Babel 转译。
  • 原理
    • 解析:当 Babel 接收到源代码时,将会调用一个叫做解析器的工具,用于将源代码转换为抽象语法树(AST)。在这个过程中,解析器会识别代码中的语法结构,并将其转换为对应的节点类型。 例如,当解析器遇到一个变量声明语句时,它将会创建一个 “VariableDeclaration” 节点,并将该节点的信息存储在 AST 中。AST 是一个以节点为基础组成的树形结构,每个节点都有相应的类型、属性和子节点等信息。

    • 转换:一旦 AST 被创建,Babel 将遍历整个树形结构,对每个节点进行转换。这些转换可以是插件、预设或手动创建的。转换器会检查 AST 中的每个节点,然后对其进行相应的修改或替换,以将新语法转换为旧语法。 例如,如果 Babel 遇到一个包含箭头函数的节点,而你已经启用了转换插件,该插件将会将箭头函数转换为其等效的体函数。代码转换后,Babel 将会生成一个新的 AST。

    • 生成: 最后,Babel 将基于转换后的 AST 生成代码文本。在这个步骤中,Babel 将遍历转换后的 AST,并创建对应的代码字符串,并将这些字符串组合成一个完整的 JavaScript 文件。如果启用了代码压缩,Babel 还可以将生成的代码进行压缩。 总结来说,Babel 的原理就是将 JavaScript 源代码转换为抽象语法树(AST),然后对 AST 进行转换,生成与源代码功能相同但向后兼容的代码。Babel 提供了一个强大的生态系统,使得开发者可以轻松扩展并自定义转换器,实现自己的功能需求。

9. Rollup

角色:Rollup 是另一种打包工具,适合用于构建库(Library)。

例子

  • 如果你将文档编辑器的核心功能(比如文本处理)抽象为一个可复用的库,Rollup 更适合,因为它支持更好的 Tree Shaking 和模块优化。