Vite 构建与开发使用方法(AI 大模型评测平台的构建工具实践)

开发服务器启动 25 秒、改一行代码 HMR 转 800 毫秒——等编译耗掉的时间比写代码还多,前端工程化被逼着换路。当时团队讨论过三条路:继续用 Webpack 5 调优、上 Turbopack、迁移 Vite 5。权衡开发体验、生态成熟度和迁移成本后,我们选了 Vite,迁移完成那天启动从 25s 降到 1.5s、HMR 从 800ms 降到 80ms,全组都松了一口气。下面把我们在生产环境里验证过的 Vite 全套配置和踩过的坑完整写出来,覆盖从基础配置到多环境构建的每一个环节。

一、为什么选 Vite

先看一组我们内部实测的对比数据:

维度 Vite 5 Webpack 5
启动速度(dev) 1.5s(按需编译) 25s(全量打包)
HMR < 100ms 500-2000ms
构建速度 快(Rollup + esbuild) 慢
配置复杂度 简单 复杂
生态 较新但活跃 成熟

选 Vite 不是因为它快这么简单。核心差异在架构:Webpack 冷启动要把整个依赖图预先打包,Vite 在 dev 期直接按需编译单个模块,借助浏览器原生 ESM 加载,文件越大优势越明显。我们这种 200+ 页面的项目,Webpack 启动 25s,Vite 只要 1.5s,几乎是零等待。迁移过程中我们也担心过生态问题,实际跑了两个月,常用插件都覆盖得不错。

二、基础配置

Vite 的配置入口是 vite.config.ts,一个文件管 dev 和 build 两种场景:

// vite.config.ts
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'

export default defineConfig(({ mode }) => {
    const env = loadEnv(mode, process.cwd(), '')
    return {
        plugins: [vue()],
        resolve: {
            alias: {
                '@': resolve(__dirname, 'src')
            }
        },
        server: {
            host: '0.0.0.0',
            port: 5173,
            open: true
        },
        build: {
            target: 'es2020',
            outDir: 'dist',
            assetsDir: 'assets',
            sourcemap: mode !== 'production',
            minify: 'terser',
            terserOptions: {
                compress: { drop_console: true }
            }
        }
    }
})

这里几个点值得说明。loadEnv 第三个参数传空字符串,才能把环境变量全部加载出来;sourcemap 只在非生产开启,生产关了能省体积也避免源码泄露;drop_console 在生产去掉 console 输出。我们最初没配 drop_console,线上排查问题时看到一堆业务日志在打,反而干扰真正的告警日志。

三、环境变量

多环境部署离不开环境变量,Vite 约定按文件区分:

// .env
VITE_API_BASE=https://api.example.com
VITE_APP_NAME=AI 评测平台

// .env.development
VITE_API_BASE=http://localhost:8080

// .env.production
VITE_API_BASE=https://api.prod.com
// 代码中
const base = import.meta.env.VITE_API_BASE

环境变量必须带 VITE_ 前缀才能在客户端代码里访问,这是 Vite 的安全设计——不带前缀的变量是给 Node 端用的,直接塞进浏览器会泄露敏感信息。我们迁移时第一版踩过这个坑,写了个不带前缀的密钥变量,好在上线前 review 拦住了。注意 .env 里只能放非敏感配置,真正的密钥要走服务端注入。

四、Vue 插件

Vue 项目必备插件组合如下,一个是 SFC 编译,一个是 JSX 支持:

import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'

plugins: [
    vue(),
    vueJsx()  // 支持 JSX/TSX
]

我们的评测配置页用了不少 JSX 写动态表单,没有 vueJsx 插件 SFC 里的 tsx 文件直接编译报错。插件顺序一般固定,vue 放前面,出错时优先检查版本是否和 Vite 大版本匹配,我们遇到过插件和 Vite 5 不兼容导致 HMR 失效的版本。

五、自动导入

手工 import 组件和 API 是样板代码的大头,我们引入 unplugin 系列解决:

import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

plugins: [
    AutoImport({
        resolvers: [ElementPlusResolver()],
        imports: ['vue', 'vue-router', 'pinia'],
        dts: 'src/auto-imports.d.ts'
    }),
    Components({
        resolvers: [ElementPlusResolver()],
        dts: 'src/components.d.ts'
    })
]

配置后模板里直接 <el-button @click="onClick"> 不用 import,代码里直接用 ref/computed 也不用手动引,Vite 编译期自动补全。两个 dts 文件是自动生成的类型声明,要提交进仓库,IDE 才能正确提示。注意自动导入省了写法,但依赖隐式,团队约定还是要在文件顶部保留关键 import 以便 code review 时快速定位依赖来源。

六、TypeScript 集成

Vite 本身不做类型检查,它只管转译,类型检查要交给 tsc 单独跑:

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

export default defineConfig({
    plugins: [
        vue()
    ],
    // tsconfig.json 的 paths 必须配
    resolve: {
        alias: {
            '@': resolve(__dirname, 'src')
        }
    }
})

tsconfig.json:

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

这是最容易踩的坑:vite.config.ts 里配了 @ 别名,但 tsconfig.json 没同步配 paths,IDE 就一路飘红。我们 CI 里加了 tsc --noEmit 做类型门禁,顺便把 @ 路径映射统一成一份配置维护,避免两边漂移。

七、CSS 处理

SCSS 变量全局注入可以省掉每个文件顶部的 import 样板:

// vite 自动处理 SCSS/Less/Stylus
export default defineConfig({
    css: {
        preprocessorOptions: {
            scss: {
                additionalData: `@import "@/styles/variables.scss";`
            }
        },
        modules: {
            localsConvention: 'camelCase'
        }
    }
})

additionalData 会把 variables.scss 的内容自动拼到每个 SCSS 文件开头,所有组件都能直接用变量。副作用是构建时间会略增,毕竟每个文件都多一次解析,我们觉得收益大于成本。CSS Modules 在 Vue 里就是 <style module>,localsConvention 设为 camelCase 后模板里访问 $style.title 这种写法更顺手。

八、静态资源处理

静态资源的输出路径可以按类型分类,保持目录整洁:

export default defineConfig({
    assetsInclude: ['**/*.md', '**/*.svg'],
    build: {
        rollupOptions: {
            output: {
                assetFileNames: (assetInfo) => {
                    if (assetInfo.name?.endsWith('.png')) {
                        return 'img/[name]-[hash][extname]'
                    }
                    if (assetInfo.name?.endsWith('.woff2')) {
                        return 'fonts/[name]-[hash][extname]'
                    }
                    return 'assets/[name]-[hash][extname]'
                }
            }
        }
    }
})

默认情况下所有资源都堆在 assets 目录,图片、字体混在一起,部署后定位问题麻烦。按类型分目录后,CDN 规则也好写,比如字体文件单独设缓存策略。assetsInclude 是给 import 特定后缀文件的场景用的,我们用它支持直接 import Markdown 说明文档。

九、构建优化

构建优化要按顺序做,跳步容易白费功夫:

  1. 先用 visualizer 分析产物体积分布;
  2. 再按依赖类型拆 chunk;
  3. 然后开启 terser 压缩并去掉调试代码;
  4. 最后用 vite-plugin-compression 生成 Gzip 和 Brotli 预压缩文件。

四步都做齐,产物才能同时拿到”体积小、缓存细、传输快”三个收益。下面分别看每步的配置。

1. 拆 chunk

按依赖来源拆 chunk,让浏览器并行加载互不阻塞:

build: {
    rollupOptions: {
        output: {
            manualChunks(id) {
                if (id.includes('node_modules')) {
                    if (id.includes('echarts')) return 'echarts-vendor'
                    if (id.includes('element-plus')) return 'element-plus'
                    if (id.includes('vue')) return 'vue-vendor'
                    return 'vendor'
                }
            }
        }
    }
}

2. 压缩

生产环境用 terser 压缩并去掉调试代码:

import terser from '@rollup/plugin-terser'

build: {
    minify: 'terser',
    terserOptions: {
        compress: { drop_console: true, drop_debugger: true }
    }
}

3. Gzip / Brotli

构建时预压缩,省去网关实时压缩的 CPU 开销:

import viteCompression from 'vite-plugin-compression'

plugins: [
    viteCompression({ algorithm: 'gzip' }),
    viteCompression({ algorithm: 'brotliCompress', ext: '.br' })
]

拆 chunk 和压缩这套组合拳打下来,我们首屏产物从 900KB 降到 320KB gzip。三个配置要一起上才有效果:只拆 chunk 不压缩,传输体积下不去;只压缩不拆 chunk,缓存粒度太粗,改一行代码整包缓存失效。

十、路径别名

多级目录下用别名代替相对路径,重构时不用改一堆 import:

// vite.config.ts
resolve: {
    alias: {
        '@': resolve(__dirname, 'src'),
        '@components': resolve(__dirname, 'src/components'),
        '@utils': resolve(__dirname, 'src/utils')
    }
}

别名的意义不只是少打字,更重要的是模块边界清晰。我们要求跨模块引用一律走 @ 别名,禁止 ../../ 相对路径,这样目录调整时能快速确认引用范围。老项目里用 vim 全局替换相对路径的那段经历,再也不想体验第二遍。

十一、代理配置

dev 期跨域问题用 Vite 代理解决,还能把 WebSocket 一并转发:

server: {
    proxy: {
        '/api': {
            target: 'http://localhost:8080',
            changeOrigin: true,
            rewrite: (path) => path.replace(/^\/api/, '/api')
        },
        '/ws': {
            target: 'ws://localhost:8080',
            ws: true
        }
    }
}

我们的评测任务进度走 WebSocket,ws: true 打开后长连接也能穿透代理。有个细节:代理只对 dev server 生效,生产环境要把同源策略交给网关处理,我们刚开始不知道这点,上线后接口全部 404,排查半天才发现是生产没配 Nginx 转发。

十二、HMR 配置

HMR 参数调整能明显改善开发手感:

server: {
    hmr: {
        overlay: false,    // 关错误遮罩
        port: 5174         // 避免与 server 端口冲突
    },
    watch: {
        usePolling: true,  // Docker/WSL 必加
        ignored: ['**/node_modules/**', '**/dist/**', '**/.git/**']
    }
}

overlay 关掉后语法错误不再用全屏红罩盖住编辑器;usePolling 是 Docker/WSL 环境的救命配置,这些环境下 inotify 监听经常失效,开了轮询文件变更才能及时触发 HMR。ignored 把 node_modules 等目录排除在监听外,能减少大量无效文件事件。我们在 CI 容器里构建过,不加 usePolling 时 HMR 完全不工作,加上之后一切正常。

十三、SVG 图标

SVG 图标统一管理,避免 iconfont 的字体加载抖动:

import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'

plugins: [
    createSvgIconsPlugin({
        iconDirs: [resolve(__dirname, 'src/assets/icons')],
        symbolId: 'icon-[dir]-[name]'
    })
]
<template>
    <svg><use href="#icon-edit" /></svg>
</template>

我们把所有图标文件放进 src/assets/icons,插件构建时自动生成雪碧图,模板里用 symbol 引用。相比 iconfont,SVG 不依赖字体加载,不会出现首屏图标变成方块的问题。命名规则 icon-[dir]-[name] 建议固定下来,新图标按目录归属,引用时一眼能找到来源。

十四、生产构建分析

体积分析是构建优化的第一步,看不清体积分布就没法做决策:

import { visualizer } from 'rollup-plugin-visualizer'

plugins: [
    visualizer({ open: true, gzipSize: true, brotliSize: true })
]

构建完会自动打开 stats.html,按包大小直观看到哪个依赖是体积大户。我们就是靠它发现 Element Plus 全量引入占了 700KB,才下定决心做按需引入。建议只在本地构建或带参数的环境打开,别把可视化报告塞进每次 CI 输出。

十五、多环境构建

一套代码打多种环境包,用 mode 区分:

// package.json
{
    "scripts": {
        "dev": "vite",
        "build:dev": "vite build --mode development",
        "build:staging": "vite build --mode staging",
        "build:prod": "vite build --mode production"
    }
}

每个环境对应一个 .env.{mode} 文件,vite build --mode staging 会加载 .env.staging。注意 --mode 只影响环境变量加载,构建产物本身没有差异,所以环境差异要全部收敛在环境变量里,不要让代码里出现写死的环境判断。

十六、SSR/SSG

评测平台的登录页和营销落地页需要秒开,我们用了 Vite SSG 预渲染:

// vite.config.ts
import { defineConfig } from 'vite'
import ViteSSG from 'vite-ssg'

export default ViteSSG(
    { /* 同上 */ },
    {
        // routes
    }
)

构建时把指定路由预渲染成静态 HTML,用户拿到的第一帧就是完整内容,首屏秒开。SSG 的前提是页面不依赖登录态和动态数据,我们只对落地页和登录页启用,评测工作台这类强交互页面继续走 SPA 模式。

十七、踩过的坑

工程化迁移的坑大多藏在细节里,列出来给后来人省点时间:

  • 环境变量不生效:必须 VITE_ 前缀,且要在 .env 而非 process.env。
  • 别名 IDE 报错:要在 tsconfig.json 也配 paths,不能只在 vite.config.ts 配。
  • HMR 不更新:Docker/WSL 下文件监听失效,加 usePolling: true。
  • CSS 闪烁:CSS 被打进 JS 里异步加载。检查 build 是否抽离 CSS。
  • 大文件 dev 期慢:import 一个 5MB 的 JSON,dev 启动慢。改用 fetch 按需加载。
  • 生产 sourcemap 泄露:生产关闭 sourcemap 或用 .map 后缀上传 Sentry。
  • 路径别名大小写:Linux 严格区分大小写,Windows 不区分。CI 跑在 Linux 上要严格统一。

十八、平台构建配置示例

把前面所有配置串起来,就是平台当前在用的完整配置:

import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import { visualizer } from 'rollup-plugin-visualizer'
import viteCompression from 'vite-plugin-compression'
import { resolve } from 'path'

export default defineConfig(({ mode }) => {
    const env = loadEnv(mode, process.cwd(), '')
    return {
        plugins: [
            vue(),
            AutoImport({ 
                resolvers: [ElementPlusResolver()],
                dts: 'src/auto-imports.d.ts'
            }),
            Components({ 
                resolvers: [ElementPlusResolver()],
                dts: 'src/components.d.ts'
            }),
            mode === 'production' && visualizer({ open: true }),
            viteCompression({ algorithm: 'gzip' })
        ],
        resolve: {
            alias: { '@': resolve(__dirname, 'src') }
        },
        server: {
            host: '0.0.0.0',
            port: 5173,
            proxy: {
                '/api': {
                    target: env.VITE_API_PROXY,
                    changeOrigin: true
                }
            }
        },
        build: {
            target: 'es2020',
            sourcemap: mode !== 'production',
            rollupOptions: {
                output: {
                    manualChunks: {
                        'vue-vendor': ['vue', 'vue-router', 'pinia'],
                        'element-plus': ['element-plus'],
                        'echarts-vendor': ['echarts']
                    }
                }
            }
        }
    }
})

这份配置可以当模板直接用,改几处域名和目录就行。Vite 的价值在于把”工程配置”从一门手艺变成一件顺手的事,剩下的精力应该留给业务本身。

常见问题(FAQ)

Q1:Vite 适合大型项目吗?

适合。Vite 5 已经在大量大型项目验证(Vue/Vite 官方文档都迁到 Vite)。

Q2:迁移 Webpack 成本大吗?

不大。Vite 兼容 Webpack 多数概念,主要改 config 文件和 import 方式。

Q3:Vite 生产构建用什么?

Rollup。Vite 在 dev 用原生 ESM,在 build 用 Rollup。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 qiqicto@qq.com 举报,一经查实,本站将立刻删除。
赞 (0)
其AI的头像其AI普通用户

相关推荐

返回顶部