从 Webpack 到 Vite:初学者也能轻松掌握的高效构建方案

28 阅读11分钟

前言

大家好,我是土豆,欢迎关注我的公众号:土豆学前端

对于许多前端开发者来说,Webpack 并不陌生。它是一个强大的模块打包工具,能够将我们项目中各种类型的文件(JavaScript、CSS、图片等)处理、转换并打包成浏览器可识别的静态资源。然而,随着项目规模的增长,Webpack 的开发环境启动速度和热更新(HMR)速度有时会变得不尽人意,尤其是在大型项目中,等待数秒甚至数十秒的启动时间屡见不鲜。

Vite(法语中“快”的意思,发音 /vit/)的出现,正是为了解决这些痛点,它带来了极致的开发体验和闪电般的启动速度。

为什么选择 Vite?Webpack 有什么痛点?

在了解 Vite 之前,我们先简单回顾一下传统打包工具(如 Webpack)在开发模式下的工作流程:

  1. 启动时打包:当你运行 npm run dev 时,Webpack 会从入口文件开始,分析整个项目的依赖关系,将所有模块打包(bundle)成一个或多个文件。
  2. 热更新(HMR):当你修改一个文件时,Webpack 需要重新计算依赖,并重新打包受影响的部分,然后将更新推送到浏览器。

痛点:

  • 启动慢:项目越大,依赖越多,首次启动时需要处理的模块就越多,打包时间越长。
  • HMR 慢:即使只修改一个小文件,Webpack 也可能需要重新构建相当一部分代码,导致 HMR 响应变慢。

Vite 则采用了截然不同的思路。

Vite 的核心原理

Vite 的核心魅力在于它充分利用了现代浏览器原生支持 ES Modules (ESM) 的特性。

1. 开发环境:基于原生 ESM 的按需编译

与 Webpack 在启动时就将所有模块打包不同,Vite 在开发环境下:

  • 不打包 (No-bundle):Vite 启动一个开发服务器,当浏览器请求某个模块时(例如 import App from './App.vue'),Vite 服务器会拦截这个请求。
  • 按需编译:如果请求的是一个需要转换的模块(如 .vue, .jsx, .ts 文件),Vite 会即时编译该模块,然后将其以原生 ESM 的形式返回给浏览器。浏览器原生处理 ESM 的 import 语句,再次按需请求依赖的模块。
  • 极速冷启动:因为不需要在启动时打包整个应用,Vite 的冷启动速度非常快,几乎是毫秒级的。

简单来说:

  • Webpack:先做好一桌满汉全席(打包),你来了直接吃。但做菜时间长。
  • Vite:你点一个菜(请求模块),我(Vite 服务器)现做一个菜给你。上菜快,不浪费。

2. 依赖预构建 (Dependency Pre-bundling)

虽然 Vite 的核心理念是不打包源码,但它会对第三方依赖(node_modules 中的库)进行预构建。这是为什么呢?

  • 兼容 CommonJS 和 UMD 模块:许多第三方库仍然以 CommonJS 或 UMD 格式发布,这些格式在浏览器中不能直接通过原生 ESM 使用。Vite 使用 esbuild(一个用 Go 编写的极速 JavaScript 打包器和压缩器)将这些模块转换为 ESM。
  • 性能优化:一些库可能包含大量的小模块(例如 lodash-es)。如果每个小模块都发起一个 HTTP 请求,会导致“请求瀑布”问题,拖慢页面加载。Vite 将这些库打包成单个或少数几个 ESM 模块,减少了浏览器的请求次数。

预构建的好处:

  • 提升页面加载速度:通过将许多小的依赖模块合并,减少了 HTTP 请求数量。
  • 转换 CommonJS/UMD 为 ESM:确保依赖能在浏览器中以 ESM 方式正确加载。
  • 缓存:预构建的结果会被缓存在 node_modules/.vite 目录中,只有当依赖本身发生变化时才会重新构建。

3. 生产环境:基于 Rollup 的优化打包

当运行 vite build 进行生产环境打包时,Vite 会使用 Rollup。这是因为:

  • Rollup 更擅长打包库和应用:Rollup 对代码的 tree-shaking(摇树优化,移除未使用的代码)和输出格式控制(如 ESM, CommonJS, UMD)有很好的支持。
  • 原生 ESM 部署问题:虽然开发时使用原生 ESM 很棒,但在生产环境中直接部署大量未打包的 ESM 文件,可能会因为过多的 HTTP 请求而影响性能(尤其是在 HTTP/1.1 环境下)。打包依然是必要的。

Vite 会对代码进行优化,包括代码分割、CSS 提取、资源压缩等,以确保生产构建包的性能。

Vite 的核心概念

  1. index.html 作为入口

    • 与 Webpack 通常以 JavaScript 文件(如 main.js)作为入口不同,Vite 项目的入口点是一个 index.html 文件,通常位于项目根目录或指定的 root 目录下。
    • Vite 会自动处理 <script type="module" src="..."></script> 标签,并将其作为 JavaScript 模块图的入口。
  2. vite.config.js (或 .ts, .mjs)

    • 这是 Vite 的配置文件,位于项目根目录。你可以在这里配置 Vite 的行为,如插件、代理、别名、构建选项等。
  3. 插件 (Plugins)

    • Vite 拥有一个设计良好的插件 API,基于 Rollup 的插件接口进行了扩展。这使得 Vite 可以通过插件支持各种功能,如 Vue SFC、React Fast Refresh、TypeScript、CSS 预处理器等。
    • 许多 Rollup 插件也可以在 Vite 中直接或稍作调整后使用。
  4. 开发服务器 (Dev Server)

    • 提供闪电般的热模块替换 (HMR)。
    • 支持配置代理(server.proxy)来解决开发时的跨域问题。
    • 支持 HTTPS、自定义中间件等。
  5. CSS 处理

    • CSS Modules:文件名以 .module.css 结尾的文件会被视为 CSS Modules。
    • CSS 预处理器:内置支持 .scss, .sass, .less, .styl, .stylus。只需安装相应的预处理器依赖即可。
    • PostCSS:自动支持,只需安装并配置 postcss.config.js
  6. 环境变量

    • Vite 使用 .env 文件加载环境变量。
    • 只有以 VITE_ 开头的变量才会暴露给客户端代码(通过 import.meta.env 访问)。
  7. Public 目录

    • 位于项目根目录下的 public 文件夹中的资源,在开发时会直接在 / 路径下提供,在构建时会被复制到输出目录的根部,且不会被处理。适合存放 favicon.icorobots.txt 等静态资源。

快速上手 Vite

最快的方式是使用官方提供的脚手架工具:

# npm
npm create vite@latest

# yarn
yarn create vite

# pnpm
pnpm create vite

然后根据提示选择你想要使用的框架(Vanilla JS, Vue, React, Preact, Lit, Svelte, Solid, Qwik等)和语言(JavaScript / TypeScript)。

例如,创建一个 React + TypeScript 项目:

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

Vite 与 React

当你使用 npm create vite@latest 并选择 React 模板时,Vite 会为你配置好大部分内容。

核心插件:@vitejs/plugin-react

这个插件是 Vite 支持 React 的关键,它提供了:

  1. React Fast Refresh (HMR):替代了传统的 react-refresh/babel,实现了组件级别的快速热更新,状态保持。
  2. JSX 转换:使用 esbuild 来转换 JSX,速度非常快。你也可以选择使用 Babel 进行转换,但通常 esbuild 就足够了。

常用配置 (vite.config.jsvite.config.ts)

一个基础的 React + TS 项目的 vite.config.ts 可能如下:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  // 其他配置...
  resolve: {
    alias: {
      // 配置路径别名,例如 '@' 指向 'src' 目录
      '@': '/src'
    }
  },
  server: {
    port: 3000, // 开发服务器端口
    open: true, // 自动打开浏览器
    proxy: {
      // 配置代理,解决开发环境跨域问题
      '/api': {
        target: 'http://localhost:8080', // 你的后端 API 地址
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  },
  build: {
    outDir: 'dist', // 打包输出目录
    // sourcemap: true, // 是否生成 source map
  }
})

常见需求:

  • SVG 作为组件:可以使用 vite-plugin-svgr 插件。

    npm install vite-plugin-svgr --save-dev
    

    vite.config.ts:

    import svgr from 'vite-plugin-svgr'
    // ...
    export default defineConfig({
      plugins: [react(), svgr()],
    })
    

    然后就可以 import { ReactComponent as Logo } from './logo.svg'

  • 环境变量: 在项目根目录创建 .env.development.env.production 文件:

    VITE_API_BASE_URL=http://localhost:3000/api
    VITE_APP_TITLE=My Awesome React App
    

    在代码中通过 import.meta.env.VITE_API_BASE_URL 访问。

Vite 与 Vue

同样,使用 npm create vite@latest 选择 Vue 模板即可快速开始。

核心插件:@vitejs/plugin-vue

这个插件是 Vite 支持 Vue 3 的核心,它提供了:

  1. Vue SFC (Single File Components) 支持:编译 .vue 文件。
  2. HMR:为 Vue 组件提供快速的热更新。
  3. <script setup> 支持:完美支持 Vue 3 的组合式 API 语法糖。

对于 Vue 2,可以使用 vite-plugin-vue2

常用配置 (vite.config.jsvite.config.ts)

一个基础的 Vue 3 + TS 项目的 vite.config.ts 可能如下:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path' // 用于路径别名

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src') // 配置路径别名
    }
  },
  server: {
    port: 3001,
    open: true,
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  },
  // ... 其他配置与 React 类似
})

常见需求 (Vue):

  • JSX/TSX in Vue:可以使用 @vitejs/plugin-vue-jsx

    npm install @vitejs/plugin-vue-jsx --save-dev
    

    vite.config.ts:

    import vueJsx from '@vitejs/plugin-vue-jsx'
    // ...
    export default defineConfig({
      plugins: [vue(), vueJsx()],
    })
    
  • 自动导入 API 和组件

    • unplugin-auto-import:自动导入 Vue 的 API (如 ref, computed) 和其他库的 API。
    • unplugin-vue-components:按需自动导入组件。 这两个插件可以极大提升开发效率。
    npm install -D unplugin-vue-components unplugin-auto-import
    

    vite.config.ts:

    import Components from 'unplugin-vue-components/vite'
    import AutoImport from 'unplugin-auto-import/vite'
    
    export default defineConfig({
      plugins: [
        vue(),
        AutoImport({
          imports: ['vue', 'vue-router'], // 自动导入vue和vue-router相关函数
          dts: 'src/auto-import.d.ts' // 生成 TypeScript 的类型声明文件
        }),
        Components({
          dirs: ['src/components'], // 按需加载的组件目录
          extensions: ['vue'],
          dts: 'src/components.d.ts' // 生成 TypeScript 的类型声明文件
        }),
      ],
      // ...
    })
    

通用插件与最佳实践

一些有用的通用 Vite 插件:

  • vite-plugin-eslint: 在开发时进行 ESLint 校验。
  • vite-plugin-mkcert: 快速为本地开发环境生成 HTTPS 证书。
  • vite-plugin-imagemin: 压缩图片资源,减小打包体积(通常在构建时使用)。
  • vite-plugin-compression: 生成 .gz.br 压缩文件,配合 Nginx 等服务器可以减小传输体积。

Vite 最佳实践:

  1. 拥抱 ESM:尽可能使用 ESM 语法的库,Vite 对其支持最好。
  2. 善用路径别名:通过 resolve.alias 配置路径别名,使导入更简洁。
  3. 合理管理环境变量:使用 .env 文件,并记得给客户端使用的变量加上 VITE_ 前缀。
  4. 理解 public 目录:将不需要 Vite 处理的静态资源(如 favicon.ico)放在 public 目录。
  5. 优化第三方依赖
    • 通过 optimizeDeps.include 强制预构建某些依赖。
    • 通过 optimizeDeps.exclude 排除某些不需要预构建的依赖(例如,你本地 link 的包)。
  6. 代码分割:Rollup 默认会进行合理的代码分割。可以通过 build.rollupOptions.output.manualChunks 自定义分割策略。
  7. CSS 策略
    • 对于组件局部样式,优先使用 CSS Modules 或 Vue SFC 的 <style scoped>
    • 全局样式可以创建一个 main.cssglobal.scss 并在 main.js/ts 中导入。
  8. 按需引入:对于 UI 库(如 Element Plus, Ant Design Vue/React),尽量使用其提供的按需引入方案(通常通过插件实现),或者使用如 unplugin-vue-componentsunplugin-auto-import 这样的工具。
  9. 异步组件/路由懒加载:利用 import() 动态导入语法实现组件和路由的懒加载,减小首屏加载体积。
    // React Router
    const AboutPage = React.lazy(() => import('./pages/AboutPage'));
    
    // Vue Router
    const AboutPage = () => import('./pages/AboutPage.vue');
    

从 Webpack 迁移到 Vite

迁移过程通常比较顺利,但需要注意几个关键差异:

  1. 入口点:Webpack 通常是 JS 入口,Vite 是 index.html 入口。
  2. public 目录:Webpack 的 public 目录(或 static)通常需要 CopyWebpackPlugin,Vite 是内置行为。
  3. 环境变量:Webpack 使用 DefinePluginEnvironmentPlugin,Vite 使用 import.meta.envVITE_ 前缀。
  4. require() vs import:Vite 开发环境基于原生 ESM,不支持 require()。需要确保代码和依赖都使用 ESM。CommonJS 依赖会被 Vite 预构建为 ESM。
  5. Node.js Polyfills:Webpack 5 之前会自动 polyfill 一些 Node.js核心模块,Vite 不会。如果你的代码依赖这些(如 process, Buffer),需要手动处理或寻找浏览器兼容的替代方案。
  6. 插件生态:许多 Webpack 插件在 Vite 中有对应的替代品,或者可以直接使用 Rollup 插件。

总结

Vite 通过巧妙地利用原生 ESM 和 esbuild 的极速能力,为前端开发带来了前所未有的速度和体验。它简化了配置,提升了开发效率,并且在生产构建方面也同样出色(得益于 Rollup)。

对于前端初学者而言,Vite 更低的上手门槛和更快的反馈循环,无疑能让你更专注于业务逻辑和学习本身,而不是与复杂的构建配置作斗争。如果你还没有尝试过 Vite,强烈建议你在下一个项目中体验一下它带来的“快”感!