webpack面试题
一、webpack热更新原理
1.使用 webpack-dev-server
(后面简称 WDS)托管静态资源、提供websocket服务
2.webpack打包时会将HMR Runtime和源代码一起编译成 bundle 文件。HMR Runtime主要 负责接收服务发送的websocket消息,处理模块更新。
3.当浏览器加载页面后,会与 WDS 建立 WebSocket 连接
4.后续源文件发生变动时,webpack会发生增量构建,生成2个文件,并通过 WebSocket 发送 hash
事件。
此阶段会生成两个文件
manifest
文件:JSON 格式文件,包含所有发生变更的模块列表,命名为[hash].hot-update.json
- 模块变更文件:js 格式,包含编译后的模块代码,命名为
[hash].hot-update.js
5.浏览器接收到 hash
事件后,发送ajax请求获取 manifest
资源文件,确认增量变更范围。
注意,在 Webpack 4 及之前,热更新文件以模块为单位,即所有发生变化的模块都会生成对应的热更新文件; Webpack 5 之后热更新文件以 chunk 为单位,如上例中,main
chunk 下任意文件的变化都只会生成main.[hash].hot-update.js
更新文件。
6.浏览器加载发生变更的增量模块,获取到更新内容后HMR触发变更模块的 module.hot.accept
回调,执行代码变更逻辑
二、webpack打包优化思路有哪些?
1. 不常变动的库,只构建一次
DllPlugin
和 DllReferencePlugin
插件,它们的主要作用是通过提前编译和缓存某些不经常改变的库。
DllPlugin
:用于创建一个“动态链接库(DLL)”的文件,该文件包含了第三方库(比如React
、Lodash
等)或者其他不常改变的代码。DllReferencePlugin
:在主应用的构建中引用已经用DllPlugin
构建的 DLL 文件,从而避免重复打包这些不常变化的第三方库。
首先,需要使用 DllPlugin
将不常变化的第三方库打包成一个 DLL 文件。这通常是在一个单独的构建配置中进行。
webpack.dll.config.js
:
const path = require('path'); module.exports = { entry: { vendor: ['react', 'react-dom', 'lodash'], // 这里是第三方库 }, output: { path: path.resolve(__dirname, 'dist'), filename: '[name].dll.js', library: '[name]_[hash]', // 为 DLL 文件指定一个全局变量名 }, plugins: [ new webpack.DllPlugin({ name: '[name]_[hash]', // 必须和 library 保持一致 path: path.join(__dirname, 'dist', '[name]-manifest.json'), // 生成的 manifest 文件 }), ], };
在这个配置中,第三方库 react
、react-dom
和 lodash
被打包成一个 DLL 文件,这样做的目的是将它们从主项目的打包中分离出来,避免重复打包。
使用时:
在主应用的 Webpack 配置中,使用 DllReferencePlugin
来引用之前生成的 DLL 文件。
const path = require('path'); const webpack = require('webpack'); module.exports = { entry: './src/index.js', // 入口文件 output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js', }, plugins: [ // 引用之前生成的 DLL 文件 new webpack.DllReferencePlugin({ context: __dirname, manifest: path.resolve(__dirname, 'dist', 'vendor-manifest.json'), // 指定对应的 manifest 文件 }), ], };
2. 使用构建缓存: webpack自带的缓存,以及babel缓存
Webpack 5 引入了持久化缓存功能,能够将构建的中间结果缓存起来,避免每次都重新编译,从而提高打包速度。
module.exports = { cache: { type: 'filesystem', // 使用文件系统缓存 }, };
babel-loader缓存
module: { rules: [ { test: /\.js$/, use: { loader: 'babel-loader', options: { cacheDirectory: true, // 启用缓存 }, }, }, ], }
3. 使用并行编译
Webpack 可以通过并行化构建过程来加速编译。启用多线程编译可以有效提升性能。
使用 thread-loader
来并行化构建过程:
const ThreadLoader = require('thread-loader'); module.exports = { module: { rules: [ { test: /\.js$/, use: ['thread-loader', 'babel-loader'], }, ], }, };
也可以通过 HappyPack
来并行化构建任务
4. 明确模块搜索范围
webpack 的 resolve.modules 用于配置 Webpack 去哪些目录下寻找第三方模块。
resolve.modules 的默认值是 [‘node_modules’],含义是先去当前目录下的 ./node_modules 目录下去找想找的模块,如果没找到就去上一级目录 ../node_modules 中找,再没有就去 ../../node_modules 中找,以此类推,这和 Node.js 的模块寻找机制很相似。
当安装的第三方模块都放在项目根目录下的 ./node_modules 目录下时,没有必要按照默认的方式去一层层的寻找,可以指明存放第三方模块的绝对路径.
5. 缩小文件后缀的数量
在导入语句没带文件后缀时,Webpack 会自动带上后缀后去尝试询问文件是否存在。 Webpack 配置中的 resolve.extensions 用于配置在尝试过程中用到的后缀列表,默认是:
extensions: ['.js', '.json']
也就是说当遇到 require(‘./data’) 这样的导入语句时,Webpack 会先去寻找 ./data.js 文件,如果该文件不存在就去寻找 ./data.json 文件,如果还是找不到就报错。
如果这个列表越长,或者正确的后缀在越后面,就会造成尝试的次数越多,所以 resolve.extensions 的配置也会影响到构建的性能。
6.使用include或exclude排除掉不需要处理的目录
将 node_modules
排除在 babel-loader
、ts-loader
等构建流程之外,避免不必要的转换。
{ test: /\.js$/, exclude: /node_modules/, // 排除 node_modules use: 'babel-loader', }
7. 分割代码(Code Splitting)
通过分割代码,可以避免一次性加载过多的代码,减少打包时间,并提高运行时性能。
动态导入(Lazy loading):