之前开发项目后,每次构建的时候都会经历较长时间的等待,且打包后文件加载速度慢的情况,结合最近vite+vue3项目构建优化,总结下构建优化方案,如有更好方案,欢迎各位大佬分享。
工欲善其事必先利其器,构建优化也是,需要先知道项目代码的整体情况
Rollup 插件可视化工具rollup-plugin-visualizer
,构建后可视化并分析你的 Rollup 包,看看哪些模块占用了空间。使用方法如下:
npm i rollup-plugin-visualizer -D
import { visualizer } from 'rollup-plugin-visualizer';
export default defineConfig({
plugins: [vue(), visualizer({
emitFile: false,
filename: 'analysis-chart.html', // 分析图生成的文件名
open:true // 如果存在本地服务端口,将在打包后自动展示
})],
})
CDN 是构建在数据网络上的一种分布式的内容分发网。
CDN的作用是采用流媒体服务器集群技术,克服单机系统输出带宽及并发能力不足的缺点,可极大提升系统支持的并发流数目,减少或避免单点失效带来的不良影响。
Tips :有以下两种配置方式,可根据个人习惯使用对应配置,不过需要注意如果CDN的资源出现错误,那么所引入的项目也会出现错误,建议把资源都Down到本地,然后在引入本地的链接,保证不会被所引入资源报错影响
开发环境不走CDN,只有构建后走
常见cdn网站
具体怎样查找库cdn包的路径呢,以 为例element-plus: @2.2.32
直接按照此格式访问 https://unpkg.com/[email protected]
链接得到 https://unpkg.com/[email protected]/dist/index.full.js
即是对应的cdn包路径了
jsDelivr 的格式为https://cdn.jsdelivr.net/npm/package@version
即https://cdn.jsdelivr.net/npm/[email protected]
npm i vite-plugin-cdn-import -D
以最近项目为例,具体配置如下
vite.config.ts
文件
import { fileURLToPath, URL } from 'node:url'
import { resolve } from 'path'
import { defineConfig, loadEnv, splitVendorChunkPlugin, type PluginOption } from 'vite'
// import importToCDN from 'vite-plugin-cdn-import'
import { autoComplete, Plugin as importToCDN } from 'vite-plugin-cdn-import';
export default defineConfig(({ command, mode }) => {
const env = loadEnv(mode, `${process.cwd()}/env`, '')
return {
base: '/',
plugins: [
vue(),
...
// Tips:如果出现 CDN 配置的资源无法访问需修改配置或者可先不使用 importToCDN
importToCDN({
// prodUrl:可选,默认指向 https://cdn.jsdelivr.net/npm/{name}@{version}/{path}
// 可使用这种格式 https://cdn.jsdelivr.net/npm/[email protected] 查看是否存在 例如打开浏览器访问得到 https://cdn.jsdelivr.net/npm/[email protected]/dist/index.full.js
// 也可指向 'https://unpkg.com/{name}@{version}/{path}'、本地根目录、获取自己的服务器 等
// prodUrl: '/{path}', // 根目录 需要格外注意配置路径是否正确,且需要把资源先down下来
// prodUrl: 'https://xxx.com/{name}@{version}/{path}', // 自己的服务器上
prodUrl: 'https://unpkg.com/{name}@{version}/{path}', // https://unpkg.com/
modules: [
autoComplete('vue'),
autoComplete('axios'),
autoComplete('lodash'),
{
name: 'element-plus',
// ElementPlus 为什么不是同下面第二种配置的elementPlus是因为这个配置同CDN资源一致,而下面的配置同需同main.ts的引入名称一致
var: 'ElementPlus', // 外部化的依赖提供一个全局变量 同rollupOptions配置中的globals的值
// https://unpkg.com/[email protected]/dist/index.full.js 或者 dist/index.full.js
path: 'dist/index.full.js',
// 可选
css: 'dist/index.css'
},
{
name: 'vue-i18n',
var: 'VueI18n',
path: 'dist/vue-i18n.global.prod.js',
},
{
name: 'vue-router',
var: 'VueRouter',
path: 'dist/vue-router.global.js'
},
// VueDemi这个是pinia用来判断是vue2还是vue3所需要的,要额外引入一下
{
name: 'vue-demi',
var: 'VueDemi',
path: 'https://unpkg.com/[email protected]/lib/index.iife.js'
},
{
name: 'pinia',
var: 'Pinia',
path: 'dist/pinia.iife.js'
},
// echarts,只有配置全局的时候有效,不然构建的时候还是会打包执行。也可以把echarts处理成按需引入
{
name: 'echarts',
var: 'echarts',
path: 'dist/echarts.js'
},
// echarts 内使用了
{
name: 'zrender',
var: 'zrender ',
path: 'dist/zrender.js'
},
],
}),
],
...
}
})
Tips:可能在构建后有时候会报以下错误,具体需要看上面代码的
var
是否为CDN全局定义字段,下面截图是本人在首次配置期间遇到的
需要考虑依赖之间支付存在关联,如果有,需要同时配置
且需要考虑配置的Var是否合理
这种方式需要单独在index.html中引入cdn资源
为啥老是提示这个错误???
多次构建验证发现externalGlobals
得配置的值需要和CDN引入的全局定义一致,不然就会报错(例如Vue、VueRouter等)
caught TypeError: Failed to resolve module specifier "vue". Relative references must start with either "/", "./", or "../".
例如配置
Vue CDN
: https://unpkg.com/[email protected]/dist/vue.global.prod.js,则需要配置为"Vue",如图
需要安装个 rollup-plugin-external-globals
插件
npm i rollup-plugin-external-globals -D
废话不多说直接上代码,即rollupOptions
部分
`vite.config.ts`文件配置如下
```ts
import externalGlobals from 'rollup-plugin-external-globals';
...
build: {
minify: "terser", // 必须开启:使用 terserOptions 才有效果
terserOptions: {
compress: {
drop_console: process.env.NODE_ENV === 'production' ? true : false,
drop_debugger: process.env.NODE_ENV === 'production' ? true : false,
},
},
rollupOptions: {
// 确保外部化处理那些你不想打包进库的依赖
external: [
'vue',
'vue-i18n',
'vue-router',
'element-plus',
// VueDemi这个是pinia用来判断是vue2还是vue3所需要的,要额外引入一下
'vue-demi',
'pinia',
],
plugins: [
// vue: 'Vue' --- vue为依赖包名 Vue为项目构建后的全局变量
// 同上,给外部化的依赖提供一个全局变量,即配置在external得值
externalGlobals({
vue: 'Vue',
'vue-i18n': 'VueI18n',
'vue-router': 'VueRouter',
elementPlus: 'element-plus',
// VueDemi这个是pinia用来判断是vue2还是vue3所需要的,要额外引入一下
'vue-demi': 'VueDemi',
pinia: 'createPinia',
})
],
},
},
这种配置需要再在inde.html
文件手动引入对应CDN文件资源
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://unpkg.com/[email protected]/dist/vue.global.prod.js">script>
<script src="https://unpkg.com/[email protected]/dist/axios.min.js">script>
<script src="https://unpkg.com/[email protected]/dist/vue-i18n.global.prod.js">script>
<script src="https://unpkg.com/[email protected]/dist/vue-router.global.prod.js">script>
<script src="https://unpkg.com/[email protected]/dist/index.full.js">script>
<script src="https://unpkg.com/[email protected]/lib/index.iife.js">script>
<script src="https://unpkg.com/[email protected]/dist/pinia.iife.js">script>
<link rel="icon" href="/src/assets/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SIStitle>
head>
<body>
<div id="app">div>
<script type="module" src="/src/main.ts">script>
body>
html>
当未配置构建工具的分包功能,构建的后的资源将会较大且是独立的一个js and css 文件,这样就会存在本地加载文件的压力
vite底层集成了rollup的功能,我们可以直接配置即可,具体可去官网查看更详细配置
build: {
minify: "terser", // 必须开启:使用 terserOptions 才有效果
terserOptions: {
compress: {
drop_console: process.env.NODE_ENV === 'production' ? true : false,
drop_debugger: process.env.NODE_ENV === 'production' ? true : false,
},
},
rollupOptions: {
// 静态资源分类打包
output: {
// 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
globals: {},
chunkFileNames: 'static/js/[name]-[hash].js',
entryFileNames: 'static/js/[name]-[hash].js',
assetFileNames: 'static/[ext]/[name]-[hash].[ext]',
manualChunks(id) { // 静态资源分拆打包
if (id.includes('node_modules')) {
return id.toString().split('node_modules/')[1].split('/')[0].toString();
}
}
}
},
},
gzip是现今Internet 上使用非常普遍的一种数据压缩格式(一种文件格式)。
HTTP协议上的GZIP编码是一种用来改进WEB应用程序性能的技术。大流量的WEB站点常常使用GZIP压缩技术来让用户感受更快的速度。这一般是指
www服务器
中安装的一个功能,当有人来访问这个服务器中的网站时,服务器中的这个功能就将网页内容压缩后传输到来访的电脑浏览器中显示出来.一般对纯文本内容可压缩到原大小的40%.这样传输就快了,效果就是你点击网址后会很快的显示出来.当然这也会增加服务器的负载.
一般服务器中都安装有这个功能模块的。
如上描述,gzip不只是我们需要在打包的时候生成,还得在服务器开
那么我们先处理打包的时候进入gzip压缩
npm i vite-plugin-compression -D
Tips:配置过程中有个小插曲插件源码上备注说明如下,由于英文水平有限,
Whether to enable compression
,使用翻译软件为是否启用压缩
,可实际配置之后发现应该是是否禁用压缩
的意思
...
/**
* Whether to enable compression
* @default: false
*/
disable?: boolean;
...
在vite.config.ts
文件引入如下:
...
import viteCompression from 'vite-plugin-compression';
...
export default defineConfig(({ command, mode }) => {
const env = loadEnv(mode, `${process.cwd()}/env`, '')
return {
mode,
// env配置文件变量前缀
envPrefix: 'SIS_',
// env配置文件夹位置
envDir: 'env',
// 构建后文件引用 相对路径
// base: env.SIS_PUBLIC_PATH, // 同webpack assetsPublicPath
base: '/',
plugins: [
vue(),
...
// 构建压缩文件
viteCompression({
// 记录压缩文件及其压缩率。默认true
verbose: true,
// 是否启用压缩,默认false
disable: false,
// 需要使用压缩前的最小文件大小,单位字节(byte) b,1b(字节)=8bit(比特), 1KB=1024B
threshold: 10240, // 即10kb以上即会压缩
// 压缩算法 可选 'gzip' | 'brotliCompress' | 'deflate' | 'deflateRaw'
algorithm: 'gzip',
// 压缩后的文件格式
ext: '.gz',
}),
...
]
}
})
这里使用nginx来做服务器
具体需要在location /位置配置gzip_static on;
,配置如下
location / {
try_files $uri $uri/ @router;#需要指向下面的@router否则会出现vue的路由在nginx中刷新出现404
index index.html index.htm;
gzip_static on;
}
#对应上面的@router,主要原因是路由的路径资源并不是一个真实的路径,所以无法找到具体的文件
#因此需要rewrite到index.html中,然后交给路由在处理请求资源
location @router {
rewrite ^.*$ /index.html last;
}
Content-Encoding: gzip
和响应头有Accept-Encoding: gzip, deflate, br
,且可以看到文件大小使用压缩后小了很多即配置成功根据上述四个步骤最终配置如下,话不多说,直接上完整vite.config.ts
配置
import { fileURLToPath, URL } from 'node:url'
import { resolve } from 'path'
import { defineConfig, loadEnv, splitVendorChunkPlugin, type PluginOption } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
const getProxyConfig = require('./build/proxy');
import viteCompression from 'vite-plugin-compression';
import htmlPlugin from "vite-plugin-html-config";
import { visualizer } from 'rollup-plugin-visualizer';
// import importToCDN from 'vite-plugin-cdn-import'
import { autoComplete, Plugin as importToCDN } from 'vite-plugin-cdn-import';
import externalGlobals from 'rollup-plugin-external-globals';
// https://vitejs.dev/config/
export default defineConfig(({ command, mode }) => {
const env = loadEnv(mode, `${process.cwd()}/env`, '')
Object.assign(process.env, env, { NODE_ENV: mode })
return {
mode,
// env配置文件变量前缀
envPrefix: 'SIS_',
// env配置文件夹位置
envDir: 'env',
// 构建后文件引用 相对路径
// base: env.SIS_PUBLIC_PATH, // 同webpack assetsPublicPath
base: '/',
plugins: [
vue(),
vueJsx(),
// splitVendorChunkPlugin(),
// 构建压缩文件
viteCompression({
// 记录压缩文件及其压缩率。默认true
verbose: true,
// 是否启用压缩,默认false
disable: false,
// 需要使用压缩前的最小文件大小,单位字节(byte) b,1b(字节)=8bit(比特), 1KB=1024B
threshold: 10240, // 即10kb以上即会压缩
// 压缩算法 可选 'gzip' | 'brotliCompress' | 'deflate' | 'deflateRaw'
algorithm: 'gzip',
// 压缩后的文件格式
ext: '.gz',
}),
// 构建时间
htmlPlugin({
metas:[
{
name: "builtTime",
content: new Date().toLocaleString(),
}
]
}),
visualizer({
emitFile: false,
filename: 'analysis-chart.html', // 分析图生成的文件名
open:true // 如果存在本地服务端口,将在打包后自动展示
}) as PluginOption,
// Tips:如果出现 CDN 配置的资源无法访问需修改配置或者可先不使用 importToCDN
importToCDN({
// 可选 配置期间可用来查看需要处理的依赖是否存在 例如 https://unpkg.com/[email protected]
prodUrl: 'https://unpkg.com/{name}@{version}/{path}',
modules: [
autoComplete('vue'),
autoComplete('axios'),
autoComplete('lodash'),
{
name: 'element-plus',
var: 'ElementPlus', // 外部化的依赖提供一个全局变量 同rollupOptions配置中的external的值
// https://unpkg.com/[email protected]/dist/index.full.js 或者 dist/index.full.js
path: 'dist/index.full.js',
// 可选
css: 'dist/index.css'
},
{
name: 'vue-i18n',
var: 'VueI18n',
path: 'dist/vue-i18n.global.prod.js',
},
{
name: 'vue-router',
var: 'VueRouter',
path: 'dist/vue-router.global.js'
},
// VueDemi这个是pinia用来判断是vue2还是vue3所需要的,要额外引入一下
{
name: 'vue-demi',
var: 'VueDemi',
path: 'https://unpkg.com/[email protected]/lib/index.iife.js'
},
{
name: 'pinia',
var: 'Pinia',
path: 'dist/pinia.iife.js'
},
// {
// name: 'echarts',
// var: 'Echarts',
// path: 'dist/echarts.js'
// },
// {
// name: 'zrender',
// var: 'zrender ',
// path: 'dist/zrender.js'
// },
],
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
server: {
// host设置为true才可以使用network的形式,以ip访问项目
host: true,
port: 8082,
open: false,
hmr: {
overlay: false
},
// 跨域设置允许
cors: false,
// 如果端口已占用直接退出
strictPort: false,
proxy: getProxyConfig()
},
// css全局配置
css: {
preprocessorOptions: {
// less: {
// javascriptEnabled: true,
// additionalData: `@import "${resolve(__dirname, 'src/assets/styles/base.less')}";`
// }
less: {
modifyVars: {
hack: `true; @import (reference) "${resolve('src/assets/styles/variables.less')}";`,
},
javascriptEnabled: true
}
}
},
build: {
minify: "terser", // 必须开启:使用 terserOptions 才有效果
terserOptions: {
compress: {
drop_console: process.env.NODE_ENV === 'production' ? true : false,
drop_debugger: process.env.NODE_ENV === 'production' ? true : false,
},
},
chunkSizeWarningLimit: 1024, // 文件最大报警阈值设置
rollupOptions: {
// 静态资源分类打包
output: {
// 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
globals: {},
chunkFileNames: 'assets/js/[name]-[hash].js',
entryFileNames: 'assets/js/[name]-[hash].js',
assetFileNames: 'assets/[ext]/[name]-[hash].[ext]',
manualChunks(id) { // 静态资源分拆打包
if (id.includes('node_modules')) {
return id.toString().split('node_modules/')[1].split('/')[0].toString();
}
}
}
},
},
}
})
–
1、视图分析可帮助我们在构建后清晰的了解项目资源情况(rollup-plugin-visualizer
插件的引入)
2、CDN引入资源,上述配置实际有三种配置方法,其中两种需要引入插件实现
vite-plugin-cdn-import
插件引入,根据配置自动引入所需资源rollup-plugin-external-globals
插件,结合rollupOptions
的external
配置资源变量,还得手动在 Index.html引入所需资源rollupOptions
的external
与output
直接配置所需资源变量,无需手动引入资源及安装其他依赖3、依赖分包直接在rollupOptions
根据官网说明配置即可
4、使用vite-plugin-compression
插件开启gzip压缩,且服务器配置开始压缩格式(本文使用了nginx的配置)