Vue 3 正式版已经发布有一段时间了,随着 Vite 脚手架注定成为下一代前端工具链,许多用户都想基于 Vite 来构建 Vue 项目,如果想基于 Vite 构建 Vue 3 项目,社区模板完全满足您的需求,如果想构建 Vite 3 + Vue 3 + JavaScript 项目,那社区模板不太能满足您的需求,因为社区模板提供 Vue 3 项目几乎是基于 Vite 2 + TypeScript 构建,对于不熟悉 TypeScript 语言的用户不是很友好,因此接下来从 0 开始手把手带大家搭建一套规范的 Vite 3 + Vue 3 + JavaScript 前端工程化项目环境。
本文章篇幅较长,从以下几个方面展开:
本项目完整代码托管在 GitHub 仓库[1],欢迎点亮小星星 🌟🌟
确保你安装了最新版本的 Node.js[18],然后在命令行中运行以下命令:
# npm 6.x
npm create vite@latest vite-vue-js-template --template vue
# npm 7+, extra double-dash is needed:
npm create vite@latest vite-vue-js-template -- --template vue
# yarn
yarn create vite vite-vue-js-template --template vue
# pnpm
pnpm create vite vite-vue-js-template --template vue
复制代码
这一指令将会安装并执行 create-vite[19],它是一个基本模板快速启动项目工具。
初始化项目
在项目被创建后,通过以下步骤安装依赖并启动开发服务器:
# 打开项目
cd <your-project-name>
# 安装依赖
npm install
# 启动项目
npm run dev
复制代码
启动项目
Vite 配置文件 vite.config.js
位于项目根目录下,项目启动时会自动读取。
本项目针对公共基础路径、自定义路径别名、服务器选项、构建选项等做了如下基础配置:
import { defineConfig } from 'vite';
import { resolve } from 'path';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
base: './',
plugins: [
vue(),
],
resolve: {
alias: {
'@': resolve(__dirname, './src') ,
},
},
server: {
// 是否开启 https
https: false,
// 端口号
port: 3000,
// 监听所有地址
host: '0.0.0.0',
// 服务启动时是否自动打开浏览器
open: true,
// 允许跨域
cors: true,
// 自定义代理规则
proxy: {},
},
build: {
// 设置最终构建的浏览器兼容目标
target: 'es2015',
// 构建后是否生成 source map 文件
sourcemap: false,
// chunk 大小警告的限制(以 kbs 为单位)
chunkSizeWarningLimit: 2000,
// 启用/禁用 gzip 压缩大小报告
reportCompressedSize: false,
},
});
复制代码
关于 Vite 更多配置项及用法,请查看 Vite 官网 vitejs.dev/config/[20] 。
├── dist/
└── src/
├── api/ // 接口请求目录
├── assets/ // 静态资源目录
├── common/ // 通用类库目录
├── components/ // 公共组件目录
├── router/ // 路由配置目录
├── store/ // 状态管理目录
├── style/ // 通用样式目录
├── utils/ // 工具函数目录
├── views/ // 页面组件目录
├── App.vue
├── main.js
├── tests/ // 单元测试目录
├── index.html
├── jsconfig.json // JavaScript 配置文件
├── vite.config.js // Vite 配置文件
└── package.json
复制代码
npm i vue-router@4
复制代码
在 src/router
目录下新建 index.js
文件与 modules
文件夹
└── src/
├── router/
├── modules/ // 路由模块
├── index.js // 路由配置文件
复制代码
关于路由表,建议根据功能的不同来拆分到 modules
文件夹中,好处是:
export default [
{
path: '/',
name: 'home',
component: () => import('@/views/HomeView.vue'),
},
{
path: '/about',
name: 'about',
component: () => import('@/views/AboutView.vue'),
},
];
复制代码
import { createRouter, createWebHistory } from 'vue-router';
import baseRouters from './modules/base';
const routes = [...baseRouters];
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes,
scrollBehavior() {
return {
el: '#app',
top: 0,
behavior: 'smooth',
};
},
});
export default router;
复制代码
根据路由配置的实际情况,需要在 src
下创建 views
目录,用来存储页面组件。
在 main.js
文件中挂载路由配置
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
createApp(App).use(router).mount('#app');
复制代码
npm i pinia
复制代码
在 src/store
目录下新建 index.js
文件与 modules
文件夹
└── src/
├── store/
├── modules/ // 仓库模块
├── index.js // 仓库配置文件
复制代码
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 1,
}),
actions: {
accumulate() {
this.count++;
},
},
});
复制代码
import { createPinia } from 'pinia';
const store = createPinia();
export default store;
export * from './modules/counter';
复制代码
开发中需要将不同功能所对应的状态,拆分到不同的 modules
,好处如同路由模块一样。
在 main.js
文件中挂载 Vuex
配置
import { createApp } from 'vue';
import App from './App.vue';
import store from './store';
import router from './router';
createApp(App).use(router).use(store).mount('#app');
复制代码
npm i tdesign-vue-next
复制代码
import { createApp } from 'vue';
import TDesign from 'tdesign-vue-next';
// 引入组件库全局样式资源
import 'tdesign-vue-next/es/style/index.css';
const app = createApp(App);
app.use(TDesign);
复制代码
使用 unplugin-vue-components
和 unplugin-auto-import
来实现自动导入:
npm install unplugin-vue-components unplugin-auto-import -D
复制代码
在 Vite 对应的配置文件 vite.config.js
添加上述插件:
import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';
import { TDesignResolver } from 'unplugin-vue-components/resolvers';
export default {
plugins: [
AutoImport({
resolvers: [TDesignResolver({
library: 'vue-next'
})],
}),
Components({
resolvers: [TDesignResolver({
library: 'vue-next'
})],
}),
],
};
复制代码
npm i axios
复制代码
在 utils
目录下创建 request.js
文件,配置好适合自己业务的请求拦截和响应拦截:
└── src/
├── api // 接口
├── utils/
├── request.js // axios 请求库二次封装
复制代码
import axios from 'axios';
// 创建请求实例
const instance = axios.create({
baseURL: '/api',
// 指定请求超时的毫秒数
timeout: 1000,
// 表示跨域请求时是否需要使用凭证
withCredentials: false,
});
// 前置拦截器(发起请求之前的拦截)
instance.interceptors.request.use(
(config) => {
/**
* 在这里一般会携带前台的参数发送给后台,比如下面这段代码:
* const token = getToken()
* if (token) {
* config.headers.token = token
* }
*/
return config;
},
(error) => {
return Promise.reject(error);
},
);
// 后置拦截器(获取到响应时的拦截)
instance.interceptors.response.use(
(response) => {
/**
* 根据你的项目实际情况来对 response 和 error 做处理
* 这里对 response 和 error 不做任何处理,直接返回
*/
return response;
},
(error) => {
const { response } = error;
if (response && response.data) {
return Promise.reject(error);
}
const { message } = error;
console.error(message);
return Promise.reject(error);
},
);
// 导出常用函数
/**
* @param {string} url
* @param {object} data
* @param {object} params
*/
export function post(url, data = {}, params = {}) {
return instance({
method: 'post',
url,
data,
params,
});
}
/**
* @param {string} url
* @param {object} params
*/
export function get(url, params = {}) {
return instance({
method: 'get',
url,
params,
});
}
/**
* @param {string} url
* @param {object} data
* @param {object} params
*/
export function put(url, data = {}, params = {}) {
return instance({
method: 'put',
url,
params,
data,
});
}
/**
* @param {string} url
* @param {object} params
*/
export function _delete(url, params = {}) {
return instance({
method: 'delete',
url,
params,
});
}
export default instance;
复制代码
之后在 api
文件夹中以业务模型对接口进行拆分,举个例子,将所有跟用户相关接口封装在 User
类中,此类称作用户模型。
在 User
类中比如有登录、注册、获取用户信息等方法,如果有业务逻辑变动,只需要修改相关方法即可。
import { post } from '@/utils/request';
export default class User {
/**
* 登录
* @param {String} username 用户名
* @param {String} password 密码
* @returns
*/
static async login(username, password) {
return post('/login', {
username,
password,
});
}
}
复制代码
把每个业务模型独立成一个 js 文件,声明一个类通过其属性和方法来实现这个模型相关的数据获取,这样可以大大提升代码的可读性与可维护性。
在需要使用接口的地方,引入对应的业务模型文件,参考如下:
<script>
import User from '@/api/user';
export default {
data() {
return {
username: '',
password: '',
};
},
methods: {
async login() {
const res = await User.login(this.username, this.password);
console.log(res);
},
},
};
</script>
复制代码
本项目使用 CSS 预处理器 Less,直接安装为开发依赖即可。
Vite 内部已帮我们集成了相关的 loader
,不需要额外配置。
npm i less -D
复制代码
在 <style></style>
样式标签中引用 lang="less"
即可。
<style lang="less"></style>
复制代码
CSS 命名规范推荐 BEM 命名规范 参考链接:CSS BEM 书写规范[21]
在 src/style
目录下创建 variables.less
全局样式文件:
└── src/
├── style/
├── variables.less // 全局样式文件
复制代码
在 vite.config.js
配置文件中新增CSS 预处理器相关配置即可实现 less 全局样式:
import { resolve } from 'path';
export default defineConfig({
css: {
preprocessorOptions: {
less: {
modifyVars: {
hack: `true; @import (reference) "${resolve('src/style/variables.less')}";`,
},
math: 'strict',
javascriptEnabled: true,
},
},
},
});
复制代码
官方文档[22]
在 Vue3 中,改变了以往样式穿透的语法,如果继续使用 ::v-deep
、/deep/
、>>>
等语法的话,会出现一个警告,下面是新的语法:
/* 深度选择器 */
:deep(selector) {
/* ... */
}
/* 插槽选择器 */
:slotted(selector) {
/* ... */
}
/* 全局选择器 */
:global(selector) {
/* ... */
}
复制代码
至此,一个基于 JavaScript + Vite3 + Vue3 + Vue Router + Pinia + Axios + Less 的前端项目开发环境搭建完毕。
项目托管在 GitHub 仓库[23],需要的同学可以去下载下来,参考学习。
接下来增加代码规范约束、提交规范约束、单元测试、自动部署等,让其更完善、更健壮。
随着前端应用逐渐变得大型化和复杂化,在同一个项目中有多个人员参与时,每个人的前端能力程度不等,他们往往会用不同的编码风格和习惯在项目中写代码,长此下去,势必会让项目的健壮性越来越差。解决这些问题,理论上讲,口头约定和代码审查都可以,但是这种方式无法实时反馈,而且沟通成本过高,不够灵活,更关键的是无法把控。不以规矩,不能成方圆,我们不得不在项目使用一些工具来约束代码规范。
本文讲解如何使用 EditorConfig + ESLint + Prettier + Stylelint 组合来实现代码规范化。
这样做带来好处:
EditorConfig[24] 主要用于统一不同 IDE 编辑器的编码风格。
在项目根目录下添加 .editorconfig
文件:
# 表示是最顶层的 EditorConfig 配置文件
root = true
# 表示所有文件适用
[*]
# 缩进风格(tab | space)
indent_style = space
# 控制换行类型(lf | cr | crlf)
end_of_line = lf
# 设置文件字符集为 utf-8
charset = utf-8
# 去除行首的任意空白字符
trim_trailing_whitespace = true
# 始终在文件末尾插入一个新行
insert_final_newline = true
# 表示仅 md 文件适用以下规则
[*.md]
max_line_length = off
trim_trailing_whitespace = false
# 表示仅 ts、js、vue、css 文件适用以下规则
[*.{ts,js,vue,css}]
indent_size = 2
复制代码
很多 IDE 中会默认支持此配置,但是也有些不支持,如:VSCode、Atom、Sublime Text 等。 具体列表可以参考官网,如果在 VSCode 中使用需要安装
EditorConfig for VS Code
插件。
EditorConfig for VS Code
ESLint[25] 是针对 EScript 的一款代码检测工具,它可以检测项目中编写不规范的代码,如果写出不符合规范的代码会被警告。
由此我们就可以借助于 ESLint 强大的功能来统一团队的编码规范。
eslint-config-airbnb-base
时必须安装的前置插件eslint-plugin-vue
时必须安装的 ESLint 解析器npm i eslint eslint-define-config eslint-config-airbnb-base eslint-plugin-import eslint-plugin-vue vue-eslint-parser -D
复制代码
Visual Studio Code 编辑器使用 ESLint
配置需要下载插件 ESLint 。
ESLint
JetBrains 系列编辑器(WebStorm、IntelliJ IDEA 等)则不用额外安装插件。
在项目根目录创建 .eslintrc.js
文件,并填入以下内容:
const { defineConfig } = require('eslint-define-config');
module.exports = defineConfig({
root: true,
env: {
browser: true,
node: true,
jest: true,
es6: true,
},
plugins: ['vue'],
parser: 'vue-eslint-parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
allowImportExportEverywhere: true,
ecmaFeatures: {
jsx: true,
},
},
extends: [
'airbnb-base',
'eslint:recommended',
'plugin:vue/vue3-essential',
'plugin:vue/vue3-recommended',
'plugin:prettier/recommended',
],
rules: {
// 禁止使用多余的包
'import/no-extraneous-dependencies': 0,
// 确保在导入路径内一致使用文件扩展名
'import/extensions': 0,
// 确保导入指向可以解析的文件/模块
'import/no-unresolved': 0,
// 首选默认导出导入/首选默认导出
'import/prefer-default-export': 0,
// 要求使用 let 或 const 而不是 var
'no-var': 'error',
// 禁止使用 new 以避免产生副作用
'no-new': 1,
// 禁止变量声明与外层作用域的变量同名
'no-shadow': 0,
// 禁用 console
'no-console': 0,
// 禁止标识符中有悬空下划线
'no-underscore-dangle': 0,
// 禁止在可能与比较操作符相混淆的地方使用箭头函数
'no-confusing-arrow': 0,
// 禁用一元操作符 ++ 和 --
'no-plusplus': 0,
// 禁止对 function 的参数进行重新赋值
'no-param-reassign': 0,
// 禁用特定的语法
'no-restricted-syntax': 0,
// 禁止在变量定义之前使用它们
'no-use-before-define': 0,
// 禁止直接调用 Object.prototypes 的内置属性
'no-prototype-builtins': 0,
// 禁止可以在有更简单的可替代的表达式时使用三元操作符
'no-unneeded-ternary': 'error',
// 禁止重复模块导入
'no-duplicate-imports': 'error',
// 禁止在对象中使用不必要的计算属性
'no-useless-computed-key': 'error',
// 强制使用一致的缩进
indent: ['error', 2],
// 强制使用骆驼拼写法命名约定
camelcase: 0,
// 强制类方法使用 this
'class-methods-use-this': 0,
// 要求构造函数首字母大写
'new-cap': 0,
// 强制一致地使用 function 声明或表达式
'func-style': 0,
// 强制一行的最大长度
'max-len': 0,
// 要求 return 语句要么总是指定返回的值,要么不指定
'consistent-return': 0,
// 强制switch要有default分支
'default-case': 2,
// 强制剩余和扩展运算符及其表达式之间有空格
'rest-spread-spacing': 'error',
// 要求使用 const 声明那些声明后不再被修改的变量
'prefer-const': 'error',
// 强制箭头函数的箭头前后使用一致的空格
'arrow-spacing': 'error',
},
overrides: [
{
files: ['*.vue'],
rules: {
// 要求组件名称总是多个单词
'vue/multi-word-component-names': 0,
},
},
],
});
复制代码
关于更多配置项信息,请前往 ESLint 官网查看 ESLint-Configuring[32]
在项目根目录添加一个 .eslintignore
文件,内容如下:
dist
node_modules
!.prettierrc.js
复制代码
Prettier[33] 是一款强大的代码格式化工具,支持 JavaScript、TypeScript、CSS、SCSS、Less、JSX、Angular、Vue、GraphQL、JSON、Markdown 等语言,基本上前端能用到的文件格式它都可以搞定,是当下最流行的代码格式化工具。
npm i prettier -D
复制代码
Visual Studio Code 编辑器使用 Prettier
配置需要下载插件 Prettier - Code formatter 。
Prettier - Code formatter
JetBrains 系列编辑器(WebStorm、IntelliJ IDEA 等)则不用额外安装插件,可直接使用 Prettier
配置。
Prettier 支持多种格式的配置文件[34],比如 .json
、.yml
、.yaml
、.js
等。
在项目根目录创建 .prettierrc.js
文件,并填入以下内容:
module.exports = {
// 一行最多 120 字符
printWidth: 120,
// 使用 2 个空格缩进
tabWidth: 2,
// 不使用缩进符,而使用空格
useTabs: false,
// 行尾需要有分号
semi: true,
// 使用单引号
singleQuote: true,
// 对象的 key 仅在必要时用引号
quoteProps: 'as-needed',
// jsx 不使用单引号,而使用双引号
jsxSingleQuote: false,
// 末尾需要有逗号
trailingComma: 'all',
// 大括号内的首尾需要空格
bracketSpacing: true,
// jsx 标签的反尖括号需要换行
jsxBracketSameLine: false,
// 箭头函数,只有一个参数的时候,也需要括号
arrowParens: 'always',
// 每个文件格式化的范围是文件的全部内容
rangeStart: 0,
rangeEnd: Infinity,
// 不需要写文件开头的 @prettier
requirePragma: false,
// 不需要自动在文件开头插入 @prettier
insertPragma: false,
// 使用默认的折行标准
proseWrap: 'preserve',
// 根据显示样式决定 html 要不要折行
htmlWhitespaceSensitivity: 'css',
// vue 文件中的 script 和 style 内不用缩进
vueIndentScriptAndStyle: false,
// 换行符使用 lf
endOfLine: 'lf',
// 格式化嵌入的内容
embeddedLanguageFormatting: 'auto',
// html, vue, jsx 中每个属性占一行
singleAttributePerLine: false,
};
复制代码
关于更多配置项信息,请前往 Prettier 官网查看 Prettier-Options[35]
在项目根目录添加一个 .prettierignore
文件,内容如下:
## OS
.DS_Store
.idea
.editorconfig
pnpm-lock.yaml
.npmrc
# Ignored suffix
*.log
*.md
*.svg
*.png
*.ico
*ignore
## Local
.husky
## Built-files
.cache
dist
复制代码
本项目中的 ESLint 配置使用了 Airbnb JavaScript 风格指南校验,其规则之一是_代码结束后面要加分号_,而在 Prettier 配置文件中加了_代码结束后面不加分号_配置项,从而冲突了。
解决两者冲突问题,需要用到 eslint-plugin-prettier 和 eslint-config-prettier。
eslint-plugin-prettier
将 Prettier 的规则设置到 ESLint 的规则中eslint-config-prettier
关闭 ESLint 中与 Prettier 中会发生冲突的规则最后形成优先级:Prettier 配置规则
> ESLint 配置规则
npm i eslint-plugin-prettier eslint-config-prettier -D
复制代码
修改 .eslintrc.js
文件,在 extends
中添加 plugin:prettier/recommended
规则(此规则一定要加在最后)。
module.exports = {
extends: [
'airbnb-base',
'eslint:recommended',
'plugin:vue/vue3-essential',
'plugin:vue/vue3-recommended',
'plugin:prettier/recommended'
],
}
复制代码
Visual Studio Code 在 settings.json
设置文件中,增加以下代码:
{
"editor.codeActionsOnSave": {
"source.fixAll": true,
"source.fixAll.eslint": true
}
}
复制代码
WebStorm 打开设置窗口,按如下操作,最后点击 Apply
-> OK
:
WebStorm
Stylelint 是一个强大、先进的 CSS 代码检查器(linter),可以帮助你规避 CSS 代码中的错误并保持一致的编码风格。
npm i stylelint stylelint-config-prettier stylelint-config-standard stylelint-config-recommended-vue stylelint-order -D
复制代码
Visual Studio Code 编辑器使用 Stylelint
配置需要下载插件 Stylelint 。
Stylelint
JetBrains 系列编辑器(WebStorm、IntelliJ IDEA 等)则不用额外安装插件。
在项目根目录创建 .stylelintrc.js
文件,并填入以下内容:
module.exports = {
root: true,
defaultSeverity: 'error',
extends: [
'stylelint-config-standard',
'stylelint-config-prettier'
],
plugins: ['stylelint-order'],
rules: {
// 不允许未知函数
'function-no-unknown': null,
// 指定类选择器的模式
'selector-class-pattern': null,
// 禁止空源码
'no-empty-source': null,
// 指定字符串使用单引号
'string-quotes': 'single',
// 禁止未知的@规则
'at-rule-no-unknown': [
true,
{
ignoreAtRules: [
'tailwind',
'apply',
'variants',
'responsive',
'screen',
'function',
'if',
'each',
'include',
'mixin',
],
},
],
// 指定@规则名的大小写
'at-rule-name-case': 'lower',
// 指定缩进
indentation: [
2,
{
severity: 'warning',
},
],
// 禁止未知的伪类选择器
'selector-pseudo-class-no-unknown': [
true,
{
ignorePseudoClasses: ['global'],
},
],
// 禁止未知的伪元素选择器
'selector-pseudo-element-no-unknown': [
true,
{
ignorePseudoElements: ['v-deep'],
},
],
'order/properties-order': [
'position',
'top',
'right',
'bottom',
'left',
'z-index',
'display',
'justify-content',
'align-items',
'float',
'clear',
'overflow',
'overflow-x',
'overflow-y',
'margin',
'margin-top',
'margin-right',
'margin-bottom',
'margin-left',
'padding',
'padding-top',
'padding-right',
'padding-bottom',
'padding-left',
'width',
'min-width',
'max-width',
'height',
'min-height',
'max-height',
'font-size',
'font-family',
'font-weight',
'border',
'border-style',
'border-width',
'border-color',
'border-top',
'border-top-style',
'border-top-width',
'border-top-color',
'border-right',
'border-right-style',
'border-right-width',
'border-right-color',
'border-bottom',
'border-bottom-style',
'border-bottom-width',
'border-bottom-color',
'border-left',
'border-left-style',
'border-left-width',
'border-left-color',
'border-radius',
'text-align',
'text-justify',
'text-indent',
'text-overflow',
'text-decoration',
'white-space',
'color',
'background',
'background-position',
'background-repeat',
'background-size',
'background-color',
'background-clip',
'opacity',
'filter',
'list-style',
'outline',
'visibility',
'box-shadow',
'text-shadow',
'resize',
'transition',
],
},
}
复制代码
在项目根目录添加一个 .stylelintignore
文件,内容如下:
# .stylelintignore
# 旧的不需打包的样式库
*.min.css
# 其他类型文件
*.js
*.jpg
*.woff
# 测试和打包目录
/test/
/dist/*
/public/*
public/*
/node_modules/
复制代码
Stylelint
v14 版本默认不支持 vue 文件中的 style 代码自动检测,详情查看官方迁移指南[41]
stylelint-config-html
依赖的模块npm i stylelint-config-html postcss-html postcss-less -D
复制代码
修改 .stylelintrc.js
文件,添加如下配置:
module.exports = {
overrides: [
{
files: ['*.vue', '**/*.vue', '*.html', '**/*.html'],
extends: ['stylelint-config-html'],
rules: {
// 指定关键帧名称的模式
'keyframes-name-pattern': null,
// 禁止未知的伪类选择器
'selector-pseudo-class-no-unknown': [
true,
{
ignorePseudoClasses: ['deep', 'global'],
},
],
// 禁止未知的伪元素选择器
'selector-pseudo-element-no-unknown': [
true,
{
ignorePseudoElements: ['v-deep', 'v-global', 'v-slotted'],
},
],
},
},
{
files: ['*.less', '**/*.less'],
customSyntax: 'postcss-less',
extends: ['stylelint-config-standard', 'stylelint-config-recommended-vue'],
},
],
};
复制代码
Visual Studio Code 在 settings.json
设置文件中,增加以下代码:
{
"stylelint.validate": ["css", "less", "postcss", "scss", "vue", "sass"]
}
复制代码
在项目中已集成 ESLint 和 Prettier,在编码时,这些工具可以对代码进行实时校验,在一定程度上能有效规范所写代码,但有些人可能觉得这些限制很麻烦,从而选择视“提示”而不见,依旧按自己编程风格来写代码,或者干脆禁用掉这些工具,开发完成就直接把代码提交到了仓库,日积月累,ESLint 也就形同虚设。
所以,还需要做一些限制,让没通过 ESLint 检测和修复的代码禁止提交,从而保证仓库代码都是符合规范的。
为了解决这个问题,需要用到 Git Hook,在本地执行 git commit
的时候,就对所提交的代码进行 ESLint 检测和修复(即执行 eslint \--fix
),如果这些代码没通过 ESLint 规则校验,则禁止提交。
实现这一功能,需要借助 husky[45] + lint-staged[46] 。
注意:本项目使用 husky 6.x 版本,6.x 版本配置方式跟之前版本有较大差异,当发现配置方法不一致时,一切以 husky 官网[47]为准。
使用 husky-init
命令快速在项目初始化 husky
配置:
# 初始化仓库
git init
# 初始化
npx husky-init
# 安装依赖
npm install
复制代码
husky 包含很多 hook
(钩子),常用有:pre-commit
、commit-msg
。
使用 pre-commit
来触发 ESLint 命令,修改 .husky/pre-commit
文件触发命令:
eslint --fix ./src --ext .vue,.js,.ts
复制代码
pre-commit
hook 文件作用是:当执行 git commit \-m "xxx"
时,会先对 src
目录下所有的 .vue
、.js
、.ts
文件执行 eslint \--fix
命令,如果 ESLint 通过,成功 commit
,否则终止 commit
。
但是又存在一个问题:有时候明明只改动了一两个文件,却要对所有的文件执行 eslint \--fix
。
假如这是一个历史项目,在中途配置了 ESLint 规则,那么在提交代码时,也会对其他未修改的“历史”文件都进行检查,可能会造成大量文件出现 ESLint 错误,显然这不是我们想要的结果。
所以只需要用 ESLint 修复此次写的代码,而不去影响其他的代码,此时需要借助 lint-staged 工具。
lint-staged 一般结合 husky 来使用,它可以让 husky 的 hook
触发的命令只作用于 git
暂存区的文件,而不会影响到其他文件。
npm i lint-staged -D
复制代码
在 package.json
里增加 lint-staged
配置项:
{
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"prettier --write",
"eslint --fix"
],
"*.vue": [
"prettier --write",
"eslint --fix",
"stylelint --fix"
],
"*.{html,vue,vss,sass,less}": [
"prettier --write",
"stylelint --fix"
],
"package.json": [
"prettier --write"
],
"*.md": [
"prettier --write"
]
},
}
复制代码
修改 .husky/pre-commit
文件触发命令为:
npx lint-staged
复制代码
pre-commit
经过以上配置之后,就可以在每次提交之前对所有代码进行格式化,保证线上代码的规范性。
多人协作项目中,在提交代码环节,也存在一种情况:不能保证每个人对提交信息的准确描述,因此会出现提交信息紊乱、风格不一致的情况。
如果 git commit
的描述信息精准,在后期维护和 Bug 处理时会变得有据可查,项目开发周期内还可以根据规范的提交信息快速生成开发日志,从而方便我们追踪项目和把控进度。
社区最流行、最知名、最受认可的 Angular[48] 团队提交规范:
Angular 团队提交规范
参考链接: Angular Style Commit Message Conventions[49]
commit message
由 Header、Body、Footer 组成。
<Header>
<Body>
<Footer>
复制代码
Header 部分包括三个字段 type(必需)、scope(可选)和 subject(必需)。
<type>(<scope>): <subject>
复制代码
type
type 用于说明 commit 的提交类型(必须是以下几种之一)。
值 | 描述 |
---|---|
feat | 新增功能 |
fix | 修复问题 |
docs | 文档变更 |
style | 代码格式(不影响功能,例如空格、分号等格式修正) |
refactor | 代码重构 |
perf | 改善性能 |
test | 测试 |
build | 变更项目构建或外部依赖(例如 scopes: webpack、gulp、npm 等) |
ci | 更改持续集成软件的配置文件和 package 中的 scripts 命令,例如 scopes: Travis, Circle 等 |
chore | 变更构建流程或辅助工具 |
revert | 代码回退 |
scope
scope 用于指定本次 commit 影响的范围。
scope 依据项目而定,例如在业务项目中可以依据菜单或者功能模块划分,如果是组件库开发,则可以依据组件划分。
subject
subject 是本次 commit 的简洁描述,长度约定在 50 个字符以内,通常遵循以下几个规范:
body 是对本次 commit 的详细描述,可以分成多行。
跟 subject 类似,用动词开头,body 应该说明修改的原因和更改前后的行为对比。
如果本次提交的代码是突破性的变更或关闭缺陷,则 Footer 必需,否则可以省略。
feat
feat(browser): onUrlChange event (popstate/hashchange/polling)
Added new event to browser:
- forward popstate event if available
- forward hashchange event if popstate not available
- do polling when neither popstate nor hashchange available
Breaks $browser.onHashChange, which was removed (use onUrlChange instead)
复制代码
fix
fix(compile): couple of unit tests for IE9
Older IEs serialize html uppercased, but IE9 does not...
Would be better to expect case insensitive, unfortunately jasmine does
not allow to user regexps for throw expectations.
Closes #392
Breaks foo.bar api, foo.baz should be used instead
复制代码
style
style(location): add couple of missing semi colons
复制代码
chore
chore(release): v3.4.2
复制代码
一款工程性更强,轻量级,高度自定义,标准输出格式的 commitizen[50] 适配器 官方网站:cz-git[51]
cz-git
npm install -D cz-git
复制代码
修改 package.json
文件,添加 config
指定使用的适配器
{
"scripts": {},
"config": {
"commitizen": {
"path": "node_modules/cz-git"
}
}
}
复制代码
cz-git 与 commitlint[52] 进行联动给予校验信息,所以可以编写于 commitlint[53] 配置文件之中。
例如:(⇒ 配置模板[54])
/** @type {import('cz-git').UserConfig} */
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {},
prompt: {
useEmoji: false,
emojiAlign: 'center',
allowCustomIssuePrefixs: false,
allowEmptyIssuePrefixs: false,
},
};
复制代码
本项目配置文件可参考:commitlint.config.js[55]
全局安装的好处在于:在任何项目下都可以利用
cz
或git cz
命令启动命令行工具,生成标准化 commit message
npm install -g cz-git commitizen
复制代码
echo '{ "path": "cz-git" }' > ~/.czrc
复制代码
方式一: 编辑 ~/.czrc
文件以 json 形式添加配置,例如:
{
"path": "cz-git",
"useEmoji": true
}
复制代码
方式二:与 commitlint[56] 配合,在 $HOME
路径下创建配置文件 (↓ 配置模板[57])
在“代码规范”章节中提到,尽管制定了规范,但在多人协作的项目中,总有些人依旧我行我素。
因此提交代码这个环节,也增加一个限制:只让符合 Angular 规范的 commit message 通过。
此功能需借助 @commitlint/config-conventional
和 @commitlint/cli
工具来实现。
npm i @commitlint/cli @commitlint/config-conventional -D
复制代码
在项目根目录创建 commitlint.config.js
文件,并填入以下内容:
module.exports = {
extends: ['@commitlint/config-conventional']
}
复制代码
使用 husky 命令在 .husky
目录下创建 commit-msg
文件,并在此执行验证命令:
npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"
复制代码
commit-msg
本项目完整代码托管在 GitHub 仓库[60],欢迎点亮小星星 🌟🌟
本章节将介绍如何使用 CI(Continuous Integration 持续集成)服务来完成项目部署工作。
常见的 CI 工具有 GitHub Actions、GitLab CI、Travis CI、Circle CI 等。
本项目使用 GitHub Actions
来完成这一操作。
🔗 参考链接:GitHub Actions 入门教程[61]
因为 GitHub Actions 只对 GitHub 仓库有效,所以创建 GitHub 仓库[62]来托管项目代码。
创建 GitHub 仓库
master
分支存储项目源代码gh-pages
分支存储打包后的静态文件创建一个有 repo 和 workflow 权限的 GitHub Token[63]
创建 GitHub Token
注意:新生成的 Token 只会显示一次。
Token
将上述创建的 Token 添加到 GitHub 仓库中的 Secrets
里,并将这个新增的 secret
命名为 VITE_VUE_DEPLOY
。
步骤:仓库 -> Settings
-> Secrets
-> Actions
-> New repository secret
。
New secret
注意:新创建的 secret
VITE_VUE_DEPLOY
在 Actions 配置文件中要用到,两个地方需保持一致!
打开 package.json
文件,新增 homepage
字段,表示该应用发布后的根目录(参见官方文档[64])。
"homepage": "https://[username].github.io/github-actions-demo",
复制代码
上面代码中,将 [username]
替换成你的 GitHub 用户名,参见范例[65]。
(1)在项目根目录下创建 .github
目录。
(2)在 .github
目录下创建 workflows
目录。
(3)在 workflows
目录下创建 deploy.yml
文件。
.github/workflows/deploy.yml
name: Vite Vue Deploy
on:
push:
# master 分支有 push 时触发
branches: [master]
jobs:
deploy:
# 指定虚拟机环境
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x, 16.x]
steps:
- name: Checkout
# 拉取 GitHub 仓库代码
uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
# 设定 Node.js 环境
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Install
# 安装依赖
run: npm install
- name: Build
# 打包
run: npm run build
- name: Deploy
uses: JamesIves/github-pages-deploy-action@v4
with:
# 部署打包目录
folder: dist
# 密钥名
token: ${{ secrets.VITE_VUE_DEPLOY }}
# 分支
branch: gh-pages
复制代码
🔗 通过此链接 ElanYoung.github.io/vite-vue-js…[66] 即可访问本项目
本文从技术选项到架构搭建、从代码规范约束到提交信息规范约束,一步一步带领大家如何从一个最简单的前端项目骨架到规范的前端工程化环境,基本涵盖前端项目开发的整个流程,特别适合刚接触前端工程化的同学学习。
因篇幅较长,所涉及技术点较多,难免会出现错误,希望大家多多指正,谢谢大家!