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 为单位,如上例中,mainchunk 下任意文件的变化都只会生成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):

浙公网安备 33010602011771号