Vite 学习全攻略:从基础到高级特性

161 阅读8分钟

引言

在前端开发的快速迭代中,构建工具扮演着至关重要的角色。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 };

主要优点:

  1. 配置文件的异步加载
  2. 数据库连接或 API 客户端初始化
  3. 条件性动态导入
  4. 依赖注入的异步处理

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 文件(代码分割)
  1. 代码分割

代码分割是打包工具(如 Vite/Rollup/Webpack)的优化技术,通过分析动态导入语句,将应用拆分为多个较小的 bundle:

  • 入口点分割:基于不同的入口文件生成多个 bundle
  • 动态导入分割:每个 import() 语句生成一个单独的 chunk
  • 共享模块分割:提取公共依赖为单独的 bundle

vite的css相关内容

  1. 直接导入css
import './global.css'; // 全局生效
  • Vite 处理方式

    • 开发时:通过 <style> 标签注入到页面
    • 构建时:合并到主 CSS 文件(如 style.css
  • 特点

    • 所有类名全局有效,可能导致命名冲突
    • 支持 @import 嵌套(Vite 会自动处理)
  1. css模块化
//使用 .module.css 后缀
import styles from './button.module.css';
<button className={styles.primary}>Click</button>

Vite 处理方式

  • 自动将类名编译为唯一哈希(如 primary__a1b2c3
  • 支持 :global() 语法声明全局类
  • 支持tree shaking,会将未使用的样式移除
  • 彻底解决了样式冲突的问题
  1. css预处理(如scss)

直接安装后直接使用,自动处理 @import@use 语句

Vite 天然支持 TypeScript

  1. 在vite中无需配置就能实现ts,Vite 原生支持 TypeScript 文件(.ts/.tsx

  2. 无需额外配置即可:

    • 编译 TypeScript 代码为 JavaScript
    • 处理类型声明文件(.d.ts
    • 支持 JSX(通过 tsx 文件)

Vite 的资源导入

  1. vite原生支持的数据类型:
  • 图片:.png.jpg.jpeg.gif.svg.webp.avif
  • 字体:.woff.woff2.ttf
  • 媒体:.mp4.webm.ogg.mp3.wav.flac
  • 其他:.json.txt.csv
  1. 资源的导入的方式
//默认导入
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)
  1. directive(指令)

    • ?raw:导入原始内容(如文本文件)
    • ?url:强制作为 URL 导入
    • ?worker:创建 Web Worker
    • React:支持 ?refresh 强制模块热更新
  2. import.meta(元信息)

  • import.meta.url:当前模块的 URL
  • import.meta.env:环境变量
  • import.meta.hot:热更新 API
  1. 环境变量的使用
# .env.development
VITE_API_URL=http://localhost:3000/api// 使用环境变量
fetch(import.meta.env.VITE_API_URL + '/data');
  1. 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(未使用的字段不会被打包)
  1. 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

  1. 库模式是 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"
  }
}
  1. 生成声明式文件

为什么要生成声明式文件呢?因为当你的库打包好后,如何是ts项目使用,他需要知道类型,做类型推断,所以需要声明式文件

通过 tsconfig.jsonVite 插件生成 .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;
}
  1. Injecting CSS(注入 CSS)
1. 自动提取 CSS
Vite 默认会将库中的 CSS 提取到单独的文件:
// src/index.js
import './style.css'; // 样式会被提取到 dist/style.css

export function MyComponent() { /* ... */ }
2. 内联 CSSJS
通过配置将 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;
        },
      },
    },
  },
});
  1. 库的使用
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,未使用的模块不会被打包

  1. 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.jsimport { 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.jsimport { 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 的插件开发

  1. 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):写入产物文件前调用
  1. 虚拟模块是一种不存在于物理文件系统的模块,由插件动态生成。常见场景包括:
  • 注入环境变量(如 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