用 Vite + Vue3 + UnoCSS 搭建企业级项目模板(一):基础骨架

栏目:01 · 前端工程化 | 1 月 · 前端基础与 Vue3 入门 | 选题 6

上个月组里并行起了两个新项目,都是中后台系统。我的做法很"传统":把老项目整个复制过来,改名,删页面,开工。结果第二个项目上线前联调,接口请求怎么都不对,排查了一下午才发现------老项目里三年前留下的 mock 拦截器还挂在 main.ts 里,把 /api 前缀的请求全劫持到本地假数据了。

当天我就决定花两天时间沉淀一个干净的项目模板,以后新项目从模板拉,不从老项目复制。这篇记录搭骨架的过程,重点是踩坑。

先说错误的做法

第一次搭,我直接 npm create vue@latest 把所有选项全勾上:TypeScript、Router、Pinia、ESLint、Prettier、Vitest 全要。生成之后光删演示代码就删了二十分钟,components 里一堆 TheWelcome.vue、WelcomeItem.vue,router 挂着两个示例路由,store 里躺着 counter。

更麻烦的是这些脚手架默认配置并不都是我想在公司项目里要的------比如 Prettier 和 ESLint 的冲突规则,默认勾选的组合跑起来会有格式打架的告警。所以后来我改成最小化起步:模板里只放"每个项目 100% 会用到的东西",Router 都不放(反正有些纯展示页用不上,加上也就一行命令的事)。

最终定稿的版本(node 22.12.0,npm 10.9.0):

bash 复制代码
npm create vue@latest enterprise-template
# 交互选项:TypeScript -> Yes,其余全部 No
cd enterprise-template
npm install
npm i -D unocss@66.4.0 @unocss/eslint-plugin@66.4.0

环境核对:

bash 复制代码
npx vite --version  # vite/7.1.4
node -e "console.log(require('vue/package.json').version)"  # 3.5.13
node -e "console.log(require('unocss/package.json').version)"  # 66.4.0

最终目录结构,刻意保持扁平:

bash 复制代码
src/
├── api/          # 按后端模块拆,一个文件对应一组接口
├── assets/
├── components/
├── composables/  # 组合式函数,和组件严格分开
├── router/
├── styles/       # 全局样式,UnoCSS 之外的少量补充
├── types/        # 公共类型,api 类型放 api/ 里就近维护
├── utils/
├── views/
├── App.vue
└── main.ts

composables/ 单独拎出来说一句:见过太多项目把 useXxx 散落在 utils/ 里,新人根本不知道哪些是响应式的哪些不是。约定先行,这比任何 lint 规则都管用。

踩坑一:路径别名,配了一半等于没配

@ 别名是每个项目的标配。我在 vite.config.ts 里配好:

ts 复制代码
// vite.config.ts
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url)),
    },
  },
})

npm run dev 一切正常,页面渲染、路由跳转都对。我顺手在 src/api/user.ts 里写了 import type { UserInfo } from '@/types/user',浏览器里跑得好好的,就提交了。

第二天 CI 挂了。vue-tsc --noEmit 输出:

bash 复制代码
src/api/user.ts(3,21): error TS2307: Cannot find module '@/types/user' or its corresponding type declarations.

我一开始以为是 vue-tsc 的 bug------dev 环境明明是好的。查了半天 Stack Overflow 才想明白:Vite 和 TypeScript 是两套独立的路径解析系统 。dev 阶段 Vite 的 esbuild 转译根本不做类型检查,别名解析走的是 Vite 自己的 resolve;而 vue-tsc 走的是 tsconfig 的 paths 配置。我只配了前者。

修法是在 tsconfig.app.json 里补上(vue-ts 模板把 tsconfig 拆成了三个,paths 要写在 tsconfig.app.json,不是根上那个只做 references 的壳):

json 复制代码
// tsconfig.app.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

这次教训我记到现在:别信"本地能跑就是对的",类型检查是独立的一道闸,必须让它过。

踩坑二:UnoCSS 装了但"没生效",而且不报错

UnoCSS 接入就三步,我照文档抄的:

ts 复制代码
// vite.config.ts 补充
import UnoCSS from 'unocss/vite'

export default defineConfig({
  plugins: [vue(), UnoCSS()],
})
ts 复制代码
// uno.config.ts
import { defineConfig, presetUno, presetAttributify } from 'unocss'

export default defineConfig({
  presets: [presetUno(), presetAttributify()],
})

然后我在页面里写 <div class="flex items-center gap-2">,刷新------样式完全没生效。诡异的是控制台没有任何报错,Vite 启动日志也正常。

我当时的第一反应是版本兼容问题,差点 downgrade。后来在 DevTools 的 Elements 面板里发现 class 名明明挂在 DOM 上,但 Computed Styles 里对应规则一条都没有------说明 CSS 文件压根没被加载。回头逐行对照文档,才发现 main.ts 里少了这行:

ts 复制代码
// main.ts,这个 import 不能少
import 'virtual:uno.css'

// 如果想统一 reset,再加(按需):
import '@unocss/reset/tailwind.css'

virtual:uno.css 是 UnoCSS 注入的虚拟模块,插件负责"生成"这些原子类,但不会自动"注入"到你的应用里。不 import 就是不加载,且全程静默。这个坑的隐蔽之处在于它违反直觉------一般依赖装完插件配完就该工作,这里偏偏缺一步手动引入。后来我把这三步固化成模板,新人再没踩过。

环境变量与类型声明

企业项目离不开多环境。约定:.env.development 本地、.env.production 生产,变量必须 VITE_ 开头。很多模板会漏掉类型声明,导致 import.meta.env.VITE_API_BASE 在 TS 里报隐式 any。补一个声明文件:

ts 复制代码
// src/types/env.d.ts
/// <reference types="vite/client" />

interface ImportMetaEnv {
  readonly VITE_API_BASE: string
  readonly VITE_UPLOAD_URL: string
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

这样写错变量名(比如手滑打成 VITE_API_BAE)在编码阶段就会标红,而不是跑到运行时才发现接口 404。这是我认为 TS 在中后台项目里性价比最高的一处应用,成本五行代码。

效果与代价

模板落地后这两个月的效果:新项目从拉模板到能写业务代码,从原来的半天缩短到 20 分钟以内(大部分时间花在跟后端对环境变量上)。代价是模板本身成了要维护的东西------上周 UnoCSS 升 minor 版本动了 preset 的默认行为,模板里的 uno.config.ts 跟着改了一行。模板不是一次性的,这点要有预期。

还有几块没做:husky + commitlint 没接(公司目前 git hook 管理不统一,接了反而跟部分老仓库的流程打架)、CI 流水线还是每个项目手配的。这些留到下一篇写"规范层"的时候一起整理。

代码我传了内部 GitLab,就不贴仓库地址了。如果你在搭类似模板时有更顺手的组织方式,评论区聊。

相关推荐
java水泥工8 小时前
什么是前端
java·vue·html5
mont evergreen1 天前
一封从外部世界寄来的信:二维码数据如何穿越三重门,抵达你的屏幕
electron·vue·事件
孟陬1 天前
不用盲调性能参数 Vitest 5.0 新推出 `vitest doctor` 一键诊断测试慢卡点,自动给出最优提速配置
性能优化·单元测试·vite
志尊宝2 天前
Vue3 零基础每日笔记(071):暗黑模式与主题切换——CSS 变量 + Element Plus 暗黑方案
前端·css·笔记·vue·html·前端开发·软件开发
怦然丶心动2 天前
用 Java 把干支历法算清楚:从节气切年到可读摘要
java·算法·vue·独立开发·spring boot / spring
cindershade3 天前
大包不是一个数字:Vite 构建体积的责任归因与边界治理
vite
我最爱吃鱼香茄子4 天前
【毕业设计优选】人力资源管理系统|Java|SpringBoot|Vue|前后端分离|带详细文档+部署教学视频
java·vue·毕业设计·springboot
骑着蜗牛撵大象3275 天前
SpringBoot+Vue3 企业智能体侧挂架构:独立服务、独立数据库与主线零侵入落地
数据库·spring boot·架构·vue·springboot·事件驱动·服务拆分
码艺-Alimjan7 天前
Tauri 网站To桌面应用实战总结
前端·javascript·vue