# Vite + Vue3 + Typescript + Pinia + Vueuse
# 一个大厂企业级前端项目
# 通过这篇文章你可以学到
- 如何使用使用 Vite 搭建项目
- 如何在 Vite 中集成
typescript
- 如何在 Vite 中集成
vue-router4
和 pinia
- 如何使用 vue3 的伴侣
vueuse
- 如何在项目中集成
eslint
和 prettier
保证代码质量 - 如何规范化
git
提交信息 - 如何为团队开发
专属的项目模板
# 环境依赖版本
- node:v14.15.4
- vite:^2.8.0
- vue:^3.2.25
- typescript:^4.5.4
- pinia:^2.0.12
- vue-router:^4.0.14
- vueuse:^8.2.0
- eslint:^8.12.0
- prettier:^2.6.1
- commitizen:^4.2.4
- husky:^7.0.4
长话短说,直接开干~
# 1. 初始化项目
# 按步骤提示初始化:
- 使用 vite-cli 命令
| |
| pnpm create vite |
| |
| |
| yarn create vite |
| |
| |
| npm init vite@latest |
- 输入项目名:
| ? Project name: vite-vue3-ts-pinia |
- 选择一个框架(vue)
| ? Select a framework: » - Use arrow-keys. Return to submit. |
| vanilla // 原生js |
| > vue // 默认就是 vue3 |
| react // react |
| preact // 轻量化react框架 |
| lit // 轻量级web组件 |
| svelte // svelte框架 |
- 使用 typescript
? Select a variant: › - Use arrow-keys. Return to submit.
vue
❯ vue-ts
- 启动项目
| cd vite-vue3-ts-pinia && pnpm install && pnpm run dev |
# 快速初始化(建议使用):
# pnpm
pnpm create vite project-name -- --template vue-ts
# yarn
yarn create vite project-name --template vue-ts
# npm 6.x
npm init vite@latest project-name --template vue-ts
# npm 7+, 需要额外的双横线:
npm init vite@latest project-name -- --template vue-ts
# 集成配置
- 为保证 node 的使用
| pnpm i @types/node --save-dev |
- 修改
tsconfig.json
| { |
| "compilerOptions": { |
| "typeRoots": [ |
| "node_modules/@types", |
| "src/types" |
| ], |
| "target": "esnext", |
| "useDefineForClassFields": true, |
| "module": "esnext", |
| "moduleResolution": "node", |
| "strict": true, |
| "jsx": "preserve", |
| "sourceMap": true, |
| "resolveJsonModule": true, |
| "esModuleInterop": true, |
| "lib": ["esnext", "dom"], |
| "baseUrl": "./", |
| "paths": { |
| "@": ["src"], |
| "@/*": ["src/*"] |
| } |
| }, |
| "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] |
| } |
- 修改
vite.config.ts
| import { defineConfig } from "vite"; |
| import vue from "@vitejs/plugin-vue"; |
| import * as path from "path"; |
| |
| |
| export default defineConfig({ |
| resolve: { |
| |
| alias: { |
| "@": path.resolve(__dirname, "src"), |
| }, |
| }, |
| plugins: [vue()], |
| server: { |
| port: 8080, |
| hmr: { |
| host: "127.0.0.1", |
| port: 8080, |
| }, |
| |
| proxy: { |
| "/api": { |
| target: "your https address", |
| changeOrigin: true, |
| rewrite: (path: string) => path.replace(/^\/api/, ""), |
| }, |
| }, |
| }, |
| }); |
# 2. 代码质量风格的统一
# 集成 eslint
- 安装
| pnpm i eslint eslint-plugin-vue --save-dev |
由于 ESLint 默认使用 Espree 进行语法解析,无法识别 TypeScript 的一些语法,故我们需要安装 @typescript-eslint/parser
替代掉默认的解析器
| pnpm install @typescript-eslint/parser --save-dev |
安装对应的插件 @typescript-eslint/eslint-plugin 它作为 eslint 默认规则的补充,提供了一些额外的适用于 ts 语法的规则。
| pnpm install @typescript-eslint/eslint-plugin --save-dev |
- 创建配置文件:
.eslintrc.js
或 .eslintrc.json
| module.exports = { |
| parser: "vue-eslint-parser", |
| |
| parserOptions: { |
| parser: "@typescript-eslint/parser", |
| ecmaVersion: 2020, |
| sourceType: "module", |
| ecmaFeatures: { |
| jsx: true, |
| }, |
| }, |
| |
| extends: ["plugin:vue/vue3-recommended", "plugin:@typescript-eslint/recommended"], |
| |
| rules: { |
| |
| }, |
| }; |
- 创建忽略文件:
.eslintignore
node_modules/
dist/
index.html
- 命令行式运行:修改
package.json
| { |
| ... |
| "scripts": { |
| ... |
| "eslint:comment": "使用 ESLint 检查并自动修复 src 目录下所有扩展名为 .js 和 .vue 的文件", |
| "eslint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src", |
| } |
| ... |
| } |
# 集成 prettier
- 安装
| pnpm i prettier eslint-config-prettier eslint-plugin-prettier --save-dev |
- 创建配置文件:
prettier.config.js
或 .prettierrc.js
| module.exports = { |
| |
| printWidth: 80, |
| |
| tabWidth: 4, |
| |
| useTabs: false, |
| |
| semi: true, |
| |
| singleQuote: true, |
| |
| quoteProps: "as-needed", |
| |
| jsxSingleQuote: false, |
| |
| trailingComma: "all", |
| |
| bracketSpacing: true, |
| |
| jsxBracketSameLine: false, |
| |
| arrowParens: "always", |
| |
| rangeStart: 0, |
| rangeEnd: Infinity, |
| |
| requirePragma: false, |
| |
| insertPragma: false, |
| |
| proseWrap: "preserve", |
| |
| htmlWhitespaceSensitivity: "css", |
| |
| endOfLine: "auto", |
| }; |
- 修改
.eslintrc.js
配置
| module.exports = { |
| ... |
| |
| extends: [ |
| 'plugin:vue/vue3-recommended', |
| 'plugin:@typescript-eslint/recommended', |
| 'prettier', |
| 'plugin:prettier/recommended' |
| ], |
| |
| ... |
| }; |
- 命令行式运行:修改
package.json
| { |
| ... |
| "scripts": { |
| ... |
| "prettier:comment": "自动格式化当前目录下的所有文件", |
| "prettier": "prettier --write" |
| } |
| ... |
| } |
# 3. 集成 pinia
Pinia
读音:['piːnə],是 Vue 官方团队推荐代替 Vuex
的一款轻量级状态管理库。
Pinia 有如下特点:
- 完整的 typescript 的支持;
- 足够轻量,压缩后的体积只有 1.6kb;
- 去除 mutations,只有 state,getters,actions(这是我最喜欢的一个特点);
- actions 支持同步和异步;
- 没有模块嵌套,只有 store 的概念,store 之间可以自由使用,更好的代码分割;
- 无需手动添加 store,store 一旦创建便会自动添加;
# 安装
pnpm i pinia --save
# 使用
- 新建 src/store 目录并在其下面创建 index.ts,导出 store
| import { createPinia } from "pinia"; |
| |
| const store = createPinia(); |
| |
| export default store; |
- 在 main.ts 中引入并使用
| import { createApp } from "vue"; |
| import App from "./App.vue"; |
| import store from "./store"; |
| const app = createApp(App); |
| app.use(store); |
| app.mount("#app"); |
- 定义 State: 在 src/store 下面创建一个 user.ts
| import { defineStore } from "pinia"; |
| |
| export const useUserStore = defineStore({ |
| id: "user", |
| state: () => { |
| return { |
| name: "张三", |
| }; |
| }, |
| actions: { |
| updateName(name) { |
| this.name = name; |
| }, |
| }, |
| }); |
- 获取 State: 在 src/components/usePinia.vue 中使用
| <template> |
| <div><!--swig0--></div> |
| </template> |
| |
| <script lang="ts" setup> |
| import { useUserStore } from '@/store/user' |
| |
| const userStore = useUserStore() |
| </script> |
- 修改 State:
| |
| userStore.name = '李四' |
| |
| |
| <script lang="ts" setup> |
| import { useUserStore } from '@/store/user' |
| |
| const userStore = useUserStore() |
| userStore.updateName('李四') |
| </script> |
更详细上手指南:链接 官方文档:链接
# 4. 集成 vue-router4
# 安装
# 使用
- 新建 src/router 目录并在其下面创建 index.ts,导出 router
| import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router"; |
| |
| const routes: Array<RouteRecordRaw> = [ |
| { |
| path: "/login", |
| name: "Login", |
| meta: { |
| title: "登录", |
| keepAlive: true, |
| requireAuth: false, |
| }, |
| component: () => import("@/pages/login.vue"), |
| }, |
| { |
| path: "/", |
| name: "Index", |
| meta: { |
| title: "首页", |
| keepAlive: true, |
| requireAuth: true, |
| }, |
| component: () => import("@/pages/index.vue"), |
| }, |
| ]; |
| |
| const router = createRouter({ |
| history: createWebHistory(), |
| routes, |
| }); |
| export default router; |
- 在 main.ts 中引入并使用
| import router from "@/router"; |
| |
| app.use(router); |
- 修改 App.vue
| <template> |
| <RouterView /> |
| </template> |
# 5. 集成 vueuse
VueUse
是一个基于 Composition API
的实用函数集合。
# 安装
pnpm i @vueuse/core
# 使用
- 创建一个新的 src/page/vueUse.vue 页面来做一个简单的 demo
| <template> |
| <h1> 测试 vueUse 的鼠标坐标 </h1> |
| <h3>Mouse: <!--swig1--> x <!--swig2--></h3> |
| </template> |
| |
| <script lang="ts"> |
| import { defineComponent } from 'vue'; |
| import { useMouse } from '@vueuse/core' |
| |
| export default defineComponent({ |
| name: 'VueUse', |
| setup() { |
| const { x, y } = useMouse() |
| |
| return { |
| x, y |
| } |
| } |
| }); |
| </script> |
useMouse 只是 vueuse 的一个最基本的函数库,还有许多,总会有一个适合你;
更多函数官方文档:链接
# 6. CSS 的集成
# 方案一:原生 css variable 新特性:
原生支持,不需要第三方插件,具体使用文档可 查看
- 新建文件 src/styles/index.css
| :root { |
| --main-bg-color: pink; |
| } |
| |
| body { |
| background-color: var(--main-bg-color); |
| } |
注:还可以增加 PostCSS 配置,(任何受 postcss-load-config 支持的格式,例如 postcss.config.js
),它将会自动应用于所有已导入的 CSS。
# 方案二:scss 或 less:
- 安装
| |
| pnpm add -D sass |
| |
| |
| pnpm add -D less |
- 使用在 .vue 文件模板中
| |
| <template> |
| <div class="root"> |
| <h3>欢迎使用 scss</h3> |
| </div> |
| </template> |
| <style lang="scss"> |
| .root {} |
| </style> |
| |
| |
| <template> |
| <div class="root"> |
| <h3>欢迎使用 less</h3> |
| </div> |
| </template> |
| <style lang="less"> |
| .root {} |
| </style> |
# 7. 集成 axios
axios
是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。
# 安装
# 使用:
- 新建 src/utils/axios.ts
| import axios, { AxiosResponse, AxiosRequestConfig } from "axios"; |
| |
| const service = axios.create(); |
| |
| service.interceptors.request.use( |
| (config: AxiosRequestConfig) => { |
| |
| return config; |
| }, |
| (error: any) => { |
| Promise.reject(error); |
| } |
| ); |
| |
| service.interceptors.response.use( |
| async (response: AxiosResponse) => { |
| |
| }, |
| (error: any) => { |
| |
| return Promise.reject(error); |
| } |
| ); |
| |
| export default service; |
- 在页面中使用即可
| <script lang="ts"> |
| import request from '@/utils/axios'; |
| const requestRes = async () => { |
| let result = await request({ |
| url: '/api/xxx', |
| method: 'get' |
| }); |
| } |
| |
| </script> |
# 封装请求参数和响应数据的所有 api (可选项)
- 新建
src/api/index.ts
| import * as login from "./module/login"; |
| import * as index from "./module/index"; |
| |
| export default Object.assign({}, login, index); |
- 新建
src/api/module/login.ts
和 src/api/module/index.ts
| import request from "@/utils/axios"; |
| |
| |
| * 登录 |
| */ |
| |
| interface IResponseType<P = {}> { |
| code?: number; |
| status: number; |
| msg: string; |
| data: P; |
| } |
| interface ILogin { |
| token: string; |
| expires: number; |
| } |
| export const login = (username: string, password: string) => { |
| return request<IResponseType<ILogin>>({ |
| url: "/api/auth/login", |
| method: "post", |
| data: { |
| username, |
| password, |
| }, |
| }); |
| }; |
- 由于使用了 typescript,所以需新增
src/types/shims-axios.d.ts
| import { AxiosRequestConfig } from "axios"; |
| |
| * 自定义扩展 axios 模块 |
| * @author Maybe |
| */ |
| declare module "axios" { |
| export interface AxiosInstance { |
| <T = any>(config: AxiosRequestConfig): Promise<T>; |
| request<T = any>(config: AxiosRequestConfig): Promise<T>; |
| get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>; |
| delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>; |
| head<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>; |
| post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>; |
| put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>; |
| patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>; |
| } |
| } |
- 在
src/pages/request.vue
页面中使用
| <script lang="ts"> |
| import API from '@/api'; |
| |
| const requestRes = async () => { |
| let result = await API.login('zhangsan', '123456'); |
| } |
| |
| </script> |
# 8. css 的 UI 样式库
可选很多,根据自己项目的需求去进行选择即可
注意:UI 库一般需要按需引入(下面以 element-plus
为例)
- 安装
vite-plugin-style-import
| pnpm i vite-plugin-style-import --save-dev |
- 修改
vite.config.ts
| ... |
| import styleImport from 'vite-plugin-style-import' |
| |
| |
| export default defineConfig({ |
| ... |
| plugins: [ |
| vue(), |
| styleImport({ |
| libs: [ |
| { |
| libraryName: 'element-plus', |
| esModule: true, |
| resolveStyle: (name) => { |
| return `element-plus/lib/theme-chalk/${name}.css`; |
| }, |
| ensureStyleFile: true |
| } |
| ] |
| }) |
| ], |
| ... |
| }) |
# 9. 使用 commitizen 规范 git 提交
为了使团队多人协作更加的规范,所以需要每次在 git 提交的时候,做一次硬性规范提交,规范 git 的提交信息
# 安装 commitizen
(交互式提交 + 自定义提示文案 + Commit 规范)
- 安装
| pnpm install -D commitizen cz-conventional-changelog @commitlint/config-conventional @commitlint/cli commitlint-config-cz cz-customizable |
- 配置
package.json
| { |
| ... |
| "scripts": { |
| "commit:comment": "引导设置规范化的提交信息", |
| "commit":"git-cz", |
| }, |
| |
| "config": { |
| "commitizen": { |
| "path": "node_modules/cz-customizable" |
| } |
| }, |
| ... |
| } |
- 新增配置
commitlint.config.js
| module.exports = { |
| extends: ["@commitlint/config-conventional", "cz"], |
| rules: { |
| "type-enum": [ |
| 2, |
| "always", |
| [ |
| "feature", |
| "bug", |
| "fix", |
| "ui", |
| "docs", |
| "style", |
| "perf", |
| "release", |
| "deploy", |
| "refactor", |
| "test", |
| "chore", |
| "revert", |
| "merge", |
| "build", |
| ], |
| ], |
| |
| "type-case": [2, "always", "lower-case"], |
| |
| "type-empty": [2, "never"], |
| |
| "scope-empty": [2, "never"], |
| |
| "scope-case": [0], |
| |
| "subject-empty": [2, "never"], |
| |
| "subject-full-stop": [0, "never"], |
| |
| "subject-case": [0, "never"], |
| |
| "body-leading-blank": [1, "always"], |
| "header-max-length": [0, "always", 72], |
| }, |
| }; |
- 自定义提示则添加
.cz-config.js
| module.exports = { |
| types: [ |
| { value: "feature", name: "feature: 增加新功能" }, |
| { value: "bug", name: "bug: 测试反馈bug列表中的bug号" }, |
| { value: "fix", name: "fix: 修复bug" }, |
| { value: "ui", name: "ui: 更新UI" }, |
| { value: "docs", name: "docs: 文档变更" }, |
| { value: "style", name: "style: 代码格式(不影响代码运行的变动)" }, |
| { value: "perf", name: "perf: 性能优化" }, |
| { value: "refactor", name: "refactor: 重构(既不是增加feature,也不是修复bug)" }, |
| { value: "release", name: "release: 发布" }, |
| { value: "deploy", name: "deploy: 部署" }, |
| { value: "test", name: "test: 增加测试" }, |
| { value: "chore", name: "chore: 构建过程或辅助工具的变动(更改配置文件)" }, |
| { value: "revert", name: "revert: 回退" }, |
| { value: "build", name: "build: 打包" }, |
| ], |
| |
| messages: { |
| type: "请选择提交类型:", |
| customScope: "请输入您修改的范围(可选):", |
| subject: "请简要描述提交 message (必填):", |
| body: "请输入详细描述(可选,待优化去除,跳过即可):", |
| footer: "请输入要关闭的issue(待优化去除,跳过即可):", |
| confirmCommit: "确认使用以上信息提交?(y/n/e/h)", |
| }, |
| allowCustomScopes: true, |
| skipQuestions: ["body", "footer"], |
| subjectLimit: 72, |
| }; |
- 交互界面测试
- 到目前只是规范了 git 的提交信息,我们对提交前代码的检查还没有做到位,例如 ESLint、Prettier,毕竟谁都会有疏忽的时候,
- 那么现在我们的 husky 就闪亮登场了
# 安装 husky
- 安装
| |
| pnpm i husky lint-staged -D |
| |
| |
| npx husky install |
| |
| |
| npx husky add .husky/pre-commit "npx --no-install lint-staged" |
| |
| |
| npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"' |
| |
| |
- 添加配置
package.json
| { |
| ... |
| "lint-staged": { |
| "*.{js,ts}": [ |
| "npm run eslint", |
| "npm run prettier" |
| ] |
| } |
| ... |
| } |
# 提交日志(可选)
- standard-version 或者 conventional-changelog