Webpack面试题

本文最后更新于 2025年10月31日 中午

Webpack 是什么

Webpack 是一种用于构建 JavaScript 应用程序的静态模块打包器,它能够以一种相对一致且开放的处理方式,加载应用中的所有资源文件(图片、CSS、视频、字体文件等),并将其合并打包成浏览器兼容的 Web 资源文件。

  • 模块打包。可以将不同模块的文件打包整合在一起,并且保证它们之间的引用正确,执行有序
  • 编译兼容。通过 Webpack 的 Loader 机制,不仅仅可以对代码做 polyfill,还可以编译转换诸如 .less.vue.jsx 这类在浏览器无法识别的格式文件,帮助开发者使用新特性和新语法,提高开发效率
  • 能力扩展。通过 Webpack 的 Plugin 机制,在实现模块化打包和编译兼容的基础上,可以进一步实现如按需加载、代码压缩等系列功能,进一步提高自动化程度,工程效率以及打包输出的质量

Loader 和 Plugin

功能不同:

  • Loader 是函数,在该函数中对接收到的内容进行转换返回转换后的结果。因为 Webpack 只认识 JavaScript,所以 Loader 就成了翻译官,对其他类型的资源进行转译的预处理工作
  • Plugin 是插件,基于事件流框架 Tapable,插件可以扩展 Webpack 的功能,在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果

用法不同:

  • Loader 在 module.rules 中配置,作为模块的解析规则,类型为数组。每一项都是一个 Object,内部包含了 test(类型文件)、loader、options (参数)等属性
  • Plugin 在 plugins 中单独配置,类型为数组,每一项是一个 Plugin 示例,参数通过构造函数传入

常见 Loader

参考

官网

  • 文件

    • val-loader:将代码作为模块执行,并将其导出为 JS 代码
    • ref-loader:用于手动建立文件之间的依赖关系
  • JSON

    • cson-loader:加载并转换 CSON 文件
  • 语法转换

    • babel-loader:把 ES6 转换成 ES5
    • buble-loader:使用 Bublé 加载 ES2015+ 代码并将其转换为 ES5
    • traceur-loader:使用 Traceur 加载 ES2015+ 代码并将其转换为 ES5
    • ts-loader:像加载 JavaScript 一样加载 TypeScript 2.0+
    • coffee-loader:像加载 JavaScript 一样加载 CoffeeScript
    • fengari-loader:使用 fengari 加载 Lua 代码
    • elm-webpack-loader:像加载 JavaScript 一样加载 Elm
  • 模板

    • html-loader:将 HTML 导出为字符串,需要传入静态资源的引用路径
    • pug-loader:加载 Pug 和 Jade 模板并返回一个函数
    • markdown-loader:将 Markdown 编译为 HTML
    • react-markdown-loader:使用 markdown-parse 解析器将 Markdown 编译为 React 组件
    • posthtml-loader:使用 PostHTML 加载并转换 HTML
    • handlebars-loader:将 Handlebars 文件编译为 HTML
    • markup-inline-loader:将 SVG/MathML 文件内嵌到 HTML,对字体或动画用 SVG 非常实用
    • twig-loader:编译 Twig 模板并返回一个函数
    • remark-loader:通过 remark 加载 markdown,且支持解析内容中的图片
  • 样式

    • style-loader:将模块导出的内容作为样式并添加到 DOM 中,通过 DOM 操作去加载 CSS
    • css-loader:加载 CSS 文件并解析 import 的 CSS 文件,最终返回 CSS 代码
    • less-loader:加载并编译 LESS 文件
    • sass-loader:加载并编译 SASS/SCSS 文件
    • postcss-loader:使用 PostCSS 加载并转换 CSS/SSS 文件
    • stylus-loader:加载并编译 Stylus 文件
  • 框架

    • vue-loader:加载并编译 Vue 组件
    • angular2-template-loader:加载并编译 Angular 组件
  • 第三方

    • awesome-typescript-loader:将 TypeScript 转换成 JavaScript,性能优于 ts-loader
    • eslint-loader:通过 ESLint 检查 JavaScript 代码
    • tslint-loader:通过 TSLint 检查 TypeScript 代码
    • mocha-loader:加载 Mocha 测试用例的代码
    • coverjs-loader:计算测试的覆盖率
    • i18n-loader: 国际化
    • cache-loader: 可以在一些性能开销较大的 Loader 之前添加,目的是将结果缓存到磁盘里

总结

  • babel-loader:把 ES6 转换成 ES5
  • html-loader:将 HTML 导出为字符串,需要传入静态资源的引用路径
  • eslint-loader:通过 ESLint 检查 JavaScript 代码
  • tslint-loader:通过 TSLint 检查 TypeScript 代码
  • i18n-loader: 国际化
  • style-loader:将模块导出的内容作为样式并添加到 DOM 中,通过 DOM 操作去加载 CSS
  • css-loader:加载 CSS 文件并解析 import 的 CSS 文件,最终返回 CSS 代码
  • less-loader:加载并编译 LESS 文件
  • sass-loader:加载并编译 SASS/SCSS 文件

常见 Plugin

参考

官网

  • define-plugin:定义环境变量
  • ignore-plugin:忽略部分文件
  • html-Webpack-plugin:简化 HTML 文件创建 (依赖于 html-loader)
  • web-Webpack-plugin:可方便地为单页应用输出 HTML,比 html-Webpack-plugin 好用
  • uglifyjs-Webpack-plugin:不支持 ES6 压缩 (Webpack4 以前)
  • terser-Webpack-plugin: 支持压缩 ES6 (Webpack4)
  • Webpack-parallel-uglify-plugin: 多进程执行代码压缩,提升构建速度
  • mini-css-extract-plugin: 分离样式文件,CSS 提取为独立文件,支持按需加载
  • serviceworker-Webpack-plugin:为网页应用增加离线缓存功能
  • clean-Webpack-plugin: 目录清理
  • ModuleConcatenationPlugin: 开启 Scope Hoisting
  • speed-measure-Webpack-plugin: 可以看到整个打包耗时、每个 Loader 和 Plugin 执行耗时
  • Webpack-bundle-analyzer: 可视化 Webpack 输出文件的体积 (业务组件、依赖第三方模块)

总结

  • splitChunkPlugin: 用于代码分割
  • webpack-merge: 提取公共配置,用于分别编写不同环境的配置文件
  • speed-measure-webpack-plugin: 分析打包过程中的 Loader 和 Plugin 的耗时,用于性能分析
  • terser-webpack-plugin: 实现更精细的代码压缩功能
  • UnusedWebpackPlugin: 反向查找项目中没被用到的文件,可在重构或者性能分析时使用
  • Webpack Analysis: 官方提供的可视化分析工具
  • BundleAnalyzerPlugin: 性能分析插件,可以在运行后查看是否包含重复模块/不必要模块等
  • webpack-dashboard: 一个命令行可视化工具,能在编译过程中实时展示编译进度、模块分布、产物信息等相关信息,性能分析时很有用

如何编写 Loader

Loader 支持链式调用,所以开发上需要严格遵循“单一职责”:每个 Loader 只负责需要负责的事。API 可以去 官网查阅

  • Loader 运行在 Node.js 中,可以调用任意 Node.js 自带的 API 或者安装第三方模块进行调用
  • Webpack 传给 Loader 的原内容都是 UTF-8 格式编码字符串,当某些场景下 Loader 处理二进制文件时,需要通过 exports.raw = true 告诉 Webpack 该 Loader 是否需要二进制数据
  • 尽可能的异步化 Loader,如果计算量很小,同步也可以
  • Loader 是无状态的,我们不应该在 Loader 中保留状态
  • 使用 loader-utils 和 schema-utils 为我们提供的实用工具
  • 加载本地 Loader 方法:Npm link、ResolveLoader

如何编写 Plugin

Webpack 在运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在特定的阶段钩入想要添加的自定义功能。Tapable 事件流机制保证了插件的有序性,使得整个系统扩展性良好。API 可以去 官网查阅

  • compiler 暴露了和 Webpack 整个生命周期相关的钩子
  • compilation 暴露了与模块和依赖有关的粒度更小的事件钩子
  • 插件需要在其原型上绑定 apply 方法,才能访问 compiler 实例
  • 传给每个插件的 compiler 和 compilation 对象都是同一个引用,若在一个插件中修改它们身上的属性,会影响后面的插件
  • 找出合适的事件点去完成想要的功能
    • emit 事件发生时,可以读取到最终输出的资源、代码块、模块及其依赖,并进行修改(emit 事件是修改 Webpack 输出资源的最后时机)
    • watch-run 当依赖的文件发生变化时会触发
  • 异步的事件需要在插件处理完任务时调用回调函数通知 Webpack 进入下一个流程,不然会卡住

Loader 写过一个简单的删除 console.log,另一个是进阶版的 Banner Loader,可以在文件开头自动加上版权信息以及构建时间。Plugin 写过一个统计构建耗时的插件,在 compile 钩子记录开始时间,在 done 钩子输出总耗时。通过这类插件,可以直观帮助团队优化构建速度。我对 Loader 的理解是“专注文件内容转换”,对 Plugin 的理解是“基于生命周期扩展构建能力”。

Tree shaking 原理

Tree Shaking 是一个利用 ES6 模块静态结构特性来去除生产环境下不必要代码的优化过程。原理:

  • 当 Webpack 分析代码时,它会标记出所有的 import 语句和 export 语句
  • 当 Webpack 确定某个模块没有被导入时,它会在生成的 bundle 中排除这个模块的代码
  • Webpack 还会进行递归的标记清理,以确保所有未使用的依赖项都不会出现在最终的 bundle 中

确保使用的是 ES6 模块语法(即 import 和 export),因为只有这样才能让 Tree Shaking 发挥作用。为了启用 Tree Shaking,需要在配置文件中添加如下设置:

1
2
3
4
5
6
7
8
9
javascriptmodule.exports = {
// ...
optimization: {
usedExports: true,
concatenateModules: true,
minimize: true,
},
// ...
};

Babel 原理

Babel 可以将代码转译为想要的目标代码,并且对目标环境不支持的 api 自动 polyfill。实现这些功能流程是解析-转换-生产三步:

  • 解析:根据代码生成对应的 AST 结构
    • 词法分析:将代码(字符串)分割为 token 流,即语法单元成的数组
    • 语法分析:分析 token 流(上面生成的数组)并生成 AST
  • 转换:遍历 AST 节点并生成新的 AST 节点
    • Taro 就是利用 babel 完成的小程序语法转换
  • 生成:根据新的 AST 生成目标代码

文件监听原理

文件监听是在发现源码发生变化时,自动重新构建出新的输出文件。开启监听模式,有两种方式:

  • 启动 Webpack 命令时,带上 --watch 参数
  • 在配置 Webpack.config.js 中设置 watch: true

原理:轮询判断文件的最后编辑时间是否变化,如果某个文件发生了变化,并不会立刻告诉监听者,而是先缓存起来,等 aggregateTimeout 后再执行。缺点:每次需要手动刷新浏览器。配置如:

1
2
3
4
5
6
7
8
9
10
11
12
13
module.export = {
// 默认false,也就是不开启
watch: true,
// 只有开启监听模式时,watchOptions才有意义
watchOptions: {
// 默认为空,不监听的文件或者文件夹,支持正则匹配
ignored: /node_modules/,
// 监听到变化发生后会等300ms再去执行,默认300ms
aggregateTimeout: 300,
// 判断文件是否发生变化是通过不停询问系统指定文件有没有变化实现的,默认每秒问1000次
poll: 1000,
},
};

热更新原理

Webpack 的热更新又称热替换(Hot Module Replacement),缩写为 HMR。这个机制可以做到不用人为手动刷新浏览器,就可以将旧的模块变更为新的模块。通过设置 devServer: {hot: true} 开启热更新,开启后便可以在发生改变后局部刷新改变的部分。

HMR 的核心是客户端从服务端拉取更新后的文件,准确来说是 chunk diff(chunk 需要更新部分),实际上 WDS 与浏览器之间维护了一个 Websocket,当本地资源变化时,webpack-dev-server(WDS)会向浏览器推送更新,并带上构建时的 hash 让客户端与上一次资源进行对比。浏览器对比出差异后会向 WDS 发 Ajax 请求来获取更改内容(文件列表、hash),这样客户端就可以再借助这些信息继续向 WDS 发起 jsonp 请求获取该 chunk 的增量更新。

后续(拿到增量更新之后如何处理、哪些状态该保留、哪些又需要更新)由 HotModulePlugin 完成,提供了相关 API 以供开发者针对自身场景进行处理, react-hot-loader 和 vue-loader 都是借助这些 API 实现 HMR。

什么是文件指纹

文件指纹是指文件打包后的一连串后缀。作用:

  • 版本管理: 在发布版本时,通过文件指纹来区分修改的文件和未修改的文件
  • 使用缓存: 浏览器通过文件指纹是否改变来决定使用缓存文件还是请求新文件

什么是 Source map

Source Map 是一种文件,它建立了构建后的代码与源代码之间的映射关系。通常开发阶段开启用来调试代码,帮助找到代码问题所在。在配置文件的 devtool 中指定 devtool: 'source-map' 开启。

Code Splitting

Code Splitting 代码分割,是一种优化技术。它允许将一个大的 chunk 拆分成多个小的 chunk,从而实现按需加载,减少初始加载时间,并提高应用程序性能。通过 optimization.splitChunks 配置项来开启代码分割。

构建流程

主要包括以下几个步骤:

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

这个流程是一个串行的过程,Webpack 的运行流程是一个串行的过程,它的工作流程就是将各个插件串联起来。在运行过程中会广播事件,插件只需要监听好它所关心的事件,就能加入到这条 Webpack 机制中,去改变 Webpack 的运作,使得整个系统扩展性良好。

提高构建速度

  • 利用缓存:利用 Webpack 的持久缓存功能,避免重复构建没有变化的代码
  • 使用多进程/多线程构建:使用 thread-loader、happypack 等插件可将过程分为多个进程或线程
  • 使用 DllPlugin:可以将第三方库预先打包成单独的文件,减少构建时间
  • 使用 HardSourceWebpackPlugin:可以缓存中间文件,加速后续构建过程
  • 移除不必要的插件:移除不必要的插件和配置,避免不必要的复杂性和性能开销

减少构建体积

  • 代码分割(Code Splitting):将应用程序的代码划分为多个代码块,按需加载
  • 使用 Tree Shaking:配置 Tree Shaking 机制,去除未使用的代码,减小生成的文件体积
  • 压缩代码:使用工具如 UglifyJS 或 Terser 来压缩 JavaScript 代码
  • 使用生产模式:在 Webpack 中使用生产模式,通过设置 mode: 'production' 来启用优化
  • 使用压缩工具:使用现代的压缩工具,如 Brotli 和 Gzip,来对静态资源进行压缩
  • 利用 CDN 加速:将静态资源路径修改为 CDN 上的路径,减少图片、字体等静态资源等打包

有哪些配置


Webpack面试题
https://xuekeven.github.io/2021/09/25/Webpack面试题/
作者
Keven
发布于
2021年9月25日
更新于
2025年10月31日
许可协议