一、速度分析
安装插件speed-measure-webpack-plugin
npm install --save-dev speed-measure-webpack-plugin
引入插件、创建插件对象
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin'); //引入插件
const smp = new SpeedMeasurePlugin(); //创建插件对象
使用插件的wrap()方法将配置包起来
module.exports = smp.wrap({
entry: {
index: './src/index.js',
search: './src/search.js',
},
output: {
path: path.join(__dirname, 'dist'), //__dirname(当前模块的目录名) + dist
filename: '[name]_[chunkhash:8].js', //打包后输出的文件名,添加文件指纹 chunkhash
},
plugpins: [],
.....
});
打包完成后控制台会输出各个loader的打包耗时,可根据耗时进一步优化打包速度
二、体积分析
体积分析可以分析哪些问题?
- 依赖的第三方模块文件大小
- 业务里面的组件代码大小
打包后可以很清晰直观的看出各个模块的体积占比
安装插件webpack-bundle-analyzer
npm install --save-dev webpack-bundle-analyzer
引入插件、创建插件对象
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
添加plugpins配置
plugins: [
new BundleAnalyzerPlugin()
],
打包完成后浏览器会打开http://127.0.0.1:8888/显示打包后的体积分析
三、打包速度优化
webpack构建过程中直接影响构建效率的,一个是文件的编译,另一个是文件的分类打包。相较之下文件的编译更为耗时,而且在Node环境下文件只能一个一个去处理,因此这块的优化需要解决。那么要怎样优化打包速度呢?
1. 使用高版本的webpack和node.js
webpack4新版本的优化使用v8引擎,v8带来的优化包括
- for of 替代 forEach
- Map和Set 替代Object
- includes 替代 indexOf()
- 默认使用更快的md4 hash算法 替代 md5算法,md4较md5速度更快
- webpack AST 可以直接从loader传递给AST,从而减少解析时间
- 使用字符串方法替代正则表达式
更高版本的node.js对原生js api和js数据结构做出进一步的优化
2. 多进程/多实例构建(资源并行解析)
在webpack构建过程中,我们需要使用Loader对js,css,图片,字体等文件做转换操作,并且转换的文件数据量也是非常大的,且这些转换操作不能并发处理文件,而是需要一个个文件进行处理,我们需要的是将这部分任务分解到多个子进程中去并行处理,子进程处理完成后把结果发送到主进程中,从而减少总的构建时间。
可选方案
- thread-loader(官方推出)
- parallel-webpack
- HappyPack
HappyPack
注:由于HappyPack作者对js的兴趣逐步丢失,所以之后维护将变少,webpack4及之后推荐使用thread-loader 原理:每次webpack解析一个模块,HappyPack会将它及它的依赖分配给worker进程中; HappyPack会将模块进行一个划分,比如我们有多个模块,这些模块交给HappyPack,首先在webpack compiler(钩子)的run方法之后,进程就会到达HappyPack,HappyPack会做一些初始化,初始化之后会创建一个线程池,线程池会将构建任务里面的模块进行一个分配,比如会将某个模块以及它的一些依赖分配给其中的一个HappyPack线程,以此类推,那么一个HappyPack的一个线程池会包括多个线程,这时候线程池的这些线程会各自去处理其中的模块以及它的依赖,处理完成之后会有一个通信的过程,会将处理好的资源传输给HappyPack的一个主进程,完成整个的一个构建过程。
将src目录下复制出多个相同页面
在没引入HappyPack之前执行打包
安装
npm install --save-dev happypack
注:如果在webpack4使用需要HappyPack5.0的版本
引入之后将rules对js的编译改为happypack/loader
rules: [
{
test: /.js$/, //对所有js后缀的文件进行编译
use: [
// 'babel-loader'
'happypack/loader',
],
},
]
在插件中加入happypack-loader
plugins: [
new HappyPack({
// 3) re-add the loaders you replaced above in #1:
loaders: ['babel-loader'],
}),
]
很明显可以看出使用happypack之后打包速度加快很多
thread-loader
原理:与HappyPack类似,每次webpack解析一个模块,thread-loader会将它及它的依赖分配给worker进程中; 安装
npm install --save-dev thread-loader
在rule中添加thread-loader,thread-loader可以进行一些配置,例如workers(进程数)
rules: [
{
test: /.js$/, //对所有js后缀的文件进行编译
include: path.resolve('src'), //表示在src目录下的.js文件都要进行一下使用的loader
use: [
'babel-loader',
{
loader: 'thread-loader',
options: {
workers: 3,
},
},
// 'happypack/loader',
],
},
]
使用thread-loader之后打包速度也有明显提升
3. 多进程/多实例进行代码压缩(并行压缩)
在代码构建完成之后输出之前有个代码压缩阶段,这个阶段也可以进行并行压缩来达到优化构建速度的目的;
可选方案
- webpack-parallel-uglify-plugin
- uglifyjs-webpack-plugin
- terser-webpack-plugin**(webpack4.0推荐使用,支持压缩es6代码)**
npm install terser-webpack-plugin --save-dev
const TerserPlugin = require('terser-webpack-plugin');
optimization中添加TerserPlugin插件,开启parallel
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
//代码压缩插件
parallel: 4, //开启并行压缩
}),
],
},
4. 通过分包提升打包速度
可以使用html-webpack-externals-plugin
分离基础包,分离之后以CDN的方式引入所需要的资源文件,缺点就是一个基础库必须指定一个CDN,实际项目开发中可能会引用到多个基础库,还有一些业务包,这样会打出很多个script标签
new HtmlWebpackExternalsPlugin({
externals: [
{
module: 'react',
entry: 'https://unpkg.com/react@16/umd/react.development.js',
global: 'React',
},
],
})
进一步分包,采用预编译资源模块
采用webpack官方内置的插件DLLPlugin进行分包,DdllReferenceRlugin对manifest.json引用
DLLPlugin可以将项目中涉及到的例如react、reactdom、redux等组件和框架库打包成一个文件,同时生成manifest.json
文件
manifest.json
是对分离出来的包进行一个描述,实际项目就可以引用manifest.json,引用之后就会关联DLLPlugin分离出来的包,这个文件是用来让 DLLReferencePlugin
映射到相关的依赖上去
- 首先使用DLLPlugin进行分包
创建一个单独的构建配置文件,webpack.dll.js
,在该配置文件中指定出需要分离的包
在package.json中添加dll的编译语句
"scripts": {
"dll": "webpack --config webpack.dll.js"
}
webpack.dll.js
const webpack = require('webpack');
const path = require('path');
module.exports = {
mode: 'development',
entry: {
//对应output 中的library
library: ['react', 'react-dom'],
},
output: {
filename: '[name]_[chunkhash].dll.js', //分离出来的文件名称,一个占位符+hash.dll.js [name]对应的是entry的library
path: path.join(__dirname, 'build/library'), //输出到当前目录下的build目录
library: '[name]', //打包后暴露出的库的名字
},
plugins: [
new webpack.DllPlugin({
name: '[name]_[hash]', //打包后library.json中的name
path: path.join(__dirname, 'build/library/[name].json'), //打包后生成[name].json的路径
}),
],
};
npm run dll
之后在build目录下会生成两个文件
也就是前面提到的manifest.json
构建好之后使用DllReferencePlugin
引用manifest.json
plugins: [
new webpack.DllReferencePlugin({
manifest: require('./build/library/library.json'),
}),
]
5. 通过缓存提升二次打包速度
- babel-loader 开启缓存
- terser-webpack-plugin 开启缓存
- 使用cache-loader或者 hard-source-webpack-plugin
new HappyPack({
loaders: ['babel-loader?cacheDirectory=true'],
})
设置babel-loader的cacheDirectory=true开启缓存
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
//代码压缩插件
parallel: 4, //开启并行压缩
cache: true,
}),
],
},
设置terser-webpack-plugin插件的cache: true开启缓存
使用hard-source-webpack-plugin
npm install --save-dev hard-source-webpack-plugin
plugins: [
new HardSourceWebpackPlugin()
]
第一次运行开始写入缓存文件
开启缓存之后明显提升了打包速度
6. 缩小构建目标
尽可能的少构建模块,比如babel-loader不解析 node_modules
- 优化resolve.modules配置(减少模块搜索层级)
- 优化resolve.mainFields配置
- 优化resolve.extensions配置
四、打包体积优化
主要对打包后图片、js、css文件的资源体积优化
1. 图片压缩
使用Node库的imagemin,配置image-webpack-loader
对图片优化,改插件构建时会识别图片资源,对图片资源进行优化
imagemin优点分析
- imagemin有很多定制选项
- 可以引入更多第三方优化插件,例如
pngquant
- 可以引入多种图片格式
imagemin的压缩原理
- pngquant:是一款PNG的压缩器,通过将图像转换为具有alpha通道(通常比24/32位PNG文件小60%-80%)的更高效的8位PNG格式,可显著减小文件大小;
阿尔法通道(Alpha Channel)是指一张图片的透明和半透明度
- pngcrush:其主要目的是通过尝试不同的压缩级别和PNG过滤方法来降低PNG IDAT数据流的大小;
- optipng:其涉及灵感来自于pngcrush。optipng可将图像文件重新压缩位更小的尺寸,而不会丢失任何信息;
- tingpng:也是将24位png文件转化为更小具有索引的8位图片,同时所有非必要的metadata也会被剥离掉;
npm install image-webpack-loader --save-dev
rules: [
{
test: /.(png|jpg|gif|jpeg)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name]_[hash:8].[ext]',
},
},
{
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
quality: 65,
},
// optipng.enabled: false will disable optipng
optipng: {
enabled: false,
},
pngquant: {
quality: [0.65, 0.9],
speed: 4,
},
gifsicle: {
interlaced: false,
},
// the webp option will enable WEBP
webp: {
quality: 75,
},
},
},
],
}
]
2. 擦除无用到的css
可以同通过插件遍历代码,识别已经用到的css class
安装插件
npm i purgecss-webpack-plugin -D
const PurgecssPlugin = require('purgecss-webpack-plugin');
const PATHS = {
src: path.join(__dirname, 'src'),
};
plugins: [
new PurgecssPlugin({
paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
}),
]
3. 动态Polyfill
什么是Polyfill? babel默认只转换新的JavaScript语法(syntax),如箭头函数等,而不转换新的API,比如Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局对象,以及一些定义在全局对象上的方法(比如Object.assign)都不会转码;因此我们需要polyfill; 链接:https://www.jianshu.com/p/4822852792d1
官方解释
- 它是一项服务,接受对一组浏览器功能的请求,并且仅返回请求浏览器所需的polyfill。
- 全世界有许多不同的浏览器和版本的浏览器在使用,每种浏览器都具有与其他浏览器完全不同的功能集。这会使浏览器开发成为一项艰巨的任务。流行浏览器的最新版本可以完成许多旧浏览器无法完成的任务-但是您可能仍必须支持旧浏览器。通过尝试使用polyfills重新创建缺少的功能,Polyfill.io使支持不同的浏览器变得更简单:您可以在支持或不支持的浏览器中使用最新和最强大的功能。
通过caniuse查询可知,promise有96.17%的兼容性
由于Polyfill是非必须的,对一些不支持es6新语法的浏览器才需要加载polyfill,为了百分之3.几的用户让所有用户去加载Polyfill是很没有必要的;
我们可以通过polyfill-service
,只给用户返回需要的polyfill
每次用户打开一个页面,浏览器端会请求polyfill-service,polyfill-service会识别用户User Agent,下发不同的polyfill
如何使用动态Polyfill service
通过polyfill.io官方提供的服务,自建polyfill服务polyfill.io/v3/url-buil…
或者通过引入cdn<script src="https://cdn.polyfill.io/v2/polyfill.min.js"></script>
来加载polyfill-service
可以通过加载polyfill.io/v3/polyfill…网址来查看不同浏览器User Agent的情况;
webpack4对打包构建速度优化和体积优化的内容到此结束,该文章通过学习程柳锋老师的《玩转webpack》课程实践总结得出,欢迎讨论和指正,以上。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!