霞的云原生之旅: 前端工程化

559 阅读8分钟

前言本

本人现为一个学生身份, 美梦是作为基础建设的架构师, 本文意为编写一个通用的前端工程的模板, 没有经过一个长久的实践, 本文可能写的比较粗糙, 有错误和改进请友善提出, 编写与审核均为本人, 可能有文字描述错误, 不清晰等问题

更新历史

  • 2024/03/19 03:03: 重新编写ESLint部分, 并添加多个扩展与配置文件的更新, 优化部分文字描述
  • 2024/03/18 21:34: 添加StyleLint部分
  • 2024/03/18: 添加配置文件与补充说明和部分文字描述修改

说明

使用Husky + Oxlint + ESLint + StyleLint + Prettier + Commitlint 作为基础设施库

包含本文内容的仓库URL

  • Husky: Git hooks工具,通过配置一系列钩子,可以在git操作的不同阶段执行相应的命令, 这里使用它在代码提交到仓库前的检查, 使用使用Oxlint快速检索错误,然后执行ESLint检查代码规范与Prettier进行代码格式化输出, 统一代码规范
  • Oxlint: 现阶段只有Lint功能开发比较能用, 对于大型项目, 在CI时使用它会运行的比较快
  • ESLint: TS/TSX/Vue等代码的代码样式限制与格式化与修复
  • Prettier: TS/TSX/Vue/Markdown等文件的代码样式格式化
  • StyleLint: CSS/SCSS/Less等文件的排序,格式化
  • Commitlint: git commit时的规则校验

本文均使用pnpm包管理器, 但是部分包含npm的使用方法, 在这里它们的区别可能就在于指令的用法不同

Husky

作用

Git hooks工具,通过配置一系列钩子,可以在git操作的不同阶段执行相应的命令

官方文档

安装

如果.git目录不是在前端项目里,如:

. 
├── .git/ 
├── backend/ # No package.json 
└── frontend/ # Package.json with husky

则需要修改prepare的内容, 不使用pnpm exec husky init, 需要手动在package.json修改为:

"prepare": "cd .. && husky frontend/.husky"

如果手动修改了scriptsprepare,需要重新执行一次

pnpm prepare

注意, 如果.git目录不是在前端项目里, 那么pre-commit也要修改进入到对应的目录, 例如:

有这样的目录结构:

. 
├── .git/ 
├── backend/ # No package.json 
└── frontend/ # Package.json with husky

那么, 对应的pre-commit文件内容就应该这样写:

cd frontend

pnpm test

Oxlint

定义

检查文件是否按照定义的规则进行编写

在工程化的作用(扮演的角色)

它现阶段不能直接代替ESLint, 它没有修复代码的功能, 只是它使用了Rust编写的工具, 运行很快, 现阶段, 作者推荐在CI阶段时运行在ESLint之前,这样,大部分常见问题还没走到 Eslint 这一步就被 Oxlint拦截, 大大减少大型项目的CI流水线的运行时间

oxc-project.github.io/docs/guide/…

pnpm add -D oxlint

Usages  用法

  • npx oxlint@latest --rules 以获取规则列表。
  • npx oxlint@latest --help 以获取配置说明

有用的选项和示例

  • --deny-warnings 将警告转换为错误,对于退出代码为 1 的 CI 失败很有用。
  • -D all 拒绝(打开)所有规则。
  • -A all -D eqeqeq 运行单个规则。
  • -D correctness -D perf -A debugger -A eqeqeq 拒绝(打开)和规则,并允许(关闭) correctness debugger 和 eqeqeq perf 规则。
  • -c ./eslintrc.json 使用该 rules 字段配置规则,如 ESLint 中所述。仅 json` 支持格式

在package.json添加:

pnpm pkg set scripts.lint-check="oxlint"

如下效果:

{
...
"scripts": {
	...
    "lint-check": "oxlint",
	...
  },
...
}

.husky/pre-commit添加

cat >> .husky/pre-commit <<EOF
pnpm lint-check
EOF

检查:

pnpm lint-check

lint-staged

作用

在提交代码前进行lint检查时,可以让lint只检查git暂存区(staged)的文件,而不会检查所有文件

pnpm add --save-dev lint-staged

package.json

{
"lint-staged": {
    "src/**/*.{ts,tsx,js,jsx}": [
      "oxlint"
    ]
  },
}

ESLint

安装

pnpm create @eslint/config

or

npm init @eslint/config

配置

选项

  • --cache:该参数启用了 ESLint 的缓存功能,以提高重复运行 ESLint 时的性能。ESLint 将会缓存 lint 结果和文件状态,从而避免不必要的重新检查。
  • --max-warnings 0:此参数规定了允许的最大警告数量。在这个例子中,设置为 0 表示如果有任何警告产生,将被视为错误而导致命令失败。
  • src:指定需要进行 ESLint 检查的目录或文件。在这里,它表示对 "src" 目录进行检查。
  • --ext ts,tsx:这个参数指定了 ESLint 将要检查的文件扩展名。在这里,它表示 ESLint 将检查扩展名为 ".ts" 和 ".tsx" 的文件。
  • --fix:该参数告诉 ESLint 尝试自动修复发现的问题。如果某些问题可以被自动修复,ESLint 将会尝试修改源代码以修复这些问题。
pnpm pkg set scripts.eslint="eslint src/**/**{.ts,.tsx}"

pnpm pkg set scripts.eslint:fix="eslint --cache --max-warnings 0 src --ext ts,tsx --fix"

忽略文件

添加忽略格式化的目录

echo node_modules > .eslintignore 

检查

是否正常运行

pnpm eslint
pnpm eslint:fix

扩展

添加扩展插件, 对库进行更严格的限制, 本文以React演示, 这里使用了

  • xo : 一个通用的规则
  • plugin:react/recommended
  • eslint:recommended :ESLint推荐的规则
  • eslint:all:ESLint 附带的所有规则
  • eslint-plugin-react
  • eslint-plugin-jsdoc: jsdoc的格式化扩展
  • eslint-plugin-markdown: Markdown 中的 Lint JS、JSX、TypeScript 等。

如果使用了eslint-plugin-react, 则需要再在eslintrc的配置文件, 它支持多个后缀(js,json,yml,toml,...), 取决于你的实际生成配置文件类型,然后指定自动检测选择当前安装的版本:

settings:
  react:
    pragma: 'React'
    version: 'detect'

eslintrc的配置文件如下所示, 以yml为例:

# Source: .eslintrc.yml

env:
  browser: true
  es2021: true
extends:
  - xo
  - plugin:react/recommended
  - prettier
overrides:
  - extends:
      - xo-typescript
    files:
      - '**/*.js'
      - '**/*.ts'
      - '**/*.tsx'
      - '**/*.vue'

parserOptions:
  ecmaVersion: latest
  sourceType: module
plugins:
  - react
  - prettier
  - eslint-plugin-jsdoc
  - eslint-plugin-markdown
rules:
  prettier/prettier: error

settings:
  react:
    pragma: 'React'
    version: 'detect'

Prettier

  1. 安装
  • --save-exact 标志用于确保安装的包版本是精确匹配的
pnpm install -D --save-exact prettier
  1. 创建配置文件 创建一个prettier配置文件,并写入基本的配置:
cat > .prettierrc.json << EOF
{
  "semi": true,
  "tabWidth": 2,
  "singleQuote": true,
  "printWidth": 80,
  "trailingComma": "all",
  "endOfLine": "lf"
}
EOF
  1. 创建prettier忽略文件(eslint 默认会忽略 node_modules):
echo node_modules > .prettierignore
  1. 解决prettier规则与ESLint规则的冲突
pnpm add -D eslint-config-prettier

因为ESLint支持多种配置文件, 本文使用yml形式来保存配置, 编辑.eslintrc.yml: 在extends添加prettierplugins对象添加prettier以启用prettier

extends:
  - prettier
plugins:
  - prettier

安装和配置eslint-plugin-prettier插件以便将Prettier作为ESLint规则运行,并将差异报告为单个ESLint问题:

pnpm add -D eslint-plugin-prettier

.eslintrc.yml文件中添加配置:

rules: {
  prettier/prettier: error
}
  1. package.jsonlint-staged选择一个规则进行添加:
  • "**/*": "prettier --write --ignore-unknown": 让可识别的文件都使用prettier进行格式化
  • "*.{ts,tsx,js,jsx,cjs,mjs,vue,css,scss,less,md}": "prettier --write": 针对特定的文件类型进行格式化

示例:

pnpm pkg set lint-staged."**/*"="prettier --write --ignore-unknown"

如下效果:

{
	"lint-staged": {
	    "*.{ts,tsx,js,jsx,cjs,mjs,vue,css,scss,less,md}": "prettier --write --ignore-unknown",
	    "**/*": "prettier --write --ignore-unknown"
  },
}

StyleLint

安装

  • pnpm
pnpm create stylelint
  • npm
npm init stylelint

配置

package.json的script添加:

pnpm pkg set scripts.stylelint="stylelint src/**/**.{css,scss,sass}"
pnpm pkg set scripts.stylelint:fix="stylelint src/**/**.{css,scss,sass} --fix"

运行测试:

pnpm stylelint:fix
pnpm stylelint

如果样式文件有错误的地方, 那么会报错: ![[img/Pasted image 20240318235553.png]]

如何样式文件正常, 那么不会报错: ![[img/Pasted image 20240318235616.png]]

ESModule

如果使用Node.js的默认模块系统配置(package.json), 如果使用"type": "module", 那么文件名必须是 stylelint.config.mjs 或 .stylelintrc.mjs 本文选择ESModule, 文件为.stylelintrc.mjs

package.json:

{
	"type": "module"
}

.stylelintrc.mjs, 可直接使用本人的rules规则(在文章后面)或者进行个性化的定制, 参考官网rules:

@type JSDoc 注解使 Typescript 能够自动完成和类型检查。

// Rules: https://stylelint.io/user-guide/rules

/** @type {import('stylelint').Config} */
export default {
	'extends': [
		'stylelint-config-standard',
		'stylelint-config-standard-scss',
		'stylelint-config-recess-order',
	],
	'rules': {},
}

CommonJS

package.json:

{
	"type": "commonjs"
}

CommonJS 示例:

module.exports = {  
  'extends': [
    'stylelint-config-standard'
  ],
  'rules': {}
};

扩展

  • SCSS/SASS 如果你在项目使用SASS库, 那么还可以安装配套的库
pnpm i -D stylelint-config-standard-scss
  • stylelint-config-recess-order 一个 Stylelint 配置,它以 Recess 和 Bootstrap 的方式对 CSS 属性进行排序
 pnpm i -D stylelint-config-recess-order

然后再stylelint的配置文件:

  • stylelint.config.mjs 或 .stylelintrc.mjs 文件使用 export default (ES 模块)
  • stylelint.config.cjs 或 .stylelintrc.cjs 文件使用 module.exports (CommonJS) 的文件 上进行添加

stylelint.config.mjs 或 .stylelintrc.mjs 文件:

export default {
  'extends': [
    'stylelint-config-standard',
    'stylelint-config-standard-scss',
    'stylelint-config-recess-order',
  ],
}

stylelint.config.cjs 或 .stylelintrc.cjs 文件:

module.exports = {  
  'extends': [
    'stylelint-config-standard',
    'stylelint-config-standard-scss',
    'stylelint-config-recess-order',
  ],
}

Commitlint

  1. 安装
  • mac:
pnpm add -D @commitlint/{cli,config-conventional}
  • windows
npm install --save-dev @commitlint/config-conventional @commitlint/cli
  1. 创建配置
  • 官方默认配置:
echo "export default { extends: ['@commitlint/config-conventional'] };" > commitlint.config.js
  • 本人使用的配置:
cat > commitlint.config.js <<EOF
export default {
  extends: ['@commitlint/config-conventional'],
  'type-enum': [
    2,
    'always',
    [
      'feat',
      'fix',
      'perf',
      'style',
      'docs',
      'test',
      'refactor',
      'build',
      'ci',
      'chore',
      'revert',
      'wip',
      'workflow',
      'types',
      'release'
    ],
  ],
}
EOF

commitlint.config.js文件内容如下:

export default {
  extends: ['@commitlint/config-conventional'],
  'type-enum': [
    2,
    'always',
    [
      'feat',
      'fix',
      'perf',
      'style',
      'docs',
      'test',
      'refactor',
      'build',
      'ci',
      'chore',
      'revert',
      'wip',
      'workflow',
      'types',
      'release'
    ],
  ],
}
  1. 添加git commit hook钩子

注意, 如果.git目录不是在前端项目里, 那么commit-msg也要修改进入到对应的目录, 例如:

有这样的目录结构:

. 
├── .git/ 
├── backend/ # No package.json 
└── frontend/ # Package.json with husky

那么, 对应的commit-msg文件内容就应该这样写:

cd frontend

pnpm commitlint ${1}
  • pnpm:
echo "cd frontend && pnpm commitlint --edit \$1" > .husky/commit-msg
  • npm:
echo "cd frontend && npx --no -- commitlint --edit \$1" > .husky/commit-msg

或者: 作为替代方法,在package.json 创建脚本

npm pkg set scripts.commitlint="commitlint --edit"
echo "pnpm commitlint \${1}" > .husky/commit-msg

还是那句话, 如果.git目录不是在前端项目里, 那么commit-msg也要修改进入到对应的目录

默认生成的.husky/commit-msg文件是这样:

pnpm commitlint ${1}

你需要添加:

cd frontend

...
  1. 测试

测试失败示例:

git commit -m "foo: add commit-msg hook"

预期会输出错误:

> frontend@0.0.0 commitlint /Users/lisa/Public/Golang/src/2024/edu-system/frontend
> commitlint --edit ".git/COMMIT_EDITMSG"

⧗   input: foo: add commit-msg hook
✖   type must be one of [build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test] [type-enum]

✖   found 1 problems, 0 warnings
ⓘ   Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint

 ELIFECYCLE  Command failed with exit code 1.
husky - commit-msg script failed (code 1)

Pasted image 20240317024548.png

测试成功示例:

git commit -m "feat: add commit-msg hook"

Pasted image 20240317024605.png

配置参考

Vite React TS为示例:

package

pnpm add -D husky 
pnpm add -D lint-staged
pnpm add -D oxlint
pnpm add -D eslint typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react eslint-config-alloy eslint-plugin-jsdoc eslint-plugin-markdown
pnpm add -D eslint-config-prettier eslint-plugin-prettier
pnpm add -D --save-exact prettier
pnpm add -D @commitlint/{cli,config-conventional}

tsconfig.json

{
	"compilerOptions": {
		"target": "ES2020",
		"useDefineForClassFields": true,
		"lib": [
			"DOM",
			"DOM.Iterable",
			"ESNext"
		],
		"allowJs": true,
		"alwaysStrict": true,
		"skipLibCheck": true,
		"esModuleInterop": true,
		"allowSyntheticDefaultImports": true,
		"strict": true,
		"noUnusedLocals": true,
		"noUnusedParameters": true,
		"noFallthroughCasesInSwitch": true,
		"forceConsistentCasingInFileNames": true,
		"experimentalDecorators": true,
		"module": "ESNext",
		"moduleResolution": "Node",
		"resolveJsonModule": true,
		"isolatedModules": true,
		"noEmit": true,
		"jsx": "react-jsx",
		"sourceMap": true,
		"noImplicitAny": true,
		"noImplicitThis": true,
		"strictNullChecks": true,
		"types": [
			"node",
			"./src/types"
		],
		"baseUrl": "./",
		"paths": {
			"@/*": [
				"src/*"
			]
		}
	},
	"include": [
		"src/**/*.ts",
		"src/**/*.tsx"
	],
	"exclude": [
		"node_modules",
		"dist"
	],
	"references": [
		{
			"path": "./tsconfig.node.json"
		}
	]
}

package.json

package.json:

{
 "lint-staged": {
    "*.{ts,tsx,js,jsx,cjs,mjs,vue}": "eslint --fix",
    "**/*": "prettier --write --ignore-unknown"
  },
"scripts": {
    "prepare": "cd .. && husky frontend/.husky",
    "prettier:check": "prettier --check src/**/**{.cjs,.mjs,.ts,.tsx,.html,.md}",
    "prettier:write": "prettier --write src/**/**{.cjs,.mjs,.ts,.tsx,.html,.md}",
    "lint": "eslint src/**/**{.ts,.tsx}",
    "lint:fix": "eslint --cache --max-warnings 0 src --ext ts,tsx --fix",
    "lint:check": "oxlint",
    "lint-staged": "lint-staged",
    "commitlint": "commitlint --edit"
  },
}

ESLint

旧配置文件

在ESLint初始化时, 我选择了alloy的react ts配置

  1. 安装依赖
pnpm add -D eslint typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react eslint-config-alloy
  1. md扩展
p i -D eslint-plugin-markdown
  1. jsdoc扩展
p i -D eslint-plugin-jsdoc

.eslintrc.yml:

root: true
env:
  browser: true
  es2021: true
  node: true
  # mocha: true,
  # jest: true,
  # jquery: true
#globals:
# Your global variables (setting to false means its not allowed to be reassigned)
#
# myGlobal: false
extends:
  - alloy
  - alloy/react
  - alloy/typescript
  - plugin:react/recommended
  - plugin:react/jsx-runtime
  - plugin:markdown/recommended-legacy
  - plugin:jsdoc/recommended-typescript-error
#overrides:
#  #- extends:
#    #- xo-typescript
#  files:
#    - **/*.js
#    - **/*.ts
#    - **/*.tsx
#    - **/*.md

parserOptions:
  ecmaVersion: latest
  sourceType: module
  ecmaFeatures:
    jsx: true

plugins:
  - react
  - prettier
  - jsdoc
rules:
  prettier/prettier: error

settings:
  react:
    pragma: React
    version: detect

新配置文件

TODO

import jsdoc from 'eslint-plugin-jsdoc'
import js from '@eslint/js'
import markdown from 'eslint-plugin-markdown'

import prettier from 'eslint-config-prettier'

export default [
	// 包含在插件中的配置
	// jsdoc.configs.recommended,
	// {
	// 	// ...js.configs.recommended,
	// 	...js.configs.all,
	// },
	// {
	// 	...prettier,
	// },
	{
		files: [
			'**/*.js',
			'**/*.jsx',
			'**/*.ts',
			'**/*.tsx',
			'**/*.vue',
			'**/*.md',
		],
		plugins: {
			markdown,
			jsdoc,
		},
		processor: 'markdown/markdown',
		settings: {
			sharedData: 'Hello',
		},
		rules: {
			// 关闭分号
			semi: ['error', 'never'],
			quotes: ['error', 'single'],
			'prefer-const': 'error',
			'jsdoc/check-values': 'error',
			'jsdoc/require-description': 'warn',
		},
		languageOptions: {
			//评估你的 JavaScript(ECMAScript)的版本,可以设置为版本号(6)、年份(2022)或 "latest"
			ecmaVersion: 'latest',
			// ECMAScript 模块(ESM) - 代码有模块作用域,并以严格模式运行。
			// CommonJS - 代码有顶层函数作用域,并在非严格模式下运行。
			// Script - 代码有共享的全局作用域,并在非严格模式下运行。
			// 通过指定 sourceType 属性来指定你的代码要在哪种模式下运行。
			// 这个属性可以被设置为 "module"、"commonjs" 或 "script"。默认情况下,.js 和 .mjs 文件的 sourceType 是 "module",而 .cjs 文件则是 "commonjs"
			sourceType: 'script',
			//npm i @babel/eslint-parser, 可以使用还在实验性的JS, 使用 Babel 解析器,而不是使用默认解析器,来解析所有以 .js 和 .mjs 结尾的文件
			// parser: 'babelParser',
		},
		ignores: ['node_modules/*'],
	},
	// applies only to code blocks
	// 对 markdown 文件中以 .js 结尾的命名代码块禁用 strict 规则。
	{
		files: ['**/*.md/*.js'],
		rules: {
			strict: 'off',
		},
	},
]

StyleLint规则

花了2h+的时间来从官网进行每一条规则的校验和验证, 并编写了符合本人的审美标准的StyleLint规则

// Rules: https://stylelint.io/user-guide/rules

/** @type {import('stylelint').Config} */
export default {
	'extends': [
		'stylelint-config-standard',
		'stylelint-config-standard-scss',
		'stylelint-config-recess-order',
	],
	'rules': {
		// 重复
		// 块不允许重复的自定义属性
		'declaration-block-no-duplicate-custom-properties': true,
		// 块不允许重复的属性
		'declaration-block-no-duplicate-properties': true,
		// 禁止字体系列中的重复名称
		'font-family-no-duplicate-names': true,
		// 禁止在关键帧块中使用重复的选择器
		'keyframe-block-no-duplicate-selectors': true,
		// 禁止重复的@import
		'no-duplicate-at-import-rules': true,
		// 禁止重复的选择器
		'no-duplicate-selectors': true,

		// 空
		// 禁止空块
		'block-no-empty': true,
		// 禁止空注释
		// 'comment-no-empty': true,
		// 禁止空源, \t\t 与 \n 都被认为是错误
		'no-empty-source': true,

		// Invalid 无效
		// 不允许无效的十六进制颜色
		'color-no-invalid-hex': true,
		// 禁止函数中使用 calc 无效的无空格运算符
		'function-calc-no-unspaced-operator': true,
		// 禁止 !important 关键帧内无效声明
		'keyframe-declaration-no-important': true,
		// 禁止无效的媒体查询
		'media-query-no-invalid': true,
		//禁止无效的命名网格区域
		'named-grid-areas-no-invalid': true,
		// 禁止无效的双斜杠注释
		'no-invalid-double-slash-comments': true,
		//不允许无效的仓 @import 位规则
		'no-invalid-position-at-import-rule': true,
		// 禁止字符串中使用无效换行符
		'string-no-newline': true,

		// Irregular 规则
		// 不允许使用不规则的空格
		'no-irregular-whitespace': true,

		// Missing 缺斤少两
		// 禁止自定义属性缺少 var 函数
		'custom-property-no-missing-var-function': true,
		//禁止字体系列中缺少通用系列关键字a { font: 1em/1.3 Times; } 是错误
		'font-family-no-missing-generic-family-keyword': true,

		// Non-standard 非标准性
		// 不允许线性梯度函数使用非标准方向值
		// 类似 .foo { background: linear-gradient(top, #fff, #000); } 是错误
		'function-linear-gradient-no-nonstandard-direction': true,

		// Overrides 重写
		// 不允许覆盖相关手写属性的速记属性
		// 错误示例:
		// a {
		//   padding-left: 10px;
		//   padding: 20px;
		// }
		'declaration-block-no-shorthand-property-overrides': true,

		// Unmatchable 不匹配的操作
		// 禁止使用不匹配的 An+B 选择器
		'selector-anb-no-unmatchable': true,

		// Unknown 未知: true,
		// '不允许未知批注': true,
		'annotation-no-unknown': true,
		//禁止未知的 at 规则
		'at-rule-no-unknown': true,
		// 不允许声明中属性的未知值
		'declaration-property-value-no-unknown': true,
		//禁止未知函数
		'function-no-unknown': true,
		//不允许未知的媒体功能名称
		'media-feature-name-no-unknown': true,
		//不允许媒体功能的未知值
		'media-feature-name-value-no-unknown': true,
		//禁止未知动画
		'no-unknown-animations': true,
		//禁止未知的自定义属性
		'no-unknown-custom-properties': true,
		//禁止未知属性
		'property-no-unknown': true,
		//不允许未知的伪类选择器
		'selector-pseudo-class-no-unknown': true,
		//禁止未知的伪元素选择器
		'selector-pseudo-element-no-unknown': true,
		//禁止未知类型选择器
		'selector-type-no-unknown': true,
		// 不允许未知单位
		'unit-no-unknown': true,

		// Enforce conventions 强制执行约定
		// 禁止供应商前缀?
		'property-no-vendor-prefix': null,
		'no-descending-specificity': null,

		// Length 长度
		// 不允许使用长度为零的单位
		// 'length-zero-no-unit': true,

		// Case 大小写
		// CSS的函数的关键名字要求为特定的大小写, 可选值, lower: 小写, upper: 大写
		'function-name-case': 'lower',
		// 为类型选择器指定小写或大写
		'selector-type-case': 'lower',
		// 为关键字值指定小写或大写
		'value-keyword-case': 'lower',

		// Empty lines 空行
		// 要求或禁止在 @ 规则之前使用空行
		// 错误示例:
		// 1. a {} @media {}
		// 2. 中间没有空行:
		// a {}
		// @media {}
		'at-rule-empty-line-before': 'always',
		// 要求或禁止在注释前使用空行 可选值: never: 注释前不允许空行, always: 注释前必须有空行
		'comment-empty-line-before': [
			'always',
			{
				'except': ['first-nested'],
			},
		],
		// 要求或禁止在自定义属性之前使用空行
		'custom-property-empty-line-before': 'always',
		// 要求或禁止在声明前, 例如 --bar: pink 使用空行可选值: always: 回车区分, never: 不回车区分
		'declaration-empty-line-before': 'never',
		// 要求或禁止在规则前使用空行
		'rule-empty-line-before': 'never',

		// Max & min 最大值和最小值
		// 限制单行声明块中的字段数量
		// a { color: pink; top: 3px; } 是错误, 因为超过了1个属性
		'declaration-block-single-line-max-declarations': 1,
		// 限制声明中属性列表的值数
		// 'declaration-property-max-values':
		// 样式嵌套最大层数
		// https://stylelint.io/user-guide/rules/max-nesting-depth/
		'max-nesting-depth': [
			4,
			{
				'ignore': ['blockless-at-rules', 'pseudo-classes'],
			},
		],
		// 限制数字中允许的小数位数
		'number-max-precision': 2,
		// 限制选择器中属性选择器的数量
		'selector-max-attribute': 3,
		//限制选择器中的类数
		'selector-max-class': 3,
		// 限制选择器中的运算器数量
		'selector-max-combinators': 1,
		// 限制选择器中的选择器数量
		// 'selector-max-compound-selectors': 1,
		// 限制选择器中 ID 选择器的数量
		'selector-max-id': 1,
		// 限制选择器中的伪类数
		'selector-max-pseudo-class': 1,
		// 限制选择器的特异性
		// 'selector-max-specificity': 2,
		// 限制选择器中的类型选择器数量
		'selector-max-type': 2,
		// 限制选择器中通用选择器的数量
		'selector-max-universal': 1,
		// 限制时间值的最小毫秒数
		'time-min-milliseconds': 1,

		// Notation 表示形式
		// 指定 alpha 值的百分比或数字表示法, 可选值: "number"|"percentage", 不选则允许数字与百分比的表示
		// 'alpha-value-notation': 'percentage',
		//指定 color-functions 的现代或传统表示法, 可选值: "modern": 需要逗号, "legacy": 不需要逗号, 以空格区分
		'color-function-notation': 'modern',
		//指定十六进制颜色的短表示法或长表示法,  可选值: "short"|"long"
		// 'color-hex-length': 'short',
		// 指定字体粗细的数字或命名表示法
		// 'font-weight-notation': '',
		//指定度数色调的数字或角度表示法
		// 'hue-degree-notation': '',
		//指定规则的 @import 字符串或 URL 表示法, "string": 以引号表示, "url": 以url(xx)表示
		'import-notation': 'url',
		//指定关键帧选择器的关键字或百分比表示法
		'keyframe-selector-notation': 'percentage-unless-within-keyword-only-block',
		// 指定亮度的数字或百分比表示法
		// 指定媒体功能范围的上下文或前缀表示法, 可选择: "percentage"|"number"
		// 'lightness-notation': '',
		// 正确示例:
		// @media (width >= 1px) {}
		// @media (1px <= width <= 2px) {}
		'media-feature-range-notation': 'context',
		// 为 :not() 伪类选择器指定简单或复杂表示法, 可选值: "simple"|"complex"
		// 'selector-not-notation': '',
		// 为适用的伪元素选择器指定单冒号或双冒号表示
		'selector-pseudo-element-colon-notation': 'double',

		// Pattern 模式
		// 指定注释的模式
		// 'comment-pattern': '',
		// 为自定义媒体查询名称指定模式
		// 'custom-media-pattern': '',
		// 指定自定义属性的模式
		// 'custom-property-pattern': '',
		// 指定关键帧名称的模式
		// 'keyframes-name-pattern': '',
		// 为类选择器指定模式允许类名以大写字母或者小写字母开头,并且可以包含连字符 -
		'selector-class-pattern': '^([a-z][a-z0-9]*)(-[a-z][a-z]*)*$',
		// 指定 ID 选择器的模式
		'selector-id-pattern': '^([a-z][a-z0-9]*)(-[a-z][a-z]*)*$',
		// 为嵌套在规则中的规则选择器指定模式
		// 'selector-nested-pattern': '',

		// Quotes 引号
		// 要求或禁止字体系列名称使用引号,  期望每个不是关键字的字体系列名称周围都有引号
		'font-family-name-quotes': 'always-unless-keyword',
		// 要求或禁止 url 使用引号
		'function-url-quotes': 'never',
		// 要求或禁止属性值使用引号, always: 必须加引号, never: 不需要加引号
		'selector-attribute-quotes': 'always',

		// Redundant 冗余
		// 禁止在声明块中使用冗余的 longhand 属性
		// 'declaration-block-no-redundant-longhand-properties': [
		// 	'true',
		// {
		// 	["padding", "/border/"],
		// },
		// ],
		// 不允许在速记属性中使用冗余值
		// 'shorthand-property-no-redundant-values': '',

		// Whitespace inside 内部空白
		// 要求或禁止在注释标记的内部使用空格。
		'comment-whitespace-inside':'always',
	},
}

Git 提交前的操作

这里为提交之前的格式化代码 .husky/pre-commit:

cd frontend

pnpm lint-staged

Git Commit Hook校验

这里使用特定的提交格式 .commitlint.config.js:

export default {
  extends: ['@commitlint/config-conventional'],
  'type-enum': [
    2,
    'always',
    [
      'feat',
      'fix',
      'perf',
      'style',
      'docs',
      'test',
      'refactor',
      'build',
      'ci',
      'chore',
      'revert',
      'wip',
      'workflow',
      'types',
      'release',
    ],
  ],
}

.husky/commit-msg:

cd frontend

pnpm commitlint ${1}

IDE支持

VSCode

为了简化代码格式化操作,需要在VSCode保存时自动格式化代码,可以在.vscode/settings.json文件添加如下配置

{
	"editor.defaultFormatter": "esbenp.prettier-vscode",
	"editor.formatOnSave": true,
	"editor.formatOnSaveMode": "file",
	"editor.codeActionsOnSave": {
		"source.fixAll.eslint": true
	},
	"files.eol": "\n",
	"editor.tabSize": 2,
	"[jsonc]": {
		"editor.defaultFormatter": "esbenp.prettier-vscode"
	}
}

后续文章

  1. 霞的云原生之旅: 后端工程化
  2. 霞的云原生之旅: 运维工程化

参考

  1. commitlint
  2. prettier
  3. eslint
  4. oxc