分析打包
webpack 打包体积分析
vue cli项目可以通过添加--report命令:"build": "vue-cli-service build --report",打包后 dist 目录会生成 report.html 文件,用来分析各文件的大小或者通过安装 webpack-bundle-analyzer 插件来分析,步骤如下:
1)安装
npm install webpack-bundle-analyzer -D
2)vue.config.js中 引入
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
configureWebpack: {
plugins: [
new BundleAnalyzerPlugin()
]
}
}
vite 分析打包
安装 rollup-plugin-visualizer 插件,此插件可以展示构建时长、chunk 数量及大小,是分析构建的绝佳利器。
pnpm add rollup-plugin-visualizer -D
在 vite.config.ts 文件中引入该插件:
import { visualizer } from 'rollup-plugin-visualizer';
export default defineConfig({
plugins: [
// ...
// 将 visualizer 插件放到最后的位置
visualizer()
]
});
配置好分析插件后,执行 构建命令,此时在根目录下会生成一个 stats.html 文件,此文件就是当前项目打包的chunk 分析图。
优化方案
文件路径查询优化
1、resolve.alias 的优化
resolve.alias配置将原始导入路径映射到新的导入路径,具有两个目的:
- 创建别名
- 减少查找时间。
例如:
resolve: {
alias: {
'@': resolve(__dirname, './src'),
}
}
2、文件扩展名的优化
resolve.extensions 表示要尝试的文件扩展名列表。它还会影响构建性能,并默认为:
extensions: ['.js', '.json']'.js', '.json']
例如,当遇到像 require('./data') 这样的导入语句时,Webpack 首先会查找 ./data.js。如果未找到,它将搜索 ./data.json,如果仍未找到,它将抛出错误。
因此,建议尽量保持扩展名列表尽可能简洁,省略不太可能出现的情况。将最常用的文件扩展名放在最前面,以加快搜索过程。
resolve: {
extensions: ['.js', '.vue', '.json'],'.js', '.vue', '.json'],
}
Tree Shaking按需引入优化
1、Tree Shaking(摇树优化)
Tree shaking 是一种从项目中删除未使用代码的技术。它依赖于 ES 模块语法。例如,当使用 lodash 时:
// Bad: This imports the entire lodash library
import _ from 'lodash';
// Good: This imports only the 'isEmpty' function from lodash
import _isEmpty from 'lodash/isEmpty';
Tree Shaking 在很大程度上减少了捆绑包的大小,是性能优化的重要部分。 Vite 和 Webpack 4.x 默认情况下都启用了 Tree Shaking。
2、组件库的按需引入
为什么没有使用 externals 的方式处理组件库呢?
externals缺点:直接在html内引入的,失去了按需引入的功能,只能引入组件库完整的js和css
组件库按需引入的原理:最终只引入指定组件和对应的样式
elementUI 需要借助 babel-plugin-component 插件实现,插件的作用如下:
import { Button } from 'element-ui'
Vue.component(Button.name, Button)
编译后的文件(自动引入 button.css):
import _Button from "element-ui/lib/button";
import _Button2 from "element-ui/lib/theme-chalk/button.css";
// base.css是公共的样式
import "element-ui/lib/theme-chalk/base.css";
Vue.component(_Button.name, _Button);
通过该插件,最终只引入指定组件和样式,来实现减少组件库体积大小
1)安装 babel-plugin-component
npm install babel-plugin-component -D
2)babel.config.js中引入
module.exports = {
presets: ['@vue/app'],
plugins: [
[
'component',
{
libraryName: 'element-ui',
styleLibraryName: 'theme-chalk'
}
]
]
};
组件库按需引入后,包体积大小变小、打包速度得到提升,同时 chunk-vendors.css 的体积也有了明显的减少
3、webpack的魔法注释
- vue-router使用按需引入,结合webpack的魔法注释
- 弹窗等懒加载组件,按需引入
4、减小三方依赖的体积
例如:剔除掉无用的语言包
打包速度优化
1、HappyPack 多线程打包
HappyPack 就能实现多线程打包,它把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程,来提升打包速度
1)安装
npm install HappyPack -D
2)vue.config.js 中引入
const HappyPack = require('happypack');
const os = require('os');
// 开辟一个线程池,拿到系统CPU的核数,happypack 将编译工作利用所有线程
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
module.exports = {
configureWebpack: {
plugins: [
new HappyPack({
id: 'happybabel',
loaders: ['babel-loader'],
threadPool: happyThreadPool
})
]
}
}
2)vue.config.js 中引入
const CompressionPlugin = require('compression-webpack-plugin');
module.exports = {
configureWebpack: {
plugins: [
new CompressionPlugin({
test: /.(js|css)(?.*)?$/i, //需要压缩的文件正则
threshold: 1024, //文件大小大于这个值时启用压缩
deleteOriginalAssets: false //压缩后保留原文件
})
]
}
}
第三方依赖抽离优化
1、webpack externals 提取项目依赖
从上面的打包分析页面中可以看到:
-
chunk-vendors.js体积中最大的几个文件都是一些公共依赖包,那么只要把这些依赖提取出来,就可以解决 chunk-vendors.js 过大的问题 -
可以使用
externals来提取这些依赖包,告诉 webpack 这些依赖是外部环境提供的,在打包时可以忽略它们,就不会再打到 chunk-vendors.js 中
1)vue.config.js 中配置:
module.exports = {
configureWebpack: {
externals: {
vue: 'Vue',
'vue-router': 'VueRouter',
axios: 'axios',
echarts: 'echarts'
}
}
2)在 index.html 中使用 CDN 引入依赖
<body>
<script src="http://lib.baomitu.com/vue/2.6.14/vue.min.js"></script>
<script src="http://lib.baomitu.com/vue-router/3.5.1/vue-router.min.js"></script>
<script src="http://lib.baomitu.com/axios/1.2.1/axios.min.js"></script>
<script src="http://lib.baomitu.com/echarts/5.3.2/echarts.min.js"></script>
</body>
2、vite 提取项目依赖
对于固定的外部库,可以直接采用CDN的方式引入,无需打包进chunk中。
首先安装 rollup-plugin-external-globals 插件,该插件可以告诉 Rollup 哪些库是不需要打包的。
pnpm add rollup-plugin-external-globals -D
使用方法:在vite.config.ts中添加打包配置
import externalGlobals from 'rollup-plugin-external-globals';
const globals = externalGlobals({
moment: 'moment',
'video.js': 'videojs',
jspdf: 'jspdf',
xlsx: 'XLSX',
});
export default defineConfig({
build: {
rollupOptions: {
external: ['moment', 'video.js', 'jspdf','xlsx'],
plugins: [globals],
}
}
});
同时需要在 index.html 模版中引入对应库的CDN。
具体的CDN链接请根据自己的需要去官网或是CDN网站查询
<script src="https://.../moment.min.js"></script>
<script src="https://.../xlsx.full.min.js"></script>
<script src="https://.../jspdf.polyfills.umd.js"></script>
<script src="https://.../jspdf.umd.min.js"></script>
<script src="https://.../video.min.js"></script>
### 3、DllPlugin 动态链接库
`DllPlugin` 与 externals 的作用相似,都是将依赖抽离出去,节约打包时间。区别是 DllPlugin 是将依赖单独打包,这样以后每次只构建业务代码,而 externals 是将依赖转化为 CDN 的方式引入
当公司没有很好的 CDN 资源或不支持 CDN 时,就可以考虑使用 DllPlugin ,替换掉 externals
DllPlugin 配置流程大致分为三步:
1)创建 dll.config.js 配置文件
```js
import { DllPlugin } from "webpack";
export default {
// 需要抽离的依赖
entry: {
vendor: ["vue", "vue-router", "axios", "echarts"]
},
mode: "production",
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
chunks: "all",
name: "vendor",
test: /node_modules/
}
}
}
},
output: {
filename: "[name].dll.js", // 输出路径和文件名称
library: "[name]", // 全局变量名称:其他模块会从此变量上获取里面模块
path: AbsPath("dist/static") // 输出目录路径
},
plugins: [
new DllPlugin({
name: "[name]", // 全局变量名称:减小搜索范围,与output.library结合使用
path: AbsPath("dist/static/[name]-manifest.json") // 输出目录路径
})
]
};
2)package.json 配置脚本
"build:dll": "webpack --config ./dll.config.js",
3)使用 DllReferencePlugin 将打包生成的dll文件,引用到需要的预编译的依赖上来,并通过 html-webpack-tags-plugin 在打包时自动插入dll文件
vue.config.js 配置如下
import { DllReferencePlugin } from "webpack";
import HtmlTagsPlugin from "html-webpack-tags-plugin";
export default {
configureWebpack: {
plugins: [
new DllReferencePlugin({
manifest: AbsPath("dist/static/vendor-manifest.json") // manifest文件路径
}),
new HtmlTagsPlugin({
append: false, // 在生成资源后插入
publicPath: "/", // 使用公共路径
tags: ["static/vendor.dll.js"] // 资源路径
})
]
}
};
先运行 npm run build:dll 打包生成依赖文件,以后只用运行 npm run build 构建业务代码即可
拆包优化
1、webpack SplitChunksPlugin拆包
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'async', // 异步加载chunk
minSize: 30000,
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~', // 文件名中chunk分隔符
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/, //
priority: -10
},
default: {
minChunks: 2, // 最小的共享chunk数
priority: -20,
reuseExistingChunk: true
}
}
}
}
};
2、Vite 拆分代码
build: {
rollupOptions: {
output: {
chunkFileNames: ‘js/[name]-[hash].js’,
entryFileNames: ‘js/[name]-[hash].js’,
assetFileNames: ‘[ext]/[name]-[hash].[ext]’
}
}
}
Gzip 压缩文件优化
1、webpack Gzip压缩
线上的项目,一般都会结合构建工具 webpack 插件或服务端配置 nginx,来实现 http 传输的 gzip 压缩,目的就是把服务端响应文件的体积尽量减小,优化返回速度
html、js、css资源,使用 gzip 后通常可以将体积压缩70%以上
这里介绍下使用 webpack 进行 gzip 压缩的方式,使用 compression-webpack-plugin 插件
1)安装
npm install compression-webpack-plugin -D
2、vite Gzip压缩
- 使用
vite-plugin-compress2插件对文件进行 gzip 压缩,减小构建包体积
import { compression } from 'vite-plugin-compression2'
export default ({ mode }) => defineConfig({
plugins: [vue(), compression()],
})
移除不必要的打包配置
1、在 Vite 中禁用不必要的构建配置
Vite 还提供了优化构建配置的选项。以下是禁用某些构建功能的示例:
build: {
terserOptions: {
compress: {
// Remove console in production// Remove console in production
drop_console: true,
drop_debugger: true,
},
},
// Disable file size reporting
reportCompressedSize: false,
// Disable source map generation to reduce bundle size (important for production)
sourcemap: false, // Disable this for production to minimize bundle size
}
polyfill 低版本浏览器兼容优化
1、在 Vite 中低版本浏览器兼容
判断当前环境是否为测试环境,测试环境下不使用@vitejs/plugin-legacy插件。
注意构建命令要使用 vite build --mode test
import legacy from '@vitejs/plugin-legacy';
export default defineConfig(({ mode }: { mode: string }) => {
// 判断当前是否为测试环境,注意构建命令要使用 vite build --mode test
const isTest = mode === 'test';
return {
plugins: [
!isTest && legacy({
targets: ['ie >= 11'],
additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
}),
]
}
})
测试环境下不使用legacy插件,但是还是需要引入Polyfill的,否则无法适配低端浏览器,这里需要在html中引入提前生成好的 Polyfill 链接。
这里我们需要使用vite-plugin-html插件,动态插入该Polyfill链接,具体实现如下:
在vite.config.ts文件中
import legacy from '@vitejs/plugin-legacy';
export default defineConfig(({ mode }: { mode: string }) => {
// 判断当前是否为测试环境,注意构建命令要使用 vite build --mode test
const isTest = mode === 'test';
return {
plugins: [
createHtmlPlugin({
minify: true,
entry: 'src/main.tsx',
template: 'index.html',
inject: {
data: {
polyfill: isTest
? 'https://polyfill.io/v3/polyfill.min.js?features=es2015%2Ces2016%2Ces2017%2Ces2018%2Ces2019%2Ces2020%2Ces2021%2Ces2022'
: '',
},
},
}),
]
}
})
在 index.html 文件中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<!-- 注意在这里引入插入的polyfill -->
<script src="<%- polyfill %>"></script>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>