不使用 WSL,在本地新建一个新项目
Vue3 + Pinia + TypeScript + ElementPlus
使用 pnpm
使用Vite官方模板创建的项目,但是要清除掉所有示例代码,保留干净的项目结构。
步骤1:使用Vite创建Vue3项目
bash
# 创建项目(使用官方模板)
pnpm create vite my-games -- --template vue-ts
# 进入项目目录
cd my-games
步骤2:安装额外依赖
bash
# 安装Pinia和Element Plus
pnpm add pinia element-plus @element-plus/icons-vue
安装路由
pnpm add vue-router@4
安装 sass-embedded
pnpm install sass-embedded
sass-embedded是官方推出的 Dart Sass 高性能版本。它并不是一个全新的框架,而是一个通过进程间通信调用原生 Dart 可执行文件来编译 Sass/SCSS 的封装器(Wrapper)。简单来说,你可以把它理解为
sass包的“涡轮增压版”,适合在大型项目或需要高频编译的场景下提升性能。
核心特点与优势
特性 sass(纯JS版)sass-embedded(嵌入式版)运行方式 纯 JavaScript 实现 调用独立的 Dart 原生可执行文件 编译速度 中等 更快,尤其适合大型 Sass 文件 API 兼容性 支持新版 JS API 完全兼容 sass包的 API,可无缝替换跨平台支持 全平台 仅限 Dart 支持的平台:Windows、macOS、Linux 异步性能 同步模式更快 异步模式更快(因为编译在独立进程中) 它如何工作?
sass-embedded的工作原理分为三个部分:
编译器:一个独立的 Dart 可执行文件,负责实际的 Sass 解析和编译工作。因为它本身是原生代码,所以速度很快。
主机 (Host):你项目中安装的
sass-embeddednpm 包。它作为桥梁,提供你熟悉的 JavaScript API,并负责启动和管理编译器进程。通信协议:主机和编译器之间通过标准输入/输出流,使用预定义的协议进行通信,完成编译任务。
使用场景与注意事项
适用场景:
大型项目或代码库,对 CSS 编译速度有较高要求。
需要利用异步模式来获得最佳性能的场景。
希望从
node-sass迁移,但又担心性能问题的用户。注意事项:
平台限制:如果你的开发或部署环境是非常规的操作系统或架构,需要确认 Dart 是否支持。
兼容性:它支持新版 JS API 和旧版(Legacy)API。但对于旧版 API,有一些不常用的配置选项(如
precision、indentWidth)不被支持。不过对于绝大多数现代项目来说,这不会有任何影响。
步骤3:清理示例代码
清空 src/App.vue
清空 src/components/ 目录
bash
# 删除示例组件
rm src/components/HelloWorld.vue
# 或者Windows PowerShell
Remove-Item src/components/HelloWorld.vue
重置 src/style.css
简化 src/main.ts
typescript
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import router from './router'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import './style.css'
import App from './App.vue'
const app = createApp(App)
app.use(router)
// 使用Pinia
app.use(createPinia())
// 使用Element Plus
app.use(ElementPlus)
app.mount('#app')
步骤4:创建基础目录结构
bash
# 创建必要的目录
mkdir src/views
mkdir src/router
mkdir src/stores
mkdir src/utils
mkdir src/types
mkdir src/api
mkdir src/layouts
创建 src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
name: 'Home',
component: () => import('../views/Home.vue')
},
]
})
export default router
步骤5:创建类型定义文件
创建 src/types/global.d.ts
typescript
// 全局类型定义
export {};
declare global {
// 在这里定义全局类型
}
步骤6:创建示例Store(可选,但建议保留模板)
创建 src/stores/index.ts
typescript
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useMainStore = defineStore('main', () => {
// 你的状态
const count = ref(0)
// 你的方法
function increment() {
count.value++
}
return {
count,
increment
}
})
步骤7:更新配置文件(可选优化)
更新 vite.config.ts
typescript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@components': path.resolve(__dirname, './src/components'),
'@assets': path.resolve(__dirname, './src/assets'),
'@utils': path.resolve(__dirname, './src/utils')
}
},
server: {
port: 3000,
open: true
}
})
更新 tsconfig.json 添加路径映射
原有的
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}
更新后
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }]
}
补充建议
-
如果使用的是 Vite + Vue 3,可以完全放弃 TypeScript 的项目引用功能,只保留一个
tsconfig.json文件(去掉references字段),这样配置更简单且无需处理composite问题。 -
若要保留项目引用,请确保
tsconfig.node.json中的module设置为CommonJS(或ESNext配合moduleResolution: node),因为 Node 环境与浏览器环境不同。
去掉 references 字段
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}
如果没去掉 references 字段,会提示
引用的项目“d:/MyProjects/my-fridge/tsconfig.app.json”必须拥有设置 "composite": true。ts
Path to referenced tsconfig or to folder containing tsconfig.
解决方法:在tsconfig.app.json和tsconfig.node.json文件中,分别添加"composite": true。
tsconfig.app.json
tsconfig.node.json
引用的项目“d:/MyProjects/my-fridge/tsconfig.node.json”可能不会禁用发出。
原因:
被引用的项目(tsconfig.app.json/tsconfig.node.json)如果设置了"noEmit": true,会导致它们无法生成供主项目引用的“输出文件”。项目引用模式通常要求这些子项目能够生成.d.ts等声明文件。
解决方法:
检查tsconfig.app.json和tsconfig.node.json中是否包含"noEmit": true。如果有,请将其移除或改为false。同时,建议显式开启"declaration": true来生成类型声明文件。
只有在设置“noEmit”或“emitDeclarationOnly”时,才能使用选项“allowImportingTsExtensions”。
这个错误的原因非常明确:
allowImportingTsExtensions选项必须与noEmit或emitDeclarationOnly同时使用。
您贴出的错误提示实际上指出了问题所在 —— 我上一条回复中建议移除
noEmit的做法可能不适合所有子项目。
错误原因:allowImportingTsExtensions允许在导入时写.ts扩展名,但这会生成无法直接运行的代码(因为最终输出应该是.js文件)。所以 TypeScript 强制要求:要么完全不生成输出文件(noEmit: true),要么只生成声明文件(emitDeclarationOnly: true)。
针对
tsconfig.node.json(通常用于 Node.js 环境,比如vite.config.ts),有两种推荐的配置方式:
方案一:保持
noEmit: true(推荐用于仅类型检查的场景)如果
tsconfig.node.json只用于类型检查,而实际构建由 Vite 等工具完成{ "compilerOptions": { "composite": true, "noEmit": true, // ✅ 保留 noEmit "allowImportingTsExtensions": true, // ✅ 允许使用 // 其他选项... } }
方案二:改为
emitDeclarationOnly: true(需要生成类型声明)如果项目引用要求生成
.d.ts文件供其他项目使用{ "compilerOptions": { "composite": true, "emitDeclarationOnly": true, // ✅ 只生成 .d.ts "declaration": true, // 需要配合此选项 "allowImportingTsExtensions": true, // ✅ 允许使用 // 其他选项... } }考虑到 Vue 3 + Vite 项目的典型结构,我建议:
tsconfig.node.json完整示例{ "compilerOptions": { "composite": true, "target": "ES2020", "module": "ESNext", "moduleResolution": "bundler", "allowSyntheticDefaultImports": true, "noEmit": true, // ← 关键:不输出文件 "allowImportingTsExtensions": true, // ← 现在合法了 "strict": true, "skipLibCheck": true, "types": ["node"] }, "include": ["vite.config.ts", "vitest.config.ts", "cypress.config.ts"] }
tsconfig.app.json配合示例{ "compilerOptions": { "composite": true, "target": "ES2020", "module": "ESNext", "moduleResolution": "bundler", "jsx": "preserve", "noEmit": true, // ← 同样保持 noEmit "allowImportingTsExtensions": true, // ← 合法 "strict": true, "skipLibCheck": true, "paths": { "@/*": ["./src/*"] } }, "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] }关于
composite的特别说明当使用
noEmit: true配合composite: true时,TypeScript 会:
允许项目引用功能正常工作
进行增量类型检查
不生成任何输出文件(包括
.js和.d.ts)这对于仅将子项目用于类型检查的场景完全没问题。如果您需要生成声明文件给其他项目引用,才需要使用
emitDeclarationOnly: true。
选项“baseUrl”已弃用,并将停止在 TypeScript 7.0 中运行。指定 compilerOption“ignoreDeprecations”:“6.0”以使此错误静音。
请访问 https://aka.ms/ts6 以获取迁移信息。
根tsconfig.json中的baseUrl废弃警告,建议直接删除"baseUrl": ".",保留paths即可
{ "compilerOptions": { // 删除 "baseUrl": ".", "paths": { "@/*": ["./src/*"] // 改为完整相对路径 } } }
关联阅读推荐
步骤8:最终项目结构
text
my-vue3-clean/
├── src/
│ ├── api/ # API接口
│ ├── assets/ # 静态资源
│ │ └── vue.svg # 默认logo(可以删除)
│ ├── components/ # 公共组件(空目录)
│ ├── layouts/ # 布局组件
│ ├── router/ # 路由配置
│ ├── stores/ # Pinia状态管理
│ │ └── index.ts # 示例store
│ ├── types/ # TypeScript类型定义
│ │ └── global.d.ts
│ ├── utils/ # 工具函数
│ ├── views/ # 页面组件
│ ├── App.vue # 根组件(已清空)
│ ├── main.ts # 入口文件(已简化)
│ ├── style.css # 全局样式(已重置)
│ └── vite-env.d.ts # Vite类型声明
├── index.html
├── package.json
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── .gitignore
步骤9:运行项目
# 安装依赖
pnpm install
# 启动开发服务器
pnpm run dev
示例代码未清除前

现在您拥有一个干净的Vue 3项目,没有任何示例代码,只有基础的技术栈配置(Vue 3 + Pinia + TypeScript + Element Plus)。
对这个 Vue 3 项目中 tsconfig.json 配置项的逐句解释
该配置主要用于支持 Vue 3 + TypeScript + Vite 的项目结构(比如 Vue CLI 或 Vite 创建的项目)。
一、compilerOptions(编译选项)
"target": "ES2020"
-
作用:指定 TypeScript 编译后的 JavaScript 版本目标。
-
含义:代码会被转译到 ES2020 标准。ES2020 支持
import.meta、BigInt、可选链?.、空值合并??等现代特性,Vite 开发环境下可直接运行,不需要降级到 ES5/ES2015。
"useDefineForClassFields": true
-
作用:是否使用 ECMAScript 标准定义的类字段初始化方式。
-
含义:设为
true后,类字段按标准行为([[Define]])处理,而不是旧版 TypeScript 的[[Set]]语义。Vue 3 响应式系统依赖此行为来正确处理class组件或类属性。
"module": "ESNext"
-
作用:指定生成的模块系统格式。
-
含义:使用最新的 ES 模块规范(
import/export),Vite 基于原生 ES 模块开发,因此需要保持为ESNext。
"lib": ["ES2020", "DOM", "DOM.Iterable"]
-
作用:指定 TypeScript 编译时包含的类型定义库。
-
含义:
-
ES2020:提供 ES2020 标准库的类型(如Promise、Map、Set)。 -
DOM:浏览器 DOM API 类型(document、window等)。 -
DOM.Iterable:支持遍历 DOM 集合类型(如NodeList的for...of)。
-
"skipLibCheck": true
-
作用:跳过声明文件(
.d.ts)的类型检查。 -
含义:只检查项目源码,忽略依赖库中的类型错误。可大幅提升编译速度,尤其当某些依赖的类型定义存在小问题时不影响构建。
"moduleResolution": "bundler"
-
作用:指定模块解析策略。
-
含义:
"bundler"是专为 Vite、Webpack 等打包器设计的解析模式,能正确处理无扩展名导入、条件导出(exports字段)等,适配 Vue 3 单文件组件的导入。
"allowImportingTsExtensions": true
-
作用:允许在导入路径中使用
.ts、.tsx扩展名。 -
含义:开启后可以写
import App from './App.tsx',但必须配合"noEmit": true或"emitDeclarationOnly": true使用。
"resolveJsonModule": true
-
作用:允许导入
.json文件作为模块。 -
含义:
import data from './config.json'可以直接工作,并会获得 JSON 的类型推断。
"isolatedModules": true
-
作用:强制将每个文件视为独立模块。
-
含义:这是 Vite 等基于 ES 模块的构建工具的要求。防止使用无法被单文件转译器(如
esbuild)正确处理的 TypeScript 语法(如常量枚举、命名空间等)。
"noEmit": true
-
作用:不生成编译输出文件(
.js、.d.ts等)。 -
含义:因为 Vite 或 Vue CLI 内部会使用
esbuild或rollup处理代码转换,TypeScript 只用来进行类型检查,不负责生成输出文件。
"jsx": "preserve"
-
作用:控制 JSX 语法的处理方式。
-
含义:
"preserve"表示保留 JSX 语法,不进行转换,交给后续的构建工具(如 Vite + Vue 插件)处理。Vue 3 支持 JSX/TSX,但通过@vitejs/plugin-vue-jsx插件转换。
"strict": true
-
作用:启用所有严格类型检查选项。
-
含义:包括
noImplicitAny、strictNullChecks、strictFunctionTypes等,大幅提升类型安全性,Vue 3 + TS 项目强烈建议开启。
"noUnusedLocals": true
-
作用:报告未使用的局部变量。
-
含义:如果一个变量声明了但没有被读取,TypeScript 会报错,帮助保持代码整洁。
"noUnusedParameters": true
-
作用:报告未使用的函数参数。
-
含义:如果一个函数参数声明了但未被使用,会报错(但参数以下划线
_arg开头可忽略)。
"noFallthroughCasesInSwitch": true
-
作用:防止
switch语句中的“穿透”行为(case 之间没有break/return)。 -
含义:如果没有
break或return显式结束,会报错,避免逻辑错误。
"baseUrl": "."
-
作用:设置非相对模块导入的基目录。
-
含义:设置为项目根目录,用于配合
paths解析路径别名。
"paths": { "@/*": ["src/*"] }
-
作用:路径别名映射。
-
含义:
@/components/Button会被映射到<项目根>/src/components/Button,使导入路径更简洁。需要构建工具(如 Vite)配合配置相同的别名。
二、顶层配置项
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
-
作用:指定需要 TypeScript 处理的文件范围。
-
含义:
-
src/**/*.ts:所有.ts文件 -
src/**/*.d.ts:类型声明文件 -
src/**/*.tsx:TSX 组件文件 -
src/**/*.vue:Vue 单文件组件(通过vue的类型支持)
-
"references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }]
-
作用:TypeScript 项目引用(Project References)。
-
含义:这个
tsconfig.json是一个“根配置”,引用了两个子项目:-
tsconfig.app.json:通常用于编译前端应用源码(src/目录) -
tsconfig.node.json:通常用于编译 Node.js 环境的配置脚本(如vite.config.ts)
-
这样可以:
-
分别配置不同环境的编译选项(如
target、module等不同) -
提升类型检查和编译效率
总结
这个配置是一个典型的 Vue 3 + Vite + TypeScript 项目的 根 tsconfig.json,主要负责:
-
配置现代语法支持(ES2020、ESNext)
-
与 Vite 的构建流程兼容(
isolatedModules、noEmit、moduleResolution: bundler) -
严格类型检查(
strict、noUnusedLocals等) -
路径别名
@/*指向src/* -
使用项目引用拆分应用代码与 Node 工具代码
通常配合 tsconfig.app.json(前端源码)和 tsconfig.node.json(Node 脚本)共同工作。
找不到“element-plus/dist/index.css”的副作用导入的模块或类型声明。
这个错误表示 TypeScript 无法识别 CSS 文件的导入类型。这是因为 TypeScript 默认只能处理 .ts/.js 等代码文件,不支持直接导入 CSS。
解决方案:添加类型声明(推荐)
创建或修改 src/vite-env.d.ts 文件:
typescript
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
// 支持 CSS 导入
declare module '*.css' {
const css: string
export default css
}
declare module '*.scss' {
const content: string
export default content
}
// 针对 Element Plus
declare module 'element-plus/dist/index.css' {
const css: string
export default css
}
检查 tsconfig.json 配置
确保 tsconfig.json 包含了正确的类型配置:
json
{
"compilerOptions": {
"types": ["vite/client"], // 确保有这一行
"skipLibCheck": true, // 可选,跳过库类型检查
// ...其他配置
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}
为什么会出现这个问题?
-
TypeScript 默认只理解
.ts、.tsx、.d.ts文件 -
导入
.css文件时,TypeScript 不知道它的类型 -
Vite 运行时能正确处理 CSS,但编译时的类型检查会报错
验证修复
创建声明文件后:
-
重启 TypeScript 服务器(VS Code 中按
Ctrl+Shift+P→ "TypeScript: Restart TS server") -
检查编辑器中的红色波浪线是否消失
这个修复不会影响运行时的功能,只是让 TypeScript 理解 CSS 导入的语法。推荐使用创建 vite-env.d.ts 文件的方式,这是 Vite 项目的标准做法。
启动项目,为什么有的是pnpm dev,有的是pnpm run dev
简单直接的回答:
-
pnpm dev是 简写形式,只有当package.json中有一个名为dev的脚本时才能用。 -
pnpm run dev是 完整形式,明确告诉 pnpm 去执行scripts块里的dev命令。
1. 为什么两种写法都能用?
pnpm(以及 npm、yarn)的设计是:
-
对于
predefined(预定义)或常见的生命周期脚本,如start、stop、test,可以省略run:pnpm start -
对于 自定义脚本(比如大家常用的
dev、build、lint),理论上需要pnpm run dev。
但是,现在绝大多数现代前端工具链(Vite、Create Vue App、Nuxt 等)生成的 package.json 里,默认脚本就叫 dev,而 pnpm 为了便捷性,允许省略 run 来执行任意 scripts 中的命令。所以两者完全等价。
2. 在哪里设置/定义的?
唯一设置的地方就是 package.json 文件中的 "scripts" 字段。
json
{
"name": "my-vue-project",
"scripts": {
"dev": "vite", // ← 这里定义了 dev 命令
"build": "vue-tsc && vite build",
"preview": "vite preview"
}
}
-
当你运行
pnpm dev或pnpm run dev→ 实际执行vite -
当你运行
pnpm build→ 实际执行vue-tsc && vite build
3. 为什么不同项目写法不一样?
这不是“设置”不同,而是文档/教程/团队习惯不同。有些人习惯写全 run(更明确),有些人省略(更简洁)。两种在任何 Vue 3 项目里都能用。
4. 什么时候必须用 pnpm run?
当你执行的脚本名 不是顶级命令,或者需要传递参数时,有时需要显式写 run,但现代 pnpm 版本中,以下情况也基本都能省略:
bash
# 如果需要给脚本传参数,建议写全(但省略也行)
pnpm run dev -- --port 3000
# 或简写(可能被解析为传给 pnpm 本身,而非脚本)
pnpm dev -- --port 3000 # 大部分情况也能工作
唯一必须写 run 的场景:当你有一个与 pnpm 内置命令重名的脚本,例如你定义了一个 install 脚本,则必须 pnpm run install,否则 pnpm 会执行包安装命令。
总结
| 写法 | 适用性 | 是否必须 |
|---|---|---|
pnpm dev | 任何 scripts 中的自定义命令 | 推荐简写 |
pnpm run dev | 同上 | 更显式,适合脚本新手 |
没有任何地方“设置”哪种写法,只是你看到的项目示例或教程作者的个人风格。
你在自己的项目里可以随意混用,效果完全相同。
&spm=1001.2101.3001.5002&articleId=159805417&d=1&t=3&u=06c637760f78405cba09d99c6668c421)
1181

被折叠的 条评论
为什么被折叠?



