前端工程化之代码质量与规范化

lxf2023-03-14 20:32:01

前言

前端代码一直以来都被人们所诟病不规范,格式也是百花齐放,经过现代化的洗礼已经有了不少的格式和规范也衍生出了不少规范化工具, ESLint、StyleLint 便是其中的规范标准工具。

一般的流程是运行时或者打包时候进行 Lint 的代码规范标准检查,但是如果是代码修改后不运行或者不进行打包操作直接提交绕过原有的 Lint 检查逻辑,就有可能将一些不规范标准甚至是一些低级错误 BUG 的垃圾代码提交到仓库上了,因此需要在代码进行提交时候也进行一次 Lint 的检查操作,降低事故发生的可能性!

这篇文章记录了下在项目当中能够做到的一些代码规范化和校验相关的操作,作为个人项目的一个优化记录。




校验的工具:

一、prettier

一个流行的代码格式化工具的名称,它能够解析代码,使用你自己设定的规则来重新打印出格式规范的代码。prettier 和 Linter系列(ESLint, stylelint) 的区别在于 Prettier 是一个专注于代码格式化的工具,prettier 并不会对代码进行质量或错误语法检查。

安装 prettier

npm install --save-dev --save-exact prettier

配置 prettier

项目根目录创建.prettierrc文件进行配置项设置:

// .prettierrc
{
  "printWidth": 120, // 超过最大值换行
  "tabWidth": 2, // 缩进字节数
  "useTabs": false, // 缩进不使用tab,使用空格
  "semi": true, // 句尾添加分号
  "singleQuote": true, // 使用单引号代替双引号
  "proseWrap": "preserve", // 默认值。因为使用了一些折行敏感型的渲染器(如GitHub comment)而按照markdown文本样式进行折行
  "arrowParens": "avoid", //  (x) => {} 箭头函数参数只有一个时是否要有小括号。avoid:省略括号
  "bracketSpacing": true, // 在对象,数组括号与文字之间加空格 "{ foo: bar }"
  "disableLanguages": ["vue"], // 不格式化vue文件,vue文件的格式化单独设置
  "endOfLine": "auto", // 结尾是 \n \r \n\r auto
  "eslintIntegration": false, //不让prettier使用eslint的代码格式进行校验
  "htmlWhitespaceSensitivity": "ignore",
  "ignorePath": ".prettierignore", // 不使用prettier格式化的文件填写在项目的.prettierignore文件中
  "jsxBracketSameLine": false, // 在jsx中把'>' 是否单独放一行
  "jsxSingleQuote": false, // 在jsx中使用单引号代替双引号
  "parser": "babylon", // 格式化的解析器,默认是babylon
  "requireConfig": false, // Require a 'prettierconfig' to format prettier
  "stylelintIntegration": false, //不让prettier使用stylelint的代码格式进行校验
  "trailingComma": "es5", // 在对象或数组最后一个元素后面是否加逗号(在ES5中加尾逗号)
  "tslintIntegration": false // 不让prettier使用tslint的代码格式进行校验
}

更详细的配置:prettier.io/docs/en/con…

.prettierignore:

若存在着部分文件/文件夹不需要 prettier 进行代码格式化操作则可以在项目的根目录下加一个 .prettierignore 文件,配置忽略检测的文件或目录的语法与 .gitignore 相同。

执行检查

npm run prettier --write


二、ESLint

ESLint 是在 ECMAScript/JavaScript 代码中识别和报告模式匹配的工具,它的目标是保证代码的一致性和避免错误。

安装 ESLint

npm install eslint --save-dev

配置 ESLint

初始化配置文件eslint --init,会在项目的根目录创建.eslintrc.js文件并写入相关的配置

// .eslintrc.js

module.exports = {
  root: true,
  env: {
    node: true,
  },
  extends: [
    'eslint:recommended',
  ],
  parserOptions: {
    ecmaVersion: 2020,
  },
  rules: {
    // 设置 javascript-eslint 规则
    'semi': ['error', 'always'],
    'quotes': ['error', 'single'],
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-useless-escape': 0,
  },
  overrides: [
    {
      files: ['**/__tests__/*.{j,t}s?(x)', '**/tests/unit/**/*.spec.{j,t}s?(x)'],
      env: {
        mocha: true,
      },
    },
  ],
};

更多配置参考:eslint.bootcss.com/docs/user-g…

执行检查

eslint "file/path"

命令行参数参考:eslint.bootcss.com/docs/user-g…


三、stylelint

一个强大的,现代的代码检查工具,可以帮助您避免错误并在您的样式中强制执行约定。

安装 stylelint

npm install --save-dev stylelint

配置 stylelint

项目根目录创建.stylelintrc.js配置文件

// .stylelintrc.js

const sortOrderSmacss = require('stylelint-config-property-sort-order-smacss/generate');

module.exports = {
  root: true,
  defaultSeverity: 'error',
  plugins: ['stylelint-order', 'stylelint-scss'],
  extends: ['stylelint-config-standard', 'stylelint-config-prettier'],
  rules: {
    // 不要使用已被 autoprefixer 支持的浏览器前缀
    'media-feature-name-no-vendor-prefix': true,
    'at-rule-no-vendor-prefix': true,
    'selector-no-vendor-prefix': true,
    'property-no-vendor-prefix': true,
    'value-no-vendor-prefix': true,
    // 最多允许嵌套20层,去掉默认的最多2层
    'max-nesting-depth': 20,
    // 颜色值要小写
    'color-hex-case': 'lower',
    // 完整的色值
    'color-hex-length': 'long',
    // 禁止在声明块中重复属性
    'declaration-block-no-duplicate-properties': null,
    // Disallow unknown at-rules
    'at-rule-no-unknown': null,
    // 不允许空的块
    'block-no-empty': null,
    // 禁止未知的伪元素选择器
    'selector-pseudo-element-no-unknown': [
      true,
      {
        ignorePseudoElements: ['v-deep'],
      },
    ],
    // 'scss/at-rule-no-unknown': true,
    // 不允许空的来源
    'no-empty-source': null,
    // 禁止样式表中重复的选择器
    'no-duplicate-selectors': null,
    // 禁止无效的命名网格区域
    'named-grid-areas-no-invalid': null,
    // 要求或不允许Unicode BOM
    'unicode-bom': 'never',
    // Disallow selectors of lower specificity from coming after overriding selectors of higher specificity.
    'no-descending-specificity': null,
    // Disallow missing generic families in lists of font family names.
    'font-family-no-missing-generic-family-keyword': null,
    // Require a single space or disallow whitespace after the colon of declarations (Autofixable).
    'declaration-colon-space-after': 'always-single-line',
    // Require a single space or disallow whitespace before the colon of declarations (Autofixable).
    'declaration-colon-space-before': 'never',
    // Require or disallow a trailing semicolon within declaration blocks (Autofixable).
    'declaration-block-trailing-semicolon': 'always',
    'unit-no-unknown': [true, { ignoreUnits: ['rpx'] }],
    // Specify the alphabetical order of the attributes in the declaration block
    'order/properties-order': [sortOrderSmacss()],
  },
  ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts'],
};

更多详细配置:stylelint.io/user-guide/…

执行检查

stylelint --fix "file/path"


四、TypeScript

在现代化的前端开发当中,越来越多的项目开始重视起前端 js 动态类型所带来的危害,因此 TypeScript 也被越来越多人青睐。这里就简单介绍 Webpack 当中是如何引入 TypeScript,具体 TS 的语法以及如何在项目当中业务使用落地这里就不累赘讲述了。

安装 TypeScript

npm install --save-dev typescript

配置 TypeScript

控制台使用 “tsc” 命令,自动在项目根目录当中生成tsconfig.json文件

{
  "compilerOptions": {
    /* Basic Options */
    "allowJs": true,
    "baseUrl": ".", // 模块解析根路径,默认为 tsconfig.json 位于的目录
    "rootDir": "src", // 编译解析根路径,默认为 tsconfig.json 位于的目录
    "target": "ESNEXT", // 指定输出 ECMAScript 版本,默认为 es5
    "module": "ESNext", // 指定输出模块规范,默认为 Commonjs
    "lib": ["ESNext", "DOM"], // 编译需要包含的 API,默认为 target 的默认值
    "outDir": "dist", // 编译输出文件夹路径,默认为源文件同级目录
    "sourceMap": true, // 启用 sourceMap,默认为 false
    "declaration": true, // 生成 .d.ts 类型文件,默认为 false
    "declarationDir": "dist/types", // .d.ts 类型文件的输出目录,默认为 outDir 目录
      
    /* Strict Type-Checking Options */
    "strict": true, // 启用所有严格的类型检查选项,默认为 true
    "esModuleInterop": true, // 兼容模式,实现 CommonJS 和 ES-module 之间的互操作性,默认为 true
    "skipLibCheck": true, // 跳过导入第三方 lib 声明文件的类型检查,默认为 true
    "forceConsistentCasingInFileNames": true, // 强制在文件名中使用一致的大小写,默认为 true
    "moduleResolution": "Node", // 指定使用哪种模块解析策略,默认为 Classic
  },
      
  "include": ["src"] // 指定需要编译文件,默认当前目录下除了 exclude 之外的所有.ts, .d.ts,.tsx 文件
}

更多详细配置:www.typescriptlang.org/tsconfig/

基本运行

tsc --watch


五、git hook 与 commitlint

前面介绍的更多是对项目源码当中具体的一些编程代码进行校验,但是我们现代化的项目开发基本都会存在着源码版本库的管理操作,笔者这里更多使用的是 git,这里就以 git 这个版本库管理工具来讲述如何进行项目代码提交的规范化以及代码质量的自动化检验。

5.1:commitlint

commitlint能够在提交 git commit 时候提交进行对所填写的描述格式进行规范化校验检查和错误提示处理。

安装 commitlint

npm install -g @commitlint/cli @commitlint/config-conventional

配置 commitlint

项目根目录创建commitlint.config.js配置文件,当然也可以是.commitlintrc.js

// commitlint.config.js

/**
 * build:主要目的是修改项目构建系统(例如 gulp,webpack,rollup 的配置等)的提交
 * ci:主要目的是修改项目继续集成流程(例如 Travis,Jenkins,GitLab CI,Circle等)的提交
 * feat:新增功能
 * fixed:bug 修复
 * perf:性能优化
 * refactor:重构代码(既没有新增功能,也没有修复 bug)
 * style:不影响程序逻辑的代码修改(修改空白字符,补全缺失的分号等)
 * docs:文档更新
 * test:新增测试用例或是更新现有测试
 * revert:回滚某个更早之前的提交
 * chore:不属于以上类型的其他类型(日常事务)
 */
module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [
      2,
      'always',
      ['build', 'ci', 'feat', 'fixed', 'perf', 'refactor', 'style', 'docs', 'test', 'revert', 'chore'],
    ],
    '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],
  },
};

详细配置参考:commitlint.js.org/#/reference…commitlint.js.org/#/reference…


5.2:git hook

husky 是 Git hooks 的工具,可以在代码提交、推送等操作时候触发对应的钩子,从而进行对应的操作,这里主要是要利用 husky 在代码提交前进行 Lint 的代码校验检查操作。

安装 husky

npm install husky lint-staged --save-dev

  • husky: 在项目中添加 git 钩子,在 git hooks(git 操作的各个生命周期) 中执行一些自定义操作。我们这里主要是用在 git 提交之前执行 linter 操作,不通过则提交无效。

  • hook 可以在提交阶段触发一些逻辑上的保护,常用的有 pre-commit 和 commit-msg。

  • lint-staged: 简而言之,就是只针对 git 提交的文件进行一些操作,而非整个项目的所有文件。我们这里主要是用在 git 提交之前进行 linter 时只针对提交的文件,以进行渐进式的重构。

Ps:husky这个在 7.x 版本以后有所调整(具体这里就不展开细说了)需要进行相关配置等初始化,

  • 因此需要在项目初始化时候执行一次 husky install 的命令:npm run husky install
  • 或者利用 npm 的钩子函数自动执行命令,如下配置,prepare 会在执行任何 npm run 操作先执行。
// package.json
{
  "scripts": {
    "prepare": "husky install"
  }
}

配置相关文件

这里以 husky 7.x 以上的版本为例

项目根目录创建 .husky 文件夹,并且创建commit-msgpre-commitlintstagedrc.js三个文件,并且在相关文件中填写相关配置(具体配置项下一小节结合着 lint 来讲述会更好,这里先简单留个印象相关配置文件)。

lintstagedrc.js:

主要是配置 git hook 执行对代码校验检查时候的配置。

// lintstagedrc.js

module.exports = {
  // something lint config
};
pre-commit:

这个文件是执行 git commit 时候触发,主要是用来对提交的代码进行检查。

// pre-commit

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

// run something shell
commit-msg:

这个文件是执行 git commit 时候触发类似 pre-commit 但是能够获取提交所填写的描述的一个hook,因此更多用来对 git commit 提交的描述进行校验是否符合提交规范。

// commit-msg

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

// run something shell

git hook 结合 lint 工具

结合着项目当中的 lint 工具在代码提交时候进行代码质量和规范化的校验才能真正 git hook 的威力。前面的各类 lint 和 husky 的前置知识现在正好派上用场。现在我们重新将目光回到 husky 的三个配置文件当中来,

针对提交的代码进行校验检查

首先是在 .husky/lintstagedrc.js 配置文件当中配置匹配相关后缀名文件所要执行的相关格式化操作和 lint 工具校验。

// .husky/lintstagedrc.js:

module.exports = {
  '*.{ts,js,jsx,vue}': ['prettier --write', 'eslint --fix', 'git add -A'],
  '*.{scss,less,css,vue,html}': [
    'prettier --write',
    'stylelint --fix "**/*.{vue,less,postcss,css,scss}" --cache --cache-location node_modules/.cache/stylelint/',
    'git add -A',
  ],
};

接着是在 .husky/pre-commit 里面配置相关的执行命令,在执行 git commit 时候进行触发,这样子就能够实现在提交代码时候对代码进行格式化和校验检查操作了。

// .husky/pre-commit

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

lint-staged -c ./.husky/lintstagedrc.js // 钩子中执行的命令
针对 git commit 提交的信息进行规范检查

使用前文提交到和安装的 commitlint 依赖来根据项目根目录所配置的commitlint.config.js或者.commitlintrc.js配置文件来检查 git commit 的规范。

// .husky/commit-msg

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx commitlint --edit $1 

六、编辑风格配置

通常除了上面一些针对不同开发语言做的一些代码规范化的限制,但是想要在不同的编辑器和 IDE 中保持一致的编码样式仍然还是需要一个额外的配置文件 -- .editorconfig。EditorConfig 项目由用于定义编码样式的文件格式和一组文本编辑器插件组成,这些插件使编辑器能够读取文件格式并遵循定义的样式。

.editorconfig 文件语法格式

  1. EditorConfig 文件采用类似 INI 的文件格式。
  2. 在 EditorConfig 文件中,每行上的所有开头空格都被认为是不相关的。每行必须是以下之一:
    • 空白
    • 注释 -- 使用八字 ( #) 或分号 ( ;) 进行注释
    • 部分标题
    • 键值对 -- 包含一个键和一个值,用=分隔。
  1. EditorConfig 文件是 UTF-8 编码的,带有 LF 或 CRLF 行分隔符。
  2. EditorConfig 文件从上到下读取,最先发现的规则优先。

.editorconfig 支持的通配符

*任何字符串,路径分隔符 ( /)除外
**任何字符串
?任何单个字符,路径分隔符 ( /)除外
[name]匹配名称中的任何单个字符
[!name]匹配名称中没有的任何单个字符
{s1,s2,s3}匹配任何给定的字符串(以逗号分隔,可以嵌套)
{num1..num2}num1和之间的任何整数num2,其中num1和num2 可以是正数或负数

.editorconfig 支持的属性

# 表示是最顶层的配置文件,发现值为true时,才会停止查找.editorconfig文件
root

# 设置使用那种缩进风格(tab是制表符,space是空格)
indent_style

# 定义用于每个缩进级别的空格数
indent_size

# 用一个整数来设置tab缩进的列数。
tab_width

# 设置换行符,值为lf、cr和crlf
end_of_line

# 设置编码格式,值为latin1、utf-8、utf-8-bom、utf-16be和utf-16le
charset

# 设置为true会删除换行符之前的任何空格字符
trim_trailing_whitespace

# 设置为true以确保文件在保存时以换行符结尾
# 如果设置为true,则文件最后一行也会确保以换行符结尾,会强制换行到下一行
insert_final_newline

一个 .editorconfig 简单的例子

# .editorconfig

root = true

[*]
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false



相关资料

  • prettier 文档:www.prettier.cn
  • ESLint 文档:eslint.bootcss.com
  • stylelint 文档:stylelint.io
  • TypeScript 文档:www.tslang.cn/index.html
  • commitlint 文档:commitlint.js.org
  • Husky - Git hooks:typicode.github.io/husky
  • editorconfig 文档:editorconfig.org