引言
在前端开发的快速迭代中,构建工具扮演着至关重要的角色。Vite 作为一款新兴的构建工具,凭借其出色的性能和便捷的使用方式,逐渐成为开发者的首选。本文将深入探讨 Vite 的各个方面,包括其核心概念、特性以及实际应用,帮助你全面掌握 Vite 的使用。
首先是 ESM 与 CommonJS 的区别
ESM是vite构建其模块系统的核心,支持top-level await
举例:
// module.js
console.log('开始加载模块...');
const data = await fetch('/api/data').then(res => res.json());
console.log('数据加载完成:', data);
// 导出加载的数据
export { data };
主要优点:
- 配置文件的异步加载
- 数据库连接或 API 客户端初始化
- 条件性动态导入
- 依赖注入的异步处理
Vite 与 Webpack 策略对比
Vite 在开发环境下采用 esbuild,基于浏览器对 ESM 的支持,无需打包即可快速构建,因此冷启动和热更新速度极快。而 Webpack 需要完全打包后才能实现热重载,随着项目规模的增大,性能会受到影响。
Vite 的便捷特性:开箱即用
您只需要一个index.html 再安装一个vite,无需任何的配置文件就可以使用vite,vite开箱即用支持 ts esnext post-css css模块化 css预处理器(只需要下载如sass就可以直接使用,无需配置) 静态资源的处理(资源导入 资源内链 json导入)环境变量 代码分割 具体的细节内容我们将在下面讨论
vite 的动态导入和代码分割
1 . 静态导入与动态导入
例如;
//静态导入
import {data} from './other.js'
特点:
模块依赖关系在编译时确定
所有依赖会被打包到同一个 bundle 中
无法条件加载模块
//动态导入
if(needSomething){
import('./other').then(module=>{
console.log(module)
})
}
特点:
模块在需要时才会加载
支持条件加载和懒加载
会生成单独的 bundle 文件(代码分割)
- 代码分割
代码分割是打包工具(如 Vite/Rollup/Webpack)的优化技术,通过分析动态导入语句,将应用拆分为多个较小的 bundle:
- 入口点分割:基于不同的入口文件生成多个 bundle
- 动态导入分割:每个
import()语句生成一个单独的 chunk - 共享模块分割:提取公共依赖为单独的 bundle
vite的css相关内容
- 直接导入css
import './global.css'; // 全局生效
-
Vite 处理方式
- 开发时:通过
<style>标签注入到页面 - 构建时:合并到主 CSS 文件(如
style.css)
- 开发时:通过
-
特点
- 所有类名全局有效,可能导致命名冲突
- 支持
@import嵌套(Vite 会自动处理)
- css模块化
//使用 .module.css 后缀
import styles from './button.module.css';
<button className={styles.primary}>Click</button>
Vite 处理方式:
- 自动将类名编译为唯一哈希(如
primary__a1b2c3) - 支持
:global()语法声明全局类 - 支持tree shaking,会将未使用的样式移除
- 彻底解决了样式冲突的问题
- css预处理(如scss)
直接安装后直接使用,自动处理 @import 和 @use 语句
Vite 天然支持 TypeScript
-
在vite中无需配置就能实现ts,Vite 原生支持 TypeScript 文件(
.ts/.tsx) -
无需额外配置即可:
- 编译 TypeScript 代码为 JavaScript
- 处理类型声明文件(
.d.ts) - 支持 JSX(通过
tsx文件)
Vite 的资源导入
- vite原生支持的数据类型:
- 图片:
.png、.jpg、.jpeg、.gif、.svg、.webp、.avif - 字体:
.woff、.woff2、.ttf - 媒体:
.mp4、.webm、.ogg、.mp3、.wav、.flac - 其他:
.json、.txt、.csv
- 资源的导入的方式
//默认导入
import logo from './logo.png';
console.log(logo); // /assets/logo.12345.png
//内联导入
import smallIcon from './icon.svg?inline';
//资源的元信息
import { width, height } from './image.png?meta';
console.log(`图片尺寸: ${width}x${height}`);
3. 构建优化
自动生成带哈希的文件名(如 logo.12345.png)
自动处理跨域问题(通过 public 目录)
小资源自动内联(默认小于 4kb 的图片转为 base64)
-
directive(指令)
?raw:导入原始内容(如文本文件)?url:强制作为 URL 导入?worker:创建 Web Worker- React:支持
?refresh强制模块热更新
-
import.meta(元信息)
import.meta.url:当前模块的 URLimport.meta.env:环境变量import.meta.hot:热更新 API
- 环境变量的使用
# .env.development
VITE_API_URL=http://localhost:3000/api
// 使用环境变量
fetch(import.meta.env.VITE_API_URL + '/data');
- Json 的命名导入
//1. 传统 JSON 导入方式
import data from './config.json';
console.log(data.key);
//Vite 支持直接导入 JSON 文件中的特定字段:
import { apiKey, baseUrl } from './config.json';
console.log(apiKey); // 直接访问特定字段
避免导入整个 JSON 对象
提高代码可读性
支持 Tree-shaking(未使用的字段不会被打包)
- global imports
通过 import.meta.glob 批量导入多个模块:
// 导入所有 .js 文件
const modules = import.meta.glob('./modules/*.js');
// 动态加载所有模块
for (const path in modules) {
modules[path]().then((mod) => {
console.log(path, mod);
});
}
使用 import.meta.globEager 进行静态导入(立即执行):
const modules = import.meta.globEager('./modules/*.js');
console.log(modules); // 已加载的模块对象
vite 的 libery mode
-
库模式是 Vite 提供的一种打包配置,用于将代码打包为可供其他项目复用的库或组件。例如:
- UI 组件库(如 Ant Design、Element UI)
- 工具函数库(如 Lodash、Axios)
- 自定义 Hooks 库(如 React Query)
//基础配置
// vite.config.js
export default {
build: {
lib: {
entry: 'src/index.js', // 入口文件
name: 'MyLibrary', // 库名称(UMD 格式使用)
fileName: (format) => `my-library.${format}.js`, // 输出文件名
formats: ['es', 'umd', 'cjs'], // 支持的模块格式
},
},
};
//Vite 会自动将 dependencies 中的依赖排除在打包产物之外:
{
"dependencies": {
"react": "^18.2.0"
}
}
- 生成声明式文件
为什么要生成声明式文件呢?因为当你的库打包好后,如何是ts项目使用,他需要知道类型,做类型推断,所以需要声明式文件
通过 tsconfig.json 和 Vite 插件生成 .d.ts 文件
// tsconfig.json
{
"compilerOptions": {
"declaration": true, // 生成声明文件
"declarationDir": "dist/types", // 输出目录
"emitDeclarationOnly": true, // 只生成声明文件
}
}
推荐使用 vite-plugin-dts 自动处理类型声明:
// vite.config.js
import dts from 'vite-plugin-dts';
export default {
plugins: [dts()],
build: {
lib: {
entry: 'src/index.ts',
},
},
};
对于包含 CSS、图片等资源的库,需在 d.ts 中声明类型:
// types.d.ts
declare module '*.css' {
const classes: { [key: string]: string };
export default classes;
}
- Injecting CSS(注入 CSS)
1. 自动提取 CSS
Vite 默认会将库中的 CSS 提取到单独的文件:
// src/index.js
import './style.css'; // 样式会被提取到 dist/style.css
export function MyComponent() { /* ... */ }
2. 内联 CSS 到 JS
通过配置将 CSS 内联到 JS 文件中:
// vite.config.js
export default {
build: {
cssCodeSplit: false, // 禁用 CSS 代码分割
},
};
3. 自定义 CSS 输出
使用插件控制 CSS 输出位置:
// vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
build: {
lib: {
entry: 'src/index.js',
fileName: 'my-library',
},
rollupOptions: {
output: {
assetFileNames: (assetInfo) => {
if (assetInfo.name.endsWith('.css')) {
return 'my-library.css';
}
return assetInfo.name;
},
},
},
},
});
- 库的使用
npm install my-library
// 使用 ESModule 导入
import { MyComponent } from 'my-library';
// 使用 CommonJS 导入
const { MyComponent } = require('my-library');
<script src="https://cdn.example.com/my-library.umd.min.js"></script>
<script>
// 通过全局变量访问库
const { MyComponent } = window.MyLibrary;
</script>
Vite 构建的库支持 Tree-shaking,未使用的模块不会被打包
- Module Federation(模块联合)
Module Federation(模块联合)是 Vite(及 Webpack 等构建工具)支持的一种跨应用模块共享技术,允许多个独立部署的项目(应用)在运行时动态共享代码模块,而无需将这些模块打包到各自的产物中。 简单理解:A 项目可以直接在运行时加载并使用 B 项目的模块,就像使用本地模块一样,无需提前安装或打包依赖。
以 “A 项目(host)加载 B 项目(remote)的模块” 为例:
1. 配置 B 项目(作为 remote,暴露模块)
安装模块联合插件:npm install @originjs/vite-plugin-federation --save-dev
修改 vite.config.js:
import { defineConfig } from 'vite'
import federation from '@originjs/vite-plugin-federation'
export default defineConfig({
plugins: [
federation({
name: 'bProject', // 远程项目名称
filename: 'remoteEntry.js', // 入口文件名称(固定)
exposes: {
'./Button': './src/components/Button.vue', // 暴露的模块路径(key为外部访问路径,value为本地文件路径)
},
shared: ['vue'] // 共享依赖(避免重复加载,如vue、react等)
})
],
build: {
target: ['chrome89', 'edge89', 'firefox89', 'safari15'] // 需指定兼容的浏览器版本
}
})
2. 配置 A 项目(作为 host,加载远程模块)
同样安装插件:npm install @originjs/vite-plugin-federation --save-dev
修改 vite.config.js:
import { defineConfig } from 'vite'
import federation from '@originjs/vite-plugin-federation'
export default defineConfig({
plugins: [
federation({
name: 'aProject',
remotes: {
bProject: 'http://localhost:5001/remoteEntry.js' // 远程项目的入口文件地址(B项目的运行地址 + remoteEntry.js)
},
shared: ['vue'] // 与远程项目共享vue,避免重复加载
})
]
})
3. 在 A 项目中使用 B 项目的模块
在 A 项目的组件中直接导入:
<template>
<div>
<RemoteButton />
</div>
</template>
<script setup>
// 动态导入远程模块(需使用异步导入)
const RemoteButton = defineAsyncComponent(() => import('bProject/Button'))
</script>
vite 的插件开发
- Vite 插件通过钩子函数介入构建流程,常用钩子包括:
1. 解析阶段
resolveId(id, importer):拦截模块解析,返回自定义模块路径load(id):加载模块内容,可返回虚拟模块
2. 转换阶段
transform(code, id):转换模块代码(如 JSX、TypeScript 转 JS)transformIndexHtml(html):转换入口 HTML 文件
3. 服务器阶段
configureServer(server):自定义开发服务器(如添加中间件)handleHotUpdate(ctx):处理模块热更新
4. 构建阶段
generateBundle(options, bundle):生成最终产物时调用writeBundle(options, bundle):写入产物文件前调用
- 虚拟模块是一种不存在于物理文件系统的模块,由插件动态生成。常见场景包括:
- 注入环境变量(如
import.meta.env) - 生成动态配置(如路由表、组件列表)
- 提供全局状态(如主题配置)
// 虚拟模块插件示例
export default function virtualModulePlugin() {
return {
name: 'virtual-module',
resolveId(id) {
// 拦截特定模块请求
if (id === 'virtual:config') {
return id; // 返回解析后的ID,表示该模块由插件处理
}
return null; // 其他模块走默认解析流程
},
load(id) {
if (id === 'virtual:config') {
// 返回模块内容(动态生成)
return `export const version = '1.0.0';
export const apiBaseUrl = 'https://api.example.com';`;
}
return null;
},
};
}
//使用虚拟模块
// 在项目中直接导入虚拟模块
import { version, apiBaseUrl } from 'virtual:config';
console.log(version); // 输出: 1.0.0