🎼 前言
新起一个前端项目,除了决定项目架构,第一件事情,你会做什么?接手现成的项目,你又会做哪些重要的事情?
我新起一个项目的时候,第一件事情,就是把 husky + 各种 linter 都安排上。
接手现成的项目,第一看本地运行,第二看依赖更新,第三就是看 husky + 各种 linter 有没有安排上。
TL;DR
关爱洁癖程序员,寻求项目可持续发展,请把二哈 husky 和它的朋友们(commitlint、lint-staged、eslint、stylelint、markdownlint、npm-package-json-lint)都安排上。
主要内容
适合读者
- 前端开发
- 代码洁癖患者
你将获得
- 自主配置 husky、commitlint、lint-staged(eslint、stylelint、markdownlint、npm-package-json-lint)的能力
- 开发 linter 们的可共享 config 包的基础知识
编辑历史
日期 | 版本说明 |
---|---|
2023/09/16 | V1 |
💋 Why
自诩「肉眼 Linter」的我,原本是甚不喜欢在提交代码之前被拦一道的,直到我的项目开始需要有别人参与进来...
从此,Git log 看到一水儿的「update」,无数次因为明显的 Eslint 格式问题导致的 CR 往复...终于忍无可忍,只好给项目加上了二哈。
结果就是,不得不说「真香」。
一来上述的往复问题明显少了;二来,即使编码习惯强如「肉眼 Linter」的我,也会有偶尔迷糊的时候。三来,大家的代码习惯都有了不同程度的提升。
从此以后,我会故意在临时代码中放几个错误,以免误提交。
一个个都安排上
本文以 pnpm 为主用命令,对 pnpm 的 Workspace 不太熟的同学,可以看一下我的 《# Workspace 那些破事 - 浅探 npm、yarn、pnpm、bun》。
husky
一切的核心就是这个二哈 husky 了,可以参考 Getting started。
官方推荐使用自动安装,然而我觉得,你至少应该先手动安装一次,自动安装看这个:
bash
# pnpm
pnpm dlx husky-init && pnpm install
# 或 npm
npx husky-init && npm install
主要步骤
- 安装依赖
- package.json
- .husky/pre-commit
安装 husky
bash
pnpm add -w -D husky # 1
安装 husky 至 devDependencies
,也可以 npm i -D husky
。
设置 husky
package.json
bash
npm pkg set scripts.prepare="husky install" # 2
npm run prepare # 3
可以 npm run prepare
手动运行一次 scripts.prepare
,也可以清理掉 node_modules 之后,pnpm i
,这样会自动执行 scripts.prepare
(即后续初始化项目不需要手动执行)。
.husky/pre-commit
bash
npx husky add .husky/pre-commit "npm test" # 4
操作完毕,会生出一个 .husky 目录,长这样:
目录 .husky/_
是以上第三步生成的,它不会被提交到远程仓库;pre-commit
是第四部生成的,内容如下:
shell
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npm test
这个钩子文件表示在执行 git commit
前将先运行 npm test
,只有 npm test
成功,才会真正执行 git commit
,这样,代码问题就会被拦截在 commit 之外。
验证 husky
现在,我们需要验证的是,敲命令 git commit
,会不会如预期先运行 npm test
:
bash
git add . # 5
git commit -m 'husky setup' # 6
报错了,因为 npm init
生的 "test": "echo \"Error: no test specified\" && exit 1"
会抛错,改:
bash
npm pkg set scripts.test="echo 'test nothing 😈'" # 7
再试(注意,这里 git commit
的参数为 -am
不是 -m
):
bash
git commit -am 'husky setup' # 8
过程图解
以上所有过程 1-8 如下图所示:
commitlint
在用 commitlint 之前,我的 commit message 长这样:
其实,这样也没有任何问题,该有的都有,这种「不成文的约定」,在只有一个人写代码的时候,毫无问题。一旦超过一个人,问题就来了。
每次我在 Git 上回顾别人代码的时候,看到一水的「update」,完全不清楚他到底干了什么。我没法要求,或者没法一直督促他们该怎样写,于是 commitlint 出场的时候到了。
同时我自己的风格也是「被教育」好了:
主要步骤
更详细的说明可以看官方文档 Guide: Local setup。
- 安装依赖
- .commitlintrc
- .husky/commit-msg
安装 commitlint
bash
pnpm add -w -D @commitlint/{cli,config-conventional} # 1
安装 @commitlint/cli + @commitlint/config-conventional 至 devDependencies
,也可以 npm i -D @commitlint/{cli,config-conventional}
。
设置 commitlint
.commitlintrc
bash
echo "{\n \"extends\": [\n \"@commitlint/config-conventional\"\n ]\n}" > .commitlintrc # 2
.husky/commit-msg
bash
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit ${1}' # 3
如上图,现在 .husky 下多了一个 commit-msg,内容为:
bash
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx --no -- commitlint --edit ${1}
验证 commitlint
bash
git add . # 4
git commit -m 'this commit shall fail..' # 5
git commit -m 'chore: commitlint setup' # 6
过程图解
以上所有过程 1-6 如下图所示:
lint-staged
接下来是「不良代码」的拦截了,它的核心是 lint-staged。
主要步骤
更详细的说明可以看官方文档 README: Installation and setup。
- 安装依赖
- .husky/pre-commit
- package.json
安装 lint-staged
bash
pnpm add -w -D lint-staged # 1
安装 lint-staged 至 devDependencies
,也可以 npm i -D lint-staged
。
设置 lint-staged
.husky/pre-commit
bash
npx husky add .husky/pre-commit "npx lint-staged" # 2
添加 pre-commit
执行的命令,也可以直接修改 .husky/pre-commit:
diff
npm test
+ npx lint-staged
package.json
也可以改成
.lintstagedrc
的方式。
添加如下代码:
diff
+ "lint-staged": {
+ "###": "do nothing"
+ }
}
以上只是为了告诉 lint-staged 已经 配置 好了,但不会有任何文件命中规则。如果没有这一段,或者这一段是 {}
,就会报错「husky - pre-commit hook exited with code 1 (error)」。
验证 lint-staged
bash
git add . # 3
git commit -m 'chore: commitlint setup' # 4
因为,没有文件命中,所以直接通过。
过程图解
以上所有过程 1-4 如下图所示:
lint-staged >> eslint
让 lint-staged 真正有用,我们把能安排上的 linter 都安排是,先 eslint。
主要步骤
- 安装依赖
- .eslintrc
- package.json
安装 commitlint
接下来,增加 eslint 让 lint-staged 能够真正地工作。
bash
pnpm add -w -D eslint @alicloud/eslint-config # 1
安装 eslint + @alicloud/eslint-config,也可以 npm i -D eslint @alicloud/eslint-config
。
注意如果安装其他 eslint-config,可能其
peerDependencies
要求你自行安装部分依赖,@alicloud/eslint-config
是我写的,基于 eslint-config-ali 但更严格,已经内置了必要的依赖。
设置 commitlint
.eslintrc
bash
echo "{\n \"extends\": \"@alicloud/eslint-config/tsx\"\n}" > .eslintrc # 2
package.json
diff
"lint-staged": {
- "###": "do nothing"
+ "*.{js,ts,tsx}": "eslint"
}
}
这告诉 lint-staged:执行 git commit
之前,对后缀为 .js
、.ts
、.tsx
的文件执行 eslint
。
验证 eslint
我们随便新建一个 TS 文件,故意安排一些问题:
ts
export default function helloWOrld(): string {
if (true) {
console.info('whatever'); //eslint-disable-line no-console
}
return 'hello world'
}
注意:对 TS 文件,eslint 必须有 tsconfig.json 才能正常工作。
WebStorm 下的报错:
尝试提交,如期被拦:
bash
git add . # 3
git commit -m 'chore: eslint setup and test' # 4
修正所有的 Error (还留有一个 Warning):
ts
export default function helloWorld(): string {
if (true) {
console.info('whatever'); // eslint-disable-line no-console
}
return 'hello world';
}
再次提交,成功:
bash
git commit -am 'chore: eslint setup and test' # 5
过程图解
以上所有过程 1-5 如下图所示:
lint-staged >> stylelint
再来搞第二重要的 stylelint。
主要步骤
- 安装依赖
- .stylelintrc
- package.json
安装 stylelint
bash
pnpm add -w -D stylelint stylelint-config-standard # 1
安装 stylelint + stylelint-config-standard,也可以 npm i -D stylelint stylelint-config-standard
。
设置 stylelint
.stylelintrc
bash
echo "{\n \"extends\": \"stylelint-config-standard\"\n}" > .stylelintrc # 2
package.json
diff
"lint-staged": {
- "*.{js,ts,tsx}": "eslint"
+ "*.{js,ts,tsx}": "eslint",
+ "*.{css}": "stylelint"
}
}
验证 stylelint
新建一个 CSS 文件,故意安排一些问题:
css
.empty {
}
.prop-dup {
color: red;
color: red;
}
// illegal comment and color shall be simple
.illegal-comment {
background-color: #FF00CC;
}
WebStorm 下的报错:
尝试提交,如期被拦:
bash
git add . # 3
git commit -m 'chore: stylelint setup and test' # 4
修正所有的 Error:
css
.prop-dup {
color: red;
}
/* this is css comment */
.illegal-comment {
background-color: #f0c;
}
再次提交,成功:
bash
git commit -am 'chore: stylelint setup and test' # 5
过程图解
以上所有过程 1-5 如下图所示:
lint-staged >> stylelint >> less
这年头,还有谁直接写 CSS 呢 🙈?Awesome Stylelint 列举了很棒的预设配置、插件和集成方案,非常值得学习研究。
假设你的项目用 less 呢?
主要步骤
- 安装依赖
- .stylelintrc
- package.json
安装 postcss-less
bash
pnpm add -w -D postcss-less # 1
安装 postcss-less,也可以 npm i -D postcss-less
。
设置 postcss-less
.stylelintrc
diff
{
- "extends": "stylelint-config-standard"
+ "extends": "stylelint-config-standard",
+ "overrides": [{
+ "files": ["*.less"],
+ "customSyntax": "postcss-less"
+ }]
}
package.json
diff
"lint-staged": {
"*.{js,ts,tsx}": "eslint",
- "*.{css}": "stylelint"
+ "*.{css,less}": "stylelint"
}
}
验证 postcss-less
新建一个 LESS 文件,故意安排一些问题:
less
.article {
p {
margin: 1em 0em;
}
em {
font-style: normal;
color: #f70;
}
button {
}
// a warning message
.WarningMessage {
padding: 12px;
padding: 14px;
background-color: #ffc;
transition: all ease 200ms;
}
}
WebStorm 下的报错:
尝试提交,如期被拦:
bash
git add . # 2
git commit -am 'chore: stylelint for less test' # 3
修正所有的 Error:
less
.article {
p {
margin: 1em 0;
}
em {
font-style: normal;
color: #f70;
}
// a warning message
.warning-message {
padding: 12px;
background-color: #ffc;
transition: all ease 200ms;
}
}
再次提交,成功:
bash
git commit -am 'chore: stylelint for less test' # 4
过程图解
以上所有过程 1-4 如下图所示:
lint-staged >> markdownlint
Markdown,程序员都喜欢,但不是每个人写出来的都让人看着舒服。
主要步骤
- 安装依赖
- .markdownlint.yml(可选,但推荐)
- package.json
安装 markdownlint-cli2
有两个包可以用:markdownlint-cli 或 markdownlint-cli2,两个不是升级或竞争关系,两个作者相互间还比较熟。两个包用起来效果接近,但 2 的效率会好一些(感觉不大出来)。
你可以随便用哪个,我用 2。
bash
pnpm add -w -D markdownlint-cli2 # 1
安装 markdownlint-cli2,也可以 npm i -D markdownlint-cli2
。
设置 markdownlint-cli2
.markdownlint.yml
bash
touch .markdownlint.yml # 2
Markdownlint 有 默认的规则,即使没有 .markdownlint.yml 也能正常工作,只是默认设置可能不是你或者你的团队希望的写法。
可以在 .markdownlint.yml 对默认规则进行覆盖,更可以把你自己的规则发布成包,然后在你所有项目下继承。
.markdownlint.yml 内容:
yml
# refer to https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md
# https://github.com/DavidAnson/markdownlint/blob/main/schema/.markdownlint.yaml
# MD004/ul-style - Unordered list style
MD004:
style: "sublist"
# MD013/line-length - Line length
MD013:
line_length: 200
heading_line_length: 128
code_block_line_length: 200
# MD024/no-duplicate-heading/no-duplicate-header - Multiple headings with the same content
MD024:
allow_different_nesting: true
# MD047/single-trailing-newline
MD047: false
当然,你可以将自己的预设发布成包,然后在需要的地方继承它:
yml
extends: "..."
package.json
diff
"lint-staged": {
+ "*.md": "markdownlint-cli2",
"*.{js,ts,tsx}": "eslint",
"*.{css,less}": "stylelint"
}
验证 markdownlint-cli2
新建或者修改 Markdown 文件,故意安排一些问题:
md
# README
# 1. Title 1
## title shall start with capital letter
[an empty link]()
* list 1
* list 2
+ list prefix shall not mix
# 2. Title 2
This is blah blah.
## Hello
Blah blah blah.
## Hello
The heading above is a duplicate
VSCode 下报错(需装插件,WebStorm 没有此福利):
尝试提交,如期被拦:
bash
git add . # 3
git commit -m 'chore: markdownlint test' # 4
修正所有的 Error:
md
# README
## 1. Title 1
### title shall start with capital letter
[an empty link](http://www.google.com/)
* list 1
* list 2
+ sub list item 1
## 2. Title 2
This is blah blah.
## Hello
Blah blah blah.
## Hello 2
The heading above is a duplicate
再次提交,成功:
bash
git commit -am 'chore: markdownlint test' # 5
过程图解
以上所有过程 1-5 如下图所示:
lint-staged >> npm-package-json-lint
「什么?package.json 都要 lint?」
如果你需要写 package,采用 Monorepo 的方式管理,这就很有必要了。
主要步骤
- 安装依赖
- .npmpackagejsonlintrc.json
- package.json
安装 npm-package-json-lint
bash
pnpm add -w -D npm-package-json-{lint,lint-config-default} # 1
安装 npm-package-json-lint + npm-package-json-lint-config-default,也可以 npm i -D npm-package-json-{lint,lint-config-default}
。
设置 npm-package-json-lint
.npmpackagejsonlintrc.json
bash
touch .npmpackagejsonlintrc.json # 2
内容如下:
json
{
"extends": "npm-package-json-lint-config-default",
"rules": {
"require-author": "error",
"require-bugs": "error",
"require-description": "error",
"require-keywords": "error",
"require-license": "error",
"require-repository": "error",
"no-repeated-dependencies": "error",
"description-format": ["error", {
"requireCapitalFirstLetter": true,
"requireEndingPeriod": true
}],
"name-format": "error",
"version-format": "error",
"no-duplicate-properties": "error",
"prefer-no-engineStrict": "error",
"prefer-property-order": ["error", [
"name",
"type",
"private",
"version",
"description",
"author",
"authors",
"license",
"homepage",
"funding",
"repository",
"bugs",
"keywords",
"engines",
"bin",
"sideEffects",
"types",
"main",
"module",
"umd",
"exports",
"publishConfig",
"files",
"scripts",
"peerDependencies",
"dependencies",
"devDependencies",
"overrides",
"workspaces",
"tsd",
"readme"
]]
}
}
package.json
diff
"lint-staged": {
+ "package.json": "npmPkgJsonLint",
"*.md": "markdownlint-cli2",
"*.{js,ts,tsx}": "eslint",
"*.{css,less}": "stylelint"
}
验证 npm-package-json-lint
现成的 package.json 已经被改了,会触发检查,有些问题:
bash
git add . # 3
git commit -m 'chore: npm-package-json-lint setup and test' # 4
改到没有问题,再次提交:
bash
git commit -am 'chore: npm-package-json-lint setup and test' # 5
过程图解
以上所有过程 1-5 如下图所示:
总结
以上,我们以 husky 为中心,为项目搭建了基本质量保障体系:
- husky -
- commitlint - git message 质量
- lint-staged - 新提交代码质量
- eslint - JS、TS 代码质量
- stylelint - 样式代码书写规范
- markdownlint - Markdown 书写规范
- npm-package-json-lint - package.json 书写规范
🍡 Shareable config
- 凡是支持
extends
的,都可以将配置发布成包进行复用 - 凡是支持
plugins
,都支持自定义扩展
所有的 Config 都可以,也都应该有可复用的 config 包,这里不十分具体地做介绍,唯一要强调的是依赖问题,个人的建议是:
xxlint-config
将xxlint
放在peerDependencies
- 各种插件作为包的
dependencies
,不要让 config 包的使用者安装 - 记得常升级
以下是相关的文档:
- Eslint - Shareable Config
- Eslint - Rules
- Stylint - Customizing
- Stylint - Rules
- Markdownlint 支持
extends
- Markdownlint - Rules
- Package.json Lint - Shareable Config
- Package.json Lint - Rules
🙋 FAQ
❓ 如何解决 lint-staged
「xx failed without output (ENOENT)」抛错?
一般是命令写错了,比如 markdownlint-cli 生成的命令是 markdownlint
,如果你写成 markdownlint-cli
,就会报这个错。
可以在 node_modules/.bin 下看命令的正确书写格式:
❓ 如何不进行 git commit
调试相关 linter 命令?
可以用 npx
,比如:
npx eslint <file>
npx stylelint <file>
npx markdownlint-cli2 <file>
npx markdownlint <file>
使用markdownlint-cli
时npx npmPkgJsonLint <file>
📌 Links
- husky
- commitlint
- lint-staged
- eslint
- stylelint
- markdownlint
- markdownlint-cli
- markdownlint-cli2
- npm-package-json-lint
🪭 写在最后
还有别的 linter 么?