初始化项目
# 初始化
yarn create vite my-vite-app --template vue-ts
# 进入项目根目录
cd my-vite-app
# 安装依赖
yarn
集成eslint
# 安装eslint
yarn add eslint -D
# 初始化eslint
npx eslint --init
最后一步会提示是否用npm安装eslint-plugin-vue@latest @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest这几个依赖,选择否,后面我们用yarn 手动安装即可。
yarn add eslint-plugin-vue@latest @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest -D
.eslintrc.js
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
'vue/setup-compiler-macros': true
},
extends: [
'eslint:recommended',
'plugin:vue/vue3-recommended',
'plugin:@typescript-eslint/recommended'
],
parser: 'vue-eslint-parser',
parserOptions: {
ecmaVersion: 'latest',
parser: '@typescript-eslint/parser',
sourceType: 'module'
},
plugins: ['vue', '@typescript-eslint'],
rules: {}
}
在package.json中添加脚本
"lint": "eslint . --ext .vue,.js,.ts,.jsx,.tsx --fix"
集成prettier
# 安装 prettier
yarn add prettier -D
根目录下新增文件.prettierrc.js
module.exports = {
eslintIntegration: true,
printWidth: 100, // 每行代码长度(默认80)
tabWidth: 2, // 每个tab相当于多少个空格(默认2)
useTabs: false, // 是否使用tab进行缩进(默认false)
singleQuote: true, // 使用单引号(默认false)
semi: false, // 声明结尾使用分号(默认true)
trailingComma: 'none', // 多行使用拖尾逗号(默认none)
bracketSpacing: true, // 对象字面量的大括号间使用空格(默认true)
jsxBracketSameLine: false, // 多行JSX中的>放置在最后一行的结尾,而不是另起一行(默认false)
arrowParens: 'avoid', // 只有一个参数的箭头函数的参数是否带圆括号(默认avoid)
proseWrap: 'always' // 超出print width(上面有这个参数)时就折行
}
在package.json中添加脚本
"format": "prettier --write ./**/*.{vue,ts,tsx,js,jsx,css,less,scss,json}"
vscode 中设置自动保存格式化,在 .vscode/settings.json文件中添加如下规则。
{
"editor.formatOnSave": true, // 开启自动保存
"editor.defaultFormatter": "esbenp.prettier-vscode", // 默认格式化工具选择prettier
}
解决eslint和prettier冲突问题
yarn add eslint-config-prettier eslint-plugin-prettier -D
在 .eslintrc.js中extends的最后添加一个配置:
extends: [
'eslint:recommended',
'plugin:vue/vue3-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended'
]
配置husty + lint-staged
安装依赖
yarn add husky lint-staged -D
在package.json新增脚本
"prepare": "husky install"
prepare会在yarn自动执行。由于我们已经运行过 yarn 了,所以我们需要手动运行一次yarn prepare,然后我们就会得到一个目录.husky
为git仓库添加一个pre-commit钩子
npx husky add .husky/pre-commit "npx --no-install lint-staged"
在package.json添加
"lint-staged": {
"*.{js,vue,ts,jsx,tsx}": [
"prettier --write",
"eslint --fix"
],
"*.{html,css,less,scss,md}": [
"prettier --write"
]
}
这样我们后续提交到暂存区的代码也就会被eslint+prettier格式化和检查
配置commitlint
安装依赖
#!/bin/bash
yarn add @commitlint/cli @commitlint/config-conventional -D
为git仓库添加一个commit-msg钩子
#!/bin/bash
npx husky add .husky/commit-msg "npx --no-install commitlint --edit"
根目录下新增commitlint.config.js
const types = [
'feat',
'fix',
'docs',
'style',
'refactor',
'perf',
'test',
'build',
'release',
'chore',
'revert'
]
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-empty': [2, 'never'],
'type-enum': [2, 'always', types],
'scope-case': [0, 'always'],
'subject-empty': [2, 'never'],
'subject-case': [0, 'never'],
'header-max-length': [2, 'always', 88]
}
}
集成stylelint
安装依赖
yarn add stylelint stylelint-config-prettier stylelint-config-standard stylelint-order stylelint-scss -D
新建.stylelintrc.js
module.exports = {
extends: ['stylelint-config-standard', 'stylelint-config-rational-order', 'stylelint-prettier/recommended'],
rules: {
// 'prettier/prettier': [true, { singleQuote: false }],
// at-rule-no-unknown: 屏蔽一些scss等语法检查
'at-rule-no-unknown': [true, { ignoreAtRules: ['mixin', 'extend', 'content'] }], // 禁止使用未知的 at 规则
'rule-empty-line-before': [
// 要求或禁止在规则声明之前有空行
'always-multi-line',
{
except: ['first-nested'],
ignore: ['after-comment'],
},
],
'at-rule-empty-line-before': [
// 要求或禁止在 at 规则之前有空行
'always',
{
except: ['blockless-after-same-name-blockless', 'first-nested'],
ignore: ['after-comment'],
},
],
'comment-empty-line-before': [
// 要求或禁止在注释之前有空行
'always',
{
except: ['first-nested'],
ignore: ['stylelint-commands'],
},
],
'block-no-empty': true, // 禁止出现空块
'declaration-empty-line-before': 'never', // 要求或禁止在声明语句之前有空行。
'declaration-block-no-duplicate-properties': true, // 在声明的块中中禁止出现重复的属性
'declaration-block-no-redundant-longhand-properties': true, // 禁止使用可以缩写却不缩写的属性。
'shorthand-property-no-redundant-values': true, // 禁止在简写属性中使用冗余值。
'function-url-quotes': 'always', // 要求或禁止 url 使用引号。
'color-hex-length': 'short', // 指定十六进制颜色是否使用缩写
'color-named': 'never', // 要求 (可能的情况下) 或 禁止使用命名的颜色
'comment-no-empty': true, // 禁止空注释
'font-family-name-quotes': 'always-unless-keyword', // 指定字体名称是否需要使用引号引起来 | 期待每一个不是关键字的字体名都使用引号引起来
'font-weight-notation': 'numeric', // 要求使用数字或命名的 (可能的情况下) font-weight 值
'property-no-vendor-prefix': true, // 禁止属性使用浏览器引擎前缀
'value-no-vendor-prefix': true, // 禁止给值添加浏览器引擎前缀
'selector-no-vendor-prefix': true, // 禁止使用浏览器引擎前缀
'no-descending-specificity': null, // 禁止低优先级的选择器出现在高优先级的选择器之后
},
};
使用别名@
- vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
resolve: {
alias: {
'@': '/src/'
}
},
plugins: [vue()]
})
- 根目录新增paths.json
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
}
}
- tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"useDefineForClassFields": true,
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"lib": ["esnext", "dom"]
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"extends": "./paths.json"
}
Pinia
- 安装依赖
yarn add pinia
- 创建store src/store/index.ts
import { createPinia } from 'pinia'
const store = createPinia()
export default store
- main.ts引入
// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import store from './store'
const app = createApp(App)
app.use(store)
- 新增一个store.ts
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => {
return {
id: '',
name: ''
}
},
actions: {
setId(id: string) {
this.id = id
}
}
})
- 组件中使用
<script setup lang="ts">
import { useUserStore } from '../store/user'
defineProps<{ msg: string }>()
const userStore = useUserStore()
const handleSet = () => {
userStore.setId(Math.floor(Math.random() * 1000) + '')
}
</script>
<template>
<h1>{{ msg }}</h1>
<h2>{{ userStore.id }}</h2>
<button @click="handleSet">设置</button>
</template>
<style scoped></style>
使用tsx
- 安装依赖
yarn add @vitejs/plugin-vue-jsx -D
- vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from "@vitejs/plugin-vue-jsx"
export default defineConfig({
resolve: {
alias: {
'@': '/src/'
}
},
plugins: [
vue(),
vueJsx()
]
})
- 编写tsx组件 Test1.tsx
import { defineComponent } from 'vue'
export default defineComponent({
name: 'test1',
props: {
msg: {
type: String
}
},
setup(props: any) {
return () => <div>{props.msg}</div>
}
})
- 自定义vuetsx代码片段 shift + ctrl(command) + p,打开搜索栏,输入snippets(英文意思为片段),找到Preferences:Configure User Snippets,选择New Global Snippet file 输入vuetsx,粘贴下面json内容
{
"Print to console": {
"prefix": "vuetsx",
"body": [
"import { defineComponent } from 'vue'\n",
"export default defineComponent({",
" props: {},",
" emits: [],",
" components: {},",
" setup(props, ctx) {",
" return () => <div></div>",
" }",
"})",
],
"description": "Create vue3 tsx template"
}
}
vue3ts
{
"Print to console": {
"prefix": "vuets",
"body": [
"<script lang=\"ts\">",
"import { defineComponent } from 'vue'",
"export default defineComponent({",
"})",
"</script>",
"<template>",
" <div></div>",
"</template>",
"<style lang=\"scss\" scoped></style>"
],
"description": "Create vue3 tsx template"
}
}
tsvue3
{
"Print to console": {
"prefix": "tsvue3",
"body": [
"<script setup lang=\"ts\">",
"</script>",
"<template>",
" <div></div>",
"</template>",
"<style lang=\"scss\" scoped>",
"</style>",
],
"description": "Create vue3 tsx template"
}
}
less/sass
- 安装依赖
yarn add less less-loader -D
yarn add sass
vue-router
- 安装依赖
yarn add vue-router
- router/index.ts
import type { RouteRecordRaw } from 'vue-router'
import { createRouter, createWebHashHistory } from 'vue-router'
export const constantRoutes: Array<RouteRecordRaw> = [
{
path: '/users',
name: 'Users',
component: () => import('@/views/demo/users/index.vue')
},
{
path: '/orders',
name: 'Orders',
component: () => import('@/views/demo/orders/index.vue')
}
]
const router = createRouter({
history: createWebHashHistory(),
routes: constantRoutes
})
export default router
- main.ts
import { createApp } from 'vue'
import App from './App.vue'
import store from './store'
import router from './router'
createApp(App).use(store).use(router).mount('#app')
- App.vue
<template>
<p>
<router-link to="/users">用户</router-link>
<router-link to="/orders">订单</router-link>
</p>
<router-view />
</template>
配置env
- 在项目根目录新建 .env.development、.env.production、.env.test
NODE_ENV=development
VITE_APP_API_BASE_URL = https://api.xxx.xxx
- 创建 env.d.ts
interface ImportMetaEnv {
VITE_APP_API_BASE_URL: string
VITE_APP_CUSTOMER_ID: string
VITE_APP_TITLE: string
VITE_APP_VERSION: string
}
- 组件中使用
import.meta.VITE_APP_API_BASE_URL
- vite.config.ts使用
import { defineConfig, loadEnv } from 'vite'
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, __dirname)
return {
plugins[],
base: env.VITE_APP_API_BASE_URL
}
})
- package.json
"scripts": {
"test": "vite build --mode test",
}
单元测试jest
- 安装依赖
yarn add -D jest @types/jest
yarn add -D babel-jest @babel/preset-env
yarn add -D @babel/preset-typescript
- jest.config.js
module.exports = {
transform: {
'^.+\\.(ts|tsx|js|jsx)$': [
'babel-jest', {
presets: [
'@babel/preset-env',
'@babel/preset-typescript'
]
}
]
}
}
- 添加脚本命令 package.json
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"test": "jest" // 新增
- 编写第一个单元测试
describe('stringUtil', () => {
it('isTelephone', () => {
expect(1 + 1).toBe(2)
})
})