本讲是M1里程碑的第一块砖。跑完你会看到浏览器里出现「Hello ShopX」,热更新生效,目录结构清楚。这是后续前端学习的地基。
建议先 点赞 + 收藏 + 关注,配环境时随时回查。
一、为什么用Vite而不是Vue CLI
很多 Vue 教程还在教vue-cli init,那是2018年的工具。Vite是2026年的标准选择,原因有四个。
1. 启动速度
Vue CLI用webpack打包,第一次启动要30-60秒,依赖越多越慢。Vite用esbuild预构建依赖,冷启动稳定在1秒以内,无论项目多大。
2. HMR速度
改一行代码,浏览器毫秒级看到变化。Vue CLI的HMR改大文件要3-5秒,Vite是50ms量级——慢的HMR让人失去即改即看的节奏。
3. 官方推荐
Vue团队已正式公告Vue CLI进入维护模式,不再新增大功能。Vite是官方推荐的下一代工具。
4. 生态更广
Vite同时支持Vue3 / React / Svelte / Solid / Vanilla JS。一个项目里要混合技术栈也方便。
💡 唯一注意点:Vite对Node版本有要求。我们装的是Node22,完全满足。
二、创建工程
进入项目目录(沿用第 3 讲的特性分支):
cd ~/workspace/shopx
git switch develop
git pull
git switch -c feature/lecture-004-frontend-scaffold
在项目根目录用 pnpm 创建前端工程:
pnpm create vite frontend –template vue-ts
–template vue-ts是关键,指定Vue 3 + TypeScript模板。frontend是目录名,本专栏用monorepo布局,前端代码都放这里。
命令会问几个交互问题,按回车确认默认即可。几秒后,目录frontend/出现。
进入并安装依赖
cd frontend
pnpm install
pnpm install会按package.json装齐所有依赖。装完后目录里有node_modules/、pnpm-lock.yaml。
启动 dev server
pnpm dev
终端输出:
VITE v7.x.x ready in xxx ms
➜ Local: http://127.0.0.1:5173/
➜ press h + enter to show help
打开浏览器访问 http://127.0.0.1:5173/,看到 Vite + Vue 3的欢迎页。
✅ 你的前端工程跑起来了。这是M1里程碑的第一块砖。
三、目录约定
Vite 默认生成一个最小可运行结构,但本专栏会做扩展。完整目录约定如下:
frontend/
├── public/ # 静态资源(不进打包,原样输出)
├── src/
│ ├── api/ # 接口请求层
│ ├── assets/ # 资源(图片、字体、SVG)
│ ├── components/ # 公共组件
│ │ └── HelloShopX.vue # 第一个组件
│ ├── composables/ # 组合式函数(useXxx)
│ ├── layouts/ # 布局组件
│ ├── router/ # 路由
│ ├── stores/ # Pinia stores
│ ├── styles/ # 全局样式
│ │ ├── reset.css # 浏览器样式重置
│ │ └── variables.css # CSS 变量(颜色、间距)
│ ├── types/ # TS 类型定义
│ │ └── goods.ts # 商品类型
│ ├── utils/ # 工具函数
│ │ └── format.ts # 格式化函数
│ ├── views/ # 页面组件
│ │ └── HomeView.vue
│ ├── App.vue # 根组件
│ ├── main.ts # 入口文件
│ └── env.d.ts # Vite环境变量类型
├── eslint.config.js # ESLint配置(ESLint9+ flat config)
├── .prettierrc.json # Prettier配置
├── index.html # HTML 入口
├── package.json
├── pnpm-lock.yaml
├── tsconfig.json
├── tsconfig.app.json
├── tsconfig.node.json
├── vite.config.ts
└── README.md
几个目录的提前说明
- api/讲Axios统一封装时建——目前空着
- composables/讲组合式API时建
- router/ 讲Vue Router时建
- stores/ 讲Pinia时建
- layouts/ 讲Element Plus布局时建
现在只建 components/ views/ assets/ 这三个,其他目录等讲到时再建。
命令一次性建好空目录
mkdir -p src/{api,assets,components,composables,layouts,router,stores,styles,types,utils,views}
touch src/{api,composables,layouts,router,stores,types,utils}/.gitkeep
.gitkeep是空目录占位文件——Git不跟踪空目录,但有了这个文件目录就跟着仓库走。
四、<script setup>语法糖
Vite创建的App.vue默认就是<script setup>写法。这是Vue 3推荐的写法,比Options API简洁50%以上。
一个最小示例
<script setup lang="ts">
import { ref, computed } from 'vue'
const count = ref(0)
const double = computed(() => count.value * 2)
function increment() {
count.value++
}
</script>
<template>
<div class="counter">
<p>count is {{ count }}</p>
<p>double is {{ double }}</p>
<button @click="increment">click</button>
</div>
</template>
<style scoped>
.counter {
padding: 20px;
text-align: center;
}
</style>
三件你需要知道的事
第一,lang="ts"启用TypeScript。没有它,<script>里写TS代码会报错。
第二,ref和reactive是响应式API。ref(0)返回一个响应式引用,模板里直接{{ count }}就能取到值(Vue自动解包),脚本里要count.value取值。后面会专门讲响应式原理。
第三,<style scoped>限定样式只作用于当前组件。.counter不会泄漏到其他组件的.counter上。这是模块化CSS的基础。
与Options API对比
老式Vue 2写法:
<script>
export default {
data() {
return { count: 0 }
},
computed: {
double() { return this.count * 2 }
},
methods: {
increment() { this.count++ }
}
}
</script>
新写法(同等功能):
<script setup>
import { ref, computed } from 'vue'
const count = ref(0)
const double = computed(() => count.value * 2)
function increment() { count.value++ }
</script>
ref / computed移到顶部、变量直接定义、方法直接写函数——样板代码减少一半。
五、TypeScript配置要点(Vite三文件结构)
最新版的Vite的vue-ts将 TypeScript 配置拆分为三个文件,这是 TypeScript 项目引用(Project References) 功能的体现。
Vite 团队采用这种结构,主要是为了将前端代码(运行在浏览器中)和 Vite 配置文件(运行在 Node.js 中)的 TypeScript 配置分离,实现更清晰、更精确的类型检查。
三个文件的分工
| tsconfig.json | 根配置,通过references引用另外两个文件 | 项目管理入口 |
| tsconfig.app.json | 你需要重点修改的文件 | src/下所有应用代码 |
| tsconfig.node.json | 构建工具配置 | vite.config.ts等 Node 环境文件 |
tsconfig.json默认内容
{
"files": [],
"references": [
{ "path": "./tsconfig.node.json" },
{ "path": "./tsconfig.app.json" }
]
}
tsconfig.app.json(核心修改文件)
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2022",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "Bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "preserve",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
}
几个关键项解释
- strict: true —— 启用所有严格类型检查。这是 TS 最大的价值所在,不要关
- noUnusedLocals / noUnusedParameters —— 没用到的变量/参数直接报错,养成干净习惯
- paths: { "@/*": ["./src/*"] } —— 路径别名,需要同时在 tsconfig.json 根文件和 tsconfig.app.json 中配置(见第六节)
- moduleResolution: "Bundler" —— Vite 推荐的解析模式,比 Node 更适合打包工具
- noEmit: true —— 只做类型检查,不输出编译产物,实际打包交给 Vite
- isolatedModules: true —— 强制每个文件都能独立编译,Vite esbuild 需要
tsconfig.node.json(保持默认即可)
tsconfig.node.json 专门用于 vite.config.ts 的编译规则,模板默认生成的内容已经可用,一般不需要修改:
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2023",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "Bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"strict": true,
"types": ["node"]
},
"include": ["vite.config.ts"]
}
这个文件告诉TS:「编译vite.config.ts时,把types设为["node"]而不是["vite/client"]」。
env.d.ts声明Vite全局类型
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_BASE_URL: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
VITE_ 前缀的环境变量才能被客户端代码访问(import.meta.env.VITE_API_BASE_URL)。
六、路径别名 @
写import Hello from '@/components/Hello.vue'比写import Hello from '../../components/Hello.vue' 优雅太多。配置分两步,两个文件必须同时改,缺一个TS会红。
步骤 1:Vite 配置
编辑vite.config.ts:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { fileURLToPath, URL } from 'node:url'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
server: {
port: 5173,
host: '127.0.0.1',
proxy: {
'/api': {
target: 'http://127.0.0.1:8000',
changeOrigin: true,
},
},
},
})
步骤 2:TS 配置
tsconfig.json中添加compilerOptions:
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
],
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}
验证别名生效
<!– src/views/HomeView.vue –>
<script setup lang="ts">
import HelloShopX from '@/components/HelloShopX.vue'
</script>
<template>
<HelloShopX />
</template>
如果两个文件都改对了,HelloShopX 组件能正常引入,TS 不会报错。
几个常见疑问
- 为什么三处都要配?
Vite需要vite.config.ts的alias来做实际解析;tsconfig.json根文件让IDE识别路径;
tsconfig.app.json让vue-tsc类型检查通过。缺任何一处都可能出现“编辑器不报错但构建失败”或反之的情况。 - 为什么fileURLToPath(new URL('./src', import.meta.url))? 因为Vite是ESM写法,普通字符串路径在ESM下会报错。fileURLToPath 把 URL 转成绝对路径。
- VSCode不识别? 重启TS服务:Ctrl+Shift+P → “TypeScript: Restart TS Server”。
七、devServer.proxy 代理
后面会让后端跑在 http://127.0.0.1:8000,前端在5173。浏览器有同源策略,前端直接fetch 8000端口会被CORS拦截。
通过Vite代理,前端代码写/api/xxx时,Vite在开发服务器里反向代理到后端,绕过浏览器同源检查。
server: {
proxy: {
'/api': {
target: 'http://127.0.0.1:8000',
changeOrigin: true,
},
},
}
怎么用
前端代码:
const res = await fetch('/api/v1/health/')
const data = await res.json()
浏览器看到的是5173 → 5173,没跨域问题。Vite在内部把请求转发到8000,用户完全无感。
完整CORS原理
浏览器只在生产环境真正需要CORS头。后面会配django-cors-headers,也会讲清整个CORS流程(简单请求、预检请求、Cookie携带、自定义头)。
八、ESLint + Prettier 落地
Linter和Formatter是工程规范的左膀右臂。Linter抓代码错误,Formatter保代码风格。本讲不展开讲它们的规则,只把它们配好。
⚠️ 2026 年重要变化:ESLint9+已全面转向 flat config(eslint.config.js),旧的.eslintrc.cjs写法已不推荐。本讲按新写法配置。
安装
pnpm add -D eslint @eslint/js typescript-eslint eslint-plugin-vue \\
vue-eslint-parser prettier eslint-config-prettier
eslint.config.js
import js from '@eslint/js'
import tseslint from 'typescript-eslint'
import pluginVue from 'eslint-plugin-vue'
import prettier from 'eslint-config-prettier'
export default [
js.configs.recommended,
…tseslint.configs.recommended,
…pluginVue.configs['flat/recommended'],
prettier,
{
rules: {
'vue/multi-word-component-names': 'off',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
},
},
]
.prettierrc.json
{
"semi": false,
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100,
"tabWidth": 2,
"arrowParens": "always"
}
package.json加lint脚本
{
"scripts": {
"dev": "vite",
"build": "vue-tsc -b && vite build",
"preview": "vite preview",
"lint": "eslint . –fix",
"format": "prettier –write ."
}
}
vue-tsc -b说明:-b是build mode,它会按照tsconfig.json的references分别对tsconfig.app.json和tsconfig.node.json做类型检查。
VSCode 工作区配置
新建 .vscode/settings.json(共享配置,可以进git):
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"eslint.validate": ["javascript", "typescript", "vue"],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}
装ESLint和Prettier – Code formatter两个VSCode扩展后,保存文件时自动格式化 + 自动修lint错。
九、「Hello ShopX」页面
把App.vue改成我们要的页面:
<script setup lang="ts">
const year = new Date().getFullYear()
</script>
<template>
<div class="hello">
<div class="brand">ShopX</div>
<h1>Hello ShopX</h1>
<p class="subtitle">从零到上线 · 全栈电商项目</p>
<p class="muted">© {{ year }} · 第 4 讲里程碑</p>
</div>
</template>
<style scoped>
.hello {
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #0f1b2d 0%, #1a2a44 100%);
color: #ffffff;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', sans-serif;
}
.brand {
font-size: 18px;
letter-spacing: 6px;
color: #ffb020;
margin-bottom: 24px;
}
h1 {
font-size: 64px;
margin: 0 0 16px;
font-weight: 500;
}
.subtitle {
font-size: 18px;
color: #cbd5e1;
margin: 0 0 48px;
}
.muted {
color: #8a93a6;
font-size: 14px;
}
</style>
保存后浏览器无刷新自动更新(HMR验证)。
✅ 这一刻可以提交了。
按第3讲的提交规范,本讲至少两个commit:
cd ~/workspace/shopx
# 第一个 commit:前端工程初始化
git add frontend/package.json frontend/pnpm-lock.yaml frontend/index.html \\
frontend/vite.config.ts frontend/tsconfig.json frontend/tsconfig.node.json
git commit -m "feat(frontend): 第 004 讲 初始化 Vite + Vue3 + TS 工程"
# 第二个 commit:Hello ShopX 页面与配置
git add frontend/src frontend/eslint.config.js frontend/.prettierrc.json frontend/.vscode
git commit -m "feat(frontend): 第 004 讲 Hello ShopX 页面与 ESLint/Prettier 配置"
# 推送特性分支
git push -u origin feature/lecture-004-frontend-scaffold

十、本讲作业
十一、常见报错速查表
| pnpm报command not found | 没装pnpm | npm install -g pnpm或corepack enable |
| Vite启动后Cannot find module '@vue/xxx' | 依赖没装 | pnpm install |
| @/components/Foo.vue TS飘红「Cannot find module」 | tsconfig.paths没配 | 同步 tsconfig的paths 配置 |
| HMR不生效 | 编辑的不是Vite监听的文件 | 检查vite.config.server.watch配置;保存后看终端日志 |
| vue-tsc报类型错误但pnpm dev能跑 | 类型检查只在build阶段 | 写pnpm build一次强制全量检查 |
| ESLint报Could not find config file | 还在用旧的.eslintrc | 改用eslint.config.js flat config |
| Prettier和ESLint打架 | ESLint默认格式化规则与Prettier冲突 | eslint.config.js里加prettier配置,放最后 |
| 端口5173被占 | 上次pnpm dev没关 | lsof -ti:5173 | xargs kill -9(macOS) |
十二、术语自查报告
- ✅ Vite(不是「Vite.js」、不是 Webpack)
- ✅ Vue3组合式API(不是「Vue3写法」、「新写法」)
- ✅ <script setup>语法糖名称准确
- ✅ TypeScript 5.x(不是泛指TS)
- ✅ HMR(Hot Module Replacement)写全称
- ✅ esbuild是Vite预构建工具(不是「Vite用webpack」)
- ✅ pnpm与npm区别写清(节省磁盘、严格依赖、防幽灵依赖)
- ✅ <style scoped>模块化CSS原理
- ✅ lang="ts"启用TypeScript的位置
- ✅ ESLint 9+ flat config(eslint.config.js),非旧版.eslintrc
自查通过。无禁用词。
最后:前端第一块砖,已经砌好
如果你跟着跑出了Hello ShopX,评论区打卡一句:
第004讲签到,Hello ShopX已跑通
- 点赞:让更多正在学前端的看到
- 收藏:配ESLint、路径别名时随时回查
- 关注:追更不迷路
我们第5讲见。
网硕互联帮助中心




评论前必须登录!
注册