之前使用webpack进行项目搭建配置时,都是操作一些基础的loader、基础插件配置与开发环境配置,没怎么考虑过打包配置。所以想整一下生产环境的配置,加深自己的理解。
当不进行任何打包配置时,使用的是production环境下webapck默认的配置。我将自己之前用webpack搭建的前端项目掏了出来,npm run build 进行打包,打出的包如下:
由图可以看出,打出的包中bundle.js文件很大。默认情况下所有的同步导入模块中的js代码会打包进bundle.js文件中,首页加载时会加载全部js代码,影响加载性能。下面对其进行优化。
代码分离的作用是将代码打包进不同的bundle中,使每个bundle文件的体积减小,可以实现按需加载,提升加载性能。
常见的代码分离方法有配置多入口、splitChunk与动态导入等实现方式。多入口配置相对简单,可以自行配置,我这里用不到,主要说一下后面两种方式。
有大佬具体分析过:https://juejin.cn/post/6844903891922862093
先说一下splitChunk分包模式,需要使用SplitChunksPlugin实现(webpack已内置此插件)。
此方法默认处理异步导入的import的模块,将使用到的公共模块进行拆分,将公共的chunk拆成一些小的chunks,供各异步模块使用,实现按需加载chunk。
在production环境下webpack已对其进行默认配置,可以不进行其它额外配置,也可以手动更改配置。在开发环境也可以进行相应的配置,大型项目推荐开发环境也进行配置。
相关的配置项有:
optimization: {
splitChunks: { // 这里一般不手动配置,最多平常只配置个 chunks: "all"
chunks: "async",
minSize: 20000,
maxSize: 20000,
minChunks: 1,
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/, //匹配规则,匹配模块是否属于node_modules文件夹中
filename: "[id]_vendors.js",
priority: -10 // 优先级
},
default: {
minChunks: 2, //匹配规则
filename: "common_[id].js",
priority: -20
}
}
},
}
搞完重新打包后发现哈哈哈没啥变化,bundle.js文件还是大,因为只处理了异步导入模块,不影响打包后的bundle.js文件。
配置runtime相关的代码是否抽取到一个单独的chunk中。抽离出来后,有利于浏览器缓存的策略。
这个production环境中应该有默认配置,懒得搞了随便设个name值。
optimization: {
runtimeChunk: {
name: 'runtime'
}
},
动态导入通常是一定会打包成独立的文件的,所以并不会再cacheGroups中进行配置。命名通常会在output中,通过 chunkFilename 属性来命名。
这里有个chunkIds配置,让webpack使用什么算法生成打包出的独立文件的id,一般使用webpack的默认值即可。开发环境下的默认值为named,生产环境下的默认值为deterministic。
output: {
chunkFilename: "js/[name].[contenthash:6].chunk.js",
},
代码分离一套流程搞完后发现体积还是没有减小,但是打包的产物文件变了。
下面接着处理css等样式资源,有以下处理流程:
主要使用mini-css-extract-plugin插件实现,将css提取到独立的css文件中,该插件需要在webpack4+才可以使用,且在生产环境才使用。
- 安装: npm install mini-css-extract-plugin -D
- 这里有个注意点,开发环境才会使用style-loader,生产环境要使用MiniCssExtractPlugin.loader,且需配合插件使用。
- 下面代码中的isProduction是我自己传的参数,如何判断开发环境还是生产环境根据项目自行处理。
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module: {
rules: [
{
test: /\.css$/,
use: [
isProduction ? MiniCssExtractPlugin.loader: "style-loader", // style-loader最好只在开发环境使用
{
loader: "css-loader",
options: {
importLoaders: 1
}
},
'postcss-loader'
]
},
]
},
// production环境下的配置
plugins: [
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({
filename: "css/[name].[contenthash:6].css"
}),
]
主要使用插件css-minimizer-webpack-plugin实现,但是功能有限,只能去除一些无用的空格等,很难去修改选择器、属性的名称、值等,也是在生产环境使用。
npm install css-minimizer-webpack-plugin -D
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
plugins: [
new CssMinimizerPlugin()
]
主要使用PurgeCSS插件来实现,用来删除未使用的CSS样式。
注意:这个方法慎用!我将产物部署到我的服务器上,发现部分样式缺失,给我删掉了
npm install purgecss-webpack-plugin -D
const PurgeCssPlugin = require('purgecss-webpack-plugin');
const glob = require('glob'); // webpack内置插件
plugins: [
new PurgeCssPlugin({
// 检测src文件夹下的所有文件夹中的所有文件,这是glob库中的固定写法
// {nodir: true} 表示不检测文件夹,只检测文件
paths: glob.sync(`${resolveApp("./src")}/**/*`, {nodir: true}),
safelist: function() {
return {
standard: ["body", "html"] // 保留html和body的样式
}
}
})
]
主要用来消除未调用的代码。
方式一:usedExports属性:通过标记某些函数是否被使用,之后通过Terser来进行优化的,这个在后面会讲到。还有这个生产环境webpack配了默认值了,一般不需手动修改。
还有一个方式可自行去了解,我不喜欢用。
可以压缩、丑化代码,让bundle(代码包)变得更小。在production模式下,默认就是使用TerserPlugin来处理我们的代码的,可以使用默认值,也可以手动配置。相关配置参数的含义可自行去了解。
局部安装: npm install terser
optimization: {
usedExports: true, // 通过标记某些函数是否被使用,之后通过Terser来进行优化
minimize: true, // 对js代码进行压缩,默认production模式下已经打开了
minimizer: [
// 由Terser将未使用的函数, 从我们的代码中删除
new TerserPlugin({
parallel: true,
extractComments: false, // 使用这个插件主要是删除build文件夹中的一个注释文件
terserOptions: {
output: {
comments: false, // 打包时去掉注释
},
compress: {
arguments: false,
dead_code: true,
pure_funcs: ['console.log'] // 打包时清除掉无用的console.log
},
mangle: true,
toplevel: true,
keep_classnames: true,
keep_fnames: true
}
})
]
},
打包成果物中有个注释文件,如果想去掉这个文件,可以配置 extractComments: false
主要是对作用域进行提升,并且让webpack打包后的代码更小、运行更快。
webpack打包会有很多的函数作用域,需要执行一系列的函数,scope Hoisting主要将函数合并到一个模块中来运行。
生产环境中该模块自启动,无需配置,开发环境可以自己配置。
const webpack = require('webpack');
plugins: [
new webpack.optimize.ModuleConcatenationPlugin()
]
上面优化流程全部走完了,发现打包优化的还是不行。我就只能在splitChunk上下功夫了。
还记得上面的配置中,splitChunks只是处理了异步导入模块吗。现在将splitChunks的chunks默认属性值改为all,就是不止对异步导入做处理了,同步与异步导入都做处理进行代码分离。再打包一次发现有效果了,每个bundle的体积都小了很多,但是还有一个文件警告,显示为538kib大小。我实在不知怎搞了,这个文件包分析也看不懂。
针对上面遗留的问题,想进行一个可视化的分析,主要使用webpack-bundle-analyzer插件,可以非常直观查看包大小与依赖关系。
npm install webpack-bundle-analyzer -D
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
new BundleAnalyzerPlugin()
添加完成配置后,我直接运行打包,发现报错,报错理由在网上搜了一下说是8888端口被占用。
因为这个插件是帮助我们打开一个8888端口上的服务,方便我们可以直接的看到每个包的大小。
网上的处理方式都是什么在任务管理器中杀死8888端口的服务,太凶残了,我选择给它换个端口使用。
new BundleAnalyzerPlugin({analyzerPort:7777})
然后重新打包后打包成功,并给我开启一个服务,展示包的依赖关系与体积大小。
从图上可看出那个最大的包是我引入的element-ui,我说我怎么解决不掉。这个原因是我直接将element-ui全部引入了,后面进行按需引入可以解决,就是使用到哪些控件就引入哪些控件。
对于webpack打包优化,感觉内容很多也很杂,这里做一下总结: