【Vue3 前端项目工程化】 集成相关生态工具链及其改造

前言

自从在项目中把 Webpack 迁移到 Vite 后,开发体验迅速提升,一个字总结:就是快!!!新的项目也都用了 Vite 搭建项目。 刚好最近项目刚发完版得一两天清闲,索性也浅浅记录一下~ 手把手教你从 0-1 搭建一套规范的 Vue3 + Vite + Typescript + Unocss 前端项目工程化项目。入门最全面最系统的方法就是查看并实践官方文档。

如果跟着一步一步搭建,建议顺序:

项目初始化 => 代码规范 => 提交规范 => Vite 基础配置 => 集成 Element Plus => 集成 Unocss => 集成 Vue Router、Pinia

本项目完整代码托管地址 GitHub 仓库

技术栈

环境

  • nodejs - 20.x
  • pnpm - 7.x
sh 复制代码
# 查看 node 版本
node -v

# 更新 node 版本到最新
nvm install stable

架构搭建

项目初始化

1、借助脚手架的力量,快速初始化一个项目。

sh 复制代码
# pnpm
pnpm create vue@latest

# npm
npm create vue@latest

# 安装指定版本
npm create vue@3.3

按照指令安装相关支持工具:create-vue - Vue 官方的项目脚手架工具,已经帮我们做了很多事情,它提供了基本的项目结构、构建流程和开发工具链等。

js 复制代码
Vue.js - The Progressive JavaScript Framework

✔ Project name: ... vue3-template // 自定义项目名称
✔ Add TypeScript? ... No / Yes // 是否支持 TypeScript
✔ Add JSX Support? ... No / Yes // 是否支持 JSX
✔ Add Vue Router for Single Page Application development? ... No / Yes // 是否支持 Vue Router
✔ Add Pinia for state management? ... No / Yes // 是否使用 Pinia 状态管理器
✔ Add Vitest for Unit Testing? ... No / Yes // 是否使用 Vitest 进行单元测试
✔ Add an End-to-End Testing Solution? › No // 是否使用端到端的测试方案
✔ Add ESLint for code quality? ... No / Yes // 是否使用 ESLint
✔ Add Prettier for code formatting? ... No / Yes // 是否使用 Prettier

项目脚手架的功能是很强大的,它为项目集成了多种相关生态工具:TypeScript、JSX、Vue Router、Pinia、Vitest、ESLint、Prettier。节约了开发者的时间,让我们更加关注业务代码的开发,接下来对这些工具进行改造,让它更规范、功能更强大,发挥其本质作用。
项目默认目录如下

2、 进入项目,安装依赖,并启动项目:

sh 复制代码
cd vue3-tamplate # 进入项目
pnpm i # 安装依赖
npm run dev # 启动项目

项目骨架搭建完毕,接下来为项目集成相关生态工具,以及对应的实用方法。

Vite 基础配置

Vite 一种新型前端构建工具,主要基于浏览器原生的 ESM 方式,真正实现了按需加载,节约了项目构建的时间开销。 更多介绍及配置请查看官网 vitejs.dev/config

对应配置文件是 vite.config.ts,位于项目根目录,在项目初始化的时候去读取。

本项目针对公共基础路径、自定义路径别名、服务器选项、构建选项等做了如下基础配置:

ts 复制代码
// vite.config.ts
import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'

export default defineConfig({
  base: './',  // 设置打包路径
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url)), // 设置 `@` 指向 `src` 目录
      '#': fileURLToPath(new URL('./types', import.meta.url)),  // 设置 `#` 指向 `types` 目录
    }
  },
  plugins: [
    vue(),
    vueJsx(),
  ],
  server: {
    open: true, // 服务启动自动打开浏览器
    host: '127.0.0.1', // 设置服务启动地址
    port: 5000, // 设置服务启动端口号
    cors: true, // 允许跨域
    // 设置代理
    proxy: {
      '/api': {
        target: 'http://xxxxxxx',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
      },
    },
  },
  build: {
    target: 'es2015', // 设置最终构建的浏览器兼容目标
    sourcemap: false, // 构建后是否生成 source map 文件
    chunkSizeWarningLimit: 2000, //  chunk 大小警告的限制
    reportCompressedSize: false,  // 启用/禁用 gzip 压缩大小报告
    outDir: 'dist', //设置打包输出目录,默认dist
    assetsDir: 'assets', //设置静态文件输出目录,默认assets
    assetsInlineLimit: 4096, // 设置引用资源 base64 内联的最大值,默认4096=4kb
  },
})

集成 Element Plus

项目脚手架没有为我们集成 UI组件库 解决方案,需要自己集成,本项目选择了基于 Vue3 的 Element Plus 组件库,具体可看个人使用习惯来选择。

Element Plus 的使用有两种方案:完整引入按需导入。本项目使用官方推荐的 按需导入。

按需导入需要安装 unplugin-vue-componentsunplugin-auto-import 这两款插件。

1、安装依赖

sh 复制代码
pnpm add element-plus

2、vite 配置

sh 复制代码
pnpm add -D unplugin-vue-components unplugin-auto-import

vite.config.ts 中添加按需导入配置,如下:

ts 复制代码
// vite.config.ts 按需导入
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
  
  
  plugins: [
    // ...
    AutoImport({
      resolvers: [ElementPlusResolver()], // 自定义解析器,配置 unplugin-vue-components 使用
      dts: 'types/auto-imports.d.ts', // 自动生成 'auto-imports.d.ts'全局声明文件
      dirs: ['./src'], // 此目录下相关 API 自动导入
    }),
    Components({
      dts: './types/components.d.ts', // 按需导入组件,相关组件声明放置于 components.d.ts
      resolvers: [ElementPlusResolver()],
    }),
  ],

3、Volar 支持

tsconfig.json 中通过 compilerOptions.type 指定全局组件类型。

json 复制代码
// tsconfig.json
{
  "compilerOptions": {
    // ...
    "types": ["element-plus/global"]
  }
}

4、使用体验

新建一个测试组件,并使用 element-plus 组件:

ts 复制代码
// src/views/test_module/element_puls.vue
<template>
 <div>
   <el-row class="mb-4">
     <el-button>Default</el-button>
     <el-button type="primary">Primary</el-button>
     <el-button type="success">Success</el-button>
     <el-button type="info">Info</el-button>
     <el-button type="warning">Warning</el-button>
     <el-button type="danger">Danger</el-button>
   </el-row>
   <el-row>
     <el-select v-model="value" class="m-2" placeholder="Select" size="middle">
       <el-option
         v-for="item in options"
         :key="item.value"
         :label="item.label"
         :value="item.value"
       />
     </el-select>
   </el-row>
 </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'

defineOptions({
 name: 'ElementPlusComponent'
})

const value = ref('')

const options = [
 {
   value: 'Option1',
   label: 'Option1',
 },
]
</script>

可以看到,页面中用到了 el-button el-select 组件,但是并没有显式的引入也能正常使用。

集成 Vue Router

Vue Router 是 Vue.js 的官方路由。为 Vue.js 提供富有表现力、可配置的、方便的路由。更多介绍及配置请查看官网 router.vuejs.org

项目脚手架已经为我们集成了 Vue Router,以及为我们搭建好配置文件和挂载路由配置,接下来看一下具体怎么改造以及使用。

1、路由目录改造

路由相关配置在 router 文件夹下,一个完整的项目路由应该是按【模块/业务功能】去分开定义的,这样才易于维护,并且有路由守卫,有404路由等。本项目搭建的基础路由目录如下:

json 复制代码
|-- src
    |-- router
       |-- index.ts         // 路由入口文件
       |-- error_route.ts   // 错误页路由,如:404
       |-- guards.ts        // 路由守卫文件
       |-- modules
          |-- modules1      // 模块1
          |-- basic         // 基础模块
             |-- index.ts   // 基础模块-入口文件

路由改造涉及到一些页面的变动,改动幅度较大,因篇幅原因,只放出部分关键代码,具体代码移步至仓库查看。

2、路由入口文件改造

ts 复制代码
// src/router/index.ts 
import type { App } from 'vue'
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'

import { BasicRoutes } from './modules/basic/index' // 导入基础模块路由
import { errorRoutes } from './error_route' // 导入错误页路由
import { createRouterGuards } from './guards' // 导入路由守卫

// 组合路由
const constantRouter: RouteRecordRaw[] = [...BasicRoutes, ...errorRoutes]

// 创建路由实例
const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  // 禁止尾部斜杠
  strict: true,
  scrollBehavior: () => ({ left: 0, top: 0 }),
  routes: constantRouter
})

// 初始化路由函数,并注入路由守卫
export function setupRouter(app: App<Element>) {
  app.use(router)
  createRouterGuards(router)
}

export default router

3、路由守卫改造

ts 复制代码
// src/router/guards.ts 
import { Router } from 'vue-router'

const whiteList = ['/home', '/404'] // 白名单列表,不用校验,直接放行

export function createRouterGuards(router: Router) {
   // 全局导航前置守卫:beforeEach,还有 afterEach、beforeResolve
  router.beforeEach(async (to, _from, next) => {
    const hasToken = false // 用于测试
    if (hasToken) {
      next()
    } else {
      const isValid = whiteList.find((path) => to.path.startsWith(path))
      if (isValid) {
        next()
      } else {
        next(`/home`)
      }
    }
  })
  router.onError((error) => {
    console.log(error, '路由错误')
  })
}

4、错误页路由配置

ts 复制代码
// src/router/error_route.ts 
import { RouteRecordRaw } from 'vue-router'

export const errorRoutes: RouteRecordRaw[] = [
  {
    path: '/404',
    name: '404',
    component: () => import('@/views/error_page/404_page.vue'),
    meta: {
      hidden: true,
    },
  },
  {
    path: '/:pathMatch(.*)*', // 匹配不到定义的路由,跳转 404 路由
    redirect: '/404',
    name: 'not-found',
  },
]

此外,项目的入口文件、模块基础路由、tsconfig文件等都做了改动。

5、初始化改造

ts 复制代码
# src/main.ts

import { setupRouter } from './router'

const app = createApp(App)
setupRouter(app)

6、使用体验

访问首页:

访问 不存在的路由:

集成 Pinia

Pinia 是开箱即用的模块化设计,基于vue3的 组合式 API 构建,支持多个store实例,更加灵活,可读性更高,基于es module 的方式,可直接通过单个store实例对象进行引用。

项目脚手架已经为我们集成了 Pinia,接下来通过改造它来看一下具体用法:

1、目录改造

json 复制代码
|-- src
    |-- stores
       |-- index.ts         // srore入口文件
       |-- modules
          |-- modules1      // 模块1
          |-- counter       // 计数器模块
             |-- index.ts   

2、counter 模块编写

ts 复制代码
// stores/modules/counter/index.ts
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'


// 基于组合式API,像写组件一样写store
export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const doubleCount = computed(() => count.value * 2)
  function increment() {
    count.value++
  }

  return { count, doubleCount, increment }
})

3、counter 模块引用

ts 复制代码
// src/views/test_module/counter_page/index.ts
<template>
  <div class="flex-col flex-y-center">
    <p>
      count: {{ count }}
      <el-button type="primary" @click="onPlusOne">plus one</el-button>
    </p>
    <p>double count: {{ doubleCount }}</p>
  </div>
</template>

<script setup lang="ts">
import { useCounterStore } from '@/stores/modules/counter'

defineOptions({
  name: 'CounterPage',
})
// 创建 counter 实例
const counterStore = useCounterStore()
// 引用 counter 实例里的属性 count,具有响应式
const count = computed(() => counterStore.count)
// 引用 counter 实例里的属性 doubleCount,具有响应式
const doubleCount = computed(() => counterStore.doubleCount)

const onPlusOne = () => {
  counterStore.increment()  // 调用 counter 实例里的方法
}
</script>

4、页面效果

集成 Unocss

UnoCSS - 具有高性能且极具灵活性的即时原子化 CSS 引擎。出现的契机以及原理可以读下作者的文章 重新构想原子化 CSS

原子化 CSS 是一种 CSS 的架构方式,它倾向于小巧且用途单一的 class,并且会以视觉效果进行命名。所有功能可以通过预设和内联配置提供。

1、依赖安装

  • unocss - 核心包。
  • @unocss/reset - 样式重置包。
  • @unocss/runtime - 运行时包:提供 CDN 构建,在浏览器中运行 UnoCSS 引擎。
  • @unocss/transformer-directives 指令转换器: 可用指令 - @apply、@screen 和 theme()。
sh 复制代码
pnpm add -D unocss @unocss/reset @unocss/runtime @unocss/transformer-directives

2、unocss 配置

在根目录下新建 unocss.config.ts,用于配置 unocss 的规则,并添加如下配置:

ts 复制代码
// `uno.config.ts`
import { defineConfig, presetUno, presetAttributify } from 'unocss'
import transformerDirectives from '@unocss/transformer-directives'

export default defineConfig({
  presets: [
    presetUno(),
    presetAttributify(),
  ],
  transformers: [transformerDirectives()], // 使用指令,如 @apply
  // 一些实用的自定义规则
  rules: [
    [/^m-h-(.+)$/, ([, d]) => ({ 'margin-left': `${d}`, 'margin-right': `${d}` })],
    [/^m-v-(.+)$/, ([, d]) => ({ 'margin-top': `${d}`, 'margin-bottom': `${d}` })],
    [/^p-h-(.+)$/, ([, d]) => ({ 'padding-left': `${d}`, 'padding-right': `${d}` })],
    [/^p-v-(.+)$/, ([, d]) => ({ 'padding-top': `${d}`, 'padding-bottom': `${d}` })],
    [/^font-s-(.+)$/, ([, d]) => ({ 'font-size': `${d}` })],
    [/^wh-(.+)$/, ([, d]) => ({ width: `${d}`, height: `${d}` })],
  ],
  // 一些实用的自定义组合
  shortcuts: {
    'wh-full': 'w-full h-full', // width: 100%, height: 100%
    'flex-center': 'flex justify-center items-center', // flex布局居中
    'flex-x-center': 'flex justify-center', // flex布局:主轴居中
    'flex-y-center': 'flex items-center', // flex布局:交叉轴居中
    'text-overflow': 'overflow-hidden whitespace-nowrap text-ellipsis',  // 文本溢出显示省略号
    'text-break': 'whitespace-normal break-all break-words', // 文本溢出换行
  },
})

3、导入

在 main.ts 文件中,添加如下配置:

ts 复制代码
import 'virtual:uno.css'
import '@unocss/reset/normalize.css'

4、使用体验

如果给一个元素定义 margin-top: 1rem

平时的做法是:先命名一个 class,如 margin-top-1,然后在样式文件在声明 .margin-top-1 {margin-top: 1rem }

ts 复制代码
// test
<template>
 <el-button class="margin-top-1"></el-button>
</template>

<style scoped>
 .margin-top-1 {margin-top: 1rem }
</style>

但是使用 unocss 就不用这么麻烦,因为 unocss 预设了大量的样式,我们直接使用类名就可以达到想要的效果,如下:

鼠标悬停到mt-1类名上,就可以看到预设的默认值,在浏览器调试面板的 style 中也可以看得到。

这样即避免了命名的繁琐,又减小了打包的体积。查询预设样式的类型,可以在 unocss.dev/interactive 查询。

本项目中使用了 Scss CSS预处理器, 所以还需安装 对应的依赖包:

sh 复制代码
pnpm add -D scss

代码规范

集成 ESlint + Prettier

ESLint 是一个用于识别和报告在 ECMAScript/JavaScript 代码中发现的不符合规则的工具,主要用于检测代码,保证代码质量,使代码更加一致并避免错误。

Prettier 用于代码格式的校验。这两种工具经常一起配合使用,达到代码检测、代码格式化的目的。

但两者在使用过程中,会因为规则不同,有出现冲突的可能性,所以需要通过插件加强两者的配合。

项目脚手架已经为我们集成了这两个工具,但目前依然没有达到我们想要的效果,存在以下问题:格式错乱,不提示, 保存不会自动纠正。

该段代码中格式错乱,缩进符长度不统一,编辑器没有任何提示,文件保存的时候也没有进行格式化。

接下来开始改造:

1、相关依赖安装

sh 复制代码
pnpm add vue-eslint-parser @typescript-eslint/eslint-plugin @typescript-eslint/parser -D
  • vue-eslint-parser:基于ESLint,对.vue文件的 ESLint 语法进行检测,使 vue 项目遵循统一的代码规范和风格。
  • @typescript-eslint/eslint-plugin: ESLint插件,为TypeScript项目提供lint规则。
  • @typescript-eslint/parser: ESLint解析器,用于将TypeScript代码解析为与ESLint兼容的节点。

2、eslintrc 配置

js 复制代码
// .eslintrc.cjs
// ...
module.exports = {
  root: true,
  extends: [
    'plugin:vue/vue3-recommended',
    'plugin:@typescript-eslint/recommended',
    'prettier',
    'plugin:prettier/recommended',
  ],
  plugins: ['@typescript-eslint'],
  parser: 'vue-eslint-parser',
  parserOptions: {
    ecmaVersion: 2020,
    // 指定eslint解析器
    parser: '@typescript-eslint/parser',
    // 允许使用 import
    sourceType: 'module',
    // 允许解析 jsx
    ecmaFeatures: {
      jsx: true,
    },
  },
}

3、prettier.json 配置

json 复制代码
{
  "$schema": "https://json.schemastore.org/prettierrc",
  "printWidth": 100,
  "tabWidth": 2,
  "useTabs": false,
  "semi": false,
  "vueIndentScriptAndStyle": true,
  "singleQuote": true,
  "quoteProps": "as-needed",
  "bracketSpacing": true,
  "trailingComma": "es5",
  "jsxBracketSameLine": false,
  "jsxSingleQuote": false,
  "arrowParens": "always",
  "insertPragma": false,
  "requirePragma": false,
  "proseWrap": "never",
  "htmlWhitespaceSensitivity": "ignore",
  "endOfLine": "lf",
  "rangeStart": 0
}

4、使用效果

配置完成之后,一些文件开始飘红,看下之前的那段代码:

点击保存之后,格式化成功:

完成了 ESlint 和 Prettier 的集成配置,就统一了团队协作的代码规范,不会因为个人的编码习惯不同而导致项目有不同的书写格式。

Husky 配置

Husky 是 Git hooks 的工具,当 git commit 时,pre-commit 钩子会启动,可以让我们在提交代码之前去执行一些脚本。

比如以下代码,声明了一个变量,但没有使用,这种 ESlint 检测出来的错误,我们想要阻止它的提交。但是现有配置没有能够达到这一点,依然能提交成功。

开始改造:

1、安装依赖、初始化

sh 复制代码
# 安装 husky
pnpm add husky -D

# 生成 .husky 文件夹
npx husky-init install

2、修改 pre-commit

.husky/pre-commit

js 复制代码
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

pnpm lint

本质上就是执行以下脚本, 进行代码的检测:

json 复制代码
"script" {
  "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
}

3、执行效果

配置好了之后,再次提交代码:

可以看到,在提交代码之前,会先去执行 pnpm lint,检测代码,检测到不符合规范的代码,会自动抛出错误,阻止代码的提交。目的达成。

lint-staged 配置

lint-staged 一个能过滤出 Git 代码暂存区文件(被 git add 的文件)的工具。

了解 Git 工作流的都知道,我们在本地改动的代码存在的区域叫 Workspace - 工作区,git add 之后,工作区的代码就会存到 Stage - 暂存区,暂存区的代码通过 git commit 提交至 Repository -本地版本库(不了解的可以查看之前的一篇文章 Git 常用命令手册,里面画了 git 工作流图)。

在上述【集成 ESlint + Prettier】中,通过 git hooks 在提交代码至本地版本库前去执行一些脚本,这些脚本是针对 工作区和暂存区 的代码都做一个检查,但实际上,我们只能提交 暂存区 的代码,所以没必要对工作区的代码也执行这些脚本,如果工作区改动较大,可能会耗时较长,所以通过 lint-staged 过滤出暂存区的代码。

1、添加配置文件

通过命令行,在 .husky 文件夹下新建 pre-commit 文件,写入配置:

sh 复制代码
pnpm husky add .husky/pre-commit "pnpm lint-staged --allow-empty $1"

2、添加 lint-staged 命令

在 package.json 里添加如下:

json 复制代码
{
  // ...
  "lint-staged": {
    "*.{vue,js,ts,jsx,tsx}": [
      "eslint --fix"
    ]
  }
}

3、使用体验

在两个文件中分别声明了一个变量,均不被使用,对其中一个文件 执行 git add 后,在进行提交,可以看到:只有暂存区的变更进行了 eslint 校验,检测到不符合规范的代码时,抛出了异常。目的达到。

提交规范

commilint 配置

配置好 Husky 之后,接下来需要约定 commit 规范。按照约定的规范提交代码,有助于我们分析提交的代码,在后续生成的 changelog 文件和语义发版中根据 commit 信息可以了解大致的改动。

1、安装依赖

sh 复制代码
pnpm add -D @commitlint/config-conventional @commitlint/cli 

2、添加钩子函数

sh 复制代码
pnpm husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"'

3、创建新配置

创建 commitlint.config.js, 写入配置:

js 复制代码
{
  "extends": ["@commitlint/config-conventional"]
}

4、配置完成,测试

git commit -m 'add commitlint 中的 add commitlint 信息不符合AngularJS提交规范,所以有错误提示:类型和简述不能为空。

外话 :之前本人也自己写了一个 git commit 命令行工具,算是一个脚手架的初尝试。主要是针对提交的 commit 信息去校验是否符合 AngularJS 提交规范,不符合则禁止提交,不足之处就是功能不够强大,也不支持 git hooks。大家有兴趣可以看看,当作脚手架入门教程。

git commit 工具仓库

超详细的前端脚手架入门篇

总结

本文从技术选型到架构搭建、从代码规范约束到提交信息规范约束、也对项目中相关生态工具链做了使用讲解,一步一步带领大家实现规范的前端工程化环境。

因篇幅较长,难免会出现错误,希望大家多多指正!

相关推荐
崔庆才丨静觅1 天前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60611 天前
完成前端时间处理的另一块版图
前端·github·web components
掘了1 天前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅1 天前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅1 天前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅1 天前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment1 天前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅1 天前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊1 天前
jwt介绍
前端
爱敲代码的小鱼1 天前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax