文章目录
- 前言
- 如何抽离压缩css文件(dev prod)
- 抽离公共代码和第三方代码(prod)
- 实现异步加载JS文件(dev prod)
- babel开启缓存(dev prod)
- IgnorePlugin忽略打包第三方库无用代码(dev prod)
- noParse不重复打包第三方库(dev prod)
- happyPack多线程打包(dev prod)
- parallelUglifyPlugin多线程压缩(prod)
- 热更新(dev)
- DllPlugin打包dll文件,提升热刷新速度(dev)
- base64 (dev prod)
- 加哈希值(dev prod)
- CDN优化(prod)
- mode启用production(prod)
- scope hosting(prod)
- 在vue-cli中配置常用的性能优化设置
前言
每个场景的方案有的只适用于dev,有的只适用于prod,有的二者都可适用。
在每个方案后面会有标注,如果适用dev环境,就会标注(dev),如果适用prod环境,会标注(prod),都适用就(dev,prod)。
以下配置都基于上一篇的拆分配置方案。
学习代码来源慕课网
如何抽离压缩css文件(dev prod)
前面的文章也说过未抽离的情况下,js代码和css代码是压缩到一个js文件里的,在开发环境可以这样,在线上是不可取的,因为还要执行js文件才能拿到css。
抽离:
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module: {
rules: [
// 抽离 css
{
test: /\.css$/,
loader: [
MiniCssExtractPlugin.loader, // 注意,这里不再用 style-loader
'css-loader',
'postcss-loader'
]
},
// 抽离 less --> css
{
test: /\.less$/,
loader: [
MiniCssExtractPlugin.loader, // 注意,这里不再用 style-loader
'css-loader',
'less-loader',
'postcss-loader'
]
}
]
},
plugins: [
// 抽离 css 文件
new MiniCssExtractPlugin({
filename: 'css/main.[contentHash:8].css'
})
],
压缩:
const TerserJSPlugin = require('terser-webpack-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
optimization: {
// 压缩 css
minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
}
打包好后就会有压缩的单独css文件
抽离公共代码和第三方代码(prod)
我们改动了业务代码,在生产环境重新打包后的js文件hash值发生变化,所有的打包文件都更新了。用户再次访问网址时又要重新获取文件资源。
但是项目里面第三方库一般是没有变化的,每次生产重新打包后这部分代码用户还要重新获取,所以我们可以隔离单独打包第三方库。这样用户下次访问网页的时候,只会拉取业务代码文件,第三方库资源拿的还是浏览器的缓存。
对于我们业务代码也一样,有些公共组件被很多路由组件引用,每次跳转到其他路由的时候,这个公共组件又被重新拉取,所以也可以把这些公共组件都合并起来单独打包成一个文件。
optimization: {
// 分割代码块
splitChunks: {
chunks: 'all',
/**
* initial 入口 chunk,对于异步导入的文件不处理
async 异步 chunk,只对异步导入的文件处理
all 全部 chunk
*/
// 缓存分组
cacheGroups: {
// 对于容量比较大的第三方库可以单独打包
antd: {
name: 'antd-chunk',
test: /antd/,
priority: 100,
},
reactDom: {
name: 'reactDom-chunk',
test: /react-dom/,
priority: 99,
},
// 其他容量较小的第三方模块都一起打包了
vendor: {
name: 'vendor', // 打包后的chunk 名称
priority: 1, // 权限更高,优先抽离,重要!!!
test: /node_modules/,
minSize: 0, // 大小限制
minChunks: 1 // 最少复用过几次
},
// 公共的模块
common: {
name: 'common', // 打包后的chunk 名称
priority: 0, // 优先级
minSize: 0, // 公共模块的大小限制
minChunks: 2 // 公共模块最少复用过几次
}
}
}
}
注意:
minSize
表示超过指定的大小的文件才进行单独抽离打包,因为一些公共js文件其实很小,没必要专门打包成一个文件,性价比不高。minChunks
表示被引用的次数,引用到达这个次数的就需要单独抽离合并打包。test
指定第三方包在哪个位置。
如果有多入口文件,其中一个入口文件并没有引用第三方库,但是我们打包后还是引入了,可以打开这个入口文件的对应html文件查看:
<script type="text/javascript" src="common.87047fb9.js"></script>
<script type="text/javascript" src="vender.87047fb9.js"></script> 多余的引入
<script type="text/javascript" src="other.a8e79968.js"></script>
所以去公共的配置文件中的new HtmlWebpackPlugin中chunks配置只引用哪些打包后的文件:
plugins: [
// new HtmlWebpackPlugin({
// template: path.join(srcPath, 'index.html'),
// filename: 'index.html'
// })
// 多入口 - 生成 index.html
new HtmlWebpackPlugin({
template: path.join(srcPath, 'index.html'),
filename: 'index.html',
// chunks 表示该页面要引用哪些 chunk (即上面的 index 和 other),默认全部引用
chunks: ['index', 'vendor', 'common'] // 要考虑代码分割
}),
// 多入口 - 生成 other.html
new HtmlWebpackPlugin({
template: path.join(srcPath, 'other.html'),
filename: 'other.html',
chunks: ['other', 'common'] // 考虑代码分割
})
]
为什么这个方案不用在dev环境,因为dev环境的时候打包成一个文件构建速度更快一些。
实现异步加载JS文件(dev prod)
其实可以直接在js代码中实现,无需配置,例子:
setTimeout(()=>{
import('./xxx.js').then(res=>{
...res
})
}, 1000)
打开网络就可以看到一秒后就会拉取这个文件,这个原理是webpack实现的,所以在vue中可以看到使用的例子,例如按需引入组件,路由按需引入。
babel开启缓存(dev prod)
如果es6代码没有改过,就直接取上一次打包后的缓存文件
rules: [
{
test: /\.js$/,
loader: ['babel-loader?cacheDirectory'], // cacheDirectory开启缓存
include: path.resolve(__dirname, 'src'), // 明确范围
// 排除范围, include 和 exclude二选一即可
exclude: path.resolve(__dirname, 'node_modules')
}
]
IgnorePlugin忽略打包第三方库无用代码(dev prod)
在引入一些第三方库的时候,往往引入了我们项目不需要的部分,例如moment.js,里面包含了十几种语言,体积很大250kb+,那么我们需要配置忽略打包所有的语言,然后手动在main.js中引人中文包,再打包就只有50kb+。
plugins: [
// 忽略 moment 下的 /locale 目录
new webpack.IgnorePlugin(/\.\/locale/, /moment/),
]
因为我们忽略打包所有moment.js的语言包,所以要在vue中的main.js单独引入moment.js的中文包。
这里只是拿moment.js做场景举例,要学会举一反三。
noParse不重复打包第三方库(dev prod)
我们引入的第三方库有些文件其实是已经打包过的,例如react.min.js,那我们就不需要再进行二次打包了。
module: {
noParse: [/react\.min\.js$/]
}
与IgnorePlugin的区别,前者打包后代码还有,后者打包后直接不要。
happyPack多线程打包(dev prod)
开启多进程打包(js是单线程的),提高构建速度。
const HappyPack = require('happypack')
module: {
rules: [
// js
{
test: /\.js$/,
// 把对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例
use: ['happypack/loader?id=babel'],
include: srcPath,
// exclude: /node_modules/
},
]
}
plugins: [
// happyPack 开启多进程打包
new HappyPack({
// 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
id: 'babel',
// 如何处理 .js 文件,用法和 Loader 配置中一样
loaders: ['babel-loader?cacheDirectory']
}),
]
parallelUglifyPlugin多线程压缩(prod)
开启多进程压缩JS,webpack内置的Uglify压缩工具是单线程的。开启多进程压缩更快。
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')
plugins: [
// 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码
new ParallelUglifyPlugin({
// 传递给 UglifyJS 的参数
// (还是使用 UglifyJS 压缩,只不过帮助开启了多进程)
uglifyJS: {
output: {
beautify: false, // 最紧凑的输出
comments: false, // 删除所有的注释
},
compress: {
// 删除所有的 `console` 语句,可以兼容ie浏览器
drop_console: true,
// 内嵌定义了但是只用到一次的变量
collapse_vars: true,
// 提取出出现多次但是没有定义成变量去引用的静态值
reduce_vars: true,
}
}
})
]
与happyPack:只有在项目很大的时候再开启这两者,因为项目小的时候加上多进程的开销,反而效果不好。
热更新(dev)
热更新和自动刷新不一样,自动刷新除了可以单独配置以外,在devServer也会开启,而热更新是新代码直接生效,网页不会刷新。
const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin');
entry: {
index: [
'webpack-dev-server/client?http://localhost:8080/',
'webpack/hot/dev-server',
path.join(srcPath, 'index.js')
],
plugins: [
new HotModuleReplacementPlugin()
],
devServer: {
hot: true,
以上配置完后只对css生效,js的话还需要手动去指定,哪些问题是可以热更新的。
import { sum } from './xxx'
if (module.hot) {
module.hot.accept(['./xxx'], () => {
// 当你在xxx文件里修改了代码,就会触发这个回调,xxx里的代码支持热更新
})
}
js的相对麻烦,一般可以在一些需要的开发场景下再去手动指定,例如几十个表单输入框的页面。
DllPlugin打包dll文件,提升热刷新速度(dev)
一般用于例如vue、react这种大型的库,一个项目中这种库的版本基本不会升级,所以我们只需要打包一次就好。(PS说实话上面也有类似的方案,整得我不知道到底开哪个好了233)好处就是每次热更新的时候这些大的库就不会重新打包了,热更新速度更快。
手动(不推荐)
webpack已经内置DllPlugin,它会把vue打包成dll文件,然后每次打包DllReference会去使用dll文件,而不会重新对vue做打包。
在webpack-config文件中新建webpack.dll.js:
const path = require('path')
const DllPlugin = require('webpack/lib/DllPlugin')
const { srcPath, distPath } = require('./paths')
module.exports = {
mode: 'development',
// JS 执行入口文件
entry: {
// 把 React 相关模块的放到一个单独的动态链接库
react: ['react', 'react-dom']
},
output: {
// 输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称,
// 也就是 entry 中配置的 react 和 polyfill
filename: '[name].dll.js',
// 输出的文件都放到 dist 目录下
path: distPath,
// 存放动态链接库的全局变量名称,例如对应 react 来说就是 _dll_react
// 之所以在前面加上 _dll_ 是为了防止全局变量冲突
library: '_dll_[name]',
},
plugins: [
// 接入 DllPlugin
new DllPlugin({
// 动态链接库的全局变量名称,需要和 output.library 中保持一致
// 该字段的值也就是输出的 manifest.json 文件 中 name 字段的值
// 例如 react.manifest.json 中就有 "name": "_dll_react"
name: '_dll_[name]',
// 描述动态链接库的 manifest.json 文件输出时的文件名称
path: path.join(distPath, '[name].manifest.json'),
}),
],
}
主要配置了如何去打包react,然后在package.json中注册脚本:
"dll": "webpack --config build/webpack.dll.js"
执行一下npm run dll
,dist文件夹中会有react.dll.js打包后的文件和react.manifest.json(前者的目录索引,为了能让webpack读懂react.dll.js的内容)
然后在dev配置文件中加上:
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');
rules: [
{
test: /\.js$/,
use: ['babel-loader'],
include: srcPath,
exclude: /node_modules/ // 第二,不要再转换 node_modules 的代码
},
]
plugins: [
// 第三,告诉 Webpack 使用了哪些动态链接库
new DllReferencePlugin({
// 描述 react 动态链接库的文件内容
manifest: require(path.join(distPath, 'react.manifest.json')),
})
]
最后在index.html中引入这个打包后的文件<script src="./react.dll.js"></script>
。
缺点就是在上传生产代码的时候需要手动去掉!
自动(推荐)
const AutoDllPlugin = require("autodll-webpack-plugin@0.4.2")
new AutoDllPlugin({
inject: true, // 在html中引入
filename: '[name]_[hash].dll.js',
entry: {
'vue',
'element-ui',
'vuex',
'vue-router'
}
})
base64 (dev prod)
之前有记录了【webpack】5-如何解析html文件、图片等其他资源
加哈希值(dev prod)
看上一篇已经配置好了。
CDN优化(prod)
原理就是公司自己建立CDN网址,然后把打包后的文件(js css 图片)放上去,项目根据CDN的方式去获取,说白了就是个私有CDN。
output: {
publicPath: 'http://cdn.abc.com' // 修改所有js css静态文件 url 的前缀(如 cdn 域名),这里暂时用不到
},
module: {
rules: [
// 图片 - 考虑 base64 编码的情况
{
test: /\.(png|jpg|jpeg|gif)$/,
use: {
loader: 'url-loader',
options: {
// 小于 5kb 的图片用 base64 格式产出
// 否则,依然延用 file-loader 的形式,产出 url 格式
limit: 5 * 1024,
// 打包到 img 目录下
outputPath: '/img1/',
// 设置图片的 cdn 地址(也可以统一在外面的 output 中设置,那将作用于所有静态资源)
// publicPath: 'http://cdn.abc.com'
}
}
},
]
}
mode启用production(prod)
前面的文章也有提到,这里再做一些补充。
- 会自动开启代码压缩
- vue react等库会自动删除调式代码(例如开发环境的warning)
- 启动tree-shaking(通过es module引入的代码,用到的才打包起来,例如工程里的tools.js文件里的用不到的函数就不会打包。注意commonJS不会开启tree-shaking)
为什么commonJS不会开启tree-shaking?
先看看关于commonJS与ES Module的区别【Node.js】学习系列2-commonJS的模块规范
所以打包的时候,webpack只能静态分析代码,只认识es module,实现tree-shaking。
scope hosting(prod)
开发环境没有开启这个东西,你去看打包的文件,会发现每一个模块的代码都是被包裹在一个立即执行函数里。这是为了作用域安全的问题,但会带来个问题就是执行函数过多,且代码量也会多一些。
在生产环境打包配置scope hosting,就会减少这种现象,带来了:
- 创建函数作用域更少(就是函数的数量更少)
- 代码可读性更好
- 代码体积更小
配置方式先不记录了。
在vue-cli中配置常用的性能优化设置
可以参考下b站这个视频:vue-cli项目实战中优化打包速度,实用技巧分享