Vite 项目搭建(vue-ts + eslint + prettier + husky)

712 阅读6分钟

初始化项目

# 初始化
yarn create vite my-vite-app --template vue-ts
# 进入项目根目录
cd my-vite-app
# 安装依赖
yarn

集成eslint

# 安装eslint
yarn add eslint -D
# 初始化eslint
npx eslint --init

11.png
最后一步会提示是否用npm安装eslint-plugin-vue@latest @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest这几个依赖,选择否,后面我们用yarn 手动安装即可。

22.png

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.jsextends的最后添加一个配置:

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)
  })
})