前置声明:
- Vue3 和 Vite 还在持续更新,中文文档仍未全部完善,该笔记的内容在未来可能过时,建议多参考英文文档,使用最新版本。
- 本案例使用的后端 API 服务,基于 Express 搭建,使用 json 文件管理数据,Git 仓库和使用文档。
- 本案例旨在学习 Vite 和 Vue3 搭建配置应用,并没有开发完整的业务功能。
Vite 官方中文文档 (vitejs.dev)
官方声明:Vite 需要 Node.js 版本 >= 12.0.0。然而,有些模板需要依赖更高的 Node 版本才能正常运行,当你的包管理器发出警告时,请注意升级你的 Node 版本。
本例使用时 Node.js 12 版本执行 build 命令报错,于是改用 Node.js 16 版本,切换版本记得重新
npm install
避免有依赖没有更新。
# 创建
npm init vite@latest
√ Project name: ... shop-admin # 项目名称
√ Select a framework: » vue # 选择框架
√ Select a variant: » vue-ts # 选择 vue 或 vue-ts
cd ./shop-admin
git init
npm install
npm run dev
访问 http://localhost:3000/
├─ public # 存放不需要编译构建的静态资源
│ └─ favicon.ico
├─ src # 存放需要编译构建的文件
│ ├─ assets
│ │ └─ logo.png # 需要编译构建的静态资源
│ ├─ components
│ │ └─ HelloWorld.vue
│ ├─ App.vue
│ ├─ env.d.ts # ts 类型声明
│ └─ main.ts # 启动入口文件
├─ .gitignore # git 忽略文件
├─ index.html # 单页文件的模板文件
├─ package-lock.json
├─ package.json
├─ README.md
├─ tsconfig.json # ts 配置文件
├─ tsconfig.node.json
└─ vite.config.ts # vite 配置文件
package.json
:
{
"scripts": {
// 启动开发服务器
"dev": "vite",
// 构建生产环境产物:校验 ts 类型,通过后执行 vite 打包命令
"build": "vue-tsc --noEmit && vite build",
// 本地预览生产构建产物:以前需要将打包文件配置到 nginx 等服务器中才能预览,vite 简化了这个流程
"preview": "vite preview"
},
}
本笔记编写时,运行 npm run build
会报大量错误:
Cannot access ambient const enums when the '--isolatedModules' flag is provided.
原因是 Vite 官方建议配置 TypeScript 的编译器选项:isolatedModules 为 true
。
{
"compilerOptions": {
"target": "esnext",
"useDefineForClassFields": true,
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
// "isolatedModules": true,
"esModuleInterop": true,
"lib": ["esnext", "dom"]
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}
由于这个导致的编译报错,Issue 并没有得到解决,暂时只能先将其设置为 false
或删除这个选项。
在 src
目录下添加一些文件夹:
├─ api # API 接口封装
├─ styles # 全局样式
├─ utils # 工具模块
├─ plugins # 插件
├─ views # 路由页面
├─ router # 路由模块
├─ store # vuex 容器模块
├─ layout # 公共布局组件
└─ composables # 项目中提取出来的组合式 API 函数模块
在 Vite 创建的项目中默认没有集成 ESLint,并且目前官方也没有任何和 ESLint 相关的内容,所以需要手动集成配置 ESLint。
# 安装 eslint(当前版本 8.5.0)
npm i eslint -D
# 初始化 eslint 配置文件
npm init @eslint/config
# 如何使用 ESLint
? How would you like to use ESLint? ...
# 检查语法 找到问题 强制代码规范
> To check syntax, find problems, and enforce code style
# 项目中使用的 JS 模块规范
√ What type of modules does your project use? · esm
# 前端框架
√ Which framework does your project use? · vue
# 是否使用 TS
√ Does your project use TypeScript? · No / Yes
# 代码运行环境
√ Where does your code run? · browser
# 代码规范
? How would you like to define a style for your project? ...
# 使用一个流行的代码规范
> Use a popular style guide
> Standard: https://github.com/standard/standard
# 配置文件生成 js 类型的文件
√ What format do you want your config file to be in? · JavaScript
# ...
生成的 eslint 配置文件:
// .eslintrc.js
module.exports = {
env: {
browser: true,
es2021: true
},
extends: [
'plugin:vue/essential',
'standard'
],
parserOptions: {
ecmaVersion: 'latest',
parser: '@typescript-eslint/parser',
sourceType: 'module'
},
plugins: [
'vue',
'@typescript-eslint'
],
rules: {
}
}
本人基于个人习惯,修改了两条规则:
rules: {
// 要求或禁止函数圆括号之前有一个空格
'space-before-function-paren': [2, {
anonymous: 'always',
named: 'never',
asyncArrow: 'always'
}],
// 要求组件名(文件名)必须是多单词的
'vue/multi-word-component-names': 0
}
package.json
中添加验证脚本:
"scripts": {
...
// 检查代码和自动修复
"lint": "eslint src/**/*.{js,jsx,vue,ts,tsx} --fix"
},
npm run lint
运行 eslint 验证:
# App.vue 和 components/HelloWorld.vue 验证失败,template 只能包含一个根节点
error The template root requires exactly one element vue/no-multiple-template-root
这个规则适用于 Vue 2,Vue 3 没有这个限制。查看 eslint 配置文件中使用的插件 plugin:vue/assential
,来自 eslint-plugin-vue
包,查看 eslint-plugin-vue\lib
目录(官方介绍):
├─ base.js
├─ essential.js # 当前配置的验证规则
├─ no-layout-rules.js
├─ recommended.js
├─ strongly-recommended.js
# 以上是 Vue2 的验证规则
# 以下是 Vue3 的验证规则
├─ vue3-essential.js
├─ vue3-recommended.js
└─ vue3-strongly-recommended.js
将 eslint 配置为 Vue 3 的验证规则(本文使用最严格的):
// .eslintrc.js
module.exports = {
...
extends: [
// 'plugin:vue/essential',
// 使用 vue3 规则
'plugin:vue/vue3-strongly-recommended',
'standard'
],
...
}
再次运行 npm run lint
:
# HelloWorld.vue 报错
error 'defineProps' is not defined no-undef
defineProps
和 defineEmits
是 Vue3 定义的编译宏(compiler macros),只能在 中(最外层)使用,它们不需要导入,在处理
会被编译掉。
但在 eslint 检查的时候被当作未被定义的变量报错,可以显示的在文件中导入:
import { defineProps, defineEmits } from 'vue'
或将它们声明为 eslint 检查时的全局变量:
// .eslintrc.js
module.exports = {
globals: {
defineProps: "readonly",
defineEmits: "readonly",
defineExpose: "readonly",
withDefaults: "readonly"
},
...
}
也可以使用 eslint-plugin-vue 官方解决办法(源码与上面配置全局变量一样):
// .eslintrc.js
module.exports = {
env: {
browser: true,
es2021: true,
// 添加:
'vue/setup-compiler-macros': true
},
...
}
如果 vscode 报错Environment key "vue/setup-compiler-macros" is unknown
,请检查 eslint-plugin-vue
下是否有这个规则,如果没有则可能版本太老,可以更新版本(本文安装的版本是8.5.0)。
再次运行 npm run lint
,没有报错了。
编辑器集成主要实现两个功能:
实现步骤:
1、卸载/禁用 vetur 插件(Vue2 插件)
2、安装 volar 插件(Vue Language Features - 相当于支持 Vue3 的 vetur,且支持 TypeScript 提示)
3、安装 ESLint 插件
只要安装并启用了这个插件,就会自动查找项目中的 eslint 配置规范,并给出验证提示。
同时 ESLint 插件提供了格式化工具,但是需要手动配置才可以,打开文件-首选项-设置(快捷键 Ctrl+,
),找到扩展-ESLint,勾选 Format: Enable
,启用 ESLint 格式化工具:
也可以直接修改 vscode 的 settings.json
,添加 "eslint.format.enable": true
。
然后继续配置 eslint.validate
选项指定 eslint 可以检查的文件类型(默认只检查 .js
和 .jsx
文件):
{
"eslint.format.enable": true,
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"vue"
],
...
}
接着将文件格式化工具选为 ESLint,打开要格式化的文件,右键代码视图-使用…格式化文档-配置默认格式化程序…,选择 ESLint。
配置完成后,就可以使用 Alt+Shift+F
格式化文档了。
PS:安装、启用 ESLint 插件,修改配置文件,都会重新向 IDE 注册,可能会导致延迟甚至不显示,最好改动之后重载一下:
Ctrl+P
,键入>reload
配置 pre-commit 钩子,将 lint 命令加入到开发构建流程,在 git 提交之前执行 lint 验证,防止不符合规范的代码提交到 git 仓库。
使用工具:okonet/lint-staged
npx mrm@2 lint-staged
# 会安装两个 npm 包
# husky - 提供 git 钩子功能,拦截 git 命令
# lint-staged - 获取 git 暂存区中的代码进行lint验证
执行完成后,会修改 package.json
:
1、安装了两个 npm 包
2、添加一个脚本
安装依赖后执行 prepare,执行 huskey 命令,安装钩子,确保每个 clone 项目的人一旦安装依赖就会将 husky 钩子初始化到本地,从而保证每个人在提交 git 之前都能执行 lint 验证。
npx mrm@2 lint-staged
执行完成后也会执行husky install
,所以要在.git
所在目录下执行,否则会报错找不到.git
。
husky install
会初始化 husky 的钩子:
- 通过配置
.git/config
中的hooksPath
修改 hooks 目录(默认.git/hooks
)- 在项目根目录下创建
.husky
文件夹,用于存放自定义钩子执行文件
"scripts": {
...
"prepare": "husky install"
},
3、添加了 lint-staged 配置
可以在此基础上修改,自定义自己的 lint 命令:
"lint-staged": {
// 执行 eslint 命令验证 js 文件
"*.js": "eslint --cache --fix"
}
修改为:
"lint-staged": {
// 提交指定文件时执行 lint 脚本进行验证,如果有 fix 修复的内容会自动 git add
"*.{js,jsx,vue,ts,tsx}": [
"npm run lint"
]
}
现在执行 git commit
命令时,会先进行 lint 检查暂存区的代码,如果有代码不符合规范,则不会执行 git commit
。
注意:保证团队都会执行 pre-commit 钩子的前提是:
- 执行
husky install
初始化.husky
文件夹和配置 git hooks 目录地址- 项目包含
.husky/pre-commit
钩子执行文件,所以要确保这个文件会提交到 git 仓库
其实就是将 ESLint 集成到 vite 开发的编译构建过程中,以提供实时的 ESLint 验证。
当前 Vite 还没有提供 ESLint 相关的插件,可以自己开发一个插件,也可以使用别人开发好的。
官网导航 Links - Awesome Vite 列出了一些 Vite 相关的优质资源,推荐使用 gxmari007/vite-plugin-eslint
# 安装
npm install vite-plugin-eslint --save-dev
vite 配置文件中加载插件:
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import eslintPlugin from 'vite-plugin-eslint'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
eslintPlugin({
/* 配置选项 */
// 禁用 eslint 缓存
cache: false
})
]
})
cache
缓存功能建议关掉,因为有时将验证失败的代码修复后,eslint 可能仍会读取缓存中的结果,而且 eslint 只会验证当前修改的文件,而不是全部文件,所以不使用缓存影响不大。
重新启动 npm run dev
。
现在开发阶段的编译构建代码时,命令行工具和页面都会验证并提示失败信息。而且在构建生产环境时(npm run build
)也会验证。
参考:Commit message 和 Change log 编写指南 - 阮一峰
Git 每次提交代码,都要写 Commit message 说明本次提交的具体含义。
git commit -m 'hello world'
Git 本身并没有要求 Commit message 的格式,如果随意编写,当有一天你需要在历史记录中通过 Commit message 检索历史中的一次提交记录,就会很麻烦。
统一团队 Git Commit message 标准,便于后续代码 review,版本发布以及日志自动化生成等等,详细参阅阮一峰的文章。
目前最流行的是 Angular提交规范
相关工具:
# windows 下安装 cli 和 常用规范
npm install --save-dev @commitlint/config-conventional @commitlint/cli
# 配置 commitlint 使用常用规范
# 建议手动创建 commitlint.config.js 文件并填充内容,命令可能创建的并不是 utf8 编码的文件,eslint 会报错 `Parsing error: Invalid character`
echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js
# 安装和初始化 husky(上面配置 eslint 校验钩子时已经完成)
# npm i husky -D
# npx husky install
# 创建 commit-msg 钩子
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'
如果使用命令无法创建 ./husky/commit-msg
文件,可以手动创建并填充以下内容:
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no -- commitlint --edit $1
执行提交命令测试:
git add .
git commit -m 'commitlint 配置巴拉巴拉巴拉'
验证失败:
规范 message:
git commit -m 'chore: commitlint 配置巴拉巴拉巴拉'
验证成功:
Vite 在创建项目时已经配置好了 TypeScript 环境,详细参考官方文档。
下面列出需要注意的几点。
Vite 仅执行 .ts
文件的转译工作,并 不 执行任何类型检查。
let count: number = 100
count = 200
// 不会报错
count = 'hello'
dev
开发阶段,Vite 假设 IDE 配置了类型检查功能,Vite 本身不负责这个任务。build
构建阶段,会通过 vue-tsc --noEmit
命令进行类型检查由于官方 vue-tsc(使用 esbuild 将 TypeScript 转译到 JavaScript)还不支持监听功能,所以暂时没有很好的插件支持开发阶段进行实时类型验证。
Vite 创建项目时会生成一个 src/env.d.ts
类型声明文件(之前的版本是两个文件 shimes-vue.d.ts
和 vite-env.d.ts
):
///
declare module '*.vue' {
import type { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
}
第一行用于添加客户端代码环境中的类型声明,为了保险起见,建议按照官方说明,将 vite/client
添加到 tsconfig
中的 compilerOptions.types
下:
{
"compilerOptions": {
"types": ["vite/client"]
}
}
后面的内容是为 .vue
资源文件添加类型声明,因为 TypeScript 默认不识别 .vue
文件,所以在使用 TypeScript 的项目中导入 Vue 组件,必须在后面加上 .vue
后缀。
Vue 官方文档《搭配 TypeScript 使用 Vue》中介绍了如何将 TypeScript 集成到项目中、推荐配置等一些 Vite 创建项目时已经完成的内容,下面提取几个关于 TS 使用的部分。
1、在单文件组件中使用 TypeScript,需要在 标签上加上
lang="ts"
属性。如果使用 JSX 则添加 lang="tsx"
。
2、为了让 TypeScript 正确地推导出组件选项内的类型,我们需要通过 defineComponent()
这个全局 API 来定义组件。
import { defineComponent } from 'vue'
export default defineComponent({
// 启用了类型推导
props: {
name: String,
msg: { type: String, required: true }
},
data() {
return {
count: 1
}
},
mounted() {
this.name // 类型:string | undefined
this.msg // 类型:string
this.count // 类型:number
}
})
3、当没有结合 使用组合式 API 时,
defineComponent()
也支持对传递给 setup()
的 prop 的推导
import { defineComponent } from 'vue'
export default defineComponent({
// 启用了类型推导
props: {
message: String
},
// 不需要声明 props 参数的类型
setup(props) {
props.message // 类型:string | undefined
}
})
4、Vue3 提供 PropType
辅助工具用来标记更复杂的 prop 类型
import { defineComponent, PropType } from 'vue'
interface Book {
title: string
author: string
year: number
}
export default defineComponent({
props: {
book: {
// 提供相对 `Object` 更确定的类型
type: Object as PropType<Book>,
required: true
},
// 也可以标记函数
callback: Function as PropType<(id: number) => void>
},
mounted() {
this.book.title // string
this.book.year // number
// TS Error: argument of type 'string' is not
// assignable to parameter of type 'number'
this.callback?.('123')
}
})
5、ref 会根据初始化时的值推导其类型,也可以手动传入类型。
<template>
<h1 ref="title">
{{ msg }}
h1>
<HelloWorld
ref="hellowWorld"
msg="Hello Vue 3 + TypeScript + Vite"
/>
template>
<script lang="ts">
import { defineComponent, onMounted, ref } from 'vue'
import HelloWorld from '../components/HelloWorld.vue'
export default defineComponent({
components: { HelloWorld },
setup () {
// 自动推断
const msg = ref('Hello H1')
// 指定 HTML 元素类型
const title = ref<HTMLHeadElement>()
// 指定实例类型
const hellowWorld = ref<InstanceType<typeof HelloWorld>>()
onMounted(() => {
console.log(title.value?.innerHTML)
console.log(hellowWorld.value?.$props.msg)
})
return {
msg,
title,
hellowWorld
}
}
})
script>
6、reactive 与 ref 一样,computed 计算属性也会自动推断类型。
7、原生 DOM 事件处理函数建议为 event 添加类型标注
<script setup lang="ts">
function handleChange(event: Event) {
console.log((event.target as HTMLInputElement).value)
}
script>
<template>
<input type="text" @change="handleChange" />
template>
语法Vue 3 支持三种写法编写组件的逻辑: