Vite
什么是 vite
为什么用 vite
- 开发环境启动快,热更新快。 Vite 以 原生 ESM 方式提供源码。这实际上是让浏览器接管了打包程序的部分工作
缺点
-
为什么生产环境仍需打包 尽管原生 ESM 现在得到了广泛支持,但由于嵌套导入会导致额外的网络往返,在生产环境中发布未打包的 ESM 仍然效率低下(即使使用 HTTP/2)。为了在生产环境中获得最佳的加载性能,最好还是将代码进行 tree-shaking、懒加载和 chunk 分割(以获得更好的缓存)。
-
为何不用 ESBuild 打包,目前使用 rollup 打包 虽然 esbuild 快得惊人,并且已经是一个在构建库方面比较出色的工具,但一些针对构建 应用 的重要功能仍然还在持续开发中 —— 特别是代码分割和 CSS 处理方面。就目前来说,Rollup 在应用打包方面更加成熟和灵活。尽管如此,当未来这些功能稳定后,我们也不排除使用 esbuild 作为生产构建器的可能。
vite 配置
export default defineConfig({
server: {
host: true,
proxy: {
'/icons': {
target: 'http://xxx:3000', // 图标服务
changeOrigin: true, //是否允许跨域
rewrite: (path) => path.replace(/^\/icons/, ''),
},
},
},
css: {
preprocessorOptions: {
scss: {
additionalData: '@use "./styles/variables.scss" as *;', // 自动注入 scss 变量
},
},
},
})
插件
加载 svg 资源
const path = require('path')
const svgIconPlugin = require('vite-plugin-svg-icons')
// 创建 svg 图标,传入 svg 集合文件路径
function createSvgIcon(svgPath = 'src/icons/svg') {
return svgIconPlugin.createSvgIconsPlugin({
// 指定要缓存的图标文件夹
iconDirs: [path.resolve(process.cwd(), svgPath)],
// 执行icon name的格式
symbolId: 'icon-[dir]-[name]',
})
}
export default defineConfig({
plugins: [ createSvgIcon() ]
})
自动导入/自动注册全局组件
// vite.config.ts
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
plugins: [
AutoImport({
dts: './src/auto-imports.d.ts',
resolvers: [ElementPlusResolver()],
}),
Components({
dts: './src/components.d.ts',
resolvers: [ElementPlusResolver()],
}),
]
})
打包优化
如果你需要在嵌套的公共路径下部署项目,只需指定 base 配置项,然后所有资源的路径都将据此配置重写。这个选项也可以通过命令行参数指定,例如 vite build --base=/my/public/path/
可视化分析 rollup-plugin-visualizer
// vite.config.js
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({
plugins: [
visualizer({
filename: 'stats.html',
gzipSize: true, // 收集 gzip 大小并将其显示
brotliSize: true, // 收集 brotli 大小并显示
// emitFile: true, // stats.html 是否生成在 dist 目录里
})
],
})
分离公共依赖,提高打包速度 (提供在内网环境下的 CDN 加载方式)
- 方式一: vite-plugin-cdn-import
// vite.config.ts
import importToCDN from 'vite-plugin-cdn-import'
export default defineConfig({
plugins: [
vue(),
importToCDN({
modules: [
{
name: 'vue',
var: 'Vue',
path: 'http://xxx/vue/vue@latest/dist/vue.global.prod.js',
},
{
name: 'vue-demi',
var: 'VueDemi',
path: 'http://xxx/-/pkg/vue-demi/vue-demi@latest/dist/index.iife.js',
},
{
name: 'element-plus',
var: 'ElementPlus',
path: 'http://xxx/-/pkg/element-plus/element-plus@latest/dist/index.full.js',
},
],
}),
// 自定义插件:打包后将 index.html 文件中静态资源链接域名部分去掉,方便在内网场景部署,根据需要添加
(function () {
return {
name: "vite:set-index-base",
apply: 'build',
writeBundle (options, bundle) {
for (const chunkName in bundle) {
if(Object.prototype.hasOwnProperty.call(bundle, chunkName) && chunkName === 'index.html') {
const chunk: any = bundle[chunkName]
if(chunk.source && options.dir) {
chunk.source = chunk.source.replace(new RegExp(devCDNBase, 'g'),'')
const fullPath = join(options.dir, chunk.fileName)
writeFileSync(fullPath, chunk.source)
}
}
}
},
}
})(),
]
})
- 方式二
1、build 分离依赖
// vite.config.js
import externalGlobals from 'rollup-plugin-external-globals'
const externalDepends = ['vue', 'vue-demi']
build: {
rollupOptions: {
external: externalDepends,
plugins: [
externalGlobals:({
vue: 'Vue',
'vue-demi': 'VueDemi',
})
]
}
}
2、生产打包时在 index.html 中注入 script 标签
// vite.config.js
import { createHtmlOlugin } from 'vite-plugin-html'
const externalDepends = ['vue', 'vue-demi']
const externalSource = {
vue: {
url: '/-/pkg/vue/vue@latest/dist/vue.global.prod.js', // 资源地址
global: 'Vue', // 全局名称
},
'vue-demi': {
url: '/-/pkg/vue-demi/vue-demi@latest/dist/index.iife.js',
global: 'VueDemi',
}
}
export default defineConfig(({ mode }) => {
const vitePlugins = []
let injectScript = '<script></script>'
if(mode !== 'development') {
externalDepends.forEach(v => {
if(externalSource[v] && externalSource[v].url) {
injectScript += `<script src="${externalSource[v].url}"></script>`
}
})
}
vitePlugins.push(
createHtmlPlugin({
minify: true,
inject: {
data: {
title: 'index',
injectScript
}
}
})
)
return {
plugins: vitePlugins
}
})
3、index.html 中插入 script 语句变量
<head>
<%- injectScript %>
</head>
按资源类型分类打包
// vite.config.js
build: {
rollupOptions: {
output: {
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')) {
let splitKey = 'node_modules/'
if (id.includes('.pnpm')) {
splitKey = 'node_modules/.pnpm/'
}
return id.toString().split(splitKey)[1].split('/')[0].toString()
}
},
},
}
}
gzip 压缩 vite-plugin-compression
// vite.config.js
import viteCompression from 'vite-plugin-compression'
export default defineConfig({
plugins: [
viteCompression({
threshold: 1024 * 10, // 超过 10kb 的压缩
algorithm: 'gzip',
ext: '.gz',
})
],
})
打包时注入构建信息
// vite.config.ts
function InjectBuildInfo(): Plugin {
return {
name: 'define-version',
transform(code, id) {
const timeStr = '__VITE_BUILD_TIME'
const versionStr = '__VITE_BUILD_VERSION'
const hashStr = '__VITE_BUILD_GIT_HASH'
let version = 'unknown'
let hash = 'unknown'
try {
const packageJson = JSON.parse(readFileSync('./package.json', 'utf-8'))
version = packageJson.version
} catch (error) {
console.error(error)
}
const isProd = process.env.NODE_ENV === 'production'
if(isProd) {
try {
// 按需配置,是否需要 git hash 值
hash = execSync('git rev-parse HEAD').toString().trim().slice(0, 8)
} catch (error) {
console.error(error)
}
}
const time = new Date().toLocalString('zh-CN', { timeZone: 'Asia/Shanghai' })
if (id.endsWith('.vue') || id.endsWith('.ts')) {
code = code.replace(timeStr, time)
code = code.replace(versionStr, version)
code = code.replace(hashStr, hash)
}
return {
code
}
}
}
}
// 使用: App.vue
window.__build_info = {
time: '__VITE_BUILD_TIME',
version: '__VITE_BUILD_VERSION',
hash: '__VITE_BUILD_GIT_HASH',
}