前端工程化代码质量: Linter/Format

2,414 阅读6分钟

Thus, programs must be written for people to read, and only incidentally for machines to execute

代码质量不言而喻,好代码辑清晰,阅读流畅,团队合作更容易。

前置知识 AST 抽象语法树

抽象语法树 AST 是远离业务逻辑,迈向编程底层,提升个人编程能力的重要知识点。虽然 AST 的工具很多可能不会用在生产环境上,但在诸如开发体验上有很大的帮助。

在 vscode AST 插件体验

如果经常使用 vscode, 可以使用 vscode-ast 插件,来直观感受 JS/TS ast 分析与编辑器中代码的映射关系。

image.png

在一个 .js 文件尝试 AST 分析:

image.png

ast 作为底层知识,这里只是介绍现在 nodejs 中常用的 JS/TS 工具:

JS解释器名称说明
esprima高性能、符合标准的 ECMAScript 解析器
Babylonbabel 的 js 解释器
espree在acorn基础上衍生而来,eslint中使用
acornwebpack使用js解释器
astexplorer支持多语言的 AST 在线工具(使用 codemirror 编写)
swc基于 rust 的工具链
typescriptts 语言词法分析
@typescript-eslint/parser在 eslint 中使用 esLint
flowjs 静态类型检查
uglifyjs压缩工具

css ast

css 解释器名称说明
cssomCSS解析器(不在维护状态)
csstreeCSS 工具集,包括基于 W3C 规范和浏览器实现的快速详细解析器、助行器、生成器和词法分析器
postcss用 js 编写的 css 转换工具
rework/csscss 解释器

AST 基本工作流内容

  • parser 转换 language -> AST
  • walker 遍历
  • generator 遍历器
  • lexer 词法分析器

image.png

使用 postcss 为例

读取 css 为 ast

// plugin.js
function plugin(opts = {}) {
  return {
    postcssPlugin: "transform-px-to-em",
    Declaration(decl) {
      decl.value = decl.value.replace(/px/g, "rem");
    },
  };
}

plugin.postcss = true;

export default plugin;
// transform.js
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";

import postcss from "postcss";
import plugin from "./plugin.js";

const __filename = fileURLToPath(import.meta.url); // file 协议转转成 绝对路径
const __dirname = path.dirname(__filename); // 获取当前文件路径

const resolvePath = (p) => path.resolve(__dirname, p);

async function init() {
  const cssCode = fs.readFileSync(resolvePath("./index.css"));
  const cssAst = await postcss([plugin])
    .process(cssCode)
    .catch((e) => {
      console.log(e);
    });
  console.log(cssAst);
  fs.writeFileSync(resolvePath("./_index.css"), cssAst.css);
}

init();
// before
div {
    padding: 0px 20px 10px 10px;
    margin: 0px; /*reset margin*/
}
// after
div {
    padding: 0rem 20rem 10rem 10rem;
    margin: 0rem; /*reset margin*/
}

Node 应用应用层面 AST 相关工具

lint说明
eslintjs 相关 linter 工具
prettier多文件格式化工具(主打格式功能,可以集成到 eslint 中)
editorconfig编辑器行为约束
stylelint样式文件 lint 约束,修复工具
commitlint提交代码时 lint

lint 与 git 配合

git 功能说明
lint-staged仅处理 lint-staged 的能力
git hook pre-commit提交之前需要执行的hook
git hook commit-msg提交信息

非 AST 工具

非 AST 解析说明
esbuildesbuild 不是基于 AST 代码解析

VScode 插件

Untitled.png

  1. eslint
  2. stylelint
  3. Error Lens
  4. prettier
  5. editorconfig for VSCode

ESLint

以下示例使用 vscode 编辑器(确保 eslint 插件):

Vite + React + TS + ESLint

image.png

// choices react-ts
pnpm create vite

// new eslint init eslint config and choice typescript
npm init @eslint/config 

自动生成.eslintrc.cjs 文件,因为是 esm 然后将其改为 .eslintrc

ESLint 配置文件示例

{
    "env": {
        "browser": true,
        "es2021": true,
        "node": true
    },
    "extends": [
        "eslint:recommended",
        "plugin:@typescript-eslint/recommended"
    ],
    "overrides": [
    ],
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "ecmaVersion": "latest"
    },
    "plugins": [
        "@typescript-eslint"
    ],
    "rules": {
    }
}

在 npm 脚本中添加 eslint 命令

"scripts": {
    "lint": "eslint --ext .ts,.tsx, ./src",
    "lint:fix": "eslint --ext .ts,.tsx, ./src --fix"
}

在 tsx 文件添加 const 常量修改代码会报错:

const val = 1;
val = 2;

image.png

Prettier

全局安装 prettier 或者 项目级别 prettier, 在 ESLint 中使用 prettier 注意使用发生冲突()

Vite + React + TS + ESLint + Prettier

pnpm install prettier -D
pnpm install eslint-config-prettier eslint-plugin-prettier -D

prettier 配置文件: .prettier/.prettierignore

{
  "semi": false,
  "trailingComma": "none",
  "singleQuote": true,
  "jsxSingleQuote": true,
  "printWidth": 80,
  "tabWidth": 2
}

配置 .eslintrc 从 eslint 中启动 prettier 进行格式化,因为上面已经安装了:

  • eslint-config-prettier
  • eslint-plugin-prettier
{
    "extends": [
        "eslint:recommended",
        "plugin:@typescript-eslint/recommended",
        "plugin:prettier/recommended"
  ],
}

这样就可以在 eslint npm 脚本中启动 prettier。

StyleLint

Vite + React + TS + ESLint + Prettier + StyleLint

对样式进行 lint 和 格式化:

pnpm install stylelint -D
pnpm install stylelint-config-prettier stylelint-prettier

npm 中添加脚本:

"scripts": {
    "stylelint": "stylelint ./src/**/*.{css,less,scss,sass}",
    "stylelint:fix": "pnpm run stylelint --fix",
},

stylelint 配置文件示例

// .stylelintrc
{
  "extends": ["stylelint-prettier/recommended"]
}

当我们有一些 lint 的时候,stylelint 就会提示正在编码的你,此时有一个很好的提示,在初期编码的你,这个更多不是约束,而是很好的提示。

Commitlint

Vite + React + TS + ESLint + Prettier + StyleLint + Commitlint

全局使用、局部使用

// 全局
npm install -g @commitlint/cli @commitlint/config-conventional
// 局部
npm install --save-dev @commitlint/config-conventional @commitlint/cli

初始化 git

git init

touch .gitignore

# 加入
node_modules

接下来就需要配合 husky(或者其他的 git 钩子管理)配合 lint-staged。

commitlint 配置文件

echo "{ \"extends\": ['@commitlint/config-conventional'] }" > .commitlintrc

lint-staged 和 husky

lint-staged 仅仅 lint 出来 git 暂存库中得文件

pnpm install lint-staged -D

配置 lint-staged

在 package.json 中配置

{ "lint-staged": { "*": "your-cmd" } }

使用配置文件 .lintstagedrc

{
  "**/*.{ts, tsx, js, jsx}": ["npm run lint"],
  "src/**/*.{css,less,sass,scss}": ["npm run stylelint"],
  "*.md": ["prettier --write"]
}

npm 添加 lint-stated 脚本

{
   "lint-staged": "lint-staged",
}

husky 安装 + 配合 lint-staged

自动化生成 husky 相关文件

pnpm dlx husky-init && pnpm install # pnpm

添加 pre-commit/commit-msg 钩子

  • pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm run lint-staged
  • commit-msg
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx --no -- commitlint --edit ""

提交一个“非法”的 message

git add .
git commit -m  'abc'

错误: subject-empty/type-empty 的错误提示:

image.png

test 工具

测试也是在自动化过程中,重要的代码质量保证:

pnpm install jest ts-jest -D
  • 添加 npm 脚本(如果需要也可以添加到 lint-staged 中)
"scripts": {
    "test": "jest"
  },
  • 配置 jest
export default {
  coverageProvider: 'v8',
  preset: 'ts-jest',
  roots: ['<rootDir>/src'],
  testEnvironment: 'node',
  transform: {
    '^.+\\.tsx?$': 'ts-jest'
  }
}

提交规范工具 commitizen

两种安装方式:全局安装-项目安装

如何提交代码?

提交行为项目不应该直接业务代码相关,推荐全局安装,写入配置文件,方便在其他的电脑切换时方便使用。

npm install -g commitizen

commitizen 使用 首先适配器(如何修改,这里不在探讨)

commitizen init cz-conventional-changelog --save-dev --save-exact

在全局配置文件中指定适配器

echo '{ "path": "cz-conventional-changelog" }' > ~/.czrc

指定 git cz 命令取代提交 git commit

npm install commitizen cz-conventional-changelog -D

局部安装

局部安装配置 commitizen 配置 package.json 中

"config": {
    "commitizen": {
      "path": "./node_modules/cz-conventional-changelog"
    }
  }

基本完成提交一次

git add .
git commit -m 'feat: init'

提交成功。 下面是基本简单的配置.

修改业务代码,使用 git cz 提交就不需要自己手写符合要求的 commit message:

image.png

抽象为一个命令

为了能快速的初始化 lint 工具,进一步提升效率,封装一个命令工具,一个命令自动配置这些:


https://github.com/yyong008/lints-compose.git

npm link # 本地使用

pnpm lints-compose create # 创建 link 配置文件 + 安装 npm 包
pnpm lints-compose reset # 移除配置文件

小结

  1. 要了 lint 解工具解决了哪些功能化的问题
  2. 尝试自己一点适用于自己的工程化工具
  3. 熟悉 AST, 了解底层知识服务于工程化

参考