Vite 从零搭建项目——超越 Webpack 的构建利器

Created on

如果你读过本博客之前的文章《从入门到入土使用 Webpack5 搭建一个项目》,你会知道配置 Webpack 是一件相当繁琐的事情。而 Vite 的出现,让开发体验有了质的飞跃——启动速度从几十秒降至几百毫秒,热更新几乎感觉不到延迟。

本文将先讲清楚 Vite 为什么快,再带你从零搭建一个完整的 Vite + React + TypeScript 项目。

Vite 为什么这么快?

Webpack 的问题:先打包再启动

Webpack 在启动开发服务器时,需要从入口文件出发,递归分析所有模块依赖,将所有模块打包成 bundle,然后才启动服务器。项目越大,模块越多,启动时间越长。

Webpack 启动流程:
入口文件 → 分析依赖 → 编译所有模块 → 打包成 bundle → 启动服务器

Vite 的解法:按需编译,直接利用 ESM

Vite 的开发服务器不做预打包。它利用现代浏览器原生支持的 ES Modules(ESM),在浏览器请求某个模块时才实时编译,实现真正的按需加载

Vite 启动流程:
启动服务器(毫秒级)→ 浏览器请求文件 → Vite 编译该文件 → 返回给浏览器

整个流程中,Vite 利用了两个关键工具:

  1. esbuild:用 Go 语言编写,编译速度比 Babel 快 10~100 倍,用于依赖预构建(将 node_modules 中的 CommonJS 模块转换为 ESM)。

  2. Rollup:用于生产环境构建,产出优化良好的静态资源。

热更新(HMR)的差异

Vite vs Webpack 核心对比

维度WebpackVite
冷启动慢(打包所有模块)极快(按需编译)
HMR随项目增大变慢恒定快速
配置复杂度低(合理的默认值)
生态成熟度非常成熟快速成长
构建工具Webpack + Babelesbuild(开发)+ Rollup(生产)
适用场景老项目、特殊需求新项目首选

从零搭建 Vite + React + TypeScript 项目

第一步:初始化项目

npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
npm run dev

Vite 官方提供了多种模板,支持 Vue、React、Svelte、Lit 等框架,也有带 TypeScript 的版本(react-tsvue-ts)。

启动后,你会看到终端输出:

  VITE v5.x.x  ready in 300 ms  ← 注意这个速度

  ➜  Local:   http://localhost:5173/

第二步:了解项目结构

my-app/
├── public/          # 静态资源(不会被 Vite 处理)
├── src/
│   ├── assets/      # 会被 Vite 处理的资源(图片等)
│   ├── App.tsx
│   ├── main.tsx     # 入口文件
│   └── vite-env.d.ts
├── index.html       # ⚠️ 入口 HTML 在根目录,不是 public/
├── vite.config.ts   # Vite 配置文件
├── tsconfig.json
└── package.json

注意:与 Webpack 不同,Vite 的入口 index.html根目录,因为 Vite 以 HTML 为入口直接服务文件。

第三步:配置路径别名

避免 ../../components/Button 这样的相对路径,用 @ 替代 src/

// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import path from "path";

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
});

同时更新 tsconfig.json 让 TypeScript 也能识别:

{
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

现在可以这样写了:

import { Button } from "@/components/Button";
import { useAuth } from "@/hooks/useAuth";

第四步:配置环境变量

Vite 使用 .env 文件管理环境变量,变量名必须以 VITE_ 开头才能暴露给客户端代码:

# .env.development(开发环境)
VITE_API_BASE_URL=http://localhost:3000/api
VITE_APP_TITLE=My App (Dev)

# .env.production(生产环境)
VITE_API_BASE_URL=https://api.example.com
VITE_APP_TITLE=My App

在代码中使用:

// 访问环境变量
const apiUrl = import.meta.env.VITE_API_BASE_URL;
const isDev = import.meta.env.DEV; // 内置变量
const isProd = import.meta.env.PROD; // 内置变量
const mode = import.meta.env.MODE; // 'development' | 'production'

为了有 TypeScript 类型提示,在 src/vite-env.d.ts 中声明:

/// <reference types="vite/client" />

interface ImportMetaEnv {
  readonly VITE_API_BASE_URL: string;
  readonly VITE_APP_TITLE: string;
}

interface ImportMeta {
  readonly env: ImportMetaEnv;
}

第五步:常用插件配置

npm install -D @vitejs/plugin-react unplugin-auto-import unplugin-icons
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import AutoImport from "unplugin-auto-import/vite";
import path from "path";

export default defineConfig({
  plugins: [
    react(),
    // 自动导入 React Hooks,无需在每个文件手动 import
    AutoImport({
      imports: ["react", "react-router-dom"],
      dts: "src/auto-imports.d.ts", // 生成类型声明文件
    }),
  ],
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
  server: {
    port: 3000,
    // 配置代理,解决开发环境跨域
    proxy: {
      "/api": {
        target: "http://localhost:8080",
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ""),
      },
    },
  },
});

第六步:生产构建优化

// vite.config.ts
export default defineConfig({
  build: {
    // 代码分割:将 node_modules 单独打包
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ["react", "react-dom"],
          router: ["react-router-dom"],
        },
      },
    },
    // 启用 gzip 压缩报告
    reportCompressedSize: true,
    // chunk 超过 500KB 时警告
    chunkSizeWarningLimit: 500,
  },
});

运行构建并分析产物:

npm run build      # 构建
npm run preview    # 本地预览构建结果

完整的 vite.config.ts 参考

import { defineConfig, loadEnv } from "vite";
import react from "@vitejs/plugin-react";
import path from "path";

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd(), "");

  return {
    plugins: [react()],

    resolve: {
      alias: {
        "@": path.resolve(__dirname, "./src"),
      },
    },

    server: {
      port: 3000,
      open: true, // 自动打开浏览器
      proxy: {
        "/api": {
          target: env.VITE_API_BASE_URL,
          changeOrigin: true,
        },
      },
    },

    build: {
      outDir: "dist",
      sourcemap: mode === "development",
      rollupOptions: {
        output: {
          manualChunks: {
            vendor: ["react", "react-dom"],
          },
          // 静态资源文件名加 hash,利于缓存
          chunkFileNames: "js/[name]-[hash].js",
          assetFileNames: "assets/[name]-[hash][extname]",
        },
      },
    },

    css: {
      // 开启 CSS Modules
      modules: {
        localsConvention: "camelCase",
      },
    },
  };
});

从 Webpack 迁移到 Vite 的注意事项

如果你正在考虑从 Webpack 项目迁移到 Vite,有几点需要注意:

  1. require() 不被支持:Vite 基于 ESM,需要将 require() 改为 import
  2. process.env 变为 import.meta.env:环境变量的访问方式不同
  3. __dirname 需要手动定义:ESM 中没有 __dirname,需要用 import.meta.url 替代
  4. CommonJS 库需要预构建:Vite 会自动处理,但某些复杂情况需要在 optimizeDeps 中配置
// 替代 __dirname
import { fileURLToPath } from "url";
const __dirname = fileURLToPath(new URL(".", import.meta.url));

总结

Vite 之所以能超越 Webpack,根本原因在于它改变了开发服务器的工作模式——从”先全量打包再服务”变成”按需编译直接服务”。这个架构上的差异,在大型项目中会带来数量级的体验提升。

新项目应该优先选择 Vite,而老项目可以评估迁移成本后逐步切换。配合本文的配置参考,你可以快速搭建起一个规范的生产级项目。