你的前端工程,二哈和它的朋友们都安排上了么?

photo © dogs-wallpapers.eu

🎼 前言

新起一个前端项目,除了决定项目架构,第一件事情,你会做什么?接手现成的项目,你又会做哪些重要的事情?

我新起一个项目的时候,第一件事情,就是把 husky + 各种 linter 都安排上。

接手现成的项目,第一看本地运行,第二看依赖更新,第三就是看 husky + 各种 linter 有没有安排上。

TL;DR

关爱洁癖程序员,寻求项目可持续发展,请把二哈 husky 和它的朋友们(commitlintlint-stagedeslintstylelintmarkdownlintnpm-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

主要步骤

  1. 安装依赖
  2. package.json
  3. .husky/pre-commit

安装 husky

bash 复制代码
pnpm add -w -D husky                          # 1

安装 huskydevDependencies,也可以 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

  1. 安装依赖
  2. .commitlintrc
  3. .husky/commit-msg

安装 commitlint

bash 复制代码
pnpm add -w -D @commitlint/{cli,config-conventional}   # 1

安装 @commitlint/cli + @commitlint/config-conventionaldevDependencies,也可以 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

  1. 安装依赖
  2. .husky/pre-commit
  3. package.json

安装 lint-staged

bash 复制代码
pnpm add -w -D lint-staged                          # 1

安装 lint-stageddevDependencies,也可以 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

主要步骤

  1. 安装依赖
  2. .eslintrc
  3. 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

主要步骤

  1. 安装依赖
  2. .stylelintrc
  3. 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 呢?

主要步骤

  1. 安装依赖
  2. .stylelintrc
  3. package.json

安装 postcss-less

bash 复制代码
pnpm add -w -D postcss-less       # 1

安装 postcss-less,也可以 npm i -D postcss-less

设置 postcss-less

.stylelintrc

参考 Getting started - Using more than one custom syntax

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,程序员都喜欢,但不是每个人写出来的都让人看着舒服。

主要步骤

  1. 安装依赖
  2. .markdownlint.yml(可选,但推荐)
  3. package.json

安装 markdownlint-cli2

有两个包可以用:markdownlint-climarkdownlint-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 的方式管理,这就很有必要了。

主要步骤

  1. 安装依赖
  2. .npmpackagejsonlintrc.json
  3. 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 包,这里不十分具体地做介绍,唯一要强调的是依赖问题,个人的建议是:

  1. xxlint-configxxlint 放在 peerDependencies
  2. 各种插件作为包的 dependencies,不要让 config 包的使用者安装
  3. 记得常升级

以下是相关的文档:

🙋 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

🪭 写在最后

还有别的 linter 么?

相关推荐
让开,我要吃人了2 小时前
HarmonyOS开发实战(5.0)实现二楼上划进入首页效果详解
前端·华为·程序员·移动开发·harmonyos·鸿蒙·鸿蒙系统
everyStudy3 小时前
前端五种排序
前端·算法·排序算法
甜兒.4 小时前
鸿蒙小技巧
前端·华为·typescript·harmonyos
Jiaberrr8 小时前
前端实战:使用JS和Canvas实现运算图形验证码(uniapp、微信小程序同样可用)
前端·javascript·vue.js·微信小程序·uni-app
everyStudy8 小时前
JS中判断字符串中是否包含指定字符
开发语言·前端·javascript
城南云小白8 小时前
web基础+http协议+httpd详细配置
前端·网络协议·http
前端小趴菜、8 小时前
Web Worker 简单使用
前端
web_learning_3218 小时前
信息收集常用指令
前端·搜索引擎
tabzzz9 小时前
Webpack 概念速通:从入门到掌握构建工具的精髓
前端·webpack
200不是二百9 小时前
Vuex详解
前端·javascript·vue.js