通关webpack5

53 阅读10分钟

概念理解

webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容。

module bundle chunk

Module 是代码的最基本单元,代表着一个个独立的文件或模块,JS文件、CSS文件或者其他任何类型的资源。

Bundle 是将多个源代码文件(如 JavaScript、CSS 或其他资源文件)组合成一个或多个输出文件的过程。这些输出文件可以是单个最终的应用程序文件,也可以是拆分成更小的代码块,以便于按需加载和懒加载等优化。

Chunk 是由打包过程生成的较小份代码,它是一个独立的代码片段,可以按需加载和执行。这样可以避免加载整个应用程序的所有代码,从而实现性能优化。在 Vite 中,当使用代码分割(Code Splitting)功能时,会将代码分解成多个 chunk 文件。

image.png


入门使用

webpack.docschina.org/guides/

构建流程

Webpack 的构建流程可以分为以下几个主要步骤:

  1. 初始化参数:从配置文件和 Shell 语句中读取并合并参数,得出最终的配置。
  2. 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译。
  3. 确定入口:根据配置中的 entry 找出所有的入口文件。
  4. 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,递归地进行编译处理。
  5. 完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系。
  6. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表。
  7. 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。

在 Webpack 中,常用的 Loader 包括但不限于以下几种:

  1. babel-loader:用于将 ES6+ 代码转换为 ES5 代码,以便在旧版浏览器中运行。
  2. css-loader:用于处理 CSS 文件,使其能够被 Webpack 打包。
  3. style-loader:将 CSS 代码注入到 DOM 中,通常与 css-loader 一起使用。
  4. sass-loader:用于将 SCSS/SASS 文件编译为 CSS。
  5. file-loader:用于处理文件(如图片、字体等),并将其输出到构建目录。
  6. url-loader:类似于 file-loader,但可以将文件转换为 base64 URL,适用于小文件。
  7. ts-loader:用于将 TypeScript 代码编译为 JavaScript。
  8. eslint-loader:在打包前对 JavaScript 代码进行 lint 检查。
  9. postcss-loader:用于处理 CSS,通常与 autoprefixer 等插件一起使用,以添加浏览器前缀。
  10. html-loader:用于处理 HTML 文件,通常用于处理模板文件。

Webpack 中常用的 Plugin 包括:

  1. HtmlWebpackPlugin:自动生成 HTML 文件,并自动注入打包后的资源。
  2. MiniCssExtractPlugin:将 CSS 提取到单独的文件中,而不是嵌入到 JS 文件中。
  3. CleanWebpackPlugin:在每次构建前清理输出目录。
  4. DefinePlugin:允许在编译时创建全局常量。
  5. HotModuleReplacementPlugin:启用模块热替换(HMR)。
  6. CopyWebpackPlugin:将文件或目录复制到构建目录。
  7. CompressionWebpackPlugin:对资源进行 Gzip 压缩。
  8. BundleAnalyzerPlugin:生成打包分析报告,帮助优化打包体积。
  9. TerserPlugin:用于压缩 JavaScript 代码。
  10. ProvidePlugin:自动加载模块,而不必到处 import 或 require。

Webpack 的构建结果优化方法主要包括以下几个方面:

  1. 代码分割(Code Splitting)

    • 使用 SplitChunksPlugin 将公共代码提取到单独的 chunk 中,避免重复打包。
    • 使用动态导入(Dynamic Imports)按需加载模块,减少初始加载时间。
  2. Tree Shaking

    • 通过 ES6 模块语法(import/export)和 Webpack 的 mode: 'production' 配置,移除未使用的代码。
  3. 压缩代码

    • 使用 TerserPlugin 压缩 JavaScript 代码。
    • 使用 CssMinimizerPlugin 压缩 CSS 代码。
  4. 缓存

    • 使用 cache 配置启用持久化缓存,加速二次构建。
    • 使用 contenthash 或 chunkhash 生成文件名,利用浏览器缓存。
  5. 优化资源加载

    • 使用 file-loader 或 url-loader 处理图片、字体等资源,减少 HTTP 请求。
    • 使用 image-webpack-loader 压缩图片。
  6. 减少打包体积

    • 使用 externals 配置排除第三方库,通过 CDN 引入。
    • 使用 bundle analyzer 分析打包结果,优化依赖。
  7. 优化构建速度

    • 使用 thread-loader 或 happypack 多线程构建。
    • 使用 DllPlugin 预编译不常变动的库。

提升构建速度

  1. 使用 speed-measure-webpack-plugin 插件:测量每个插件和 loader 的耗时,找出性能瓶颈。
  2. 使用 cache-loader 或 babel-loader 的缓存功能:缓存编译结果,避免重复编译。
  3. 使用 thread-loader:将耗时的 loader 放在多线程中执行,提升构建速度。
  4. 使用 DllPlugin 和 DllReferencePlugin:将不常变动的库提前打包,减少重复构建。
  5. 合理配置 resolve:减少模块查找时间,如设置 extensions 和 alias
  6. 使用 HardSourceWebpackPlugin:为模块提供中间缓存,加速二次构建。
  7. 减少 babel-loader 的编译范围:通过 exclude 或 include 缩小编译范围。
  8. 使用 terser-webpack-plugin 的多线程压缩:提升代码压缩速度。
  9. 优化 devtool 配置:在开发环境中使用 eval 或 cheap-module-eval-source-map,减少 source map 生成时间。
  10. 使用 webpack-bundle-analyzer:分析打包体积,优化依赖。

webpack热更新内部流程

  • 监听文件变化:Webpack 使用 webpack-dev-serverwebpack-dev-middleware 监听源文件的变化。

  • 重新编译模块:当检测到文件变化时,Webpack 只重新编译受影响的模块,而不是整个项目。

  • 发送更新信息:通过 WebSocket 将更新信息(如模块 ID)发送给浏览器。

  • 应用更新:浏览器端的 HMR 客户端接收到更新信息后,使用 webpack/hot/dev-server 提供的 API 应用新的模块代码。

  • 模块更新与状态保持:如果模块支持 HMR(即实现了对应的接口),它可以在更新时保持其状态,否则会触发页面刷新。

image.png

HMR原理原文地址

Vite 的 HMR 基本流程

  1. 原生 ES 模块:Vite 利用浏览器支持的 ES 模块,避免了打包步骤,加快了开发启动速度。

  2. 文件变化监听:使用 chokidar 监听文件变化。

  3. 模块重载:当检测到文件变化时,Vite 仅重新加载受影响的模块,通过 WebSocket 通知浏览器。

  4. 浏览器更新:浏览器端接收到更新信息后,利用原生 ES 模块的动态 import 功能重新加载模块,保持应用状态。

Treeshaking

Tree Shaking 是一种通过 静态分析 消除 JavaScript 项目中未使用代码(Dead Code)的优化技术。其核心思想是基于 ES6 模块的静态特性(import/export),通过构建依赖图谱并标记未使用的导出项,最终在打包时将其移除,从而减小最终输出文件的体积。

Tree Shaking 的实现原理 (1)依赖 ES6 模块的静态特性 Tree Shaking 依赖于 ES6 模块的静态语法(import/export),因为这些语法在编译阶段就能明确模块的依赖关系。例如:

// math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;

// main.js
import { add } from './math';
console.log(add(1, 2)); // 仅使用 add,subtract 会被标记为未使用

关键特性:

  • 静态导入导出:依赖关系在编译时确定,而非运行时。
  • 无副作用:未使用的代码不会影响程序行为,可安全删除。

(2)静态分析与死代码标记 打包工具(如 Webpack、Rollup)会通过以下步骤实现 Tree Shaking:

  1. 依赖图谱构建:分析模块间的 import/export 关系,构建依赖树。

  2. 未使用导出标记:通过遍历依赖图,识别未被引用的导出项。

  3. 代码消除:在压缩阶段(如 Terser)移除标记的代码。

示例:

// utils.js
export const log = () => console.log("This will be removed!");
export const greet = () => console.log("Hello, world!");

// app.js
import { greet } from './utils';
greet(); // log 会被标记为未使用

(3)与传统 DCE 的区别 传统 DCE(Dead Code Elimination)关注 不可达代码(如 if (false) { ... }),而 Tree Shaking 更关注 未被引用的代码。例如:

// 传统 DCE 可识别并删除
if (false) {
  console.log("Dead code");
}

// Tree Shaking 可识别并删除
import { unusedFunction } from './module'; // 未被使用

原文

源码解读

image.png

webpack核心完成了 「内容转换 + 资源合并」 两种功能,实现上包含三个阶段:

  1. 初始化阶段:「初始化参数」:从配置文件、 配置对象、Shell 参数中读取,与默认配置结合得出最终的参数**「创建编译器对象」:用上一步得到的参数创建 Compiler 对象「初始化编译环境」:包括注入内置插件、注册各种模块工厂、初始化 RuleSet 集合、加载配置的插件等「开始编译」:执行 compiler 对象的 run 方法「确定入口」**:根据配置中的 entry 找出所有的入口文件,调用 compilition.addEntry 将入口文件转换为 dependence 对象
  2. 构建阶段:「编译模块(make)」:根据 entry 对应的 dependence 创建 module 对象,调用 loader 将模块转译为标准 JS 内容,调用 JS 解释器将内容转换为 AST 对象,从中找出该模块依赖的模块,再 递归 本步骤直到所有入口依赖的文件都经过了本步骤的处理**「完成模块编译」**:上一步递归处理所有能触达到的模块后,得到了每个模块被翻译后的内容以及它们之间的 「依赖关系图」
  3. 生成阶段:「输出资源(seal)」:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会**「写入文件系统(emitAssets)」**:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统

源码概述 github.com/gweid/webpa…

面试题

webpack5+vue3+ts+代码规范构建企业级前端项目

需要做什么配置

base配置:

  • 配置入口文件

  • 配置出口文件

  • 配置alias 别名

  • 配置extensions 引入模块时不带文件后缀时

  • vue的loader

  • ts的打包规则

  • css的打包规则

  • postcss的打包规则, 给浏览器css加前缀

  • less的打包规则

  • 图像的打包规则

  • 媒体资源的打包规则

  • 字体的打包规则

  • 构建好的静态资源都引入到一个html文件

  • 兼容各系统的设置环境变量的包、并注入到业务代码里面去

  • 低版本js兼容垫片和babel.config.js配置

  • externals: 外包拓展,打包时会忽略配置的依赖,会从上下文中寻找对应变量。

  • module.noParse: 匹配到设置的模块,将不进行依赖解析,适合jquery,boostrap这类不依赖外部模块的包

  • ignorePlugin: 可以使用正则忽略一部分文件,常在使用多语言的包时可以把非中文语言包过滤掉

  • 开发环境: 启动服务器、热更新、source-map

  • 生产环境:打包持久化缓存

  • 生产环境:生产环境打包的时候把public下内容复制到构建出口文件夹

  • 生产环境:抽取css样式文件

  • 生产环境:压缩js

  • 生产环境:配置打包文件hash-js、css、图片媒体资源

  • 生产环境:代码分割第三方包和公共模块

  • 生产环境:tree-shaking清理未引用js、css

还可以加:

  • 缩小loader作用范围
  • 缩小模块搜索范围
  • 构建耗时分析
  • 分析webpack打包后文件的插件
  • 多线程loader
  • 生产环境:打包时生成gzip文件

juejin.cn/post/724677…