通过这篇文章可以学到
- 🚀 使用 Vite 快速搭建 Vue3 企业级项目架构
- 🔍 配置 Prettier + ESLint 实现代码规范化
- 🔒 通过 Git Hooks 实现提交规范管控
- 🧩 集成 Element-Plus、Pinia、Vue Router 等核心库
项目模版源码
依赖版本
- vite:6.1.0
- vue:3.5.13
- typescript:5.7.2
- eslint:8.57.0
- prettier:3.5.3
- element-plus:2.9.5
- pinia:3.0.1
- vue-router:4.5.0
- sass:1.85.1
- axios:1.8.1
- husky:9.1.7
- commitizen:4.3.1
1、新建项目
为什么选择 pnpm?
- 比 npm/yarn 更快的安装速度
- 磁盘空间节省高达 50%
- 严格的依赖管理避免幽灵依赖
pnpm create vite project --template vue-ts
2、基础配置
- 添加
@types/node
,使得在使用 TypeScript 时可以获得更好的类型检查和智能提示
pnpm install @types/node -D
tsconfig.app.json
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true,
"baseUrl": ".", //查询的基础路径
"paths": { "@/*": ["src/*"] }, //路径映射,配合别名使用
"types": ["node", "element-plus/global"],
// 使用jsx才需要配置
"jsx": "preserve",
"jsxImportSource": "vue"
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
}
vite.config.ts
import path from 'path';
import vue from '@vitejs/plugin-vue';
import { defineConfig } from 'vite';
const pathSrc = path.resolve(__dirname, 'src');
const fePort = 1118;
const serverOrigin = 'http://localhost:1110';
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue()
],
// 服务器设置
server: {
cors: true, // 默认启用并允许·任何源
host: '0.0.0.0', // 指定服务器主机名
port: fePort, // 指定服务端口号
open: true, // 运行自动打开浏览器
strictPort: true, // 若3030端口被占用,直接结束项目
proxy: {
'/api': {
target: serverOrigin,
changeOrigin: true,
secure: false // 忽略自签名证书
}
}
},
//这里进行配置别名
resolve: {
alias: {
'@': pathSrc // @代替src
}
},
});
3、代码规范
3.1、EditorConfig
- 有助于为不同 IDE 编辑器上处理同一项目的多个开发人员维护一致的编码风格。
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
quote_type = single
3.2、ESLint
安装
pnpm install eslint eslint-plugin-vue @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-import -D
使用
- 创建配置文件
.eslintrc
{
"env": {
"browser": true,
"node": true,
"es2021": true
},
"parser": "vue-eslint-parser",
"extends": [
"eslint:recommended",
"plugin:vue/vue3-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
"eslint-config-prettier"
],
"parserOptions": {
"ecmaVersion": "latest",
"parser": "@typescript-eslint/parser",
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"plugins": ["vue", "@typescript-eslint", "prettier", "import"],
"rules": {
"eqeqeq": 2, // 严格等号逻辑
"no-unused-vars": 1, // 未使用的变量进行警告
"arrow-parens": [0, "always"], // 箭头函数参数应该始终包含括号
"arrow-body-style": [1, "as-needed"], // 箭头函数可以去掉括号就去掉括号
"prettier/prettier": ["error", { "semi": true }],
"vue/multi-word-component-names": "off", // 禁用vue文件强制多个单词命名
"@typescript-eslint/no-explicit-any": ["off", {}], //允许使用any
"@typescript-eslint/no-this-alias": [
"error",
{
"allowedNames": ["that"] // this可用的局部变量名称
}
],
"@typescript-eslint/ban-ts-comment": "off", //允许使用@ts-ignore
"@typescript-eslint/no-non-null-assertion": "off", //允许使用非空断言
"no-console": [
//提交时不允许有console.log
"warn",
{
"allow": ["warn", "error"]
}
],
"no-debugger": "warn", //提交时不允许有debugger
"vue/comment-directive": "off",
// 确保没有重复引入
"import/no-duplicates": "error",
// 确保 import 语句按照组分类并排序
"import/order": [
"error",
{
// builtin:内置模块;external:第三方模块;internal:内部引用;sibling:兄弟依赖;parent:父节点依赖;index:index 文件依赖;unknown:未知依赖
"groups": [
"builtin",
"external",
"internal",
"parent",
"sibling",
"index",
"object",
"type",
"unknown"
],
// vue的引入将会排在第一位
"pathGroups": [
{
"pattern": "vue",
"group": "external",
"position": "before"
}
],
"pathGroupsExcludedImportTypes": ["builtin"],
// newlines-between 不同组之间是否进行换行
"newlines-between": "always",
// alphabetize 根据字母顺序对每个组内的顺序进行排序
"alphabetize": {
"order": "asc",
"caseInsensitive": true
}
}
]
}
}
- 创建忽略文件
.eslintignore
# eslint 忽略检查 (根据项目需要自行添加)
node_modules
dist
3.3、Prettier
安装
pnpm i prettier eslint-config-prettier eslint-plugin-prettier -D
使用
- 创建配置文件
.prettierrc
{
"useTabs": false,
"tabWidth": 2,
"printWidth": 80,
"singleQuote": true,
"trailingComma": "none",
"semi": true,
"arrowParens": "always",
"bracketSpacing": true
}
- 创建忽略
.prettierignore
# 忽略格式化文件 (根据项目需要自行添加)
node_modules
dist
4、提交规范
4.1、husky+lint-staged
安装
pnpm install husky lint-staged -D
使用
- 初始化
pnpm exec husky init
.husky/pre-commit
pnpm --no-install lint-staged
- 在
package.json
中
"lint-staged": {
"src/**/*.{vue,js,jsx,ts,tsx,json}": [
"pnpm run lint",
"prettier --write"
]
}
4.2、commitizen
安装
pnpm install -D commitizen cz-conventional-changelog @commitlint/config-conventional @commitlint/cli commitlint-config-cz cz-customizable
使用
> git-cz
cz-cli@4.3.1, cz-customizable@7.4.0
Unable to find a configuration file. Please refer to documentation to learn how to set up: https://github.com/leonardoanalista/cz-customizable#steps "
Cannot read properties of null (reading 'subjectLimit')
如遇以上错误,是由于
package.json
指定了"type": "module"
,且cz不支持ES Module
,需要使用.cjs
作为文件类型,并重新指定路径名
- 创建文件
.cz-config.cjs
module.exports = {
types: [
{ value: 'init', name: 'init: 初始提交' },
{ value: 'feat', name: 'feat: 新特性、新功能' },
{ value: 'fix', name: 'fix: 修复bug' },
{ value: 'docs', name: 'docs: 文档变更' },
{ value: 'style', name: 'style: 样式修改不影响逻辑' },
{ value: 'perf', name: 'perf: 优化相关,比如提升性能、体验' },
{ value: 'refactor', name: 'refactor: 代码重构' },
{ value: 'test', name: 'test: 添加测试' },
{ value: 'chore', name: 'chore: 更改配置文件' },
{
value: 'build',
name: 'build: 编译相关的修改,例如发布版本、对项目构建或者依赖的改动'
},
{ value: 'ci', name: 'ci: 持续集成修改' },
{ value: 'revert', name: 'revert: 回滚到上一个版本' },
{ value: 'merge', name: 'merge: 合并其他分支代码' }
],
messages: {
type: '选择一种你的提交类型:',
scope: '选择一个scope (可选):',
// 如果allowcustomscopes为true,则使用
customScope: '请输入修改范围(可选):',
subject: '请输入简要描述(必填):',
body: '详细描述. 使用"|"换行:\n',
breaking: 'Breaking Changes列表:\n',
footer: '关闭的issues列表. E.g.: #31, #34:\n',
confirmCommit: '确认提交?'
},
allowCustomScopes: true,
allowBreakingChanges: ['feat', 'fix'],
skipQuestions: ['breaking', 'footer']
};
- 创建文件
commitlint.config.cjs
module.exports = {
extends: ['@commitlint/config-conventional', 'cz'],
rules: {
'type-enum': [
2,
'always',
[
'init', // 初始化
'feat', // 新功能(feature)
'fix', // 修补bug
'ui', // 更新 ui
'docs', // 文档(documentation)
'style', // 格式(不影响代码运行的变动)
'perf', // 性能优化
'release', // 发布
'deploy', // 部署
'refactor', // 重构(即不是新增功能,也不是修改bug的代码变动)
'test', // 增加测试
'chore', // 构建过程或辅助工具的变动
'revert', // feat(pencil): add ‘graphiteWidth’ option (撤销之前的commit)
'merge', // 合并分支, 例如: merge(前端页面): feature-xxxx修改线程地址
'build' // 打包
]
],
// <type> 格式 小写
'type-case': [2, 'always', 'lower-case'],
// <type> 不能为空
'type-empty': [2, 'never'],
// <scope> 范围能为空,选填
'scope-empty': [0, 'never'],
// <scope> 范围格式
'scope-case': [0],
// <subject> 主要 message 不能为空
'subject-empty': [2, 'never']
}
};
- 创建文件
.husky/commit-msg
pnpm --no-install commitlint --edit "$1"
- 在
package.json
文件中
"scripts": {
"prepare": "husky",
"commit": "node_modules/cz-customizable/standalone.js",
},
"config": {
"commitizen": {
"path": "node_modules/cz-customizable"
},
"cz-customizable": {
"config": "./.cz-config.cjs"
}
},
pnpm run commit
,最后如下图
4、Element-plus
安装
pnpm install element-plus @element-plus/icons-vue
pnpm install -D unplugin-vue-components unplugin-auto-import
使用
main.ts
import { createApp } from 'vue';
// 全部引入
// import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
// eslint-disable-next-line import/order
import * as ElementPlusIconsVue from '@element-plus/icons-vue';
import App from './App.vue';
const app = createApp(App);
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component);
}
// app.use(ElementPlus);
app.mount('#app');
- 配置按需引入
import path from 'path';
import vue from '@vitejs/plugin-vue';
import AutoImport from 'unplugin-auto-import/vite';
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
import Components from 'unplugin-vue-components/vite';
import { defineConfig } from 'vite';
const pathSrc = path.resolve(__dirname, 'src');
const fePort = 1118;
const serverOrigin = 'http://localhost:1110';
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
AutoImport({
// 自动导入 Element Plus 相关函数,如:ElMessage, ElMessageBox... (带样式)
resolvers: [ElementPlusResolver()]
}),
Components({
resolvers: [
// 自动导入 Element Plus 组件
ElementPlusResolver()
]
})
],
// 服务器设置
server: {
cors: true, // 默认启用并允许·任何源
host: '0.0.0.0', // 指定服务器主机名
port: fePort, // 指定服务端口号
open: true, // 运行自动打开浏览器
strictPort: true, // 若3030端口被占用,直接结束项目
proxy: {
'/api': {
target: serverOrigin,
changeOrigin: true,
secure: false // 忽略自签名证书
}
}
},
//这里进行配置别名
resolve: {
alias: {
'@': pathSrc // @代替src
}
}
});
5、Pinia
安装
pnpm install pinia
使用
- 在
main.ts
中使用pinia
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';
const app = createApp(App);
const pinia = createPinia();
app.use(pinia);
app.mount('#app');
- 新建
src/store
文件夹,新建index.ts
文件
// Option Store
import { defineStore } from 'pinia';
export const useStore = defineStore('store', {
state: () => ({
username: '晚风予星',
count: 0
}),
// 相当于Vue中的计算属性,具有缓存属性,值不改变多次使用,只调用一次
// 箭头函数第一个参数就是state
getters: {
doubleCount: (state) => state.count * 2,
},
// 方法 支持同步和异步
actions: {
increment() {
this.count++
},
}
});
// Setup Store
export const useStore = defineStore('store', () => {
const username = ref('晚风予星')
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { username, count, doubleCount, increment }
})
在 Setup Store 中:
ref()
就是state
属性computed()
就是getters
function()
就是actions
- 使用
<script setup lang="ts">
import { ref } from 'vue';
import { useStore } from '@/store/index';
const store = useStore();
</script>
<template>
<div>{{ store.username }}</div>
</template>
<style lang="scss" scoped>
</style>
- 和在 Vue 中如何选择组合式 API 与选项式 API 一样,选择你觉得最舒服的那一个就好。两种语法都有各自的优势和劣势。Option Store 更容易使用,而 Setup Store 更灵活和强大。
6、Vue-Router
安装
pnpm install vue-router
使用
src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
import type { RouteRecordRaw } from 'vue-router';
import * as PAGE_URL from '@/constants/page-url-constants';
const routes: RouteRecordRaw[] = [];
const router = createRouter({
history: createWebHistory(),
routes
});
// 添加全局前置守卫
router.beforeEach((to, from, next) => {
next();
});
router.afterEach((to) => {
// 修改当前标签页的名称
const title = to.meta.title;
const titleNode = document.querySelector('head > title');
if (title && titleNode) {
titleNode.textContent = title.toString();
}
});
export default router;
main.ts
import { createApp } from 'vue';
import App from './App.vue';
import router from '@/router';
const app = createApp(App);
app.use(router);
app.mount('#app');
7、SCSS
安装
pnpm install sass
- vite配置导入
scss
全局变量文件
css: {
preprocessorOptions: {
scss: {
additionalData: `@use "@/assets/css/mixins.scss" as *;`
}
}
}
8、axios
安装
pnpm install axios
使用
utils/request.ts
import axios from 'axios';
import type { AxiosInstance, InternalAxiosRequestConfig } from 'axios';
const request: AxiosInstance = axios.create({});
// request interceptor
request.interceptors.request.use(
(config): InternalAxiosRequestConfig => config,
(error) => Promise.reject(error)
);
// response interceptor
request.interceptors.response.use(
(response) => {
// 只要状态码是这些之一就认为是成功的响应
if (
response.status === 200 ||
response.status === 201 ||
response.status === 304
) {
const data: any = response.data;
if (data) {
return data;
} else {
return Promise.reject(data);
}
} else {
return Promise.reject(response);
}
},
async (error) => Promise.reject(error)
);
export default request;
- 某
service
文件中
import request from '@/utils/request';
export function handleTest(type: string) {
return request.get('/api/paint/test', { params: { type } });
}
export function handleHttpRequest(path: string) {
return request.post('/api/request', {
path
});
}
9、配置打包分类输出
vite.config.ts
build: {
rollupOptions: {
output: {
manualChunks: {
vue: ['vue', 'pinia', 'vue-router']
},
chunkFileNames: 'static/js/[name]-[hash].js',
entryFileNames: 'static/js/[name]-[hash].js',
assetFileNames: 'static/[ext]/[name]-[hash].[ext]'
}
}
}
10、配置使用TSX
安装
pnpm install @vitejs/plugin-vue-jsx -D
使用
vite.config.ts
import path from 'path';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import { defineConfig } from 'vite';
const pathSrc = path.resolve(__dirname, 'src');
const fePort = 1118;
const serverOrigin = 'http://localhost:1110';
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
vueJsx(),
],
build: {
rollupOptions: {
output: {
manualChunks: {
vue: ['vue', 'pinia', 'vue-router']
},
chunkFileNames: 'static/js/[name]-[hash].js',
entryFileNames: 'static/js/[name]-[hash].js',
assetFileNames: 'static/[ext]/[name]-[hash].[ext]'
}
}
}
});
Hello.tsx
import { defineComponent } from 'vue';
const Hello = defineComponent({
name: 'Hello',
props: {
msg: {
type: String,
default: 'Hello TSX'
}
},
setup(props) {
return () => <div>{props.msg}</div>;
}
});
export default Hello;
最后
感谢大家观看到最后,若有错误,望指出,记得点赞关注加收藏哦 ~