之前写过webpack的基本用法,用到了一些loader plugin 来处理js ,css ,图片,字体等模块,本文主要是对于项目优化,以及打包优化,我们知道随着项目的增大,打包速度也会越来越慢,所有我们可以靠一些优化手段提高效率
1.工具
我们想要知道如何优化就得知道优化哪些东西,我们可以通过一些工具去判断哪些文件需要优化,或者哪些打包过程需要优化
1. webpack-bundle-analyzer
这个是一个可视化打包的分析工具,使用这个插件之后会自动启动一个本地8888服务,用来查看打包后的包的信息
let BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
plugins: [
new BundleAnalyzerPlugin()
]
我们页面代码,可以看出引入了 react、react dom、jquery、lodash 包
可视化页面如下,我们可以看到打包之后的index.js的信息 和每个包大小分布情况
从这上面我们就可以看到几个问题,都会增加打包后的包的大小和打包时间
- jquery 我们没有使用,但是还是打包了
- lodash 我们只用了 map 方法,整个lodash 都打包进去了
后面我们会提到如何优化这些,这里先介绍一下这个工具
2. speed-measure-webpack-plugin
这个插件可以测试出每个plugin 和 loader 所花费的时间
let SpeedMeasurePlugin = require('speed-measure-webpack-plugin')
let config = {
//...webpack 配置
}
module.exports = new SpeedMeasurePlugin().wrap(config)
这样配置之后打包后就可以看到打包信息
2.include/exculde
可以缩小解析的文件范围,include指定解析某个文件夹,exclude排除某个文件夹,这个是最简单并且效果最明显的优化,一般做webpack配置的时候都会配置上
上面的代码目录结构未配置前
module: { rules: [ { test: /\.js$/, loader: 'babel-loader' } ] },
打包速速 Time: 7595ms
配置后,缩小范围到 src目录下
module: {
rules: [
{ test: /\.js$/,
loader: 'babel-loader',
include: [path.resolve(__dirname, 'src')]
}
]
},
打包速度 Time: 1558ms
效果还是很明显的,exclude 优先级高于 include 我一般只配置上include
3.cache-loader
cache-loader会在首次打包时候加上一些缓存,再次打包的时候会读取缓存节省打包时间
module: {
rules: [
{ test: /\.js$/,
loader: ['chche-loader', 'babel-loader'],
include: [path.resolve(__dirname, 'src')]
}
]
},
加入cache-loader 之前速度是 Time: 1594ms , 再次构建加入缓存之后是 Time: 818ms,速度提升还是很明显
4. externals
防止将某些 import 的包打包出去,而是在运行时候再去从外部获取这些依赖,一般都是使用CDN缓存,打包的时候不会将这些打包进去,我们再模板文件(index.html)中引入jquery 和 lodash cdn
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script><script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
webpack上配置上externals属性
externals: { 'jquery': '$', 'lodash': '_', },
这样js中再引入 jquery 和 lodash的时候就不会将两个文件打包了
设置上 externals 打包速度从加入****Time: 818ms,缩减到了 Time: 606ms 速度又提升了一些
5.noParse
如果已经知道某个包没有依赖项目,设置不去解析某个包中的依赖,比如上面的jquery ,lodash,我们先还原会不用externals 和 cdn,并设置noPase, module中 noPase属性,接收正则
module: {
noParse: /jquery|lodash/
}
经过测试打包速度确实是快了一些
6. happypack
这个项目已经不再维护了,这个插件是多线程处理,小型项目其实还用不到,项目越大效果约明显
loader检测js时候配置如下
let Happypack = require('happypack')
module: {
rules: [
test: /\.js$/,
use: 'Happypack/loader?id=js'
]
}
plugin处配置如下, 与loader中配置的 id一一对应,用了多个loader就根据id配置多个 plugin
new Happypack({
id: 'js',
use: ['babel-loader']
})
7.tree-shaking
webpack@4自带了tree-shaking功能
tree-shaking 用于消除引用了但是没有被使用的模块,它得益于ES6模块的特性,依赖关系是确定的,和运行时候的状态无关,可以进行可靠的静态分析,
但是tree-shaking也有问题,没有用到的类的方法不能消除
8.code spliting
也就是我们常说的代码分割,那什么是代码分割呢?
- 我们项目会引入很多第三方库,因为第三方库几乎不会改动,我们想把这些库抽离出去可以给浏览器做缓存(一般是vendor)
- 给webpack自己的runtime的代码单独打包(一般是manifest)
- 为公共业务代码单独打包(一般是common)
- 异步加载的module 单独打包(一般都是模块的编号)
webpack 4 之前都是用 CommonsChunkPlugin 来抽取公共代码,webpack4 只有使用splitChunks插件
1. 首先我们先抽取vendor 打包会生成一个vendor.js文件
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
chunks: 'initial',
test: /node_modules/,
name: 'vendor',
minChunks: 1,
},
}
}
},
chunks 默认是async ,一定要改成all ,或者 initial 才能抽取node_modules下的库
2. 提取 webpack runtime 代码 打包会会多出一个manifest文件
optimization: {
runtimeChunk: {
name: 'manifest'
},
splitChunks: {
cacheGroups: {
vendor: {
chunks: 'initial',
test: /node_modules/,
name: 'vendor',
minChunks: 1,
},
}
}
},
3. 提取公共业务代码 打包后会多出一个 common.js
optimization: {
runtimeChunk: {
name: 'manifest'
},
splitChunks: {
cacheGroups: {
commons: {
chunks: 'initial',
test: path.resolve(__dirname, 'src'),
name: 'commons',
minSize: 0,
minChunks: 2,
},
vendor: {
chunks: 'initial',
test: /node_modules/,
name: 'vendor',
minChunks: 1,
},
}
}
},
4. 抽取异步加载代码
webpack 提供两种异步加载方式
- import() //ES的提案,返回一个promise,导入的模块在then中获取到
- require.ensure()
上面的异步方式都依赖于promise,旧浏览器中使用就要用es6-promise polyfill
babel7 默认就有 syntax-dynamic-import 所以页面中直接使用 import()就可以打包出按需加载块了,vue-router的按需加载就是很好的应用
import(/* webpackChunkName: "打包后的模块名称" */'路径' ).then( res => {})
9. DllPlugin
我用了两个项目来做优化,感觉DllPlugin对打包速度优化的不是很明显,最多也就几秒钟,感兴趣的可以自己试试,这里就不多说了
下篇文章将会聊聊webpack的源码并手写一些loader 和 plugins