Vite
打包: 使用工具抓取、处理并将我们的源码模块串联成可以在浏览器中运行的文件。
更快的启动: 传统冷启动开发服务器,需要优先抓取并构建你的整个应用(依赖分析并转换)。Vite 原生 ESM 方式提供源码,浏览器请求后按需(如路由拆分)提供源码;此外对依赖预构建(esbuild),对依赖模块内的依赖项整合单个模块
更快的更新: 传统打包器即使拥有热模块替换(HMR),依旧要编译并构建与更改内容有依赖关系的部分,vite 通知请求最新 ESM 模块即可;此外还利用缓存机制(对源码协商缓存,对依赖强缓存)
生产环境打包: 进行 tree-shaking、懒加载和 chunk 分割(以获得更好的缓存)
创建项目
npm create vite@latest
pnpm create vite
社区模板集合:GitHub - vitejs/awesome-vite: ⚡️ A curated list of awesome things related to Vite.js
可执行命令
{
"scripts": {
"dev": "vite --force", // 启动开发服务器,别名:`vite dev`,`vite serve`
"build": "vite build", // 为生产环境构建产物
"preview": "vite preview" // 本地预览生产构建产物
}
}
功能概览
TS转译
天然支持TS,额外提供vite相关类型 vite/client
:
"compilerOptions": {
"types": ["vite/client", ...],
}
它将补充以下类型
- 资源导入 (例如:
.svg
文件、.module.css
样式文件) import.meta.env
上 Vite 注入的环境变量的类型定义import.meta.hot
上的 HMR API 类型定义
Vue 单文件组件支持
JSX转译
CSS:PostCSS、CSS Modules、sass/less(安装即可)
静态资源导入
默认构建优化:css代码独立为文件、预加载生成、异步 Chunk 加载优化(同时加载取代层层加载)
Glob 导入
json
静态资源处理
Vite 会将 ESM 方式引入静态资源解析为路径
import imgUrl from "./images/img.png";
import imgUrl from "/src/images/img.png"; // 相对于项目根路径
显示引入
未包含静态资源处理列表的资源也可以显示引入
import workletURL from 'extra-scalloped-border/worklet.js?url' // 引入为url
import shaderString from './shader.glsl?raw' // 引入为字符串
public
目录
用于存放不被引用,但希望通过根路径url访问的资源,可通过 publicDir
配置目录
new URL(url, import.meta.url)
会暴露当前模块的 URL的ESM 原生功能,配合相对路径得到被完整解析的URL
VIte在开发环境不会额外处理
const module = new URL("@/xxx", import.meta.url)
// 动态URL
export function getAssetFileURL(url) {
return new URL(`../assets/files/${url}`, import.meta.url).href;
}
// 非静态URL字符串无法转换
const imgUrl = new URL(imagePath, import.meta.url).href; // Vite无法转换
// SSR环境无法使用 new URL(url, import.meta.url)
相关配置
base(资源公共基础路径)
assetsInclude(拓展静态资源处理类型)
build.assetsInlineLimit(内联base64资源最大限制)
Glob导入
Vite支持通过 import.meta.glob
导入多模块
默认动态加载模块
const modules = import.meta.glob('./dir/*.js')
// modules
{
'./dir/foo.js': () => import('./dir/foo.js'),
'./dir/bar.js': () => import('./dir/bar.js'),
}
// 懒加载
modules[path]().then((mod) => {
console.log(path, mod)
})
直接引入多模块,添加参数 eager:true
const gameModules = import.meta.glob(
"@/assets/img/game/**/*.png", { eager: true }
);
export function getAssetsGameFile(url) {
return gameModules[`/src/assets/${url}`].default;
}
eager: true
以静态资源 module.default 为 value;
as: 'url'
以资源路径为 value;
import: default
具名导入
query: '?url|?raw'
查询参数
匹配模式
// 多目录
const modules = import.meta.glob(['./dir/*.js', './another/*.js'])
// 从结果中排除
const modules = import.meta.glob(['./dir/*.js', '!**/bar.js'])
构建生产
base
开发或生产环境服务的公共基础路径。如:
- 绝对 URL 路径名,例如
/foo/
- 完整的 URL,例如
https://foo.com/
- 空字符串或
./
(常用于开发环境)
splitVendorChunk(已弃用)
通过配置中添加 splitVendorChunkPlugin
插件来使用 “分割 Vendor Chunk” 策略,要求 build.rollupOptions.output.manualChunks
函数形式使用
// vite.config.js
import { splitVendorChunkPlugin } from 'vite'
export default defineConfig({
plugins: [splitVendorChunkPlugin(function(){
if (id.includes('node_modules')) {
return id.toString().split('node_modules/')[1].split('/')[0].toString();
}
}
)],
})
build.rollupOptions
vite 默认根据依赖关系智能生成chunk,自动分割chunk对产物进行优化;
产物优化原则:
-
对于单个模块依赖的大文件库,如 xlsx、pdf.js应独立为chunk;
-
不要将多个依赖库合并为 vendor-[hash].js 单个chunk文件;
如需自定义构建,通过 rollup build参数
output
rollupOptions: {
output: {
chunkFileNames: "js/[name]-[hash].js",
entryFileNames: "js/[name]-[hash].js",
assetFileNames: "[ext]/[name]-[hash].[ext]",
//
manualChunks: {
vue: ["vue"],
vueRouter: ["vue-router"],
elementPlus: ["element-plus"],
},
// 全部
// manualChunks(id) {
// if (id.includes("node_modules")) {
// console.log(id);
// return id.toString().split("node_modules/")[1].split("/")[0].toString();
// }
// },
},
}
多页面应用
// vite.config.js
const { resolve } = require('path')
const { defineConfig } = require('vite')
module.exports = defineConfig({
build: {
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
nested: resolve(__dirname, 'nested/index.html')
}
}
}
})
build.target
vite 默认 browerlist 构建目标为支持原生 ESM script 标签、原生 ESM 动态导入、import.meta
的浏览器
Chrome >=87
Firefox >=78
Safari >=14
Edge >=88
build.target
可以是vite提供的特殊值:
modules
默认值
esnext
假设有原生动态导入支持,并将转译得尽可能小
或有效 esbuild 目标选项:[‘es2015’, ‘edge88’, ‘firefox78’, ‘chrome87’, ‘safari14’]
加载报错
重新部署时,可能会删除之前部署的资源;访问用户尝试导入相应的旧代码块
window.addEventListener('vite:preloadError', (event) => {
window.reload() // 刷新页面
})
部署
[部署静态站点 | Vite 中文文档](https://ptymt.cn/guide/static-deploy.html) |
环境变量
内部变量
-
import.meta.env.MODE
返回当前运行模式 -
import.meta.env.BASE_URL
应用当前部署的基本 URL(根据base配置项) -
import.meta.env.PROD
应用是否运行在生产环境(根据NODE_ENV) -
import.meta.env.DEV
应用是否运行在开发环境(根据NODE_ENV) -
import.meta.env.SSR
应用是否运行在 server
.env
文件
Vite 使用 dotenv 从你的 环境目录 中的下列文件加载额外的环境变量
.env # 所有情况下都会加载
.env.local # 所有情况下都会加载,但会被 git 忽略
.env.[mode] # 只在指定模式下加载
.env.[mode].local # 只在指定模式下加载,但会被 git 忽略
加载的环境变量通过 import.meta.env
以字符串形式暴露给客户端(以 VITE_
为前缀)
html中使用
<title>Using data from %VITE_HTML_TITLE%</title>
模式
同 webpack
相同,vite 也支持定义模式(mode)以实现读取对应模式配置(env),可以搭配环境(NODE_ENV)实现不同模式的应用开发/构建
如指定以 staging
构建生产
vite build --mode staging
vite将读取 .env.staging
环境变量,如有需要可自定义以开发模式构建
# .env.staging
NODE_ENV=development
后端集成
对于后端提供 html 页面服务的情况, vite 支持单独 js 入口文件模块,而非 index.html;
在开发环境如何提供加载模块,以及生产环境如何资源提供资源引入,详见 [后端集成 | Vite 官方中文文档](https://cn.vitejs.dev/guide/backend-integration.html) |
配置
访问环境变量
默认执行完 Vite 配置后加载环境变量文件
import { defineConfig, loadEnv } from 'vite'
export default defineConfig(({ command, mode }) => {
// 根据当前工作目录中的 `mode` 加载 .env 文件
// 设置第三个参数为 '' 来加载所有环境变量,而不管是否有 `VITE_` 前缀。
const env = loadEnv(mode, process.cwd(), '')
return { }
})
base
开发或生产环境服务的公共基础路径,以 /
结束的相对/绝对路径
- 绝对 URL 路径名,例如
/foo/
- 完整的 URL,例如
https://foo.com/
(原始的部分在开发环境中不会被使用) - 空字符串或
./
(用于嵌入形式的开发)
pubicDir
作为静态资源服务的文件夹,将在打包后直接被复制输出,默认 public
resolve.alias
{
resolve: {
alias: {
"@": resolve(__dirname, "src"),
"@pages": resolve(__dirname, "src/pages")
}
}
}
css.preprocessorOptions
传递给css预处理器的选项
{
css: {
preprocessorOptions: {
less: {
math: 'parens-division',
},
scss: {
additionalData: `$injectedColor: orange;`,
},
},
},
}
assetsInclude
拓展额外的模块类型被vite处理为静态资源URL
server
hosts:服务监听IP,默认 localhost(127.0.0.1)
port:监听端口号,默认 5173
https:启动 TLS @vitejs/plugin-basic-ssl 提供基础证书
server.proxy
为开发服务器配置自定义代理规则,^
开头,将被识别为 RegExp
,对匹配的路径进行更改target
{
server: {
proxy: {
// http://localhost:5173/foo -> http://localhost:3000/foo
'/admin': 'http://localhost:3000',
// http://localhost:5173/ai/imagine -> http://www.ai.com/imagine
'/ai': {
target: 'http://www.ai.com',
changeOrigin: true, //
rewrite: (path) => path.replace(/^\/ai/, ''), // 修改路径
},
'^/fallback/.*': ... // 正则匹配
}
}
}
warmup:提前预热文件(转换和缓存)
origin:定义开发环境生成资源引用的 origin
build
target:构建兼容目标,详见 构建生产-build.target章节
outDir:输出目录(相对于项目根目录)
assetsDir:指定静态资源存放路径,默认 assets
sourcemap:是否生成 sourcemap文件,boolean | ‘inline’ | ‘hidden’,默认关闭 |
rollupOptions:自定义 Rollup 打包配置
-
outDir:资源打包输出目录
-
commonjsOptions
值设为
{ transformMixedEsModules: true }
用于转换require
导出/引入;部分库使用commonJS规范,需声明此选项转换,否则生产环境报错;
-
rollupOptions:rollup打包配置
资源分类
rollupOptions: { output: { assetFileNames: (assetInfo) => { let info = assetInfo.name.split("."); let extType = info[info.length - 1]; if (/\.(png|jpe?g|gif|svg)(\?.*)?$/.test(assetInfo.name)) { extType = "img"; } else if (/\.(woff2?|eot|ttf|otf)(\?.*)?$/i.test(assetInfo.name)) { extType = "fonts"; } return `${extType}/[name]-[hash][extname]`; }, chunkFileNames: "js/[name]-[hash].js", entryFileNames: "js/[name]-[hash].js" } }
commonjsOptions
传递给 @rollup/plugin-commonjs 插件选项(该插件用于只支持)
其中设置 transformMixedEsModules
为true,将混合模块(ESM/CJS两种模块规范)也转为 ESM
minify
混淆选择 false | “esbuild” | “terser” |
reportCompressedSize
是否启动压缩大小报告,默认 true
optimizeDeps
依赖优化选项,注意是依赖优化
SSR
插件
基础使用
与 rollup 相同,安装插件后 plugins 数组中使用
// vite.config.js
import legacy from '@vitejs/plugin-legacy'
import typescript2 from 'rollup-plugin-typescript2'
import image from '@rollup/plugin-image'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [
legacy({
targets: ['defaults', 'not IE 11']
}),
{
...image(),
enforce: 'pre' // 强制顺序 pre/post(在vite核心插件之前)
},
{
...typescript2(),
apply: 'build' // build/serve 模式生产
}
]
})
更多官方插件、社区插件/模板、Rollup插件见 [插件 | Vite 官方中文文档](https://cn.vitejs.dev/plugins/) |
vite-babel-plugin
// vite.config.js
import babel from "vite-babel-plugin";
// export default defineConfig
plugins: [
babel()
]
有了 vite-plugin-legacy,为什么还需要babel
vite-plugin-legacy 对IE11等旧版本提供备份产出代码,在实际环境请求对应版本,特点是按需;
而babel是新版本JavaScript(ES6+)代码转化为旧版本JavaScript(如ES5)代码,以便在旧版本环境(如旧版浏览器或节点环境)中运行,特点是兼容。
此外babel核心功能除了语法转换,支持ts,jsx转译、压缩优化等;
vite-plugin-html
自定义html模板,支持注入数据
import { createHtmlPlugin } from "vite-plugin-html";
// export default defineConfig
plugins: [
createHtmlPlugin({
template: "index.html", // 相对于根路径
inject: {
data: { build_time: new Date().toLocaleString() },
},
})
]
index.html
<!doctype html>
<!-- built at <%- build_time %> -->
<html lang="zh-cmn-Hans">
<head>
<meta charset="UTF-8" />
<link href="/favicon.ico" rel="icon" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script>
window.addEventListener("vite:preloadError", () => {
window.reload();
});
</script>
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script src="https://turing.captcha.qcloud.com/TCaptcha.js"></script>
<script type="module" src="/src/main.js"></script>
</body>
</html>
postcss
vite内部集成 postcss
,无需手动安装;配置与 webpack
同理:
// postcss.config.js
module.exports = {
plugins: {
"autoprefixer": {
overrideBrowserslist: ['last 2 version','>1%']
},
"postcss-px-to-viewport": {
viewportWidth: 750,
exclude: [/node_modules/],
mediaQuery: true,
},
},
};
vite-plugin-svg-icons
- 预加载 在项目运行时就生成所有图标,只需操作一次 dom
- 高性能 内置缓存,仅当文件被修改时才会重新生成
// vite.config.js
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import path from 'path'
export default () => {
return {
plugins: [
createSvgIconsPlugin({
// 指定需要缓存的图标文件夹
iconDirs: [path.resolve(process.cwd(), 'src/icons')],
// 指定symbolId格式
symbolId: 'icon-[dir]-[name]',
/**
* 自定义插入位置
* @default: body-last
*/
inject?: 'body-last' | 'body-first'
/**
* custom dom id
* @default: __svg__icons__dom__
*/
customDomId: '__svg__icons__dom__',
}),
],
}
}
引入
// main.js
import 'virtual:svg-icons-register'
import SvgIcon from '@/base-ui/SvgIcon/index.vue';
const app = createApp(App);
app.component('SvgIcon', SvgIcon);
SvgIcon
<template>
<svg aria-hidden="true">
<use :xlink:href="symbolId" :fill="color" />
</svg>
</template>
<script>
import { defineComponent, computed } from 'vue'
export default defineComponent({
name: 'SvgIcon',
props: {
prefix: {
type: String,
default: 'icon',
},
name: {
type: String,
required: true,
},
color: {
type: String,
default: '#333',
},
},
setup(props) {
const symbolId = computed(() => `#${props.prefix}-${props.name}`)
return { symbolId }
},
})
</script>
<style scoped>
.svg-icon {
overflow: hidden;
width: 1em;
height: 1em;
fill: currentColor;
vertical-align: -0.15em;
}
</style>
使用
<SvgIcon name="edit" :class="{ activated }" />
vite-svg-loader
支持加载svg为vue组件或其它格式;
import svgr from "vite-plugin-svgr;
export default defineConfig(({ mode }) => {
plugins: [ svgLoader() ]
});
动态导入
import { defineComponent, defineAsyncComponent, h } from "vue";
const svgModules = import.meta.glob("./svg/**/*.svg", {
query: "?component",
});
export default defineComponent({
name: "SvgIcon",
props: {
name: { type: String, default: "" },
},
setup(props) {
const SvgComponent = defineAsyncComponent(svgModules[`./svg/${props.name}.svg`]);
return () => h(SvgComponent);
},
});
vite-plugin-svgr
将 SVG 转换为 React 组件的 Vite 插件。 使用 svgr引擎;
import svgr from "vite-plugin-svgr;
export default defineConfig(({ mode }) => {
plugins: [ svgr() ]
});
使用
import Logo from "./logo.svg?react";
优化
分类输出
// vite.config.js
rollupOptions: {
output: {
chunkFileNames: "js/[name]-[hash].js",
entryFileNames: "js/[name]-[hash].js",
assetFileNames: "[ext]/[name]-[hash].[ext]",
},
},
分析输出
借助 rollup-plugin-visualizer
插件可视化输出分析
动态导入/按需导入
动态导入:
路由动态引入,SvgIcon动态导入方案(SvgIcon组件+批量动态导入)
按需引入:
vite支持分析依赖,并对 import {xxx} from "xxx"
进行tree-shaking
注意按需引入,举例:
-
在使用 lodash 等一些库的时候尝试利用 esm 特性,如
lodash-es
-
不要出现
import _ as xxx from "xxx"
,这样会导致整个模块被引入;或者export from "xxx"
,这样会导致整个模块被导出
拆分 vendor
单独功能模块使用的进行拆分
简单粗暴
// vite.config.js
import { splitVendorChunkPlugin } from 'vite'
export default defineConfig({
plugins: [splitVendorChunkPlugin(function(){
if (id.includes('node_modules')) {
return id.toString().split('node_modules/')[1].split('/')[0].toString();
}
}
)],
})
关于CDN或static
对将部分固定包存放 cdn加速 保持怀疑,假设开发/测试环境环境修改但生产未改变情况可能引发问题,而且开发环境每次去访问cdn不合适;
尝试改为 static
资源,假设项目中使用 exceljs
,videojs
,pdfjs
这种大文件放到public目录
vite-plugin-legacy-swc
使用 vite-plugin-legacy-swc(基于SWC进行转码,Rust编写的超快的JavaScript/TypeScript编译器) 取代 vite-plugin-legacy
更多细节
打包阶段
vite打包阶段包括:
Transforms 阶段(即转换阶段,Vue转换、TS转换、高级语法转化等)
Render Chunk 阶段(对代码进行合并、分割、代码分析等操作,生成目标运行代码)耗时长