# 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
{{ msg }}
```
## 支持 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的分支
```