本主题主要是针对上线前,webpack应该做哪些优化处理。
在主要介绍之前先介绍一下基本的知识点,下面的注意点是我遇到的一些问题总结。
一,配置webpack命令
官网给出了全面的配置命令
此处使用文件配置
使用配置文件
npx webpack [--config webpack.config.js]
复制代码
配置文件中的相关选项,请参阅配置。
二,配置基础的配置文件
给出基础的配置文件,可以实现打包,后续在对优化进行梳理
const path = require('path')
// 使用全局定义
const { DefinePlugin } = require('webpack')
// 清空打包出来的dist
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
// 使用html模板
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/index', // webpack 读取的入口文件
// 输出的资源文件
output: {
filename: 'index.js',
path: path.resolve(__dirname, 'dist')
},
mode: 'development',
target: 'web',
devServer: {
hot: true,
open: true,
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: ['babel-loader']
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader', 'postcss-loader']
},
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'postcss-loader', 'less-loader']
},
{
test: /\.(png|jpg|webp)/,
use: ['file-loader']
},
{
test: /\.(ttf|woff2?)$/,
type: 'asset/resource',
generator: {
filename: 'font/[name].[hash:3][ext]'
}
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: '插件使用',
template: './index.html'
}),
new DefinePlugin({
BASE_URL: '"./"'
}),
]
}
复制代码
css抽离和压缩
使用 MiniCssExtractPlugin插件来抽离css
本插件会将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载。
本插件基于 webpack v5 的新特性构建,并且需要 webpack 5 才能正常工作
与 extract-text-webpack-plugin 相比:
- 异步加载
- 没有重复的编译(性能)
- 更容易使用
- 特别针对 CSS 开发
npm install --save-dev mini-css-extract-plugin
复制代码
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
plugins: [new MiniCssExtractPlugin()],
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader']
},
{
test: /\.less$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'less-loader']
},
],
},
};
复制代码
注意点:less 和css的模块都必须配置MiniCssExtractPlugin.loader,因为test: /.css$/
匹配的是css文件只会把css的模块提取出来。同理test: /.less$/
只会匹配less文件,只会把less文件提取出来。所以都必须配置。
使用css-minimizer-webpack-plugin 插件来压缩css
这个插件使用 cssnano 优化和压缩 CSS。
就像 optimize-css-assets-webpack-plugin 一样,但在 source maps 和 assets 中使用查询字符串会更加准确,支持缓存和并发模式下运行
$ npm install css-minimizer-webpack-plugin --save-dev
复制代码
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports = {
module: {
rules: [
{
test: /.s?css$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
},
],
},
optimization: {
minimizer: [
// 在 webpack@5 中,你可以使用 `...` 语法来扩展现有的 minimizer(即 `terser-webpack-plugin`),将下一行取消注释
// `...`,
new CssMinimizerPlugin(),
],
},
plugins: [new MiniCssExtractPlugin()],
};
复制代码
注意点: 开启压缩插件一般是在生产环境,mode为development下不会生效。如果还想在开发环境下启用 CSS 优化,请将 optimization.minimize
设置为 true
。
三,Optimization
没有配置 scope hositing / devtool / terser: 5k
没有配置 devtool / terser: 4k
没有配置 terser /: 3k
都有配置 725B
都有配置 开启tree-shaking: 534B
1. 使用scope hoisting
开启Scope Hoisting:
optimization: {
concatenateModules: true,
},
复制代码
由于 Scope Hoisting 需要分析出模块之间的依赖关系,因此源码必须采用 ES6 模块化语句,不然它将无法生效。
使用scope hoisting前:
使用scope hoisting后:
Scope Hoisting 的实现原理其实很简单:分析出模块之间的依赖关系,尽可能的把打散的模块合并到一个函数中去,但前提是不能造成代码冗余。 因此只有那些被引用了一次的模块才能被合并。
这样做的好处是:
- 代码体积更小,因为函数申明语句会产生大量代码;
- 代码在运行时因为创建的函数作用域更少了,内存开销也随之变小
了解socpe hositing 查看这篇文章,4-14 开启 Scope Hoisting · 深入浅出 Webpack。这里表示上生产的时候要开启该模式。
2. sideEffects
webpack副作用开启:
optimization: {
sideEffects: true,
},
复制代码
此配置表示webpack开启副作用文件的检测。
那检测哪些文件呢,在项目根目录下的package.json文件中配置:
// package.json
"sideEffects": false,
复制代码
"sideEffects": false,表示所有的文件都没有副作用,可以安心的剔除没有使用到的代码。
经过测试,副作用的检测好像是文件级别的,什么意思呢?
这里有一个文件,如果该文件export许多变量, 以及包含export defualt,只要外部文件引入其中一个导出的变量,那么改文件都会被视为没有副作用,其中所有的代码都不会做任何删除。
只有导出变量外部没有任何引用,webpack会标记为有副作用,同时打包的时候会剔除全文件代码。
这里引申一下,如果外部只引用了其中一个export代码,此时为没有副作用的文件,其余代码怎么剔除呢?
使用tree-shaking(下面会讲到)摇掉那些没有使用的export.注意一点是非export的代码不会摇掉,比如window.xxx='xxx'还会保留。
除了webpack去检测哪些文件没有副作用外,还可以手动配置。
"sideEffects": [
'/src/js/login.js',
'src/js/api.js'
],
复制代码
数组中的值为文件路径,表示这些文件没有副作用,可以不用剔除。
在项中引入的css一般会作为有副作用的代码。如果不做特殊处理都会被剔除掉。
import '../font/iconfont.css'
复制代码
手动配置sideEffects看效果可以参考这篇文章webpack 优化配置之 sideEffects | 飘香豆腐の博客
3. Tree-shaking
js的tree-shaking
js的树摇分4个步骤:
-
代码要采用ES6模块化写法来书写咋们的业务代码。
Tree Shaking 可以用来剔除 JavaScript 中用不上的死代码。它依赖静态的 ES6 模块化语法。
-
配置 Babel 让其保留 ES6 模块化语句。
{
"presets": [
[
"env",
{
"modules": false // "modules": false 的含义是关闭 Babel 的模块转换功能,保留原本的 ES6 模块化语法。
}
]
]
}
复制代码
-
usedExports设置为 true
把没用的方法标记
-
使用js的压缩软件
webpack v5 开箱即带有最新版本的 terser-webpack-plugin
。如果你使用的是 webpack v5 或更高版本,同时希望自定义配置,那么仍需要安装 terser-webpack-plugin
。如果使用 webpack v4,则必须安装 terser-webpack-plugin
v4 的版本。
$ npm install terser-webpack-plugin --save-dev
复制代码
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
optimization: {
minimize: true,
minimizer: [new TerserPlugin()],
},
};
复制代码
这几步完成基本可以做到tree-shaking的效果,但进一步的优化点可以参考 # 4-10 使用 Tree Shaking
css的tree-shaking
$ npm install purgecss-webpack-plugin glob -D
复制代码
const PurgecssPlugin = require('purgecss-webpack-plugin');
module.exports = {
// 配置插件的地方
plugins: [
// 創建實例 (第三步)
new PurgecssPlugin({
// 配置需解析檔案 (第四步)
paths: glob.sync(`${path.resolve(__dirname, 'src')}/**/*`, {
nodir: true, // 過濾資料夾結果 (第五步)
}),
}),
],
};
复制代码
详见: Webpack 前端打包工具 - 使用 purgecss-webpack-plugin 清除多餘的 CSS | Roya's Blog
多入口打包
多入口打包可以减少首页的打包体积。
多入口文件打包:
module.exports = {
mode: 'none',
entry: {
index: './src/index.js',
album: './src/album.js'
},
// entry: './src/index.js',
output: {
filename: '[name].bundle.js'
}
}
复制代码
splitChunks
splitChunks作用简单描述就是可以控制webpack在最后合并文件的时候,按在splitChunks设置的要求去拆分不同的文件。
基本用法:
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'async',
minSize: 30000,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
};
复制代码
这里涉及的内容太多,参考下一片篇文章。
或者这个系列文章:
理解webpack4.splitChunks - 渴望做梦 - 博客园
理解webpack4.splitChunks之cacheGroups - 渴望做梦 - 博客园
按需加载 / 魔法注释
按需加载做法很简单:
const render = () => {
console.log('hash改不了')
const mainElement = document.querySelector('.main')
const hash = window.location.hash || '#post'
mainElement.innerHTML = ''
if (hash === '#post') {
// 按需加载
import(/* webpackChunkName: 'post' */'./post').then(post => {
console.log(post, 'post...');
post['default'](mainElement)
})
} else {
// 按需加载
import(/* webpackChunkName: 'post' */'./album/album').then(album => {
album['default'](mainElement)
})
}
}
复制代码
webpack在打包的时候,会把import()动态引入的文件单独打包出来,第一次加载并不会网络请求该文件,从而可以做到减少第一次加载文件的大小。
这里的魔法注释名字如果一样,会被打包到一起。
输出文件名Hash
生产文件带hash有三种类型
第一种:普通hash
module.exports = {
mode: 'none',
entry: './src/index.js',
output: {
filename: '[name]-[hash].bundle.js'
}
}
复制代码
纬度是项目级别的,项目任何文件内容改变,生成的文件hash值全部改变。
第二种:chunkhash
module.exports = {
mode: 'none',
entry: './src/index.js',
output: {
filename: '[name]-[chunkhash].bundle.js'
}
}
复制代码
每次改变文件内容会改变对应的chunk的hash值(打包出来的所有chunk,包括css和js)。
第三种:contenthash
module.exports = {
mode: 'none',
entry: './src/index.js',
output: {
filename: '[name]-[contenthash].bundle.js'
}
}
复制代码
是文件级别的,只有对应文件改变,对应生产的文件的hash就会改变,css文件内容改变只会改变css的打包以后的hash值,js同理。
所有contenthash模式是缓存最好的方法。