01 - 项目初始化
通过如下命令创建项目
npm init vite@latest
# or
yarn create vite
# or
pnpm create vite
得到如下的 package.json 和 .eslintrc.cjs 文件
文件:package.json
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"preview": "vite preview --port 5050",
"test:unit": "vitest --environment jsdom",
"test:e2e": "start-server-and-test preview http://127.0.0.1:5050/ 'cypress open'",
"test:e2e:ci": "start-server-and-test preview http://127.0.0.1:5050/ 'cypress run'",
"typecheck": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
},
"dependencies": {
"pinia": "^2.0.13",
"vue": "^3.2.33",
"vue-router": "^4.0.14"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.1.0",
"@types/jsdom": "^16.2.14",
"@types/node": "^16.11.27",
"@vitejs/plugin-vue": "^2.3.1",
"@vitejs/plugin-vue-jsx": "^1.3.10",
"@vue/eslint-config-prettier": "^7.0.0",
"@vue/eslint-config-typescript": "^10.0.0",
"@vue/test-utils": "^2.0.0-rc.20",
"@vue/tsconfig": "^0.1.3",
"cypress": "^9.5.4",
"eslint": "^8.5.0",
"eslint-plugin-cypress": "^2.12.1",
"eslint-plugin-vue": "^8.2.0",
"jsdom": "^19.0.0",
"prettier": "^2.5.1",
"start-server-and-test": "^1.14.0",
"typescript": "~4.6.3",
"vite": "^2.9.5",
"vitest": "^0.9.3",
"vue-tsc": "^0.34.7"
}
文件:.eslintrc.cjs
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = defineConfig({
env: {
browser: true,
'vue/setup-compiler-macros': true
},
extends: [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-typescript/recommended',
'@vue/eslint-config-prettier'
],
rules: {
'node/no-extraneous-require': 'off',
'node/no-missing-import': 'off'
},
overrides: [
{
files: ['cypress/integration/**.spec.{js,ts,jsx,tsx}'],
extends: ['plugin:cypress/recommended']
}
]
})
按正常来说,此时编写项目代码应该不会有太多问题。
由于我个人倾向于使用 SFC 方式进行编写,那么 Vue 的一些 API 使用就需要自己手动引入了。
// 文件:componentA.vue
import { ref, onMounted, computed, provide, useSlots, ... } from 'vue'
如果再来有使用到 vue-router、pinia 这些生态包的话那么就有可能需要手动引入更多。
// 文件:componentA.vue
import { ref, onMounted, computed, provide, useSlots, ... } from 'vue'
import { useRouter, useRoute, ... } from 'vue-router'
import { storeToRefs, mapActions, mapGetters, mapStores, ... } from 'pinia'
这样的操作对于少量的组件倒是还行 CV 大法加加减减完事。但如果是要在一个有规划的项目里这样的东西多了,就会显得比较繁琐杂乱。不利于规范的建立。
02 - 自动导入
如何能够解决这样的场景那就成为了一个痛点。
好在 @Anthony Fu 开源了一个支持了 Vite 的插件 unplugin-auto-import ,这个插件能够按需自动导入所需的包。
将 unplugin-auto-import 加入到项目之后,修改 vite.config.ts 文件,新增如下内容。
import ...
+ import AutoImport from 'unplugin-auto-import/vite'
...
export default defineConfig({
...,
plugins: [
...,
+ AutoImport({imports: ['vue', 'vue-router', 'pinia']})
]
})
修改完成后,如果不是使用了 TypeScript 的话,那么此时就可以将组件文件里关于 vue、vue-router、pinia 相关的 import 都可以删除了。
- import { ref, onMounted, computed, provide, useSlots, ... } from 'vue'
- import { useRouter, useRoute, ... } from 'vue-router'
- import { storeToRefs, mapActions, mapGetters, mapStores, ... } from 'pinia'
+
但由于我这里的项目是使用了 TypeScript 所以这时候删除的话,VS Code 还是会提示 eslint的错误:ref is not defined.。
根据 [unplugin-auto-import 文档](https://github.com/antfu/unplugin-auto-import#readme) 说明,插件也是支持了 TypeScript 的 d.ts 类型描述文件的生成,vite.config.ts 里改动如下。
import ...
import AutoImport from 'unplugin-auto-import/vite'
...
export default defineConfig({
...,
plugins: [
...,
- AutoImport({ imports: ['vue', 'vue-router', 'pinia'] }),
+ AutoImport({
+ imports: ['vue', 'vue-router', 'pinia'],
+ dts: 'types/auto-imports.d.ts',
+ eslintrc: {
+ enabled: false,
+ filepath: 'types/.eslintrc-auto-import.json',
+ globalsPropValue: true,
+ },
+ }),
],
})
参数解释看这里:[configuration](https://github.com/antfu/unplugin-auto-import#configuration)
新增的这些参数主要还是用于支持 TypeScript,我这里是习惯于将 *.d.ts 这类文件统一放置在 types 文件夹下。
将文件生成后,那么 types 文件夹下将会出现 auto-imports.d.ts 与 .eslintrc-auto-import.json 两个文件。这两个文件将需要分别配置到 tsconfig.json 与 .eslintrc.cjs 中。
关系如下:
-
tsconfig.json <- auto-imports.d.ts
-
.eslintrc.cjs <- .eslintrc-auto-import.json
修改 tsconfig.json
{
"include": [
...,
+ "types/auto-imports.d.ts"
]
}
修改 .eslintrc.cjs
module.exports = defineConfig({
extends: [
...,
+ './types/.eslintrc-auto-import.json'
]
})
修改完成后就能得到正确的类型提示了。
03 - 自定义类型支持
使用 TypeScript 的项目中难免会有一些自定义的类型,虽然 与文件同名.d.ts 能够解决当前文件的类型定义,但总是会有一些公共的类型需要定义,例如请求返回的自定义数据体。
假设自定义一个类型文件:types/request.d.ts
// 假设返回数据结构如下
type ResultData = {
code: number
msg: string
data: unknown
}
这时候在非当前文件中使用该类型定义的时候,则会提示:ResultData is not defined.
非 *.ts 文件是由 eslint 提示错误。
*.ts 文件是由 TS 自身提示错误。
如有多个文件中都使用了请求函数,那么就需要编写多次或者引入类型文件使用该类型,这样并不利于管理也过于散乱,也有可能修改一处,其它处都需要修改。
所以我就想着是否可以通过配置的方式实现全局使用该类型呢?
其实是可以的。
解决 *.ts 使用自定义类型报错提示
解决 *.ts 文件的报错就比较简单了。
types/request.d.ts 文件修改如下。
- type ResultData = {
+ declare type ResultData = {
code: number
msg: string
data: unknown
}
tsconfig.json 文件修改如下。
{
"include": [
...,
+ "types/request.d.ts"
]
}
修改完成后即可得到正确的类型提示,也不需要再次定义或者引入额外的类型文件。
解决非 *.ts 使用自定义类型报错提示
*.ts 文件的类型使用报错解决了,但这只是针对于 *.ts 的,而非 *.ts 的文件使用该类型还是会有 eslint 的错误提示。
新增一个 types/.eslintrc-request.json 文件用于 eslint 错误提示的解决。
{
"globals": {
"ResultData": true
}
}
.eslintrc.cjs 文件修改如下。
module.exports = defineConfig({
extends: [
...,
+ './types/.eslintrc-request.json'
]
})
文件添加完成后也可得到了正确的类型提示了。
文件:componentA.vue
04 - 组件命名
关于给组件命名的问题,Vue 3 所提供的方式有限,且也不够优雅,写起来终是感觉有点奇怪。
对于这个问题,我去看了一些开源库的应用最终目标指向为 unplugin-vue-define-options 由三咲智子开源的插件。
正常来说仅需要在 vite.config.ts 新增如下内容即可实现。
import ...
+ import DefineOptions from 'unplugin-vue-define-options/vite'
...
export default defineConfig({
...,
plugins: [
...,
+ DefineOptions()
]
})
TypeScript 项目则需要修改 tsconfig.json 文件,新增类型支持。
{
...,
"compilerOptions": {
+ "types": ["unplugin-vue-define-options/macros-global"]
}
}
使用 defineOptions 函数即可定义。
文件:componentA.vue
defineOptions({
name: 'Dialog'
})
pnpm-workspace package 分项目的问题
但由于我这使用了 pnpm-workspace,那么 A项目 的 UI 组件由 B项目 提供。
目录结构如下:
- packages
- A
- B
其关系是使用 "workspace:*" 方式绑定,而 B项目 除必要包外都由 A项目 引入。
那么这个时候在 B项目 中使用 defineOptions 就是会出现报错的:defineOptions is not defined.。
解决:在 A项目 中 tsconfig.json 配置 B项目 别名路径即可。