在以前的文章中,咱们了解了webpack的基本使用和原理:css
而当项目愈来愈复杂时,会面临着构建速度慢和构建出来的文件体积大的问题。webapck构建优化对于大项目是必需要考虑的一件事,下面咱们就从速度和体积两方面来探讨构建优化的策略。html
在优化以前,咱们须要了解一些量化分析的工具,使用它们来帮助咱们分析须要优化的点。vue
webpackbar能够在打包时实时显示打包进度。配置也很简单,在plugins数组中加入便可:node
const WebpackBar = require('webpackbar')
module.exports = {
plugins: [
...
new WebpackBar()
]
}
复制代码
使用speed-measure-webpack-plugin
能够看到每一个loader和plugin的耗时状况。webpack
和普通插件的使用略有不一样,须要用它的wrap方法包裹整个webpack配置项。git
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin')
const smp = new SpeedMeasurePlugin()
module.exports = smp.wrap({
entry: './src/main.js',
...
})
复制代码
打包后,在命令行的输出信息以下,咱们能够看出哪些loader和plugin耗时比较久,而后对其进行优化。github
webpack-bundle-analyzer
以可视化的方式让咱们直观地看到打包的bundle中到底包含哪些模块内容,以及每个模块的体积大小。咱们能够根据这些信息去分析项目结构,调整打包配置,进行优化。web
在plugins数组中加入该插件。构建完成后,默认会在http://127.0.0.1:8888/
展现分析结果。ajax
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports = {
plugins: [
...
new BundleAnalyzerPlugin()
]
}
复制代码
webpack-bundle-analyzer
会计算出模块文件在三种情形下的大小:vue-router
使用speed-measure-webpack-plugin
和webpack-bundle-analyzer
自己也会增长打包时间(webpack-bundle-analyzer
特别耗时),因此建议这两个插件在开发分析时使用,而在生产环境去掉。
运行在Node.js之上的 Webpack 是单线程的,就算有多个任务同时存在,它们也只能一个一个排队执行。当项目比较复杂时,构建就会比较慢。现在大多数CPU都是多核的,咱们能够借助一些工具,充分释放 CPU 在多核并发方面的优点。
比较常见的有happypack
、thread-loader
。
happypack可以将构建任务分解给多个子进程去并发执行,子进程处理完后再把结果发送给主进程。使用配置以下,就是把原有的loader的配置转移到happyPack中去处理。
const Happypack = require('happypack')
module.exports = {
module:{
rules:[
{
test: /\.js$/,
use: 'happypack/loader?id=babel' //问号后面的查询参数指定了处理这类文件的HappyPack实例的名字
},
]
},
plugins:[
new Happypack({
id: 'babel', //HappyPack实例名,对应上面rules中的“id=babel”
use: ['babel-loader'] //本来要使用的loader
})
]
}
复制代码
happypack的做者已经没有这个项目进行维护了,在webpack4以后,可使用thread-loader
。
thread-loader
使用起来很简单,就是把它放置在其它loader以前,以下所示。放置在这个thread-loader
以后的 loaders会运行在一个单独的worker池中。
module.exports = {
module:{
rules:[
{
test: /\.js$/,
use: ['thread-loader','babel-loader']
}
]
},
}
复制代码
若是是小项目,不建议开启多进程构建,由于开启进程是须要花费时间的,构建速度反而会变慢。
利用缓存能够提高二次构建速度(下面的对比图都是二次构建的速度)。使用缓存后,在node_modules中会有一个.cache
目录,用于存放缓存的内容。
在一些性能开销较大的 loader 以前添加此cache-loader
,以将结果缓存到磁盘中。
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: ['cache-loader','babel-loader']
}
]
}
}
复制代码
能够看出,使用cache-loader
后,构建速度有很是明显的提高。
对
babel-loader
使用缓存,也能够不借助cache-loader
,直接在babel-loader
后面加上?cacheDirectory=true
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: ['babel-loader?cacheDirectory=true']
}
]
}
}
复制代码
hard-source-webpack-plugin
用于开启模块的缓存。
const HardSourceWebpackPlugin = require("hard-source-webpack-plugin")
module.exports = {
plugins:[
new HardSourceWebpackPlugin()
]
}
复制代码
使用hard-source-webpack-plugin
后,二次构建速度大概提高了90%。
一般来讲,loader会处理符合匹配规则的全部文件。好比babel-loader,会遍历项目中用到的全部js文件,对每一个文件的代码进行编译转换。而node_modules里的js文件基本上都是转译好了的,不须要再次处理,因此咱们用 include/exclude 来帮咱们避免这种没必要要的转译。
module.exports = {
module:{
rules:[
{
test: /\.js$/,
use: ['babel-loader'],
exclude: /node_modules/
//或者 include: [path.resolve(__dirname, 'src')]
}
]
},
}
复制代码
include直接指定查找文件夹,比exclude效率更高,更能提高构建速度。
上面的babel-loader能够经过include/exclude,避免处理node_modules里的第三方库。
但若是将第三方库代码和业务代码都打包进一个bundle文件,那么处理这个bundle文件的插件,好比uglifyjs-webpack-plugin、terser-webpack-plugin等,就没办法不处理里面第三方库内容。
其实第三方库代码基本都是成熟的,不用做什么处理。所以,咱们能够将项目的第三方库代码分离出来。
常见的处理方式有三种:
Externals能够避免处理第三方库,但每个第三方库都得在html文档中增长一个script标签来引入,一个页面过多的js文件下载会影响网页性能,并且有时咱们只使用第三方库中的一小部分功能,用script标签全量引入不太合理。
SplitChunks在每一次构建时都会从新构建第三方库,不能有效提高构建速度。
这里推荐使用DllPlugin和DLLReferencePlugin(配合使用),它们是webpack的内置插件。DllPlugin会将不频繁更新的第三方库单独打包,当这些第三方库版本没有变化时,就不须要从新构建。
使用方法:
webpack.dll.js
用于打包第三方库(第1步)const path = require('path')
const webpack = require('webpack')
module.exports = {
mode: 'production',
entry: {
three: ['three', 'dat.gui'] // 第三方库数组
},
output: {
filename: '[name].dll.js', //[name]就是在entry
path: path.resolve(__dirname, 'dist/lib'),
library: '[name]'
},
plugins: [
new webpack.DllPlugin({
name: '[name]',
path: path.resolve(__dirname, 'dist/lib/[name].json') //manifest.json的存放位置
})
]
}
复制代码
打包好后,能够看到,在dist
目录下增长了一个lib文件夹。
webpack.base.js
作一下修改,去关联第1步中已经打好的包(第2步)module.exports = {
plugins:[
//修改CleanWebpackPlugin配置
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: [
'!lib/**' //在每次清楚dist目录时,不清理lib文件夹的内容
]
}),
// dll相关配置
new webpack.DllReferencePlugin({
// 将manifest字段配置成咱们第1步中打包出来的json文件
manifest: require('./dist/lib/three.json')
})
]
}
复制代码
再次打包后能够看到,相比于一开始整个项目的体积 9.11MB,体积减少了90%,由于这是一个多页面打包(多页面打包配置)的应用,每一个页面都引用了体积庞大的three.js核心文件
,咱们把体积最大的three.js核心文件
从每一个页面的bundle中抽离出来后,bundle的体积大大减少。
再来看看构建时间:相比于使用DllPlugin以前,时间减小了30% 。
不只仅是第三方库,业务代码中的基础库也能够经过进行DllPlugin分离。
分离第三方库和业务代码中的基础库,能够避免单个bundle.js体积过大,加载时间过长。而且在多页面构建中,还能减小重复打包。
常见的操做是经过SplitChunks(以前的文章已经详细地写过了:SplitChunks)和 动态连接库(如上所示),这里都再也不赘述。
动态import的做用主要减小首屏资源的体积,非首屏的资源在用到的时候再去请求,从而提升首屏的加载速度。一个常见的例子就是单页面应用的路由管理(好比vue-router
)
{
path: '/list',
name: 'List',
component: () => import('../views/List.vue')
},
复制代码
不是直接import组件(import List from '../views/List.vue'
),那样会把组件都打包进同一个bundle。而是动态import组件,凡是经过import()
引用的模块都会打包到独立的bundle,使用到的时候再去加载。对于功能复杂,又不是首屏必须的资源都推荐使用动态import。
<span @click="loadModal">show弹窗</span>
/***
methods: {
loadModal(){
import('../modal/index.js')
}
}
***/
复制代码
使用ES6的import/export语法,而且使用下面的方式导入导出你的代码,而不要使用export default。
// util.js 导出
export const a = 1
export const b = 2
export function afunc(){}
或
export { a, b, afunc }
// index.js 导入
import { a, b } from './util.js'
console.log(a,b)
复制代码
那么在mode:production
生产环境,就会自动开启tree-shaking
,移除没有使用到的代码,上面例子中的afunc函数
就不会被打包到bundle中。
经常使用的js代码压缩插件有:uglifyjs-webpack-plugin
和 terser-webpack-plugin
。
在webpack4中,生产环境默认开启代码压缩。咱们也能够本身配置去覆盖默认配置,来完成更定制化的需求。
v4.26.0版本以前,webpack内置的压缩插件是uglifyjs-webpack-plugin,从v4.26.0版本开始,换成了terser-webpack-plugin
。咱们这里也以terser-webpack-plugin
为例,和普通插件使用不一样,在optimization.minimizer
中配置压缩插件
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
parallel: true, //开启并行压缩,能够加快构建速度
sourceMap: true, //若是生产环境使用source-maps,则必须设置为true
})
]
}
}
复制代码
雪碧图将多张小图标拼接成一张大图,在HTTP1.x环境下,雪碧图能够减小HTTP请求,加速网页的显示速度。
用于合成雪碧图的图标体积要小,较大的图片不建议拼接成雪碧图;同时要是网站静态图标,不是经过ajax请求动态获取的图标。因此一般是做为网站logo、icon之类的图片。
开发时,能够是UI提供雪碧图,可是每新增一个图标,就要从新制做一次,从新计算偏移量,比较麻烦。经过webpack插件合成雪碧图,就能够在开发时直接使用单个小图标,在打包时,自动合成雪碧图,并自动自动修改css中的background-position
的值。
下面,咱们借助postcss-sprites
来自动合成雪碧图。
首先,在webpack.base.js
中配置postcss-loader
:
//webpack.base.js
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ['vue-style-loader','css-loader', 'postcss-loader'] //配置postcss-loader
},
{
test: /\.less$/,
use: [
'vue-style-loader','css-loader', 'postcss-loader', 'less-loader'] //配置postcss-loader
}
]
}
};
复制代码
而后在项目根目录下新建.postcssrc.js
,配置postcss-sprites
。
module.exports = {
"plugins": [
require('postcss-sprites')({
// 默认会合并css中用到的全部静态图片
// 使用filterBy指定须要合并的图片,好比这里这里只合并images/icon文件夹下的图片
filterBy: function (image) {
if (image.url.indexOf('/images/icon/') > -1) {
return Promise.resolve();
}
return Promise.reject();
}
})
]
}
复制代码
默认会把图片合并到名为sprite.png
的雪碧图中。
在css中直接指定小图标当背景:
.star{
display: inline-block;
height: 100px;
width: 100px;
&.l1{
background: url('../icon/star.png') no-repeat;
}
&.l2{
background: url('../icon/star2.png') no-repeat;
}
&.l3{
background: url('../icon/star3.png') no-repeat;
}
}
复制代码
打包完成后能够看到,自动修改了background-image
和background-position
。
gzip的原理,参考探索HTTP传输中gzip压缩的秘密
开启gzip压缩,能够减少文件体积。在浏览器支持gzip的状况下,能够加快资源加载速度。服务端和客户端均可以完成gzip压缩,服务端响应请求时压缩,客户端应用构建时压缩。但压缩文件这个过程自己是须要耗费时间和CPU资源的,若是存在大量的压缩需求,会加大服务器的负担。
因此能够在构建打包时候就生成gzip压缩文件,做为静态资源放在服务器上,接收到请求后直接把压缩文件返回。
使用webpack生成gzip文件须要借助compression-webpack-plugin
,使用配置以下:
const CompressionWebpackPlugin = require("compression-webpack-plugin")
module.exports = {
plugins: [
new CompressionWebpackPlugin({
test: /\.(js|css)$/, //匹配要压缩的文件
algorithm: "gzip"
})
]
}
复制代码
打包完成后除了生成打包文件外,还会额外生成 .gz后缀的压缩文件。能够看出,gzip压缩文件的体积比未压缩文件的体积小不少。
项目地址: github.com/alasolala/w…