Vue3 + Vite 前端工程化-基础篇

2,958 阅读6分钟

前言

Vue3 距首次发布时间近一年了,Vite 也从 1.0 到了 2.x,周边的生态,技术方案足够成熟了,是时候使用新技术来优化开发体验了,因此有了本篇文章。

初始化项目

通过官方脚手架初始化项目

# 输入项目名,选择模板,选择是否支持 ts
npm init vite@latest

# 也可直接指定快速生成
npm init vite@latest json-cms --template vue-ts

执行结束后进入项目目录,安装依赖后执行 npm run dev 即可秒开项目

cd json-cms

npm install
npm run dev

查看项目结构,对于生成的目录结构不足以支持项目的复杂度,因此我们结构进行扩展,扩展后结构为右图

3.png

Vite 定制化配置

在初始化的项目中 vite.config.js 只是引入了提供 Vue 3 单文件组件支持的 plugin,接下来推荐一些优秀的 vite-plugin,更多 plugin 详见 awesome-vite

@vitejs/plugin-legacy

为打包后的文件提供传统浏览器兼容性支持。因为 vite 是基于现代浏览器支持的 ESM 机制,所以构建后文件模块仍是 ESM,如果需要支持旧版浏览器就需要使用 @vitejs/plugin-legacy

安装及使用

npm i -D @vitejs/plugin-legacy
// vite.config.ts
import { defineConfig } from 'vite'
import legacy from '@vitejs/plugin-legacy'

export default defineConfig({
  plugins: [
    legacy({
      targets: ['defaults', 'not IE 11'],
    }),
  ],
})

vite-plugin-element-plus

为 ElementPlus 提供按需引入能力。全量导入 ElementPlus 导致构建包的体积过大,按需引入有效的减小包的体积。此包的原理是动态将每个按需引入的组件 css 写入。

import { ElButton } from 'element-plus'

      ↓ ↓ ↓ ↓ ↓ ↓

import { ElButton } from 'element-plus'
import 'element-plus/es/components/button/style/css'

安装及使用

npm i -D vite-plugin-element-plus
// vite.config.ts
import { defineConfig } from 'vite'
import importElementPlus from 'vite-plugin-element-plus'

export default defineConfig({
  plugins: [
    // @ts-ignore 此处暂时需要使用 ignore
    // 原因是包内部的 options 未做非必填兼容
    // 目前已有人提了 PR,未合并,使用可以观望下
    importElementPlus(),
  ],
})

@vitejs/plugin-vue-jsx

提供 Vue 3 JSX & TSX 支持(通过 专用的 Babel 转换插件)。

安装及使用

npm i -D @vitejs/plugin-vue-jsx
// vite.config.ts
import { defineConfig } from 'vite'
import vueJsx from '@vitejs/plugin-vue-jsx'

export default defineConfig({
  plugins: [
    vueJsx({
      // options 参数将传给 @vue/babel-plugin-jsx
    }),
  ],
})

rollup-plugin-visualizer

可视化并分析构建包,查看哪些模块占用空间大小,以此来优化构建包的大小。这是一个 Rollup 的 plugin,推荐这个也是 vite 的一个特性,vite 默认已经支持大部分的 Rollup 的 plugin,从这点来看,vite 的 plugin 库更加丰富了。

安装及使用

npm i -D rollup-plugin-visualizer
// vite.config.ts
import { defineConfig } from 'vite'
import visualizer from 'rollup-plugin-visualizer'

export default defineConfig({
  plugins: [visualizer()],
})

vite-plugin-html

为 index.html 扩展了动态能力,提供压缩及 EJS 模板功能,动态注入,如果不了解 EJS,移步查看

安装及使用

npm i -D vite-plugin-html
// vite.config.ts
import { defineConfig } from 'vite'
import html from 'vite-plugin-html'

// 以下是实现动态设置标题,及注入 js 路径
export default defineConfig({
  plugins: [
    html({
      inject: {
        injectData: {
          title: 'JSON CMS',
          tinymce: '/js/tinymce/tinymce.min.js',
        },
      },
    }),
  ],
})

编译前

<head>
  <meta charset="UTF-8" />
  <link rel="icon" href="/favicon.ico" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title><%- title %></title>
  <script src="<%- tinymce %>" rel="preload"></script>
</head>

编译后

<head>
  <meta charset="UTF-8" />
  <link rel="icon" href="/favicon.ico" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>JSON CMS</title>
  <script src="/js/tinymce/tinymce.min.js" rel="preload"></script>
</head>

基于 husky + lint-staged 项目规范化

husky 是一个让 Git hooks 更简单好用的工具。安装后,它会自动在仓库中的 .git/ 目录下增加相应的钩子,比如 pre-commit 钩子就会在你执行 git commit 的触发。

那么我们可以在 pre-commit 中实现一些比如 lint 检查、单元测试、代码美化等操作。当然,pre-commit 阶段执行的命令当然要保证其速度不要太慢,每次 commit 都等很久也不是什么好的体验。

lint-staged,一个过滤出 Git 代码暂存区文件(被 git add 的文件)的工具。这个很实用,因为我们如果对整个项目的代码做一个检查,可能耗时很长,如果是老项目,要对之前的代码做一个代码规范检查并修改的话,这可能就麻烦了呀,可能导致项目改动很大。

所以 lint-staged,对团队项目和开源项目来说,是一个很好的工具,它是对个人要提交的代码的一个规范和约束。

编码规范

通过 eslint prittiter stylelint 进行编码规范,修复。

安装及使用

npm i -D eslint prittiter stylelint eslint-config-prettier eslint-define-config eslint-plugin-prettier eslint-plugin-vue vue-eslint-parser @typescript-eslint/eslint-plugin @typescript-eslint/parser

以下为 eslint 和 prittier 配置文件

// .eslintrc.js
const { defineConfig } = require('eslint-define-config')
module.exports = defineConfig({
  root: true,
  env: {
    browser: true,
    node: true,
    es6: true
  },
  parser: 'vue-eslint-parser',
  parserOptions: {
    parser: '@typescript-eslint/parser',
    ecmaVersion: 2020,
    sourceType: 'module',
    jsxPragma: 'React',
    ecmaFeatures: {
      jsx: true
    }
  },
  extends: [
    'plugin:vue/vue3-recommended',
    'plugin:@typescript-eslint/recommended',
    'prettier'
    'plugin:prettier/recommended'
  ],
  rules: {
    // ...
  }
})
// prettier.config.js
module.exports = {
  printWidth: 100,
  tabWidth: 2,
  semi: false,
  singleQuote: true,
  bracketSpacing: true,
  trailingComma: 'none',
  jsxBracketSameLine: false,
  jsxSingleQuote: false,
  arrowParens: 'always',
  insertPragma: false,
  requirePragma: false,
  proseWrap: 'never',
  htmlWhitespaceSensitivity: 'strict',
}

有了相关的 lint 的配置文件,接下需要配置 huskylint-staged,这里提供一行快速配置的命令

npx mrm@2 lint-staged

这条命令将根据 package.json 依赖中的 eslintprettier 安装配置 huskylint-staged,这样 Git hooks 就配好了,将新增的配置 git add 后,再执行 git commit 就会触发 lint 校验,会将有问题的文件修复并再次 git add 到暂存区最终完成 commit 操作。

{
  "scripts": {
    "prepare": "husky install"
  },
  "devDependencies": {
    // ...
    "eslint": "^7.32.0",
    "husky": "^7.0.2",
    "lint-staged": "^11.1.2",
    "prettier": "^2.4.0"
    // ...
  },
  "lint-staged": {
    "*.js": "eslint --cache --fix",
    "*.{js,css,md}": "prettier --write"
  }
}

代码提交规范

目前提交规范使用最广泛的就是 conventional commits (约定式提交), coding 平台使用的也是约定式提交,coding 平台的校验是开发者 push 的时候才会触发,这时候已经执行过 commit 了,因此我们需要将提交规范前置,在 commit 时进行校验并给出修改提示。

conventional-commits.png

以上效果主要需要 commitizen 实现,全局安装或作为 npm script 使用,官方推荐 Commitizen-friendly 方式使用,全局安装后初始化配置。

# 全局安装
npm install -g commitizen

# Commitizen-friendly 方式
commitizen init cz-conventional-changelog --save-dev --save-exact
// package.json
{
  "scripts": {
    "commit": "cz"
  },
  "config": {
    "commitizen": {
      "path": "cz-conventional-changelog"
    }
  }
}

通过以上配置,可以用 git cznpm run commit 替换 git commit 命令,实现规范提交的选择。

conventional-commits.png

但是如果有人执意要使用 git commit 自定义提交信息也是没有办法,因此需要增加 commitlint 来检查提交信息是否符合规范。

安装使用过程

npm i -D @commitlint/config-conventional @commitlint/cli

# 因为上面已经安装了 husky,所以我们只要再新增一个 hook
npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"'

这样就实现了提交信息的检查。另外可以通过 cz-customizable 设置成中文的提交提示。

根据版本生成 changelog

正是因为有了以上的“约定式提交”规范,我们还可以通过 conventional-changelog-cli 生成对应版本的 ChangeLog。

{
  "scripts": {
    "log": "conventional-changelog -p angular -i CHANGELOG.md -s"
  }
}

Vue3 开发生态

路由

vue-router4.x 第一个 4.0 版本已经在 20 年 12 月已经发布,至今已相对稳定,使用方式相对 Vue2 时变化不是很大。通过官方提供的迁移指南能够对比出与 3.x 版本的区别,详情移步至迁移指南

状态管理

状态管理有了新的选择,可以选择 pinavuex

  • pinia 是根据 vuex5.x 的提案设计的,可以说是面向未来的 vuex,该提案提出了非集中式的 store 管理模型,没有嵌套的 store 模块,移除了 mutations,留下 stategettersactions,我个人也比较看好这种模式。
  • vuex4.x 正式版发布在 21 年年初,作为官方状态管理,想必大家也都很熟悉,就不做过多介绍,使用方式相对 Vue2 时变化也不大。

UI 组件库

可供选择的 UI 组件库也很丰富

  • PC 端:
  • M 端:
    • 出自京东零售团队的 nutui
    • 出自有赞的 vant

另外与 CSS 相关的好用的工具

  • tailwindcss 一个可以让你不写一行 CSS 就能实现布局等一系列操作的工具,

tailwindcss.gif

Vue3 & Vite 开发注意事项

本节会介绍在实际项目遇到的问题,以此作为开发注意事项,仅组合式 API 相关。

Vue3.2 发布,SFC 新特性

  • 支持 <script setup> 语法糖,在组合式 API 中,无需将模板需要的值 return 处理,使代码更简洁。
  • 支持 <style> v-bind 语法糖,可以直接传值至 <style> 中,使用更方便。
<script setup>
  import { ref } from 'vue'

  const color = ref('red')
</script>

<template>
  <button @click="color = color === 'red' ? 'green' : 'red'">
    Color is: {{ color }}
  </button>
</template>

<style scoped>
  button {
    color: v-bind(color);
  }
</style>
  • 组件的 propsemit 写法发生变化以及在 TypeScript 中的类型定义。
// js 写法
<script setup>
const props = defineProps({
  foo: String
})

const emit = defineEmits(['change', 'delete'])
// setup code
</script>
// ts 写法
<script lang="ts" setup>
const props = defineProps<{
  foo: string
  bar?: number
}>()

const emit = defineEmits<{
  (e: 'change', id: number): void
  (e: 'update', value: string): void
}>()
</script>

Vue3 废弃 filter 及替代方案

从 Vue 3.0 开始,过滤器已移除,且不再支持。

  • 在 2.x 中,可以使用过滤器来处理通用文本格式。
<template>
  <p>今天是 {{ date | weeekFormat }}</p>
</template>

<script>
  export default {
    props: {
      date: {
        type: [Date | String | Number],
        required: true,
      },
    },
    filters: {
      /**
       * 返回 星期五
       * */
      weeekFormat(value) {
        return new Intl.DateTimeFormat('zh-CN', { weekday: 'long' }).format(
          new Date(value)
        )
      },
    },
  }
</script>
  • 在 3.x 中,需要用方法调用或计算属性来替换它们。
<template>
  <p>今天是 {{ date | weeekFormat }}</p>
</template>

<script>
  export default {
    props: {
      date: {
        type: [Date | String | Number],
        required: true,
      },
    },
    computed: {
      /**
       * 返回 星期五
       * */
      weeekFormat() {
        return new Intl.DateTimeFormat('zh-CN', { weekday: 'long' }).format(
          new Date(this.date)
        )
      },
    },
  }
</script>

另外全局过滤器可以使用 globalProperties 实现。较好的实践方式是实现自定义 plugin,在 plugin 中定义全局属性。

// /plugin/filters.ts
import type { App } from 'vue'
import { timeFormat } from '@/utils'

export default {
  install: (app: App) => {
    app.config.globalProperties.$filters = {
      format(value: string | Date | number) {
        return timeFormat(value)
      },
    }
  },
}
// main.ts
import { createApp } from 'vue'
import filters from '@/plugins/filters'

// 这里的类型定义是必须的,否则会在 build 时类型出错
declare module '@vue/runtime-core' {
  interface ComponentCustomProperties {
    $filters: Record<string, any>
  }
}

createApp(App).use(filters).mount('#app')

经过以上配置即可在支持全局使用,关于类型问题可以参考相关PR源码

<el-table-column label="创建时间">
  <template #default="{ row }">
    {{ $filters.format(row.createTime) }}
  </template>
</el-table-column>

关于 global is not defined 问题

因为 Vite 是 ESM 机制,有些包内部使用了 node 的 global 对象,解决此问题可以通过自建 pollfill,然后在 main.ts 顶部引入,不是最优解,有想法的同学可以相互交流下:)。

// polyfills
if (typeof (window as any).global === 'undefined') {
  ;(window as any).global = window
}

// main.ts
import './polyfills'
import { createApp } from 'vue'

总结

至此,使用 Vue3 + Vite 的工程化基础篇搭建已完成,后续会深入在业务上提高效率及规范,已达到工程化的目的。