Optimize the construction speed of nextjs webpack

使用工具

speed-measure-webpack-plugin

测算打包过程中各plugin,loader所耗费的时间

webpack-bundle-analyzer

分析打包出来的各资源大小,方便对过大的包进行优化

提速方案

cache-loader

效果

image.png

image.png

image.png

原理

image.png
image.png

公共模块提取

autodll-webpack-plugin

dll全称dynamic link library,是微软率先提出的概念,译作动态链接库。
分别通过两个plugin来运行
dllPlugin
进行构建资源文件,生成一个mainfest.json字典,字典里存储模块与id的映射
dllRefrencePlugin
通过id对模块进行引入
autodll-webpack-plugin是对两个插件的整合,减少使用的复杂度。

原理

资源加载方式

配置
mainfest.json

vendor
vendor不太好辨认,我们可以粘入控制台

image.png
其他模块同理,输入模块索引,获取对应模块。

打包缓存方式

package.json.hash

package.json

image.png
validateCache.js

  1. 缓存目录下autodll-webpack-plugin内存在production_instance_0_cda54207bafebb237a6e726378cf1b00目录
  2. 存在packge.json.hash
  3. 当前packge.jsonpackge.json.hash对比无有变化

若不满足上述条件,就会重新写入packge.json.hash

对比dll方式和spiltChunks方式

可参考这个问题
webpack-common-chunks-plugin-vs-webpack-dll-plugin

因为在模块打包方式上,dll需要手动声明需要打入一起的模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const dllLibs = [
'react',
'react-dom',
'scheduler',
'lottie-web',
'html2canvas',
'lodash',
'@xyz/antd-mobile',
'dayjs'
]
new AutoDllPlugin({
inject: true, // will inject the DLL bundle to index.html
debug: true,
filename: '[name]_[hash].js',
path: './dll',
context: __dirname,
entry: {
vendor: dllLibs
}
})

spiltChunks可以自由定义模块提取的最小引用最小体积,以及正则匹配,功能更加强大智能。
(需要webpack4及以上才能使用,低版本可用commonChunkPlugin替代)
image.png

模块分布

优化前
image.png
优化后
image.png

资源体积大小

优化前
image.png
优化后
image.png

打包耗时

优化前
image.png
优化后
image.png

接入方式

因为nextjs内置了webpack打包脚本,所以我们要在外侧复写

内置配置 image.png

复写

next.config.js中进行配置
image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// customWebpack

/**
* @title: webpack.extend.js
* @projectName fe-xyz-wap-next
* @description: webpack 扩展配置
* @author zhangyunpeng0126
* @date 2022/3/29:37
*/
const path = require('path')

module.exports = (config, {isServer}) => {
const isDevelopment = process.env.NODE_ENV === 'development'

let splitChunksConfig
const splitChunksConfigs = {
dev: {cacheGroups: {default: false, vendors: false}},
prod: {
chunks: 'all',
cacheGroups: {
default: false,
vendors: false,
commons: {name: 'commons', chunks: 'all', minChunks: 10},
react: {
name: 'commons',
chunks: 'all',
test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/
},
},
},
}

if (isDevelopment) {
splitChunksConfig = splitChunksConfigs.dev
} else {
splitChunksConfig = splitChunksConfigs.prod
}

config.optimization = {
...config.optimization,
splitChunks: isServer ? false : splitChunksConfig,
}


config.module.rules = config.module.rules.filter(
(item) => `${item.test}` !== '/\\.(jpe?g|png|svg|gif|ico|webp)$/',
)


config.module.rules.push({
test: /\.(jpe?g|png|svg|gif|ico|webp)$/,
use: [
'cache-loader',
{
loader: 'url-loader',
options: {
limit: 2048,
fallback: 'file-loader',
publicPath: `/_next/static/images/`,
outputPath: `${isServer ? '../' : ''}static/images/`,
name: '[name]-[hash].[ext]',
},
},
],
})
config.resolve.alias = {
...config.resolve.alias,
'@/components': path.resolve(__dirname, '.', 'src/components'),
'@/utils': path.resolve(__dirname, '.', 'src/utils'),
'@/models': path.resolve(__dirname, '.', 'src/models'),
'@/requests': path.resolve(__dirname, '.', 'src/requests'),
'@/assets': path.resolve(__dirname, '.', 'src/assets'),
}
return config
}

测试

ci打包速度测试

image.png

可用性测试

增量

image.png

image.png

存量更新

改变标签颜色
image.png)image.png

其他优化思路

提升依赖查找速度

webpack对模块的搜索本质上是遍历资源树

提升loader解析速度

loader要对对应扩展名的文件进行解析,遍历,替换,例如babel-loader

提高插件处理速度

插件对代码的压缩和混淆,cpu会是速度的瓶颈

好消息是next内置配置都从这三个方面进行了优化,让笔者轻松了不少。

知识拓展

IgnorePlugin

一个可忽略包中某些资源的库,比如moment.js中的时区信息
image.png
方式是使用正则匹配

modules-with-no-loaders

使用speed-measure-webpack-plugin时发现有些模块未被loader处理
image.png
因为存在loader需要exclude的模块,比如node_modules里的三方包,已经经过相应loader转换,我们不必再进行处理,这样可以提高loader转换速度,是正常的
如果modules-with-no-loaders耗时较长,可能是引入的库比较多导致的。

查看评论