系列文章目录
webpack指南(基础篇)——手把手教你配置webpack
文章目录
前言
面试的时候总是会问用webpack对项目做过哪些优化,今天我们就来详细列举一下常用的优化手段;
前面我们利用webpack构建了一个项目,今天我们在原项目基础上进行优化,还没有观看的小伙伴可以移步看一下。
一、概述
关于webpack常见的性能优化,我们可以从两个方面去着手考虑:
1.传输性能优化;通过优化代码体积等手段提高资源传输速度,达到优化目的;
2.构建过程优化;主要是提高webpack的打包速度;
二、传输性能优化
1.代码压缩
进行代码压缩,减少资源包体积
1.1 css压缩
CssMinimizerWebpackPlugin这个插件使用 cssnano 优化和压缩 CSS
安装
yarn add css-minimizer-webpack-plugin -D
使用
// 引入
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
// 配置
// webpack内置的优化配置项
optimization: {
// 为true时 告知webpack 使用配置的插件压缩 bundle。生产环境默认开启
minimize: true,
// 配置优化插件
minimizer: [
new CssMinimizerPlugin(),
],
},
效果
配置前:
配置后:
1.2 js压缩
webpack自带的配置默认开启js压缩,我们保留原来配置即可
minimizer: [
// 使用 `...` 语法来扩展现有的 minimizer,保留原webpack的优化措施
// `...`,
new CssMinimizerPlugin(),
],
开启前:
开启后:
1.3 进一步压缩js
有时候默认的配置无法满足我们的需求,我们需要自定义一些压缩规则,这时候就需要使用terser-webpack-plugin自己配置了;
安装
yarn add terser-webpack-plugin -D
使用
const TerserPlugin = require('terser-webpack-plugin');
devtool: 'source-map',
// webpack内置的优化配置项
optimization: {
// 为true时 告知webpack 使用配置的插件压缩 bundle。生产环境默认开启
minimize: true,
// 配置优化插件
minimizer: [
// 使用 `...` 语法来扩展现有的 minimizer,保留原webpack的优化措施
// `...`,
new CssMinimizerPlugin(),
new TerserPlugin({
// 匹配要压缩的文件
test: /\.js(\?.*)?$/i,
// 配置参数
terserOptions: {
format: {
// 删除注释
comments: false,
},
compress: {
// 移除所有console相关代码;
drop_console: true,
// 移除自动断点功能;
drop_debugger: true,
// 配置移除指定的指令,如console.log,alert
pure_funcs: ['console.log', 'console.error'],
},
},
// 是否抽离注释
extractComments: false,
}),
],
},
压缩后
值得注意的是,开启TerserPlugin 优化必须配置devtool为terse-webpack-plugin支持的选项:source-map,inline-source-map!!否则不生效这个配置项的作用我们随后再介绍;
1.4 压缩html
我们通过修改html-webpack-plugin的配置来实现压缩
new HtmlWebpackPlugin({
template: './index.html',
minify: {
// 移除空格
collapseWhitespace: true,
// 移除注释
removeComments: true,
},
}),
压缩前:
压缩后:
1.5图片压缩
image-webpack-loader基于webpack的图片压缩工具,压缩方案采用 imagemin ,代码实现比较全面,支持 Minify PNG, JPEG, GIF, SVG and WEBP images 等,支持各个版本 webpack,github start 1625
安装
cnpm install --save-dev image-webpack-loader file-loader
使用
{
test: /\.(png|jpe?g|gif|svg)$/i,
// type: 'asset',
use: [
'file-loader',
{
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
},
optipng: {
enabled: false,
},
pngquant: {
quality: [0.5, 0.65],
speed: 4,
},
gifsicle: {
interlaced: false,
},
// the webp option will enable WEBP
webp: {
quality: 75,
},
},
},
],
},
效果:5kb压缩至2kb
踩坑:
enn…一言难尽
1.安装loader必须用cnpm,别不信邪…
2.不是所有的图片都能压缩成功,目前没有找出规律,所以谨慎使用吧。
ok,关于代码压缩就先介绍到这里;更多参数配置都可以移步npm查看;
2.摇树优化(Tree Shaking)
简单来说就像摇晃一颗树一样,把没有根的叶子晃下来;在代码里就是移除没有被引用的代码;
tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块语法的 静态结构 特性,例如 import 和 export
演示
我们新建个模块文件module.js,导出module1和module2代码如下:
export function module1() {
console.log('module1');
}
export function module2() {
console.log('module2');
}
在index.js中只引入module1
import { module1 } from './utils/module';
module1();
打包后可以看到,module2函数也被打包在内
解决
在optimization中配置usedExports: true;
// 检查代码是否使用
usedExports: true,
打包后module2被排除
但是这样也是有问题的,因为usedExports 依赖于 terser 去自动检测语句中的副作用,他的策略比较保守,有时候我们需要告诉插件我们的这个代码没有副作用,可以全部优化,这个时候我们需要对代码进行标记,让terser 强制优化;
使用sideEffects标记代码
可以通过 package.json 的 sideEffects声明哪些文件可以被优化;fasle表示无副作用,可以优化,true表示谨慎对待,也可以提供一个数组;
演示
在全局对象上挂一个方法,terser 不会优化此方法
window.onload = function () {
console.log(123);
};
现在我们声明我们的代码没有副作用,可以优化;
// 所有代码都可以优化
"sideEffects": false
可以看到,确实没有了onload,但是同时也发现项目中的css和静态资源也没有了
sideEffects使用数组,告诉插件数组内的是有副作用的,不能优化,其他的无副作用,可以优化;
"sideEffects": [
"*.css",
"*.jpg"
]
sideEffects原理
webpack 能将标记为 side-effects-free 的包由 import {a} from xx 转换为 import {a} from ‘xx/a’,是根据你的引入关系进行判断的,从而自动修剪掉没必要要的 import,如果写一个方法没有被导出的话,是无法被优化的;
如下图,onscroll打包有依旧存在
3.作用域提升
将具有引用关系的代码尽可能的合并,减少代码量;
// optimization中插入如下
concatenateModules: true,
4.抽取公共模块(splitChunks)
SplitChunks插件是webpack中用来提取或分离代码的插件,主要作用是提取公共代码,减少代码被重复打包,拆分过大的js文件,合并零散的js文件
webpack 将根据以下条件自动拆分 chunks:
- 新的 chunk 可以被共享,或者模块来自于 node_modules 文件夹
- 新的 chunk 体积大于 20kb(在进行 min+gz 之前的体积)
- 当按需加载 chunks 时,并行请求的最大数量小于或等于 30
- 当加载初始化页面时,并发请求的最大数量小于或等于 30
我们可以自己去修改一些配置项,常用配置如下:
// 分包
splitChunks: {
// 选取使用哪些chunks进行优化
chunks: 'all',
// 被引用次数超过该阈值的模块才会被拆包处理
minChunks: 2,
// 打包后的分包数量
maxInitialRequests: 10,
maxAsyncRequests: 10,
// 生成 chunk 的最小/大体积(kb)
minSize: 2000,
maxSize: 200000,
// 为不同的包配置不同的策略,缓存组可以继承和/或覆盖来自 splitChunks.* 的任何选项
// 当不配置cacheGroups时,内部会存在vendors和default默认配置,我们也可以通过手动更改默认值。
cacheGroups: {
// 自定义的缓存组名称
common: {
name: 'common',
// 始终为此缓存组创建分包
enforce: true,
},
},
},
5.按需加载
对于一些不需要一打开页面就展示的资源,我们可以使用按需加载的方式,减少起始页面压力;
标准用法的 import 导入的模块是静态的,会使所有被导入的模块,在加载时就被编译(无法做到按需编译,降低首页加载速度)。有些场景中,你可能希望根据条件导入模块或者按需导入模块,这时你可以使用动态导入代替静态导入
import()返回一个promise对象;
import('/modules/my-module.js')
.then((module) => {
// Do something with the module.
});
6.优化按需加载
有时候模块包比较大,用户操作后再进行加载会有一定的延时,影响用户体验,我们可以配合webpack的魔法注释,使用预加载或预拉取进行优化;
使用prefetch或preload标签预加载模块;prefetch标签可以告诉浏览器在后台加载指定的资源,以便在未来某个时刻使用。preload标签则可以告诉浏览器在当前页面加载时立即加载指定的资源。通过使用这两种标签,可以在浏览器闲置时提前加载一些模块,从而提高程序响应速度。
// 预加载
import(/*webpackPrefetch:true*/ '@/assets/01.png');
// 预拉取
import(/*webpackPreload:true*/ '@/assets/01.png');
7.利用浏览器缓存
浏览器回对加载资源默认进行缓存,当加载资源名称没有改变时,会从缓存数据库中读取;我们可以配置打包后没有修改的文件hash值不变,使浏览器从缓存中读取文件;
// 修改output中filename属性,contenthash代表文件内容改变时更新hash
filename: '[name].[contenthash].js',
8.切换为cdn资源
1.打包分析
// 安装webpack-bundle-analyzer
yarn add -D webpack-bundle-analyzer
// 引入
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
// 使用
new BundleAnalyzerPlugin(),
执行命令后发现:jquery占了很大体积,我们尝试把它切换为cdn资源
2.切换为cdn
// 配置排除打包
externals: {
// 排除打包 npm包名:window.对象名
jquery: 'jQuery',
},
效果:jquery确实没有了,包小了很多,但同时,用到jquery的功能也失效了,我们需要在页面中将jquery的引入换为cdn
// index.html中引入
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
三、构建过程优化
随着项目体积越来越大,打包时耗费时间也越来越多,我们可以通过一些配置去优化这个过程;
1.忽略对第三方包的解析
webpack打包过程会先解析再打包,对于比较成熟的第三方包我们可以忽略解析这个过程l;
// module中配置noParse
noParse: /jquery/,
2.忽略第三方包的指定目录
对于多语言组件库如moment,我们用不到的语言包就可以排除;使用方法如下:
let Webpack = require('webpack');
plugins:[
new Webpack.IgnorePlugin(/\.\/locale/,/moment/),//moment这个库中,如果引用了./locale/目录的内容,就忽略掉,不会打包进去
]
这时候locale目录都会被忽略,我们需要手动引入下中文包:
import 'moment/locale/zh-cn';
moment.locale('zh-cn');
3.并行打包
顾名思义,开启多线程打包,使用happypack
// 下载
yarn add happypack -D
// 使用
// 多线程打包
const happypack = require('happypack');
const os = require('os');
// 创建进程池
const happyThreadPool = happypack.ThreadPool({
// 获取电脑cpu数,作为线程数
size: os.cpus().length,
});
// 配置plugin,以babel-loader为例
new HappyPack({
// 进程名称,每个线程独一无二
id: 'happypack1',
// 当前线程要使用的loader
loaders: [
{
loader: 'babel-loader',
options: {
// 降级es6
presets: ['@babel/preset-env'],
// 修改辅助代码引入
plugins: ['@babel/plugin-transform-runtime'],
},
},
],
threadPool: happyThreadPool,
}),
// 改写js babel-loader关联到进程
{
test: /\.js$/i,
// 排除node_modules下的文件
exclude: /node_modules/,
// 用url的形式把规则和线程关联起来
use: 'happypack/loader?id=happypack1',
// use: {
// loader: 'babel-loader',
// options: {
// // 降级es6
// presets: ['@babel/preset-env'],
// // 修改辅助代码引入
// plugins: ['@babel/plugin-transform-runtime'],
// },
// },
},
happypack目前并不支持所有的loader,有一些兼容性问题,使用需注意
ok,关于webpack打包优化就介绍到这里,码字不易,欢迎三连;