企业级前端工程化配置指南:vite4 + vue3 + ts + pinia + vue-router + axios + commit规范 + 代码质量检验

10,655 阅读10分钟

大家好,我是苏先生,一名热爱钻研、乐于分享的前端工程师,跟大家分享一句我很喜欢的话:人活着,其实就是一种心态,你若觉得快乐,幸福便无处不在

github与好文

你可以学到什么?

  • 如何使用 vite 搭建项目
  • 如何集成与使用 web-localstorage-plus
  • 如何集成与使用 vue-router4
  • 如何集成与使用 pinia
  • 如何集成与使用 element-plus
  • 如何封装axios
  • 如何借力 eslint 和 prettier 保证代码质量
  • 如何借力 commitlint 规范git提交信息

源码地址

传送门

1.创建项目

按提示选择:

1.运行vite

yarn create vite

2.输入自定义的项目名称

name: › your-project-name

3.选择你想要的技术框架

? Select a framework: › - Use arrow-keys. Return to submit.
❯   Vanilla
    Vue
    React
    Preact
    Lit
    Svelte
    Others

4.选择ts模板

? Select a variant: › - Use arrow-keys. Return to submit.
❯   TypeScript
    JavaScript
    Customize with create-vue ↗
    Nuxt

5.按提示安装并运行项目

Done. Now run:

  cd vite-project
  yarn
  yarn dev

一键初始化

除了上述问答形式创建外,vite官方也提供了快捷语法:通过命令行参数创建

# npm 6.x
npm create vite@latest my-vue-app --template vue

# npm 7+, extra double-dash is needed:
npm create vite@latest my-vue-app -- --template vue

# yarn
yarn create vite my-vue-app --template vue

# pnpm
pnpm create vite my-vue-app --template vue

2.优化项目结构

修剪vite默认生成的项目结构

1.保留public文件夹,删除vite.svg文件,同时删除index.html中对该文件的引入

2.删除HelloWorld.vue文件,同时从App.vue中删除引入

3.删除App.vue中的默认代码,只保留默认的三个根元素

我个人习惯将template放到最前边

<template>
</template>

<script setup lang="ts">
</script>

<style scoped>
</style>

4.清空assets文件夹

定制化目录

1.创建store文件夹

放置关于pinia的数据状态

2.创建directive文件夹

放置我们的自定义指令,如:v-auth

3.创建utils文件夹

项目中的公共方法或常量

4.创建styles文件夹

管理公共css样式文件,如:reset.css

5.创建http文件夹

处理axios的封装和调用

6.创建router文件夹

管理vue-router的路由模块

7.创建pages文件夹

管理业务代码

3.配置vite.config.ts

配置别名

项目中,不同模块之间往往需要互相引入,使用别名能够帮助我们省去一级一级查找的繁琐

...
import { resolve } from 'node:path'

export default defineConfig({
  plugins: [vue()],
  resolve:{
    alias:{
      '@':resolve(__dirname,'src')
    }
  }
})

此时,node:path__dirname会报错,我们还需要安装下对应的ts类型包

yarn add @types/node --D

设置代理

我们本地开发完跟后端联调阶段,经常会遇到跨域的问题,需要我们暂时的在前端进行下处理

export default defineConfig({
  ...
  server: {
    proxy: {
      "/api": {
        target: "http url",
        changeOrigin: true,
        rewrite: (path: string) => path.replace(/^\/api/, ""),
      },
    },
  },
});

其中"/api"是我们要代理的接口标识,target是我们实际要访问的接口地址

设置自动导入

每次都手动导入依赖项是一件很麻烦的事情,幸运的是,我们可以借助第三方库来帮我们实现,它内置了常见的库,比如vue

1.安装

yarn add unplugin-auto-import -D

2.在vite.config.ts中导入并作为plugin使用

...
import AutoImport from 'unplugin-auto-import/vite'

export default defineConfig({
  plugins: [...,AutoImport()],
  ...
});

4.集成web-storage-plus

对于需要使用到持久缓存的地方,localstorage是优选的方案,不过原生接口比较难用,而该npm包对其进行了二次封装,使其支持了命名空间、过期时间、监听变化、批量操作等特性,且其为我们提供了发布订阅模式来弥补vue3中对bus的缺失,文档看这里:传送门

1.安装

yarn add web-localstorage-plus

2.在main.ts中引入并初始化根存储

...
import createStorage from 'web-localstorage-plus'
createStorage({
    rootName:'spp-storage'
})
...

3.在.vue文件中引入并使用

<script lang="ts" setup>
import { useStorage } from 'web-localstorage-plus'

const storage = useStorage()
storage.setItem('user',{
    name:'spp',
    age:28
})
</script>

5.集成pinia

对于非持久化数据,我们选择使用pinia来进行管理,它帮我们托管了全局状态并且提供了响应式能力,文档看这里:传送门

1.安装pinia

yarn add pinia

2.在store文件夹下新建index.ts文件作为pinia的根仓库文件

import { createPinia } from "pinia"; 
const pinia = createPinia()
export default pinia

3.在main.ts中导入并将 pinia 作为 plugin 注册给 vue

import { createApp } from 'vue'
...
import pinia from '@/store'
...
const app = createApp(App)
app.use(pinia)
...

4.在store文件夹下新建xxx.ts文件作为子存储模块

import { defineStore } from 'pinia'

export default defineStore('spp', {
  state() {
      return {
        spp:''
      }
  },
  actions:{
    updateSpp(spp:string){
        this.spp = spp
    }
  }
})

5.在.vue文件中使用或修改pinia的状态

<script lang="ts" setup>
...
import useLoginStore from '@/store/login.ts'
// 获取状态
const store = useLoginStore()
// 修改状态
store.updateSpp('spp')
...
</script>

6.集成vue-router4

作为spa项目,路由是我们进行页面切换的必备工具,文档看这里:传送门

1.使用yarn安装

yarn add vue-router@4

2.在router文件夹下新建index.ts文件作为根路由

import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";

const routes: Array<RouteRecordRaw> = [
  {
    path: "/login",
    name: "Login",
    component: () => import("@/pages/login/index.vue")
  },
];

const router = createRouter({
  history: createWebHistory(),
  routes,
});

export default router;

3.在main.ts中导入并作为plugin注册给vue

import { createApp } from 'vue'
import router from '@/router';
...
const app = createApp(App)
app.use(router)
...

4.将根App.vue作为路由出口

<template>
  <RouterView/>
</template>

7.集成less

1.安装

yarn add less -D

2.使用

<style lang="less" scoped>
.root-app {
  .spp {
    // 自定义样式
  }
}
</style>

8.集成element-plus

element-plus是vue侧比较流行的pc端ui框架之一,文档看这里:传送门

1.安装

yarn add element-plus

2.按需自动导入

  • 安装依赖包
yarn add unplugin-vue-components unplugin-auto-import -D
  • 修改vite.config.ts
import { defineConfig } from "vite";
...
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";

export default defineConfig({
  plugins: [
    ...
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
  ...
});

3.在.vue文件夹下直接使用即可

9.集成axios(仅提供封装思路,可选)

axios是目前最流行的前端发起ajax请求的库,其基于promise实现,同时支持在浏览器和nodejs中使用,文档看这里:传送门

1.安装

yarn add axios

2.在http文件夹下新建index.ts文件

该文件对我们业务中的请求进行基类封装

import request from "./request";
import getPrefix from "./urlPrefix";
import { TgroupType } from '@/utils/types'
import { warn } from '@/utils/function'

class Http{
  protected prefix:string=getPrefix(undefined);
  protected config:any={}
  constructor(group?:TgroupType){
    this.prefix = getPrefix(group)
  }
  private combineUrl(url:string){
    return this.prefix + url
  }
  setExtraConfig(config:any){
    this.config = config
  }
  get<T>(url: string, arg?: T,message?:string){
    return new Promise((resolve, reject) => {
      request
        .get(this.combineUrl(url), {
          params: arg,
          ...this.config
        })
        .then((res:any)=>{
          // 根据与后端的约定format数据,并做resolve或reject
          ...
        })
        .catch(reject)
        .finally(()=>{
          this.config={}
        })
    });
  }
  post<T>(url: string, message?: string | T, arg?: T) {
    const isFull = arguments.length === 3
    if (!isFull) {
      arg = message as T;
    }
    const errMessage = '你的自定义错误'
    return new Promise((resolve, reject) => {
      request
        .post(this.combineUrl(url), arg,{
          ...this.config,
        })
        .then((res: any) => {
          // 根据与后端的约定format数据,并做resolve或reject
          ...
        })
        .catch(()=>{
          warn(errMessage)
          reject(errMessage);
        })
        .finally(()=>{
          this.config={}
        })
    });
  }
}

export const http = new Http()
export const httpRequest = Http
export const usePrefix = getPrefix

3.在http文件夹下新建request.ts文件

该文件用于配置axios,并通过拦截器对接口状态进行检测和错误的统一处理

import axios from "axios";

axios.defaults.timeout = 10000000;

axios.defaults.withCredentials = true;

axios.interceptors.request.use(
  (config) => {
    config.headers = Object.assign(config.headers,{
      // 配置header
    })
    return config;
  },
  (error) => {
    // 处理错误
    return Promise.reject(error);
  }
);

axios.interceptors.response.use(
  (response) => {
    // 统一拦截验证
    return response;
  },
  (error) => {
    // 处理错误
    return Promise.reject(error);
  }
);

export default axios;

4.在http文件夹下新建urlPrefix.ts文件

该文件用于统一管理不同域名对应的不同环境下的url

import { IurlConfig,TgroupType} from '@/utils/types'
const mode = import.meta.env.MODE;
const urlConfig:IurlConfig = {
  // 前缀-{dev:'',pro:''}
}

const getPrefix = (key:TgroupType)=>{
  if(key === undefined){
    key = ''
    urlConfig[key][mode]
  }
  return urlConfig[key][mode]
}

export default getPrefix;

5.在业务中引入并使用

import { http } from "@/http";
http
    .post<传递与当前接口参数匹配的类型>(url, "添加成功", params)
    .then(() => {
      // 请求成功的业务处理
    });

10.常用工具推荐

  • 时间处理:moment

文档地址

  • 万能工具库:lodash-es

文档地址

11.代码质量与提交规范

eslint

因为eslint无法识别.vue文件,因此我们还需要一个定制化插件:eslint-plugin-vue,文档看这里:传送门

1.安装依赖

yarn add eslint eslint-plugin-vue -D

2.创建.eslintignore忽略文件

node_modules
dist
yarn.lock
index.html

3.添加ts支持

eslint-plugin-vue只针对.vue文件或者.js文件中的vue写法,我们还需要对ts进行兼容

文档传送门:@typescript-eslint/parser@typescript-eslint/eslint-plugin

yarn add @typescript-eslint/parser @typescript-eslint/eslint-plugin -D

4.在根目录下创建.eslintrc.cjs配置文件

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: {
      "no-console": "error",
  }
};

5.按环境区分

实际项目中,我们一般会根据不同的开发环境来区分规则,比如console应在开发阶段可用,生产时禁用

1-安装依赖

文档看这里:cross-env@rollup/plugin-eslint

yarn add cross-env @rollup/plugin-eslint -D

2-修改命令行

在打包阶段配置环境变量,我这里以production为例子

"scripts": {
    ...
    "build": "cross-env NODE_ENV=production && vue-tsc --noEmit && vite build"
  },

3-将rollup插件加入vite的plugin

...
import eslint from '@rollup/plugin-eslint';

export default defineConfig({
  plugins: [
    ...
    eslint({
      include:['src/**']
    })
  ],
  ...
});

4-修改.eslintrc.cjs

cross-env会将定义的环境变量暴露在env上,我们获取并做三元判断即可

const mode = process.env.NODE_ENV
module.exports = {
  ...
  rules: {
      "no-console": mode === 'production' ? "error" : "off",
      ...
  }
};

prettier

相关文档看这里:prettiereslint-config-prettiereslint-plugin-prettier

1.安装依赖

yarn add prettier eslint-config-prettier eslint-plugin-prettier -D

2.创建配置文件:.prettierrc.js

以我司的某个项目为例

module.exports = {
  // 一行最多 150 个字符
  printWidth: 150,
  // 使用 4 个空格缩进
  tabWidth: 4,
  // 不使用 tab 缩进,而使用空格
  useTabs: false,
  // 行尾需要有分号
  semi: true,
  // 使用单引号代替双引号
  singleQuote: true,
  // 末尾使用逗号
  trailingComma: 'es5',
  // 箭头函数,只有一个参数的时候,也需要括号
  arrowParens: 'always',
}

3.修改 .eslintrc.js 配置

强制当和eslint冲突时,以prettier为准

module.exports = {
    ...

    extends: [
        ...
        'prettier',
        'plugin:prettier/recommended'
    ],
    ...
};

提交规范

我们需要在提交代码前对代码质量、代码格式和commit信息进行约束,为此,我们需要先注册commit提交前钩子

1-安装husky

文档看这里:传送门

yarn add husky -D

2-初始化husky

在package.json文件夹下新增prepare脚本,并立即运行一次

"scripts": {
    ...
    "prepare": "husky install"
  },

3-注册hook

我们使用pre-commit钩子来拦截提交行为,

npx husky add .husky/pre-commit "npm run check"
git add .husky/pre-commit

此时,当git commit发生时,将会调用check脚本,但这默认事针对全部文件的,因此我们需要借助另一个npm包帮我们把当前更改的文件提取出来单独校验

1-安装lint-staged

文档看这里:传送门

yarn add lint-staged -D

2-修改package.json配置

"lint-staged": {
    "*.{js,ts,vue}": [
      "npm run eslint",
      "prettier --parser=typescript --write"
    ]
}

3-与husky关联

将lint-staged作为check的指向脚本

"scripts": {
    ...
    "check": "lint-staged"
  },

最后我们来对commit提交格式进行约束,这可以通过commitlint来帮我们完成,文档看这里:传送门

1-安装

yarn add @commitlint/config-conventional @commitlint/cli -D

2-将其校验位置放在check脚本执行前

npx husky add .husky/commit-msg  'npx --no -- commitlint --edit ${1}'

3-创建配置文件commitlint.config.cjs

module.exports = {
    extends: ['@commitlint/config-conventional'],
    rules: {
        'type-enum': [
            2,
            'always',
            [
                'feature', // 迭代功能
                'conf', // 修改构建配置
                'fixbug', // 修复bug
                'refactor', // 代码重构
                'optimize', // 代码优化
                'style', // 仅修改样式文件
                'docs', // 文档补充说明
            ],
        ],
        'header-max-length': [0, 'always', 72], //限制最长72
    },
};

4-测试使用

迭代更新

2023-6-28

有jy反馈使用less会报错(将eslint插件替换成了@nabla/vite-plugin-eslint),已修复,需要的jy重新从仓库clone哦🥰

2023-7-28

增加pinia持久化存储

  • 安装依赖
yarn add @web-localstorage-plus/pinia
  • main.ts中设置持久化
import createStorage from 'web-localstorage-plus';
import setPiniaPersist from '@web-localstorage-plus/pinia';
// 设置根存储库
createStorage({
    rootName: 'spp-storage',
});
// 将pinia中的数据持久化到本地
setPiniaPersist(pinia);
  • vite.config.ts中引入热更新插件
import { getPlugin } from '@web-localstorage-plus/pinia';
const piniaHmrPlugin = getPlugin('vite');
export default defineConfig({
    ...,
    plugins:[piniaHmrPlugin(resolve(__dirname, 'src/store'))]
})

如果本文对您有用,希望能得到您的点赞和收藏

订阅专栏,每周更新2-3篇类型体操,等你哟😎