# vue3-template **Repository Path**: my_vue_study/vue3-template ## Basic Information - **Project Name**: vue3-template - **Description**: vue3-template - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-07-24 - **Last Updated**: 2025-01-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # vue3-template ## 基础 ```js // 1. 初始化 pnpm init // 2. 安装 vite // vite分为两部分 // 开发服务器 esbuild(go) // 打包构建 rollup pnpm install vite -D // 3. 在package.json中新增script 两部分 "dev": "vite", "build": "vite build" // 4. 配置文件 vite.config.js import { defineConfig } from 'vite'; export default defineConfig({}); // 5. index.html 注意设置 type
// 6. 入口文件 main.js console.log('hello world') // 7. .gitignore 文件 ``` ## 支持 vue3 ```js // 1. 安装依赖 pnpm install vue pnpm install @vitejs/plugin-vue -D // 2. 修改 vite.config.js 配置文件 import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; export default defineConfig({ plugins: [vue()], // 插件 }); // 3. main.js import { createApp } from 'vue'; import App from './App.vue'; createApp(App).mount('#app'); // 4. App.vue // 5. HelloWorld.vue ``` ## 支持 ts ```js // 1. 安装依赖 vue-tsc是对vue3进行ts类型校验的 pnpm install typescript vue-tsc -D // 2. 修改 index.html和主入口文件 // 3. 修改script脚本文件 "build": "vue-tsc --noEmit && vite build" // 4. ts配置文件 tsconfig.json { "compilerOptions": { "target": "esnext", // 指定编译之后的版本 "module": "esnext", // 生成的模块形式 "moduleResolution": "node", // 模块的解析策略 "strict": true, // 类型检查 严格模式 "jsx": "preserve", // 不编译jsx(vue3也是支持jsx的) React.createElement "sourceMap": true, "esModuleInterop": true, // 为导入内容创建命名空间 commonjs和es "lib": ["esnext", "dom", "es2018.promise"] // 编译引入的es库 }, // include可以是文件也可以是文件夹 "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] } // 5. 新增类型定义 env.d.ts vite默认的类型定义是写给他的nodejs api的 // 1. /// 三斜线指令告诉编译器在编译过程中要引入的额外的文件 /// // 2. .vu单文件 模块类型声明 declare module '*.vue' { import type { DefineComponent } from 'vue'; const component: DefineComponent<{}, {}, any>; export default component; } // 6. 在单文件的 script 增加 lang = 'ts' // 修改 HelloWorld.vue ``` ## eslint + prettier ```js // 1. 下载 vscode 插件 // volar(vue) // 代码质量: eslint ESLint Chinese Rules // EditorConfig for VS Code // 代码风格: prettier // 2. 安装依赖 // eslint-plugin-vue vue的官方eslint插件 // @typescript-eslint/parser eslint的解析器 // @typescript-eslint/eslint-plugin eslint插件 为ts代码提供lint规则 // @vue/eslint-config-typescript vue的 eslint-config-typescript pnpm install eslint eslint-plugin-vue @typescript-eslint/parser @typescript-eslint/eslint-plugin @vue/eslint-config-typescript -D // prettier 代码格式化 // eslint-plugin-prettier 使用eslint规则运行 prettier // eslint 和 prettier 有一些规则是冲突的 pnpm install prettier eslint-plugin-prettier @vue/eslint-config-prettier -D // 3. 配置文件 // (1) .editorconfig 不同编辑器和ide之间定义编码 # * 针对所有文件,设置了编辑器每行的换行风格、字符编码、缩进风格和数量、末尾添加一行 [*] root = true [*] charset = utf-8 indent_style = space indent_size = 2 end_of_line = lf trim_trailing_whitespace = true insert_final_newline = true // (2) .eslintrc.js .eslintignore module.exports = { root: true, env: { // 运行环境 browser: true, es2021: true, node: true }, // 继承 规则的合集 定义自己的插件? extends: [ 'plugin:vue/vue3-recommended', 'eslint:recommended', '@vue/typescript/recommended', 'prettier', '@vue/eslint-config-prettier' ], // 编译选项 parserOptions: { parser: '@typescript-eslint/parser', ecmaVersion: 2021 }, // 重写规则 rules: { 'prettier/prettier': [ 'error', { singleQuote: false, tabWidth: 2, indent: 2, semi: false, trailingComma: 'none', endOfLine: 'auto' } ], 'no-unused-vars': 'off', 'vue/no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'off' }, // 定义全局变量 globals: { defineProps: 'readonly' } }; // (3) .prettierrc.js .prettierignore module.exports = { printWidth: 120, // 一行的字符数,如果超过会进行换行,默认为80 tabWidth: 2, // 一个tab代表几个空格数,默认为2 useTabs: false, // 是否使用tab进行缩进,默认为false,表示用空格进行缩减 singleQuote: true, // 字符串是否使用单引号,默认为false,使用双引号 semi: true, // 行位是否使用分号,默认为true trailingComma: 'none', // 是否使用尾逗号,有三个可选值"" bracketSpacing: true, // 对象大括号直接是否有空格,默认为true,效果:{ foo: bar } jsxSingleQuote: false, // jsx是否使用单引号 // jsxBracketSameLine is deprecated. // jsxBracketSameLine: false, arrowParens: 'always' // 剪头函数参数是否使用() // "parser": "babylon" //代码的解析引擎,默认为babylon,与babel相同。 }; // 4. package.json文件中新增 脚本 "lint": "eslint --ext .ts,.tsx,vue src/** --no-error-on-unmatched-pattern --quiet", "lint:fix": "eslint --ext .ts,.tsx,vue src/** --no-error-on-unmatched-pattern --quiet --fix" // 5. 自动格式化 在.vscode的 settings.json中添加 有的团队习惯git忽略 .vscode的提交 "eslint.alwaysShowStatus": true, "editor.codeActionsOnSave": { "source.fixAll.eslint": true }, "eslint.validate": ["typescript"], "editor.formatOnSave": true, // 保存时自动格式化 "editor.defaultFormatter": "esbenp.prettier-vscode", // 使用 prettier 作为默认格式化工具 // 6. 自定义代码规则 如何制定团队的代码规范? // eslint插件 ``` ## git hook ```js // 在git提交之前检查代码 保证代码都是符合规范的 // git push之前经过单元测试(gitlab的pipeline) // lint-staged 每次提交只检查本地修改的文件 // husky 添加git hooks 自动在仓库 .git/增加对应的钩子 // pre-commit // 1. 安装依赖 https://www.npmjs.com/package/mrm // mrm 安装 lint-staged 的同时会安装 husky // 根据 package.json文件依赖项中的代码质量工具来安装和配置 husky 和lint-staged pnpm install mrm -D // 2. 执行命令 npx mrm lint-staged // package.json文件发生变化 // (1) "prepare": "husky install" // (2) 自动安装了husky和lint-staged // (3) 新增了配置 "lint-staged": { "*.{ts,tsx,vue}": "eslint --cache --fix" } // 新增了 .husky文件夹 // pre-commit 会执行 npx lint-staged // 3. 在pre-commit中增加一个指令 // npm run check-keyword // 在提交的代码中不能包括指定的关键字 (在调试代码的时候加上 防止调试代码提交到git) // check-keyword.sh #!/bin/bash for FILE in `git diff --name-only --cached`; do # echo $FILE # 忽略检查的文件 if [[ $FILE == *".sh"* ]] || [[ $FILE == *".md"* ]] || [[ $FILE == *".json"* ]]; then continue fi # 匹配不能上传的关键字 grep 'debugger\|alert(' $FILE 2>&1 >/dev/null if [ $? -eq 0 ]; then # 将错误输出 echo -e $FILE '文件中包含了debugger、alert其中一个关键字请删除后再提交' exit 1 fi done exit ``` ## commitlint ```js // 规范代码提交 提交规范 commitlint // git commit -m [optional scope]: // type: 提交的类型 新增功能还是修改bud // optional scope: 可选的修改范围 标识此次提交涉及的代码中模块 // description: 提交的描述 // 1. 安装依赖 pnpm install @commitlint/cli @commitlint/config-conventional -D // 2. 新增 husky 配置 npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1" // 3. 配置文件 commitlint.config.js module.exports = { extends: ["@commitlint/config-conventional"], rules: { "type-enum": [ 2, "always", [ "build", "chore", "ci", "docs", "feature", "fix", "perf", "refactor", "revert", "style", "test" ] ] } } // 4. 界面提交 changelog // (1) 安装依赖 pnpm install commitizen cz-customizable conventional-changelog conventional-changelog-cli cz-conventional-changelog -D // (2) package.json 新增 script "commit": "cz", "changelog:all": "conventional-changelog -p angular -i CHANGELOG.all.md -s -r 0", "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s" // (3) 增加配置 package.json "config": { "commitizen": { "path": "node_modules/cz-customizable" } } // (4) 配置文件 .cz-config.js // (5) 使用 pnpm commit ``` ## 配置别名 ```js // 1. 修改 vite.config.ts import { resolve } from 'path'; resolve: { // 配置别名 alias: { '@': resolve('src') } } // 2. 修改 tsconfig.json文件 "baseUrl": ".", "paths": { "@/*": ["src/*"] } ``` ## 样式处理 ```js // 1. scoped data-v-hash 对应模块的标识 // 2. CSS modules // 使用 超链接 // 也可以使用外链的 css module 文件 import style from "./HelloWorld.module.css" 超链接 // 3. 预处理器 // scss less stylus // (1) 安装依赖 pnpm install sass less -D // (2) 可以直接使用

less

sass

// (3) 全局注入 修改 vite.config.ts 配置文件 css: { preprocessorOptions: { scss: { additionalData: '@import "@/styles/theme.scss";' }, less: { additionalData: '@import "@/styles/theme.less";' } } } // 4. postcss 兼容性 // (1) 安装依赖 pnpm install postcss autoprefixer -D // (2) 配置文件 // postcss.config.js module.exports = { plugins: [require("autoprefixer")] } // .browserslistrc >0.2% not dead not op_mini all // 5. styleLint // (1) 安装依赖 pnpm install stylelint stylelint-config-standard stylelint-prettier stylelint-config-prettier stylelint-config-css-modules stylelint-config-rational-order stylelint-order stylelint-declaration-block-no-ignored-properties -D // (2) 下载vscode 插件 styleLint // (3) 配置文件 .stylelintrc.js module.exports = { extends: [ 'stylelint-config-standard', 'stylelint-prettier/recommended', 'stylelint-config-css-modules', 'stylelint-config-rational-order', 'stylelint-config-prettier' ], plugins: [ 'stylelint-order', 'stylelint-declaration-block-no-ignored-properties' ], rules: { 'no-descending-specificity': null, 'plugin/declaration-block-no-ignored-properties': true, 'value-keyword-case': null, 'font-family-no-missing-generic-family-keyword': null } }; ``` ## mock ```js // 1. 安装依赖 pnpm install mockjs vite-plugin-mock -D pnpm install axios // 2. 配置文件 vite.config.ts import { viteMockServe } from "vite-plugin-mock" plugins: [vue(), viteMockServe()] // 3. 新增mock文件 mock/list.ts import { MockMethod } from 'vite-plugin-mock'; export default [ { url: '/api/list', method: 'get', response: ({ query }) => { return { code: 0, data: [{ name: 'opa' }] }; } } ] as MockMethod[]; // 4. 使用 mock 文件 onMounted(() => { axios.get('/api/list').then((res) => console.log(res.data)); }); ``` ## axios ```js // 封装请求 // 1. 安装依赖 pnpm install axios // VueRequest https://next.attojs.com/guide/gettingStarted.html pnpm install vue-request@next // 后端返回的下划线 前端使用小驼峰 pnpm install camelcase-keys snakecase-keys // 2. 封装axios请求 api/http.ts 请求拦截 相应拦截 import axios, { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'; import camelcaseKeys from 'camelcase-keys'; import snakeCaseKeys from 'snakecase-keys'; axios.defaults.baseURL = '/api'; axios.defaults.timeout = 10000; axios.defaults.headers.post['Content-Type'] = 'application/json;charset=UTF-8'; axios.defaults.withCredentials = true; const handleRequest = (request: AxiosRequestConfig): AxiosRequestConfig => { if (request.data) { request.data = snakeCaseKeys(request.data, { deep: true }); } if (request.params) { request.params = snakeCaseKeys(request.params, { deep: true }); } // 增加token验证 const token = window.sessionStorage.getItem('token'); if (token) { (request.headers = request.headers || {}).token = token; } return request; }; const handleSuccess = ( response: AxiosResponse ): Promise> => { response = camelcaseKeys(response, { deep: true }); return response.data; }; const handleErr = (error: AxiosError): void => { Promise.reject(error); }; axios.interceptors.request.use(handleRequest); axios.interceptors.response.use(handleSuccess, handleErr); export default axios; // 3. 使用 // (1) 类型文件定义 typings/List export interface ListParams { username?: string; } // (2) 定义接口 api/list.ts import { ListParams } from '@/typings'; import http from './http'; export const getList = (params: ListParams) => { return http.get('/list', { params }); }; // (3) 使用 import { useRequest } from 'vue-request'; const { data, loading, error, run } = useRequest(getList); ``` ## 状态管理 ```js // vuex yyx演讲会使用 pinia代替 // pinia: https://pinia.web3doc.top/introduction.html // 1. 安装依赖 安装google的扩展插件 devtool pnpm install pinia // 2. 创建仓库 store/counter.ts redux(createStore) import { defineStore } from 'pinia'; export const useCounterStore = defineStore('counter', { state: () => ({ count: 0 }), actions: { increment() { this.count++; } } }); // 3. 在 main.ts 中使用 import { createPinia } from "pinia" app.use(createPinia()) // 4. 组件中使用 import { useCounterStore } from '@/store'; const countStore = useCounterStore();

{{ countStore.count }}

``` ## 路由 ```js // vue-router // 1. 安装依赖 pnpm install vue-router // 2. 新建路有 router\index.ts import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'; const routes: RouteRecordRaw[] = [ { path: '/', name: 'home', component: () => import('@/pages/Home.vue') } ]; const router = createRouter({ history: createWebHistory(), routes }); export default router; // 3. 在 main.ts中使用 import router from "@/router" app.use(router) // 4. 在App.vue中加入 ``` ## 组件库 ```js // element-plus: https://element-plus.gitee.io/zh-CN/ // naive-ui: yyx推荐 https://www.naiveui.com/zh-CN/os-theme/docs/installation // 1. 安装依赖 vfonts 字体 xicons 图标库 https://www.xicons.org/#/ pnpm install naive-ui vfonts @vicons/ionicons5 // 2. 在 SFC中使用 App.vue 添加导航 import { h, Component } from 'vue'; import { NIcon, NMenu } from 'naive-ui'; import type { MenuOption } from 'naive-ui'; import { RouterLink } from 'vue-router'; import { HomeOutline as HomeIcon } from '@vicons/ionicons5'; function renderIcon(icon: Component) { return () => h(NIcon, null, { default: () => h(icon) }); } const menuOptions: MenuOption[] = [ { label: () => h(RouterLink, { to: { name: 'home' } }, { default: () => '首页' }), key: 'home', icon: renderIcon(HomeIcon) } ]; ``` ## 环境变量 ```js // vite在一个特殊的对象 import.meta.env对象上暴露 环境变量 // import.meta.env.MODE 应用运行的模式 // import.meta.env.BASE_URL 部署应用时的基本 URL bash // import.meta.env.PROD 应用是否运行在生产环境 // import.meta.env.DEV // 1. 创建文件 // .env.development // .env.production // 2. 添加配置 // VITE_APP_WEB_URL = '/dev' // VITE_APP_WEB_URL = '/prod' // 3. 查看 console.log(import.meta.env); // BASE_URL: "/" // DEV: true // MODE: "development" // PROD: false // SSR: false // VITE_APP_WEB_URL: "/dev" // 自定义的 必须要vite开头的 // 4. 在 package.json中添加 script "build:dev": "vue-tsc --noEmit && vite build --mode development", "build:prod": "vue-tsc --noEmit && vite build --mode production" ``` ## babel ```js // 1. 安装依赖 pnpm install @babel/core @babel/preset-env @babel/preset-typescript -D // 2. 配置文件 babel.config.ts export default { presets: ['@babel/preset-env', '@babel/preset-typescript'] }; ``` ## 单元测试 ```js // 单元测试 // 1. 安装依赖 pnpm install jest ts-jest ts-node @types/node @types/jest babel-jest @vue/vue3-jest @jest/types jest-environment-jsdom -D pnpm install @vue/test-utils@next jest-transform-stub -D // 2. 增加script脚本 "test": "jest", "test:coverage": "jest --coverage" // 3. 配置文件 jest.config.ts // https://www.jestjs.cn/docs/configuration import type { Config } from '@jest/types'; const config: Config.InitialOptions = { transform: { '^.+\\.js$': 'babel-jest', '^.+\\.ts$': 'ts-jest', '^.+\\.vue$': '@vue/vue3-jest', '.+\\.(css|scss|png|jpg|svg)$': 'jest-transform-stub' }, moduleNameMapper: { '^@/(.*)$': '/src/$1' }, // 匹配 testMatch: ['/src/**/*.spec.(t|j)s'], testEnvironment: 'jsdom', transformIgnorePatterns: ['/node_modules/'], // 测试覆盖率 coverageDirectory: 'coverage', coverageProvider: 'v8', collectCoverageFrom: ['src/**/*.{js,vue}', '!src/main.ts', '!src/App.vue'], coverageThreshold: { global: { branches: 40, functions: 80, lines: 90, statements: 80 } } }; export default config; // 4. 测试 utils // (1) 新建 sum函数 /utils/sum.ts export const sum = (a: number, b: number) => a + b; // (2) 测试 /__test__/utils/sum.spec.ts import { sum } from '@/utils'; it('test sum', () => { expect(sum(1, 2)).toBe(3); }); // 5. 测试组件 // (1) 组件 /components/TodoApp.vue // (2) 测试 /__test__/components/TodoApp.spec.ts import { mount } from '@vue/test-utils'; // vue官方提供的测试套件 import TodoApp from '@/components/TodoApp.vue'; test('render todoApp', () => { const wrapper = mount(TodoApp); const addTodo = wrapper.get('[data-test="addTodo"]'); expect(addTodo.text()).toBe('addTodo'); }); // 6. 测试覆盖率 ``` ## e2e 测试 ```js // https://docs.cypress.io/ // 1. 安装依赖 pnpm install cypress -D pnpm install @cypress/vue@next @cypress/vite-dev-server -D pnpm install --save-dev start-server-and-test // 2. 初始化 会打开一个界面 Choose a Browser npx cypress open // 配置文件: cypress.config.ts // 文件夹: cypress // 3. 编写测试 // (1) e2e测试: cypress/e2e/todo.spec.js describe('TodoApp', () => { beforeEach(() => { cy.visit('http://0.0.0.0:3000'); }); it('can add new todo items', () => { const text = 'play'; // https://docs.cypress.io/api/commands/get cy.get('[data-test="newTodo"]').type(text); cy.get('[data-test="addTodo"]').click(); cy.get('.todo-list li') .should('have.length', 1) .last() .should('have.text', text); }); }); // (2) 组件测试: cypress/component/Todo.cy.ts import { mount } from '@cypress/vue'; import TodoApp from '@/src/components/Todo.vue'; describe('TodoApp', () => { it('render TodoApp', () => { mount(TodoApp); const text = 'play'; // https://docs.cypress.io/api/commands/get cy.get('[data-test="newTodo"]').type(text); cy.get('[data-test="addTodo"]').click(); cy.get('.todo-list li') .should('have.length', 1) .last() .should('have.text', text); }); }); // (3) 启动服务器 // (4) 新增 ts配置文件 /cypress/tsconfig.json { "extends": "../tsconfig.json", "compilerOptions": { "noEmit": true, // be explicit about types included // to avoid clashing with Jest types "types": ["cypress"] }, "include": ["../node_modules/cypress", "./**/*.ts", "../src/**/*.d.ts"] } // 4. package.json新增指令 "test:e2e": "cypress open --e2e --browser chrome", "test:component": "cypress open --component --browser chrome", // 5. 测试 pnpm test:component // 6. 修改vite配置 // https://github.com/cypress-io/cypress/issues/22784 server: { port: 3000, host: '127.0.0.1' } ``` ## ci-cd ```js // .DockerFile // .drone.yml // .jenkinsFile // deploy.yaml 文件 // tekton // argo // .gitlab-ci.yml gitlab流水线 ``` ## 其他 ```js // .nvmrc // .npmrc // .sentryclirc 监控 // .gitlab 配置 mr模板 mr之后自动修改jira状态 创建jira的task自动生成gitlab的分支 ```