前言
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
查看项目结构,对于生成的目录结构不足以支持项目的复杂度,因此我们结构进行扩展,扩展后结构为右图
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 的配置文件,接下需要配置 husky
和 lint-staged
,这里提供一行快速配置的命令
npx mrm@2 lint-staged
这条命令将根据 package.json
依赖中的 eslint
和 prettier
安装配置 husky
和 lint-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 时进行校验并给出修改提示。
以上效果主要需要 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 cz
或 npm run commit
替换 git commit
命令,实现规范提交的选择。
但是如果有人执意要使用 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 版本的区别,详情移步至迁移指南。
状态管理
状态管理有了新的选择,可以选择 pina
和 vuex
。
pinia
是根据 vuex5.x 的提案设计的,可以说是面向未来的 vuex,该提案提出了非集中式的store
管理模型,没有嵌套的store
模块,移除了mutations
,留下state
、getters
和actions
,我个人也比较看好这种模式。vuex4.x
正式版发布在 21 年年初,作为官方状态管理,想必大家也都很熟悉,就不做过多介绍,使用方式相对 Vue2 时变化也不大。
UI 组件库
可供选择的 UI 组件库也很丰富
- PC 端:
- 曾一度被怀疑弃更的 ElementUI,现在名为 elementPlus
- 很早就支持 Vue3 的 Ant Design Vue
- 出自图森未来的,一个名字很特别的 Naive UI
- M 端:
另外与 CSS 相关的好用的工具
- tailwindcss 一个可以让你不写一行 CSS 就能实现布局等一系列操作的工具,
- windcss 也是同类型的 css 工具,兼容 tailwindcss
- 现代浏览器 CSS 样式重置 modern-normalize
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>
- 组件的
props
和emit
写法发生变化以及在 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 的工程化基础篇搭建已完成,后续会深入在业务上提高效率及规范,已达到工程化的目的。