【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 工具仓库

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

总结

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

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

相关推荐
IT女孩儿1 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡2 小时前
commitlint校验git提交信息
前端
天天进步20152 小时前
Vue+Springboot用Websocket实现协同编辑
vue.js·spring boot·websocket
虾球xz2 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇2 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒2 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员3 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐3 小时前
前端图像处理(一)
前端
程序猿阿伟3 小时前
《智能指针频繁创建销毁:程序性能的“隐形杀手”》
java·开发语言·前端
疯狂的沙粒3 小时前
对 TypeScript 中函数如何更好的理解及使用?与 JavaScript 函数有哪些区别?
前端·javascript·typescript