为什么要进行性能优化?
webpack是我们重要的代码打包及构建工具,因此它的性能优化也影响到我们的代码质量
所以webpack的性能优化还是很有必要的
可以从以下几点来说:
- 优化开发体验
- 提升效率
- 优化构建速度
- 优化使⽤体验
- 优化输出质量
- 优化要发布到线上的代码,减少⽤户能感知到的加载时间
- 提升代码性能,性能好,执⾏就快
缩小搜索Loader的文件范围
优化loader配置
- test include exclude三个配置项来缩⼩loader的处理范围
- 推荐include
//string
include: path.resolve(__dirname, "./src"),
//array
include: [
path.resolve(__dirname, 'app/styles'),
path.resolve(__dirname, 'vendor/styles')
]
优化resolve.alias配置
resolve.alias配置通过别名来将原导⼊路径映射成⼀个新的导⼊路径
类似于起别名
resolve: {
alias: {
// 起别名
"@img": path.resolve(__dirname, "./src/images")
},
}
//index.less
body {
#app {
height: 400px;
background: url("@img/logo.png") 0 0 no-repeat;
}
}
//直接使用@img
优化resolve.extensions配置
resolve.extensions在导⼊语句没带⽂件后缀时,webpack会⾃动带上后缀后,去尝试查找⽂件是否存 在。
默认值:
// 导入模块后缀列表
// 推荐所有的模块都加上后缀
extensions: [".js", ".json"]
- 后缀尝试列表尽量的小。
- 导⼊语句尽量的带上后缀。
优化resolve.modules配置
resolve.modules会告诉 webpack 解析模块时应该搜索的目录
寻找第三⽅模块,默认是在当前项⽬⽬录下的node_modules⾥⾯去找,
如果没有找到,就会去上⼀级 ⽬录../node_modules找,再没有会去../../node_modules中找,以此类推,和Node.js的模块寻找机制 很类似。 如果我们的第三⽅模块都安装在了项⽬根⽬录下,就可以直接指明这个路径。
module.exports={
resolve:{
modules: [path.resolve(__dirname, "./node_modules")]
}
}
使用externals优化cdn静态资源
三种情况下可使用:
- 公司有cdn
- 静态资源有部署到cdn 有链接了
- 我想使⽤cdn!!!!!!!!
使用以后我的bundle⽂件⾥,就不⽤打包进去这个依赖了,体积会⼩
我们可以将⼀些JS⽂件存储在 CDN 上(减少 Webpack 打包出来的 js 体积),在 index.html 中通过 标签引⼊,
如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="root">root</div>
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
</body>
</html>
我们希望在使⽤时,仍然可以通过 import 的⽅式去引⽤(如 import $ from 'jquery' ),并且希望 webpack 不会对其进⾏打包,此时就可以配置 externals 。
//webpack.config.js
module.exports = {
//...
externals: {
//jquery通过script引⼊之后,全局中即有了 jQuery 变量
'jquery': 'jQuery'
}
}
使用静态资源路径publicPath(CDN)
CDN通过将资源部署到世界各地,使得⽤户可以就近访问资源,加快访问速度。要接⼊CDN,需要把⽹ ⻚的静态资源上传到CDN服务上,在访问这些资源时,使⽤CDN服务提供的URL。
//webpack.config.js
output:{
publicPath: '//cdnURL.com', //指定存放JS⽂件的CDN地址
}
注意要点:
- 咱们公司得有cdn服务器地址
- 确保静态资源⽂件的上传与否
CSS文件的处理
-
使用less或者sass当做css技术栈
npm install less less-loader --save-dev //webpack.config.js--lodaer { test: /\.less$/, use: ["style-loader", "css-loader", "less-loadr"] } -
使用postcss为样式自动补齐浏览器前缀
npm i postcss-loader autoprefixer -D //安装对应依赖//新建postcss.config.js module.exports = { plugins: [ require("autoprefixer")({ overrideBrowserslist: ["last 2 versions", ">1%"] }) ] }; //index.less body { div { display: flex; border: 1px red solid; } } //webpack.config.js { test: /\.less$/, include: path.resolve(__dirname, "./src"), use: [ "style-loader", "css-loader", "less-loader", "postcss-loader" ] },提醒:postcss 使用一定要创建postcss.config.js进行配置
-
借助MiniCssExtractPlugin 完成抽离css
如果不做抽取配置,我们的 css 是直接打包进 js ⾥⾯的,我们希望能单独⽣成 css ⽂件。 因为单独⽣ 成css,css可以和js并⾏下载,提⾼⻚⾯加载效率
npm install mini-css-extract-plugin -D //安装mini-css-extract-plugin插件使用:
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); rules: [ { test: /\.less$/, include: path.resolve(__dirname, "./src/css"), use: [ // "style-loader", // 不再需要style-loader,⽤MiniCssExtractPlugin.loader代替 { loader: miniCssExtractPlugin.loader, options: { publicPath: "../", }, }, "css-loader", "postcss-loader", "less-loader", ] }, ] plugins: [ new MiniCssExtractPlugin({ filename: "css/[name]_[contenthash:6].css", chunkFilename: "[id].css" }) ]
CSS文件的压缩
- 借助 optimize-css-assets-webpack-plugin
- 借助cssnano
安装:
npm install cssnano -D
npm i optimize-css-assets-webpack-plugin -D
使用:
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
new OptimizeCSSAssetsPlugin({
cssProcessor: require("cssnano"), //引⼊cssnano配置压缩选项
cssProcessorOptions: {
discardComments: { removeAll: true }
}
})
压缩HTML
使用:
new htmlWebpackPlugin({
template: "./index.html",
filename: "index.html",
minify: {
// 压缩HTML⽂件
removeComments: true, // 移除HTML中的注释
collapseWhitespace: true, // 删除空⽩符与换⾏符
minifyCSS: true // 压缩内联css
}
}),
图片优化
在 Webpack 中可以借助img-webpack-loader来对使⽤到的图⽚进⾏优化。它⽀持 JPG、PNG、GIF 和 SVG 格式的图⽚,因此我们在碰到所有这些类型的图⽚都会使⽤它。
安装:
npm install image-webpack-loader --save-dev
macos系统版本需要依赖libpng,需要翻墙!!!!!
使用:
rules: [{
test: /\.(gif|png|jpe?g|svg)$/i,
use: [
'file-loader',
{
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
quality: 65
},
// optipng.enabled: false will disable optipng
optipng: {
enabled: false,
},
pngquant: {
quality: [0.65, 0.90],
speed: 4
},
gifsicle: {
interlaced: false,
},
// the webp option will enable WEBP
webp: {
quality: 75
}
}
},
],
}],
development vs Production模式区分打包
我们需要安装一个依赖
npm install webpack-merge -D
使用案例:
const merge = require("webpack-merge")
const commonConfig = require("./webpack.common.js")
const devConfig = {
//这里写对应模式的配置
}
module.exports = merge(commonConfig,devConfig)
//package.js
"scripts":{
"dev":"webpack-dev-server --config ./webpack.dev.js",
"build": "webpack --config ./webpack.pro.js"
}
基于环境变量区分
-
借助cross-env
npm i cross-env -Dpackage⾥⾯配置命令脚本,传⼊参数
//package.json
"test": "cross-env NODE_ENV=test webpack --config ./webpack.config.test.js",
在webpack.config.js⾥拿到参数
process.env.NODE_ENV
//外部传⼊的全局变量
module.exports = (env)=>{
if(env && env.production){
return merge(commonConfig,prodConfig)
}else{
return merge(commonConfig,devConfig)
}
}
//外部传⼊变量
scripts:" --env.production"
tree Shaking
webpack2.x开始⽀持 tree shaking 概念,顾名思义,"摇树",
摇树 - 大力的晃动树木 => 会把树上的枯叶都摇下来
清除无用的css ,无用的js 也就是 Dead-code
Dead Code ⼀般具有以下⼏个特征
- 代码不会被执⾏,不可到达
- 代码执⾏的结果不会被⽤到 代码只会影响死变量(只写不读)
- Js tree shaking只⽀持ES module的引⼊⽅式!!!!(import || module.exports)
Css tree shaking
首先我们要安装依赖
npm i glob-all purify-css purifycss-webpack --save-dev // 安装
使用:
const PurifyCSS = require('purifycss-webpack')
const glob = require('glob-all')
plugins:[
// 清除⽆⽤ css
new PurifyCSS({
paths: glob.sync([
// 要做 CSS Tree Shaking 的路径⽂件
// 请注意,我们同样需要对 html ⽂件进⾏ tree shaking
path.resolve(__dirname, './src/*.html'),
path.resolve(__dirname, './src/*.js')
])
})
]
注意一点就是Css tree shaking 只能处理使用mini-css-extract-plugin插件抽离过的css
JS tree shaking
只⽀持import⽅式引⼊,不⽀持commonjs的⽅式引⼊
案例:
//expo.js
export const add = (a, b) => {
return a + b;
};
export const minus = (a, b) => {
return a - b;
};
//index.js
import { add } from "./expo";
add(1, 2);
//webpack.config.js
optimization: {
usedExports: true // 哪些导出的模块被使⽤了,再做打包
}
只要mode是production就会⽣效,develpoment的tree shaking是不⽣效的,因为webpack为了 ⽅便你的调试
可以查看打包后的代码注释以辨别是否⽣效。
⽣产模式不需要配置,默认开启
sideEffects 处理副作用
因为我们不知道某些导入是否为有用,有可能只是还没有引用
sideEffects字段,默认为true,即默认这个npm包有副作用,就可以把一些import语句保留下来
可以设置为false,但是需要配合usedExports。
//package.json
"sideEffects":false //正常对所有模块进⾏tree shaking , 仅⽣产模式有效,需要配合usedExports
//或者 在数组⾥⾯排除不需要tree shaking的模块
"sideEffects":['*.css','@babel/polyfill']
代码分割 code Splitting
单⻚⾯应⽤spa:
打包完后,所有⻚⾯只⽣成了⼀个bundle.js
- 代码体积变⼤,不利于下载
- 没有合理利⽤浏览器资源
多⻚⾯应⽤mpa:
如果多个⻚⾯引⼊了⼀些公共模块,那么可以把这些公共的模块抽离出来,单独打包。公共代码只需要 下载⼀次就缓存起来了,避免了重复下载。
main.js 170kb
Lodash.js 150kb
Main.js 20kb
import _ from "lodash";
console.log(_.join(['a','b','c','****']))
假如我们引⼊⼀个第三⽅的⼯具库,体积为1mb,⽽我们的业务逻辑代码也有1mb,那么打包出来的体积⼤ ⼩会在2mb
导致问题:
- 体积⼤,加载时间⻓
- 业务逻辑会变化,第三⽅⼯具库不会,所以业务逻辑⼀变更,第三⽅⼯具库也要跟着变。
其实code Splitting概念 与 webpack并没有直接的关系,只不过webpack中提供了⼀种更加⽅便的⽅法 供我们实现代码分割
详细配置:
optimization: {
splitChunks: {
chunks: 'async',//对同步 initial,异步 async,所有的模块有效 all
minSize: 30000,//最⼩尺⼨,当模块⼤于30kb
maxSize: 0,//对模块进⾏⼆次分割时使⽤,不推荐使⽤
minChunks: 1,//打包⽣成的chunk⽂件最少有⼏个chunk引⽤了这个模块
maxAsyncRequests: 5,//最⼤异步请求数,默认5
maxInitialRequests: 3,//最⼤初始化请求书,⼊⼝⽂件同步请求,默认3
automaticNameDelimiter: '-',//打包分割符号
name: true,//打包后的名称,除了布尔值,还可以接收⼀个函数function
cacheGroups: {//缓存组
vendors: {
test: /[\\/]node_modules[\\/]/,
name: "vendor", // 要缓存的 分隔出来的 chunk 名称
priority: -10//缓存组优先级 数字越⼤,优先级越⾼
},
other: {
chunks: "initial", // 必须三选⼀: "initial" | "all" | "async"(默认就是async)
test: /react|lodash/, // 正则规则验证,如果符合就提取 chunk,
name: "other",
minSize: 30000,
minChunks: 1,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true//可设置是否重⽤该chunk
}
}
}
}
使⽤下⾯配置即可:
optimization:{
//帮我们⾃动做代码分割
splitChunks:{
chunks:"all",//默认是⽀持异步,我们使⽤all
}
}
作用域提升 Scope Hoisting
作⽤域提升(Scope Hoisting)是指 webpack 通过 ES6 语法的静态分析,分析出模块之间的依赖关 系,尽可能地把模块放到同⼀个函数中。下⾯通过代码示例来理解:
// hello.js
export default 'Hello, Webpack';
// index.js
import str from './hello.js';
console.log(str);
打包后, hello.js 的内容和 index.js 会分开
通过配置 optimization.concatenateModules=true`:开启 Scope Hoisting
// webpack.config.js
module.exports = {
optimization: {
concatenateModules: true
}
};
我们发现hello.js内容和index.js的内容合并在⼀起了!所以通过 Scope Hoisting 的功能可以让 Webpack 打包出来的代码⽂件更⼩、运⾏的更快。
HardSourceWebpackPlugin
HardSourceWebpackPlugin 是webpack的插件,用于为模块提供中间缓存步骤。
作用:
- 提供中间缓存的作⽤
- ⾸次构建没有太⼤的变化
- 第⼆次构建时间就会有较⼤的节省
安装:
npm install --save-dev hard-source-webpack-plugin
使用:
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')
const plugins = [
new HardSourceWebpackPlugin()
]
使用happypack并发执行任务
构建时间较久
项⽬复杂度较⾼
运⾏在 Node.之上的Webpack是单线程模型的,也就是说Webpack需要⼀个⼀个地处理任务,不能同 时处理多个任务。 Happy Pack 就能让Webpack做到这⼀点,它将任务分解给多个⼦进程去并发执⾏,⼦进程处理完后再将结果发送给主进程。从⽽发挥多核 CPU 电脑的威⼒。
安装:
npm i -D happypack
使用:
var happyThreadPool = HappyPack.ThreadPool({ size: 5 });
//const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length })
// webpack.config.js
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: [
{
// ⼀个loader对应⼀个id
loader: "happypack/loader?id=babel"
}
]
},
{
test: /\.css$/,
include: path.resolve(__dirname, "./src"),
use: ["happypack/loader?id=css"]
},
]
//在plugins中增加
plugins: [
new HappyPack({
// ⽤唯⼀的标识符id,来代表当前的HappyPack是⽤来处理⼀类特定的⽂件
id: 'babel',
// 如何处理.js⽂件,⽤法和Loader配置中⼀样
loaders: ['babel-loader?cacheDirectory'],
threadPool: happyThreadPool,
}),
new HappyPack({
id: "css",
loaders: ["style-loader", "css-loader"]
}),
]
参考资料: