基于webpack的性能优化
基本优化方案
- 使用高版本的webpack和node
- 静态资源优化
- JavaScript文件压缩、合并、TreeShaking
- CSS文件压缩、优化、TreeShaking
- Img压缩、合并雪碧图:
- HTML文件压缩
- 上传CDN
- 使用prefetch、preload预加载资源文件
- preload:本次加载需要的资源,告诉浏览器,提高下载速度权重
- prefetch:未来可能使用到的资源文件,在浏览器空闲的时候下载
- 代码分割Code Spliting
- entry:入口文件手动分割
- splitChunks:optimization.splitChunks,将相同的代码抽离到一个单独的文件;也可以将第三方库文件抽离
- import():动态导入。
- 利用缓存
- 使用cache-loader,缓存每次打包之后的文件。
- 多核
- 使用happypack(已不维护)、thread-laoder、webpack-parallel-uglify-plugin等,开启多核执行。
- 分包加载:使用DllPlugin 和 DLLReferencePlugin
- 缩小搜索范围:使用exclude、include尽量缩小webapck的搜索范围。
- 配置resolve .extensions 尽量将优先级高的放在前面,缩短命中时间。
- 预渲染:prenderer-spa-plugin 将重要页面采用预渲染功能,有利于SEO。
- 服务端渲染:vue-server-render 在服务端将页面渲染完成,直接返回html页面给客户端。
- 同构:SSR + CSR,刷新SSR ,路由切换CSR。
静态资源优化
JavaScript
使用DllPlugin
和DLLReferencePlugin
,拆分bundles
。具体使用请看这一篇文章。也可以使用hard-source-webpack-plugin替换DllPlugin
和DLLReferencePlugin
。
生产环境webpack默认开启uglify-js
。
压缩js
optimization: {
minimizer: [
new UglifyJsPlugin({
uglifyOptions: {
mangle: {
safari10: true
},
// 清除生产环境的控制台输出
compress: {
warnings: false,
drop_debugger: true,
drop_console: true
}
},
sourceMap: config.build.productionSourceMap,
cache: true,
parallel: true
})
]
}
抽离公共js文件
splitChunks: {
chunks: "all",//进行代码分割的时候,all:针对所有的导入 async:只针对异步导入 initial:针对同步代码导入。
minSize: 30000,//设置最小阀值,只有大于该阀值,才会进行代码分割。
minChunks: 1,//在分割模块之前共享一个模块的最小块数(设置代码最少被引用次数)
maxAsyncRequests: 5,//按需加载时的最大并行请求数 超过就不会在做代码分割打包
maxInitialRequests: 3,//一个入口点的最大并行请求数 超过就不会做代码分割
automaticNameDelimiter: '~',//打包生成之后,默认情况下,webpack将使用块的来源和名称来生成名称,比如vendor~main.js。
name: true,//使得cacheGroups中打包生成的文件名称
cacheGroups: {//缓存组 打包分组
vendors: {//配置同步导入
test: /[\\/]node_modules[\\/]/,//只有node_module中的才会进入
priority: -10,//值越大 优先级越高
filename: 'vendors.js'
},
default: {
// minChunks: 2,
priority: 0,
reuseExistingChunk: true,// 如果模块已经被打包了,在此遇到的时候 直接忽略,直接使用以打包好的模块。
filename:'default.js'
}
}
}
关于代码分离
请移步https://www.yuque.com/walter-glskq/zf7grn/zcamxf
CSS
extract-text-webpack-plugin
和mini-css-extract-plugin
使用该插件将css文件从js中剥离出来,进行优化压缩。配合postcss-loader
进行css预处理和后处理。
//压缩css 并且将css从js抽离出来
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
//优化css
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
module: {
rules: [{
test: /\.css$/,
use: [{
loader: MiniCssExtractPlugin.loader,
options: {
esModule: true,
filename: '/css/[name].[hash:7].css'
},
}, 'css-loader', 'postcss-loader','less-loader']
}]
},
plugins:[
new MiniCssExtractPlugin({
filename: 'css/[name].[hash:5].css',
chunkFilename: 'css/[id].[hash:5].css',
ignoreOrder: false
}),
new OptimizeCssAssetsPlugin({
assetNameRegExp: /\.css$/g,
cssProcessor: require('cssnano'),
cssProcessorPluginOptions: {
preset: ['default', { discardComments: { removeAll: true } }],
},
canPrint: true
})
]
}
Img
使用file-loader和image-webpack-loader
处理图片文件,进行压缩。
module.exports = {
// ...
module: {
rules: [
{
test: /.*\.(gif|png|jpe?g|svg|webp)$/i,
use: [
{
loader: 'file-loader',
options: {}
},
{
loader: 'image-webpack-loader',
options: {
mozjpeg: { // 压缩 jpeg 的配置
progressive: true,
quality: 65
},
optipng: { // 使用 imagemin-optipng 压缩 png,enable: false 为关闭
enabled: false,
},
pngquant: { // 使用 imagemin-pngquant 压缩 png
quality: '65-90',
speed: 4
},
gifsicle: { // 压缩 gif 的配置
interlaced: false,
},
webp: { // 开启 webp,会把 jpg 和 png 图片压缩为 webp 格式
quality: 75
},
},
],
},
],
},
}
使用url-loader将一些小图片转为base64格式,减少http请求:
{
test: /\.(png|jpg|jpeg|gif)(\?.+)?$/,
exclude: /favicon\.png$/,
use: [{
loader: 'url-loader',
options: { //动态抽离img 给一个阀值 小于该阀值的 以base64的形式存在与js中
esModule: false,
limit: 10000,
name: 'image/[name].[hash:7].[ext]'
}
}]
}
压缩HTML
配置minify,优化html文件。生产环境生效。
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true,
title: 'admin',
minify: {
// 合并空格
collapseWhitespace: true,
// 移除注解
removeComments: true,
// 移除多余的属性
removeRedundantAttributes: true,
// 移除脚本类型属性
removeScriptTypeAttributes: true,
// 移除样式类型属性
removeStyleLinkTypeAttributes: true,
// 使用简短的文档类型
useShortDoctype: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
}
}),
使用DllPlugin和DLLReferencePlugin
- 使用
DllPlugin
和DLLReferencePlugin
:DLLPlugin
和DLLReferencePlugin
用某种方法实现了拆分bundles
,同时还大大提升了构建的速度。
包含大量复用模块的动态链接库只需被编译一次,在之后的构建过程中被动态链接库包含的模块将不会重新编译,而是直接使用动态链接库中 的代码 由于动态链接库中大多数包含的是常用的第三方模块。例如 vue、vue-router
,所以只要不升级这些模块的版本,动态链接库就不用重新编译。
优化Loader配置
由于 Loader
对文件的转换操作很耗时,所以需要让尽可能少的文件被 Loader
处理。可以通过 test
include
exclude
三个配置项来命中 Loader
要应用规则的文件。
module.exports = {
module : {
rules : [{
//如果项目源码中只有js文件,就不要写成/\jsx?$/,以提升正则表达式的性能
test: /\.js$/,
//babel -loader 支持缓存转换出的结果,通过 cacheDirectory 选项开启
use: ['babel-loader?cacheDirectory'] ,
//只对项目根目录下 src 目录中的文件采用 babel-loader
include: path.resolve(__dirname,'src'),
}],
}
}
优化 resolve.extensions 配置
在导入语句没带文件后缀时,Webpack 会自动带上后缀去尝试询问文件是否存在。如果这个列表越长,或者正确的后缀越往后,就会造成尝试的次数越多,所以resolve .extensions 的配置也会影响到构建的性能。
建议:
- 后缀尝试列表要尽可能小,不要将项目中不可能存在的情况写到后缀尝试列表中。
- 频率出现最高的文件后缀要优先放在最前面,以做到尽快退出寻找过程。
- 在源码中写导入语句时,要尽可能带上后缀 从而可以避免寻找过程
module.exports = {
resolve : {
//尽可能减少后缀尝试的可能性
extensions : ['js'],
}
}
优化打包加速
使用webpack-parallel-uglify-plugin
。webpack默认提供了UglifyJS插件来压缩JS代码,但是它使用的是单线程压缩代码,也就是说多个js文件需要被压缩,它需要一个个文件进行压缩。所以说在正式环境打包压缩代码速度非常慢(因为压缩JS代码需要先把代码解析成用Object抽象表示的AST语法树,再去应用各种规则分析和处理AST,导致这个过程耗时非常大)。当webpack有多个JS文件需要输出和压缩时候,原来会使用UglifyJS去一个个压缩并且输出,但是ParallelUglifyPlugin插件则会开启多个子进程,把对多个文件压缩的工作分别给多个子进程去完成,但是每个子进程还是通过UglifyJS去压缩代码。无非就是变成了并行处理该压缩了,并行处理多个子任务,效率会更加的提高。
// 引入 ParallelUglifyPlugin 插件
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
module.exports = {
plugins: [
// 使用 ParallelUglifyPlugin 并行压缩输出JS代码
new ParallelUglifyPlugin({
// 传递给 UglifyJS的参数如下:
uglifyJS: {
output: {
/*
是否输出可读性较强的代码,即会保留空格和制表符,默认为输出,为了达到更好的压缩效果,
可以设置为false
*/
beautify: false,
/*
是否保留代码中的注释,默认为保留,为了达到更好的压缩效果,可以设置为false
*/
comments: false
},
compress: {
/*
是否在UglifyJS删除没有用到的代码时输出警告信息,默认为输出,可以设置为false关闭这些作用
不大的警告
*/
warnings: false,
/*
是否删除代码中所有的console语句,默认为不删除,开启后,会删除所有的console语句
*/
drop_console: true,
/*
是否内嵌虽然已经定义了,但是只用到一次的变量,比如将 var x = 1; y = x, 转换成 y = 1, 默认为不
转换,为了达到更好的压缩效果,可以设置为false
*/
collapse_vars: true,
/*
是否提取出现了多次但是没有定义成变量去引用的静态值,比如将 x = 'xxx'; y = 'xxx' 转换成
var a = 'xxxx'; x = a; y = a; 默认为不转换,为了达到更好的压缩效果,可以设置为false
*/
reduce_vars: true
}
}
}),
]
}
在通过 new ParallelUglifyPlugin() 实列化时,支持以下参数配置如下:
test: 使用正则去匹配哪些文件需要被 ParallelUglifyPlugin 压缩,默认是 /.js$/.
include: 使用正则去包含被 ParallelUglifyPlugin 压缩的文件,默认为 [].
exclude: 使用正则去不包含被 ParallelUglifyPlugin 压缩的文件,默认为 [].
cacheDir: 缓存压缩后的结果,下次遇到一样的输入时直接从缓存中获取压缩后的结果并返回,cacheDir 用于配置缓存存放的目录路径。默认不会缓存,想开启缓存请设置一个目录路径。
workerCount:开启几个子进程去并发的执行压缩。默认是当前运行电脑的 CPU 核数减去1。
sourceMap:是否为压缩后的代码生成对应的Source Map, 默认不生成,开启后耗时会大大增加,一般不会将压缩后的代码的
sourceMap发送给网站用户的浏览器。
uglifyJS:用于压缩 ES5 代码时的配置,Object 类型,直接透传给 UglifyJS 的参数。
优化小插件
speed-measure-webpack-plugin
//添加打包时各种资源构建小时钟提示 有助于分析
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
module.exports = smp.wrap({})
webpack-bundle-analyzer
打包分析工具
npm install --save-dev webpack-bundle-analyzer
在webpack的plugins中配置: new BundleAnalyzerPlugin()
在package.json 中增加: "analyz": "NODE_ENV=production npm_config_report=true npm run build"
webpack-dashboard
更加直观的显示webpack的构建。
npm install webpack-dashboard --save-dev
const Dashboard = require('webpack-dashboard');
const DashboardPlugin = require('webpack-dashboard/plugin');
const dashboard = new Dashboard();
plugins: [
new DashboardPlugin(dashboard.setData)
]