一种高效的统一前端团队代码规范的落地方案
代码规范
项目code-specification-unid的出现,是为了解决团队中多个项目代码规范不统一的问题。
概括来讲,它具备以下好处:
所有的项目统一使用一份约定好的代码规范;
将大量的第三方依赖模块后置以精简业务代码;
继承统一规范的基础上仍然提供个性化定制能力;
支持 React、Vue2/3 工程,且支持文件名校验;
当前痛点
代码规范落地难:代码规范是一个团队乃至公司团队协作的契约,同时这些规范是可以让团队成员在后续开发中少走很多弯路,但是现状是一份代码规范通过口头约定往往很难落地,归根结底在于需要工具去强行保证代码必须经过代码开发规范的扫描;
低质量代码推上线:在实际开发现状中开发人员可以很容易地将本地代码 push 到远程分支上,虽然合并的时候可以进行 CR,但反馈链路太长,最好的方式是本地进行 commit 前就进行代码质量检查,如果不通过,就无法 commit,这样能够从最源头保证代码质量问题;
代码格式不统一:每个项目或单独维护着一份代码规范或压根没有规范,比如有的用双引号,有的用单引号,代码格式化也因编辑器或插件的不同而不同,十分影响可读性,需要一种工具能够保证团队内代码的格式是一致的;
通用方案
针对以上痛点,市面上提供了一些工具来分别解决:
ESLint: 查找并修复 JavaScript 代码中的问题;
Stylelint: 可使你在样式中避免错误并强制执行约定;
Prettier: Prettier 是一个推荐的代码格式化工具;
Husky: 可以阻止坏的 commit、push 等操作;
Lint-staged: 是一个在 git 暂存文件上运行 linters 的工具;
Commitlint: 用于校验提交的 commit msg 是否符合约定的规范;
其中,后三者Husky
+ Lint-staged
+ Commitlint
结合,可以完美地实现在 commit 前执行一系列 Linter 并校验提交消息格式;而前三者ESLint
、Stylelint
、Prettier
在功能上既有侧重也有交叉,如下图所示:
关于Prettier
与Linters
的不同,官方文档中有对这个问题的解释。以 JS 为例,Prettier
包含(几乎)所有ESLint
中关于代码样式 (Formatting) 的规则,但不包含ESLint
中关于代码质量 (Code-quality) 的规则。因此将Prettier
与Linters
结合起来使用是一个不二的选择。
最终方案
将上述工具ESLint
+ Stylelint
+ Prettier
+ Husky
+ Lint-staged
+ Commitlint
统一收敛到一个项目中,以发挥其各自的优势。具体来讲:
定义一份公共的
Prettier
配置文件,以覆盖ESLint
与Stylelint
默认配置中关于代码格式化的部分,即让ESLint
与Stylelint
专注于做 JS 与 CSS 的质量检查, 而所有文件的代码格式化工作都交由Prettier
完成;借助于 Git 生命周期钩子函数,在 commit 前强制完成一次
ESLint
+Stylelint
+Prettier
的质量与格式化的检查, 在 commit 时完成对提交消息的格式化检查,当上述两步都通过后才可以 commit 成功;将上述工具的配置文件分别导出,用户可通过在业务项目添加对应的 js 类型的配置文件进行继承与重载;
下面详细阐述该方案的定制过程:
1、首先来定义一份公共、默认的Prettier
配置文件:
module.exports = {
// 句尾添加分号
semi: true,
// 使用单引号
singleQuote: true,
// 所有可能的地方都加上逗号,比如数组、对象最后一个元素
trailingComma: 'all',
// 大括号内的首尾需要空格
bracketSpacing: true,
// jsx 标签的反尖括号不需要换行
jsxBracketSameLine: false,
// 箭头函数,总是需要括号
arrowParens: 'always',
// 不需要自动在文件开头插入 @prettier
insertPragma: false,
// 使用 2 个空格缩进
tabWidth: 2,
// 不使用缩进符,而使用空格
useTabs: false,
// 一行最多 100 字符
printWidth: 100,
// 不强制换行
proseWrap: 'never',
// 换行符使用 lf
endOfLine: 'lf',
overrides: [
{
files: '.prettierrc',
options: {
parser: 'json',
},
},
{
files: 'document.ejs',
options: {
parser: 'html',
},
},
],
};
2、针对 React、Vue2、Vue3 分别提供一份默认的ESLint
配置文件,并将Prettier
配置置于配置列表的最后:
React
module.exports = {
extends: ['plugin:react/recommended']
.concat(
isTsProject ? ['plugin:@typescript-eslint/recommended', 'prettier/@typescript-eslint'] : [],
)
.concat(['prettier', 'prettier/react', 'plugin:prettier/recommended']),
parser: isTsProject ? '@typescript-eslint/parser' : '@babel/eslint-parser',
plugins: ['eslint-comments', 'react', 'jest', 'unicorn', 'react-hooks', 'filename'],
//...
}
Vue2
module.exports = {
extends: [
'plugin:vue/base',
'plugin:vue/essential',
'plugin:vue/strongly-recommended',
'plugin:vue/recommended',
'prettier/vue',
]
.concat(
isTsProject ? ['plugin:@typescript-eslint/recommended', 'prettier/@typescript-eslint'] : [],
)
.concat(['plugin:prettier/recommended']),
parser: 'vue-eslint-parser',
plugins: ['eslint-comments', 'jest', 'unicorn', 'filename'],
//...
}
Vue3
module.exports = {
extends: [
'plugin:vue/base',
'plugin:vue/vue3-essential',
'plugin:vue/vue3-strongly-recommended',
'plugin:vue/vue3-recommended',
'prettier/vue',
]
.concat(
isTsProject ? ['plugin:@typescript-eslint/recommended', 'prettier/@typescript-eslint'] : [],
)
.concat(['plugin:prettier/recommended']),
parser: 'vue-eslint-parser',
plugins: ['eslint-comments', 'jest', 'unicorn', 'filename'],
//...
}
在定制 ESLint 过程中,会自动识别是否为 TypeScript 项目,针对 TS 与非TS 项目,分别使用不同的 parser 与 plugins;
Vue2 与 Vue 于语法规则差别较大,也需要分别引用不同的 plugins;
3、定义默认的Stylelint
配置文件, 并将Prettier
配置置于配置列表的最后:
module.exports = {
extends: [
'stylelint-config-standard',
'stylelint-config-css-modules',
'stylelint-config-rational-order',
'stylelint-config-prettier',
'stylelint-no-unsupported-browser-features',
'stylelint-prettier/recommended',
],
plugins: ['stylelint-order', 'stylelint-declaration-block-no-ignored-properties'],
//...
}
4、定义默认的Husky
配置文件:
module.exports = {
hooks: {
'commit-msg': 'commitlint -E HUSKY_GIT_PARAMS',
'pre-commit': 'lint-staged',
},
};
5、定义默认的Lint-staged
配置文件:
module.exports = {
'*.{js,jsx,ts,tsx,vue}': ['eslint --fix', 'prettier --write'],
'*.{less,postcss,css,scss,vue}': ['stylelint --fix', 'prettier --write'],
};
6、定义默认的CommitLint
配置文件:
module.exports = {
extends: [
"@commitlint/config-conventional"
],
rules: {
'type-enum': [2, 'always', [
'upd', 'feat', 'fix', 'refactor', 'docs', 'chore', 'style', 'revert'
]],
'type-case': [0],
'type-empty': [0],
'scope-empty': [0],
'scope-case': [0],
'subject-full-stop': [0, 'never'],
'subject-case': [0, 'never'],
'header-max-length': [0, 'always', 72]
}
};
文件名校验
文件名校验依赖 eslint-plugin-filename 插件完成,支持别名与自定义正则两种形式。
默认情况下,支持的文件名格式为camelcase
或pascalcase
。在这种情况下,你不需要修改任何配置信息。
小驼峰命名法(Lower Camel Case):第一个单词的首字母小写;第二个单词开始每个单词的的首字母大写。例如:firstName、lastName。
大驼峰命名法(Upper Camel Case:每一个单词的首字母都大写。例如:FirstName、LastName、CamelCase。也被称为 Pascal 命名法(Pascal Case)。
内置的别名有 pascalcase
/PascalCase
, camelcase
/camelCase
, snakecase
/snake_case
, kebabcase
/kebab-case
, 还支持以数组的形式传入多个别名。
例如:
rules: {
'filename/match': [2, 'camelcase'],
},
或
rules: {
'filename/match': [2, ['camelcase','pascalcase']], //此配置为默认配置
},
如果上述内置的别名不能满足需求,你还可以自定义正则表达式,例如:
'filename/match': [2, /^([a-z]+-)*[a-z]+(?:\..*)?$/],
甚至可以针对不同的文件类型使用不同的规则,例如:
'filename/match': [2, { '.js': 'camelCase', '.ts': /^([a-z]+-)*[a-z]+(?:\..*)?$/ }],
使用方法
安装
使用 yarn 或 npm 安装依赖包
yarn add -D code-specification-unid
或
npm i --save-dev code-specification-unid
由于code-specification-unid
依赖已经对大部分可能用到的代码规范相关依赖包做了统一收敛,因此不需要再重新安装以下依赖,如果已经安装,请先自行删除(包括对应的配置文件):
"@babel/core": "^7.12.10",
"@babel/eslint-parser": "^7.12.1",
"@babel/plugin-proposal-class-properties": "^7.13.0",
"@babel/plugin-proposal-decorators": "^7.13.5",
"@babel/preset-env": "^7.12.11",
"@babel/preset-react": "^7.12.10",
"@babel/preset-typescript": "^7.12.7",
"@commitlint/cli": "^11.0.0",
"@commitlint/config-conventional": "^11.0.0",
"@typescript-eslint/eslint-plugin": "^4.10.0",
"@typescript-eslint/parser": "^4.10.0",
"eslint": "^7.18.0",
"eslint-config-prettier": "^7.1.0",
"eslint-formatter-pretty": "^4.0.0",
"eslint-plugin-babel": "^5.3.0",
"eslint-plugin-compat": "^3.1.1",
"eslint-plugin-eslint-comments": "^3.1.1",
"eslint-plugin-import": "^2.17.3",
"eslint-plugin-jest": "^24.0.1",
"eslint-plugin-jsx-a11y": "^6.2.0",
"eslint-plugin-markdown": "^1.0.0",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-promise": "^4.1.1",
"eslint-plugin-react": "^7.22.0",
"eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-unicorn": "^20.0.0",
"eslint-plugin-filename": "^1.0.0",
"eslint-plugin-vue": "^7.5.0",
"husky": "^4.3.8",
"lint-staged": "^10.5.3",
"prettier": "^2.2.1",
"stylelint": "^13.7.0",
"stylelint-config-css-modules": "^2.2.0",
"stylelint-config-prettier": "^8.0.2",
"stylelint-config-rational-order": "^0.1.2",
"stylelint-config-standard": "^20.0.0",
"stylelint-declaration-block-no-ignored-properties": "^2.1.0",
"stylelint-no-unsupported-browser-features": "^4.1.4",
"stylelint-order": "^4.0.0",
"stylelint-prettier": "^1.1.2",
配置
依次创建以下配置文件,并按下面格式进行配置;
1、创建 .prettierrc.js
, 配置如下:
const spec = require('@es/kspack-specification');
module.exports = {
...spec.prettier,
// your rules
};
2、创建 .eslintrc.js
,配置如下:
module.exports = {
extends: [require.resolve('@es/kspack-specification/dist/eslintReact')],
rules: {
// your rules
},
};
注意针对不同工程,请引入不同的 eslint 配置包
react 对应 eslintReact
vue2 对应 eslintVue2
vue3 对应 eslintVue3
3、创建 .stylelintrc.js
,配置如下:
module.exports = {
extends: [require.resolve('@es/kspack-specification/dist/stylelint')],
rules: {
// your rules
},
};
4、创建 .huskyrc.js
, 配置如下:
const spec = require('@es/kspack-specification');
module.exports = {
...spec.husky,
// your rules
};
5、创建 .lintstagedrc.js
, 配置如下:
const spec = require('@es/kspack-specification');
module.exports = {
...spec.lintstaged,
// your rules
};
6、创建 .commitlintrc.js
, 配置如下:
const spec = require('@es/kspack-specification');
module.exports = {
...spec.commitlint,
// your rules
};
注意:提交消息的格式类型有 'upd', 'feat', 'fix', 'refactor', 'docs', 'chore', 'style', 'revert'
, 比如:feat: 添加国际化支持
执行
如果是 react 项目,在你项目 package.json 的 script 中添加以下命令:
"lint": "npm run lint:js && npm run lint:css && npm run lint:format",
"lint:js": "eslint src --fix --ext .js,.jsx,.ts,.tsx --cache --cache-location node_modules/.cache/eslint/",
"lint:css": "stylelint --fix \"src/**/*.{less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
"lint:format": "prettier --write \"src/**/*.{js,json,ts,tsx,css,less,scss,html,md}\"",
如果是 vue 项目,在你项目 package.json 的 script 中添加以下命令:
"lint": "npm run lint:js && npm run lint:css && npm run lint:format",
"lint:js": "eslint src --fix --ext .js,.jsx,.ts,.tsx,.vue --cache --cache-location node_modules/.cache/eslint/",
"lint:css": "stylelint --fix \"src/**/*.{less,postcss,css,scss,vue}\" --cache --cache-location node_modules/.cache/stylelint/",
"lint:format": "prettier --write \"src/**/*.{js,json,ts,tsx,vue,css,less,scss,html,md}\
完整的使用示例可以参考这个react-snowpack