阅读 988

详解前端规范工具链落地实战【别让你定的规范只停在口头上】

✨✨相信稍微对代码有些追求的小伙伴,从HTML、CSS、JS,再到Git等等,都会尽量的规范自己的代码。如果不止自己一个前端开发,相信大家都会遵循一套代码开发规范(自己制定的也好,遵循行业内比较通用的规范也罢)。

人非圣贤,即使再自律的小伙伴,也难免会犯错误,或者说一不小心就在某些地方忘记了规范的代码书写。那么怎么办呢?如果是流程比较完成的小Team,可能会有代码review,嗯!这是一个非常好的习惯,可以提高大家代码质量减少可能的错误等等。但是,如果是一些比较基础的像代码格式,书写顺序等等这些错误,要放在review阶段来做吗?或者说,team中好些个小伙伴,你作为小leader能review的过来这些错误吗?

既然有了需求,那就要有对应的解决方案才行!!!而这些前端代码规范相关的内容,如果交给工具去做,那不就是省去了很多力气了吗,而且更准确、更高效,不是吗?说到这里,前端代码规范要做哪些事情呢?来,看下面这张图:

🌟🌟 git-hooks工具

工欲善其事,必先利其器。Git的hooks钩子函数可以让我们在执行git的一些命令之前或之后执行一些其他操作,我们后续很多的代码检查都会放在代码commit时或者push之前进行检查等。而husky(俗名哈士奇)就是一个git的hooks工具,可以让我们更方便的使用git hooks。

  • 第一步,先给项目初始化git(如果没有被git初始化的话)
# 项目根目录下
git init
复制代码
  • 安装Husky
# 安装 (局部安装即可)
npm i husky -D
复制代码
  • 在package.json文件中配置husky
{
  "husky": {
    "hooks": {
      "pre-commit": "npm test",
      "pre-push": "npm test",
    }
  }
}
复制代码

通过配置husky.hooks['pre-commit'],则每次当我们执行git commit之前都会先运行我们这里定义的命令(比如进行一些代码规范检查等等)。重点强调:

  1. 一定要==先git初始化项目==,然后才是安装husky依赖(安装husky之后它会自动往.git/hooks/文件中添加很多钩子文件)。这就是为什么要先git后husky的原因。

  2. 如果先安装了husky,也可以删除依赖包,待初始化git之后再重新安装husky的依赖(最简单的做法就是删除node_modules文件夹 -> git init -> 安装项目依赖)。

  3. 查看husky的钩子文件,通过查看.git/hooks/里面的文件,可以看到自动添加了很多husky的钩子文件,如下图:

  1. husky支持所有的git-hooks,常见的有:
名称说明绕过方法
pre-commitcommit之前的钩子--no-verify参数
commit-msgcommit之前或merge之前的钩子--no-verify参数
post-commitcommit之后的钩子
pre-pushpush之前的钩子,可用于阻止push

更多的钩子参考git钩子文档

🌟🌟仅校验当前改动内容

俗话说,弱水三千只取一瓢饮
复制代码

通过上面介绍,我们知道来可以通过husky来在commit时进行各种lint操作(具体的lint操作后面会一一讲解)。那么,问题来了,如果我们想只针对当前我们改动的内容进行lint,而不是每次都全部lint应该怎么办呢?具体原因后面会进行讲解。

lint-staged可以结合husky只针对当前提交到暂存区的内容(即git add的内容)进行前置的操作,比如eslint检查修复,prettier美化等等。👇下面看如何具体使用:

  • 安装lint-staged
cnpm i lint-staged -D
复制代码
  • 修改husky的钩子配置

比如,我们配置的是commit的前置钩子函数,在commit之前先会运行lint-staged命令

"husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
},
复制代码
  • 配置lint-staged

根目录下新建配置文件,也可以设置在package.json:

# 根目录下新建配置文件(Mac),也可以手动创建
touch .lintstagedrc
复制代码

写入配置,比如我们只对所有改动的src文件夹下的js文件进行lint-staged的操作,这里配置了eslint进行检查和自动修复一些格式,然后对lint-staged改动过的文件再add一次。配置文件中写入:

{
  "./src/*.js": [
    "eslint --fix"
  ]
}
复制代码

这里就是配置,对于我们当前改动的所有js文件,执行后面的命令。配置对象的key就是配置的文件匹配规则,值则是一个对匹配到的文件执行的命令数组。这里我们不需要在"eslint --fix"之后额外再添加git add的操作了,因为lint-stage会把所有的改动自动添加暂存区中。还可以进行一些美化的操作。扩展一下,如果同时需要针对多种文件类型,既可以增加配置项,也可以直接:

{
  "./src/*.{js,jsx}": [
    "eslint --fix",
    "git add"
  ],
  "./src/*.其他类型的文件匹配规则": []
}
复制代码

lint-staged最大的区别就是只针对当前改动的文件进行操作,而不是全部,这对于没有规范的老项目,需要维护进行引入eslint的时候是个福音,不然给老项目进行代码格式化可是个灾难。

更多内容请查阅lint-staged文档

🐢🐢Angular的Git提交信息规范

有了这些基础技能加持,我们可以开始前端规范相关的lint了。首先我们来说说如果规范Git的提交规范,Git工作流不再此次讨论范围内,这不属于本次lint工具的范围内,和大家团队的工作模式有关。

这里讨论一下如果限制Git的提交规范,首先要指定一下Git的提交规范,不然的一大堆无用信息的提交注释,是会让人抓狂的!!!这里推荐使用Angular的Git提交信息规范,在业界算是比较通用的,主要是相关的一些工具基本是可以满足一些常见需求的。这对于前端基建薄弱的小Team,应该是比较有帮助的。那么接下来花一点时间,介绍一下Angular的Git提交规范:

  • 规范包含三个部分,Header、Body、Footer

Header包含三部分, type是当前提交类型(必选),scope是当前提交涉及到的文件范围(可选),subject是当前提交的描述<type>(<scope>): <subject>

  • type类型说明
type说明
feat新的功能
fixBug的修复
docs仅改变说明文档
style代码格式的美化(formatting)
refactor重构(没有feat和fix操作)
perf性能优化
test增加单元测试、修改当前单元测试
build项目构建脚本或者外部依赖的改动(webpack、npm等)
ciCI/CD相关文件、脚本的改动(Travis, Circle, BrowserStack, SauceLabs)
chore出src/test以外的其他改动(比如根目录下各种rc文件的改动等)
revert版本回退等
# 举个例子
git commit -am "feat(TheNav): 完成导航组件的封装"
复制代码

🌟🌟辅助生成git commit信息的工具

有了提交规范,肯能有些小伙伴会记不住那么多的type类型导致在提交时犯了难。别着急,我们有解决办法,让你可以安安心心的懒。

commitizen用于辅助生成git commit信息规范的工具。如下图,使用效果:

是不是很Nice,内心是不是有那么一丝丝❤️心动❤️的感觉~~~来吧,看看如果使用:

  • 安装
cnpm i commitizen -D
复制代码
  • 配置参数(package.json)
{
  "scripts": {
    "commit": "git-cz"
  },
  "config": {
    "commitizen": {
        "path": "cz-conventional-changelog"
    }
  },
}
复制代码

config.commitizen.path是配置commitizen的适配器的,我们在安装commitizen的时候自动安装了符合Angular规范的适配器cz-conventional-changelog,不需要单独安装的,也可以安装其他适配器或者自定义commitizen的提示内容(自定义插件工具文档)的生成规则。

  • 以后使用npm run commit 代码git commit操作即可。

🌟🌟校验commit信息是否符合规范

即使有了自动生成的工具,我们还是得要在commit-msg时检查一下滴,不能放过落网之鱼滴~~~你不能保证小伙伴一定使用npm run commit命令对吧,比如有些小伙伴就是喜欢使用git commit -am ""来提交。这时候还是我们得针对用户的提交信息校验一次,不管他使用什么方式commit的,commitlint就派上用场了!!!

  • 安装
cnpm i @commitlint/{config-conventional,cli} -D
复制代码
  • 配合husky的hooks使用
{
  "husky": {
    "hooks": {
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
  }
}
复制代码
  • 配置文件

根目录下新建commitlint.config.js文件:

module.exports = {
  extends: [
    '@commitlint/config-conventional'
  ]
};

复制代码

这样,在我们使用commit功能的时候就会进行commit信息的规范验证。如果不符合Angular规范的提交,就会自动提交失败。也可以自定义一些类型,具体的还是看文档吧!!!

🌟🌟自动生成CHANGELOG

我们可以看到很多库都有变更日志的,如果手写那岂不是类似宝宝了。我们是不是期望也有一款工具可以辅助我们完成这件事?

在我们的git提交规范是基于Angular的前提下,我们可以使用conventional-changelog-cli来自动生成的,下面看看如何使用吧:

  • 安装
cnpm i conventional-changelog-cli -D
复制代码
  • 配置script命令

我们定义一个用于生成changeLog的脚本命令,注意这里是每次都重新生成覆盖以前的。也可以设置每次都在文件前追加

"scripts": {
    "version": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0 && git add CHANGELOG.md"
},
复制代码

详情可以查阅conventional-changelog-cli文档,最终效果如下图:

🌟🌟校验css规范

上面聊了一些本地开发时的一些git提交规范Lint相关的内容。接下来我们就介绍一下代码开发相关的一些规范来。那么首先,我们从css的规范Lint开始。就像JS的Lint一样,css我们也需要校验各种错误,比如像空格、分号的代码格式问题啊;禁止无效的颜色值啊;设置最大的嵌套深度啊;指定缩进、换行啊;属性值必须缩写啊等等。

stylelint是一个用于约束css规范的lint工具。可以配合css/less/scss/sass/stylus等一起用,理论上post-css解析器支持的,都可以兼容使用。会自动根据文件类型推导需要使用的解析语法,当然了也可以直接指定解析的语言类型。👇下面看下如何使用吧:

  • 安装stylelint和规则配置集合
cnpm i stylelint stylelint-config-standard -D
复制代码

stylelint-config-standardstylelint的一套代码规则集合,包含了常用的css规范约束,是官方推荐的一套基本的规则集合。而stylelint-config-standard规范继承了stylelint-config-recommended规范集合,在次基础上又开启和关闭的一些规则。

  • 配置文件

在安装好来依赖之后,我们需在项目中进行配置。根目录下新建如下类型的文件都可以:

/**
 * .stylelintrc.json or .stylelintrc
 * 使用标准的json格式即可
 * 注意,json文件是不可以写注释的,这里这是为了说明
 * 当然了,json注释有一些取巧的方式,这里不做讨论
 */
{
    "extends": "stylelint-config-standard"
}

/**
 * .stylelintrc.js or stylelint.config.js
 * 注意导出方式是cmd规范的,不是es的方式
 */
module.exports = {
  plugins: [],
  extends: 'stylelint-config-standard',
  rules: {}
}

// 更多方式暂且不说了,常用这些的即可
// 个人更喜欢js的方式,语法写着比json舒服
复制代码

extends字段就是使用我们的安装的stylelint规则集合。当然了,也可以不使用这些集合规则,我们也完全可以自己定义一套。extends是也可以是个数组(只有一个也可以是字符串),可以后面的会合并到前面,即同一个rule规则,后面的会覆盖前面的。数组每一项既可以是下载的包,也可以是本地的文件路径(符合require.resolve()算法解析规则的路径都可以)。

  • 配置文件的其他字段说明

rules定义规则的集合,是一个对象,键是规则名称,值是规则配置:

// 例如:
module.exports = {
  rules: {
    "block-no-empty": null,
    // 例如,定义缩进规则
    'indentation': [
      // 2个空格
      2,
      {
        // 接收xxx
        "except": ["block"],
        // 错误的提示信息
        "message": "Please use 2 spaces for indentation.",
        // 规则级别,是警告,也可以是错误error
        "severity": "warning"
      }
    ]
  }
}
复制代码
  • 内置规则

stylelint默认内置集成了170个规则(文档地址),但是默认都没有开启(需要自己指定,包括一些推荐的配置库,也是一一列举出来的)。列举一些常见的规则:

规则说明
color-no-invalid-hex禁止使用无效的十六进制颜色
unit-no-unknown禁止使用未知单位
property-no-unknown禁止未知属性
keyframe-declaration-no-importantkeyframe中不允许使用!important
declaration-block-no-duplicate-properties禁止在声明块中使用重复的属性
block-no-empty禁止空块
selector-pseudo-class-no-unknown禁止未知的伪类选择器
selector-pseudo-element-no-unknown禁止使用未知的伪元素选择器
selector-type-no-unknown禁止未知类型选择器
comment-no-empty禁止空注释
no-extra-semicolons禁止使用多余的分号
no-invalid-double-slash-comments禁止//...CSS不支持的双斜杠注释
color-named禁止命名的颜色
color-no-hex禁止使用十六进制颜色
length-zero-no-unit不允许长度为零的单位(可自动修复)
color-hex-case指定十六进制颜色的小写或大写(可自动修复)
shorthand-property-no-redundant-values属性值可以简写的需要简写
max-nesting-depth最大嵌套深度
number-leading-zero数字小于1时的前导0
number-no-trailing-zeros禁止尾随0
unit-case为单位指定小写或大写(可自动修复)
string-quotes字符串指定单引号或双引号
value-keyword-case将关键字值指定为小写或大写
property-case为属性指定小写或大写
comment-whitespace-inside在注释标记的内部需要或不允许空格(可自动修复)
comment-empty-line-before在注释前要求或禁止空白行(可自动修复)
indentation指定缩进(可自动修复)
max-empty-lines限制相邻的空行数(可自动修复)
max-line-length限制一行的长度
no-eol-whitespace禁止行尾空格(可自动修复)
at-rule-name-newline-after在规则名称后需要换行符
  • stylelint进行忽略检查

忽略所有规则:

/* stylelint-disable */
a {}
/* stylelint-enable */
复制代码

忽略特定规则:

/* stylelint-disable selector-no-id, declaration-no-important */
#id {
  color: pink !important;
}
/* stylelint-enable selector-no-id, declaration-no-important */
复制代码

忽略某一行:

#id { /* stylelint-disable-line */
  color: pink !important; /* stylelint-disable-line declaration-no-important */
}
复制代码

忽略下一行:

#id {
  /* stylelint-disable-next-line declaration-no-important */
  color: pink !important;
}
复制代码

支持叠加使用:

/* stylelint-disable */
/* stylelint-enable foo */
/* stylelint-disable foo */
/* stylelint-enable */
/* stylelint-disable foo, bar */
/* stylelint-disable baz */
/* stylelint-enable baz, bar */
/* stylelint-enable foo */
复制代码

stylelint文档地址

stylelint-config-standard文档地址

stylelint-config-recommended文档地址

  • stylelint资源集合推荐

更多关于stylelint的推荐,请查阅stylelint资源集合地址

🌟🌟校验css属性摆放顺序

其实,有css开发经验的小伙伴,在书写css顺序的基本上都有那么一套方法论的,比如很多人会常识性的把会影响到元素布局的属性写前面,像position | display,然后是宽高内外边距等width | height | padding | margin。其实除了css属性,还有更多的比如变量、预处理器中的混入等等,在css规范中,我们对这些属性摆放顺序也是可以进行校验和自动修复css的。

通过stylelint-order插件,可以设置css属性的摆放顺序等,比如什么属性应该放在前面,什么属性应该放在后面。下面看看如何使用到项目中去吧:

  • 安装方法
# 前提是安装了stylelint
cnpm i stylelint-order -D
复制代码
  • 在stylelint的配置文件中进行配置
module.exports = {
  extends: [
    // css规范配置
    'stylelint-config-standard',
  ],
  // 通过插件使用stylelint-order
  plugins: ['stylelint-order'],
  rules: {
    'order/order': [
      'custom-properties', // eg: --property: 10px;
      'dollar-variables', // eg: $variable
      'at-variables', // eg: @someVar
      'declarations', // css属性,display: flex
      'rules', // 嵌套的属性,eg: .head { .top: {...} },这里.top{}就是嵌套在.head中的集合
      // 'at-rules', // 嵌套的属性中含有
      // 'less-mixins' // less的mixin,eg: .mixin()
    ],
    'order/properties-order': [
        'width',
        'height'
        // ...
    ]
  }
}
复制代码

注意注意:所有的rules的插件配置,都是在对象属性中指定修改那个插件的配置,比如修改的order,然后可以是修改order/orderorder/properties-orde。如上所示,我们可以在order/order中配置大的规则集合,比如custom-properties类型的内容放前面,css属性的集合放在后面等,这是大的方向。而order/properties-order则是具体配置css属性相关的摆放顺序,比如width放前面,紧接着是height等。

关于order/properties-order的设置规则可以查阅文档。github上也有一些配置好的方案(虽然star数不多),比如官方提到的这些资源(我这里截了个图,可以去文档查看):

🌟🌟修复css顺序的集成方案

stylelint-config-recess-order是对stylelint-order的一层封装,使用起来比较简单,把相关的order配置都写好了。

  • 安装方法
cnpm i stylelint-config-recess-order -D
复制代码
  • 因为该插件已经内置了stylelint-order,所以不需要重复安装。
// 使用方式
module.exports = {
  extends: [
    'stylelint-config-standard',
    'stylelint-config-recess-order'
  ]
}
复制代码
  • 其规则列表:
module.exports = {
  plugins: ['stylelint-order'],
  rules: {
    'order/properties-order': [{
      // Must be first.
      properties: ['all'],
    },
    {
      // Position.
      properties: ['position', 'top', 'right', 'bottom', 'left', 'z-index', ],
    },
    {
      // Display mode.
      properties: ['box-sizing', 'display', ],
    },
    {
      // Flexible boxes.
      properties: ['flex', 'flex-basis', 'flex-direction', 'flex-flow', 'flex-grow', 'flex-shrink', 'flex-wrap', ],
    },
    {
      // Grid layout.
      properties: ['grid', 'grid-area', 'grid-template', 'grid-template-areas', 'grid-template-rows', 'grid-template-columns', 'grid-row', 'grid-row-start', 'grid-row-end', 'grid-column', 'grid-column-start', 'grid-column-end', 'grid-auto-rows', 'grid-auto-columns', 'grid-auto-flow', 'grid-gap', 'grid-row-gap', 'grid-column-gap', ],
    },
    {
      // Align.
      properties: ['align-content', 'align-items', 'align-self'],
    },
    {
      // Justify.
      properties: ['justify-content', 'justify-items', 'justify-self', ],
    },
    {
      // Order.
      properties: ['order'],
    },
    {
      // Box model.
      properties: ['float', 'width', 'min-width', 'max-width', 'height', 'min-height', 'max-height', 'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left', 'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left', 'overflow', 'overflow-x', 'overflow-y', '-webkit-overflow-scrolling', '-ms-overflow-x', '-ms-overflow-y', '-ms-overflow-style', 'clip', 'clear', ],
    },
    {
      // Typography.
      properties: ['font', 'font-family', 'font-size', 'font-style', 'font-weight', 'font-variant', 'font-size-adjust', 'font-stretch', 'font-effect', 'font-emphasize', 'font-emphasize-position', 'font-emphasize-style', '-webkit-font-smoothing', '-moz-osx-font-smoothing', 'font-smooth', 'hyphens', 'line-height', 'color', 'text-align', 'text-align-last', 'text-emphasis', 'text-emphasis-color', 'text-emphasis-style', 'text-emphasis-position', 'text-decoration', 'text-indent', 'text-justify', 'text-outline', '-ms-text-overflow', 'text-overflow', 'text-overflow-ellipsis', 'text-overflow-mode', 'text-shadow', 'text-transform', 'text-wrap', '-webkit-text-size-adjust', '-ms-text-size-adjust', 'letter-spacing', 'word-break', 'word-spacing', 'word-wrap', // Legacy name for `overflow-wrap`
      'overflow-wrap', 'tab-size', 'white-space', 'vertical-align', 'list-style', 'list-style-position', 'list-style-type', 'list-style-image', ],
    },
    {
      // Accessibility & Interactions.
      properties: ['pointer-events', '-ms-touch-action', 'touch-action', 'cursor', 'visibility', 'zoom', 'table-layout', 'empty-cells', 'caption-side', 'border-spacing', 'border-collapse', 'content', 'quotes', 'counter-reset', 'counter-increment', 'resize', 'user-select', 'nav-index', 'nav-up', 'nav-right', 'nav-down', 'nav-left', ],
    },
    {
      // Background & Borders.
      properties: ['background', 'background-color', 'background-image', "-ms-filter:\\'progid:DXImageTransform.Microsoft.gradient", 'filter:progid:DXImageTransform.Microsoft.gradient', 'filter:progid:DXImageTransform.Microsoft.AlphaImageLoader', 'filter', 'background-repeat', 'background-attachment', 'background-position', 'background-position-x', 'background-position-y', 'background-clip', 'background-origin', 'background-size', 'background-blend-mode', 'isolation', 'border', 'border-color', 'border-style', 'border-width', 'border-top', 'border-top-color', 'border-top-style', 'border-top-width', 'border-right', 'border-right-color', 'border-right-style', 'border-right-width', 'border-bottom', 'border-bottom-color', 'border-bottom-style', 'border-bottom-width', 'border-left', 'border-left-color', 'border-left-style', 'border-left-width', 'border-radius', 'border-top-left-radius', 'border-top-right-radius', 'border-bottom-right-radius', 'border-bottom-left-radius', 'border-image', 'border-image-source', 'border-image-slice', 'border-image-width', 'border-image-outset', 'border-image-repeat', 'outline', 'outline-width', 'outline-style', 'outline-color', 'outline-offset', 'box-shadow', 'mix-blend-mode', 'filter:progid:DXImageTransform.Microsoft.Alpha(Opacity', "-ms-filter:\\'progid:DXImageTransform.Microsoft.Alpha", 'opacity', '-ms-interpolation-mode', ],
    },
    {
      // SVG Presentation Attributes.
      properties: ['alignment-baseline', 'baseline-shift', 'dominant-baseline', 'text-anchor', 'word-spacing', 'writing-mode',

      'fill', 'fill-opacity', 'fill-rule', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width',

      'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'flood-color', 'flood-opacity', 'image-rendering', 'lighting-color', 'marker-start', 'marker-mid', 'marker-end', 'mask', 'shape-rendering', 'stop-color', 'stop-opacity', ],
    },
    {
      // Transitions & Animation.
      properties: ['transition', 'transition-delay', 'transition-timing-function', 'transition-duration', 'transition-property', 'transform', 'transform-origin', 'animation', 'animation-name', 'animation-duration', 'animation-play-state', 'animation-timing-function', 'animation-delay', 'animation-iteration-count', 'animation-direction', ],
    },
    ],
  },
}
复制代码

注意一点,如果是未在上面列表进行定义的属性,则放在任何属性的任何位置都可以,但是也可以设置特定位置(比如,未定义的属性只能放在其他属性前面、后面等等)。

stylelint-order会默认读取stylelint的fix选项,如果开启了,则自动会进行fix。也可以对stylelint-order自定义。

  • 结合husky使用
// package.json中的husky配置
"husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
}

// .lintstagedrc配置
{
  "./src/*.{css,less,html,htm,vue}": [
    "stylelint --fix"
  ]
}
复制代码
  • vue中配置

下面也顺便也现代的vue项目演示一下怎么使用配置,react也基本类似,都是配置webpack。首先安装webpack对应的插件:

# stylelint和其webpack的插件
cnpm i -D stylelint stylelint-webpack-plugin

# 配置规范和order配置
cnpm i stylelint-config-standard stylelint-config-recess-order -D

复制代码

使用方式,在webpack.config.js进行配置:

const StyleLintPlugin = require('stylelint-webpack-plugin');
module.exports = {
  // ... 其它选项
  plugins: [
    new StyleLintPlugin({
      // 指定检测的文件
      files: ['**/*.{vue,htm,html,css,sss,less,scss,sass}'],
      // 启动自动修复
      fix: true,
      cache: true // 启用缓存
    })
  ]
}

// cli4写法
/**
 * 添加stylelint插件
 */
config.plugin('stylelint')
  .use(StyleLintPlugin, [{
    // 指定检测的文件
    files: ['./src/**/*.{vue,css,less}'],
    // 启动自动修复
    fix: true,
    cache: true, // 启用缓存
  }])
复制代码

本地的配置文件:

// .stylelintrc.js文件
module.exports = {
  extends: [
    'stylelint-config-standard',
    'stylelint-config-recess-order'
  ],
}
复制代码
参数说明
files指定针对哪些文件进行lint
fix是否自动修复
cache启动stylelint的缓存功能,极大提升速度

vue项目中如何配置stylelint的文档说明

🌟🌟校验BEM命名规范

基本上css命名规范,业界比较多的有suitbem,如果还不清楚这些命名规范的可以先学习一下。

我们可以通过postcss-bem-linter进行css的命名规范检查,其在styelint中支持的插件为stylelint-selector-bem-pattern。下面说明postcss-bem-linter相关的一些配置等:

  • 安装postcss-bem-linter
# postcss-bem-linter的安装
# 如果是作为stylint插件使用,则不需要安装
cnpm i postcss-bem-linter -D
复制代码
  • 安装stylelint-selector-bem-pattern
# 如果是在stylelint中使用bemlint,则只需安装该插件即可
cnpm i stylelint-selector-bem-pattern -D
复制代码
  • stylelint中使用stylelint-selector-bem-pattern
module.exports = {
  plugins: [
    'stylelint-selector-bem-pattern'
  ],
  rules: {
    'plugin/selector-bem-pattern': {
      // 选择Preset Patterns,支持suit和bem两种,默认suit规范;
      // 不管哪种都需要手动指定,因为该插件未给源插件默认指定
      'preset': 'bem',
      /**
       * 自定义模式规则
       * 指定组合的选择器检查规则,其实就是指定class名称规则
       * 支持正则字符串、返回正则的函数、包含2个选项参数的对象等写法
       */
      componentSelectors: {
        // 只初始的选择器规则(可以理解为外层的class规则)
        initial: '^\\.{componentName}(?:__[-a-z]+)?(?:--[a-z]+)?$',
        // 可以理解为外层以内的选择器的规则,
        // 如果不指定,则跟initial同样的规则,
        // 注意这里配置的时候比上面少一个问号,
        // 是希望内层就不应该只有componentName作为选择器了
        combined: '^\\.{componentName}(?:__[-a-z]+)(?:--[a-z]+)?$'
      },
      "utilitySelectors": "^\\.u-[a-z]+$",
      ignoreSelectors: ['^\\.el-', '/deep/', '>>>', '^\\.icon-'],
      ignoreCustomProperties: [],
    }
  }
}
复制代码

作为插件进行配置即可,通过rules['plugin/selector-bem-pattern]进行配置参数。stylelint-selector-bem-pattern只是对postcss-bem-linter的封装,所以本质是对postcss-bem-linter进行参数配置,而stylelint-selector-bem-pattern是没有参数的。但是需要注意的一点是: postcss-bem-linter默认是规则是suit,但是stylelint-selector-bem-pattern却没有给postcss-bem-linter默认规则,所以即使使用suit也需要手动指定,如上preset参数

🌟🌟详解postcss-bem-linter

postcss-bem-linter的文档看起来刚开始还是稍微有些饶人的,对于不熟悉的小伙伴可能会有写绊脚的东西。所以这里,我把一些基本常见的会用到的再介绍一下,帮助小伙伴们快速理解和上手:

  • 两种mode:default和weak

默认为default模式,即选择器和自定义属性,都必须符合lint规则检测;weak模式,只需要最外层的符合lint规则即可,嵌套在里面的选择器、自定义属性等不需要符合规则。一般不做设置即可,也可以在文件内通过注释特别指定为weak模式:

/** @define app; weak */
.app--active {
    color: #f00;
}
复制代码
  • 定义pattern模式

支持suitbem两种,遵循其对应的命名规范,详情可以查阅其规范文档:suit文档bem文档

// 在stylelint中使用:
rules: {
    'plugin/selector-bem-pattern': {
         preset: 'bem'
    }
}
复制代码
  • 定义组件名称的lint规则

componentName可以是正则表达式、返回RegExp()的函数:

rules: {
    'plugin/selector-bem-pattern': {
        preset: 'bem',
        componentName: '[a-z]+',
    }
}   
复制代码
  • 组件内,通过组件注释的方式定义组件名称

定义组件名,比如app:

/** @define app */
复制代码

定义utilities(工具选择器),这个名称是定死的

组件内(或者其他文件)通过注释定义utilities,则选择选择器必须满足配置参数中的规则
/** @define utilities */

例如配置规则,则所有的工具选择器必须是.u-开头+字母的组合:
rules: {
    'plugin/selector-bem-pattern': {
      preset: 'bem',
      utilitySelectors: '^\\.u-[a-z]+$',
    }
  }
复制代码

可组合使用:

/** @define app */

/** @define app2 */

/** @define app3 */

/** @define utilities */
复制代码
  • 指定选择器的组合规则
/**
 * 指定组合的选择器检查规则,其实就是指定class名称规则
 * 支持正则字符串、返回正则的函数、包含2个选项参数的对象等写法
 */
componentSelectors: {
    // 只初始的选择器规则(可以理解为外层的class规则)
    initial: '^\\.{componentName}(?:__[-a-z]+)?(?:--[a-z]+)?$',
    // 可以理解为外层yi
    combined: '^\\.{componentName}(?:__[-a-z]+)(?:--[a-z]+)?$'
},
复制代码
  • 忽略某些选择器检查
/**
 * 可以在配置参数中,直接配置需要忽略的选择器,
 * 比如,我们组件内覆盖第三方样式的时候,会写第三方样式
 * 常见的例如.el-xxxx等,在配置中直接忽略.el-开头的class
 */
rules: {
    // 'max-nesting-depth': 4,
    'plugin/selector-bem-pattern': {
      preset: 'bem',
      // 忽略.el开头的选择器验证
      ignoreSelectors: ['^\\.el-', '/deep/', '>>>', '^\\.icon-'],
      // 忽略自定义属性相关的验证
      ignoreCustomProperties: [],
    }
}
复制代码

这里注意下,可以通过ignoreSelectors忽略我们日常开发中可能常用的deep、.icon等选择器对lint工具的干扰。

  • css中直接忽略下一行的选择权验证

注意,只对下一行的选择器进行的忽略:

/* postcss-bem-linter: ignore */
.xxx-ssssss {
    border-right: 0;
}
复制代码
  • 最后放一个基本stylelint的配置
module.exports = {
  extends: [
    // 标准集合,继承自stylelint-config-recommend
    'stylelint-config-standard',
    // 内置stylelint-order作为排序
    'stylelint-config-recess-order'
  ],
  plugins: [
    'stylelint-selector-bem-pattern'
  ],
  rules: {
    'plugin/selector-bem-pattern': {
      /**
       * 选择Preset Patterns,支持suit和bem两种,默认suit规范;
       * 不管哪种都需要手动指定,因为该插件未给源插件默认指定
       */
      preset: 'bem',
      // 知道组件名称的检查规则
      componentName: '[a-z]+',
      /**
       * 指定组合的选择器检查规则,其实就是指定class名称规则
       * 支持正则字符串、返回正则的函数、包含2个选项参数的对象等写法
       */
      componentSelectors: {
        // 只初始的选择器规则(可以理解为外层的class规则)
        initial: '^\\.{componentName}(?:__[-a-z]+)?(?:--[a-z]+)?$',
        // 可以理解为外层yi
        combined: '^\\.{componentName}(?:__[-a-z]+)(?:--[a-z]+)?$'
      },
      utilitySelectors: '^\\.u-[a-z]+$',
      ignoreSelectors: ['^\\.el-', '/deep/', '>>>', '^\\.icon-'],
      ignoreCustomProperties: [],
    }
  }
}
复制代码

🌟🌟校验HTML代码规范

上面讲完了style部分的内容,下面来看看html如何约束。这里讲解一款用于lint html代码规范的工具htmlhint

  • 安装
cnpm i htmlhint -D

# 如果是在webpack中使用,则只安装其loader
cnpm i htmlhint-loader -D
复制代码
  • 配置

根目录下新建配置文件.htmlhintrc,注意只能是json语法:

{
  "tagname-lowercase": false,
  "attr-lowercase": true,
  "attr-value-double-quotes": true,
  "alt-require": true,
  "tag-pair": true,
  "src-not-empty": true,
  "id-class-ad-disabled": true,
  "id-unique": true,
  "inline-style-disabled": true
}
复制代码
  • 配置说明

配置比较少,也就30个左右吧,下面说下常见的:

配置说明类型
tagname-lowercasehtml标签小写boolean
attr-lowercasehtml属性小写boolean
attr-value-double-quotes属性值必须要有双引号boolean
alt-requireimg等元素必须要有alt属性boolean
tag-pair标签必须匹配正确boolean
src-not-emptysrc属性不能为空boolean
id-class-ad-disabledid或class不能使用adboolean
id-uniqueid必须唯一boolean
inline-style-disabled不允许出现行内styleboolean
  • 配置lint-staged使用
// .lintstagedrc.js
module.exports = {
  './src/**/*.{vue,ts,tsx,js,jsx}': [
    'eslint --fix'
  ],
  './src/**/*.{vue,css,less}': [
    'stylelint --fix'
  ],
  './src/**/*.vue': [
    'htmlhint --config ./.htmlhintrc'
  ],
}
复制代码

注意,只对vue文件使用了该htmlhint-loader,目前测试其和webpack-html-loader冲突,所以舍弃了对index.html模板的检查。

  • vue中使用

在vue.config.js文件中进行配置:

config.module
  .rule('html')
  .enforce('pre')
  .test(/\.(vue)(\?.*)?$/)
    .exclude
    .add(path.join(__dirname, 'node_modules'))
    .end()
  .use('htmlhint-loader')
    .loader('htmlhint-loader')
    .tap(() => ({ configFile: './.htmlhintrc' }))
    .end()
复制代码

🌟🌟校验JS规范

ESLint是js的代码规范lint和风格检查工具,这个大家现在cli创建的项目基本是标配了,相信大家都是有些熟悉的。同类型的还有JSLint、JSHint,但是基本已经out了。prettier是代码美化的工具,可以和ESLint配合使用,两者的冲突规则有单独的库可以进行覆盖。更多的,个人觉得ESLint可能基本够用了。

  • 安装
cnpm i eslint -D
复制代码
  • 配置文件

根目录创建.eslintrc.eslintrc.js文件

// .eslintrc.js
module.exports = {
    rules: {
        semi: ["", "always"],
        quotes: ["error", "double"]
    }
}
复制代码

支持的所有配置文件类型,如图:

  • rules
{
    "rules": {
        "semi": ["error", "always"],
        "quotes": ["error", "double"]
    }
}
复制代码

key是规则名称,数组第一项为规则等级(关闭:0 | off、警告: 1 | warn、错误: 2 | errors)

  • 通过注释关闭规则
// 针对某一段代码关闭指定规则
/* eslint-disable no-alert, no-console */
alert('foo');
console.log('bar');
/* eslint-enable no-alert, no-console */

// 关闭整个文件规则
// 在文件顶部加入该注释
/* eslint-disable */

// 针对指定行忽略规则
alert('foo'); // eslint-disable-line
// eslint-disable-next-line
alert('foo');
/* eslint-disable-next-line */
alert('foo');
alert('foo'); /* eslint-disable-line */
复制代码
  • plugin

插件配置可以省略包名的前缀eslint-plugin-

{
    "plugins": [
        "react",
        // 等同于
        "eslint-plugin-react"
    ]
}
复制代码
  • extends

  • 覆盖设置

可以对指定的文件重新指定一些配置参数,比如需要忽略的文件等。需要忽略的文件也可以通过.eslintignore指定。

{
  "overrides": [
    {
      "files": ["bin/*.js", "lib/*.js"],
      "excludedFiles": "*.test.js",
      "rules": {
        "quotes": ["error", "single"]
      }
    }
  ]
}
复制代码
  • vue中如何使用eslint
// 配合webpack,在vue.config.js中

module.exports = {
  publicPath: process.env.PUBLIC_PATH,
  chainWebpack: config => {
    config.module
      .rule('eslint')
      .use('eslint-loader')
      .loader('eslint-loader')
      .tap(options => {
        // 让eslint在save时自动fix code
        options.fix = true
        // 开启缓存
        options.cache = true
        return options
      }).end()
  }
}
复制代码

更多webpack中的eslint配置,参考eslint-loader文档

🌟🌟详解eslint-plugin-vue

vue-cli4初始化的js项目eslint部分配置:

vue-cli4初始化的ts项目eslint部分配置:

ts版本和js版本的项目,区别是增加了一个typescript/recommend规则集合

plugin:vue/essential是eslint-plugin-vue中针对vue2+的基本错误lint(即优先级为A的级别),可以如果需要开启其他规则集合的,可以继续增加配置:

{
  extends: [
      'plugin:vue/essential',
      '@vue/airbnb',
      '@vue/typescript/recommended',
      // 开启优先级为B的校验
      'plugin:vue/strongly-recommended',
      // 开启优先级为C的校验
      'plugin:vue/strongly-recommended'
  ],
  rules: {
      // 注意,这里个人不喜欢html标签的>在多行时自动换行
      // 所以关闭该特性
      "vue/html-closing-bracket-newline": ["error", {
        "singleline": "never",
        "multiline": "never"
      }]

  },
}
复制代码

注意eslint-plugin-vue的版本,目前cli给安装的是6.2.2稳定版本,新的版本以及到7.0.0-beta.1(当前时间2020-08-12),所以文档上的一些新的lint,是基于新版本的(例如自定义事件名称必须使用连字符而不是小驼峰等),而文档也是新版本的文档,这个稍微注意一下。

还有一些未分进这些级别的规则,可以自行开启或配置

eslint-plugin-vue文档

🌟🌟JSDoc代码注释规范

上面说到了git、html、css、js以及vue的规范,那么除此之外,还有注释的规范。良好的注释可以减少代码的理解成本和辅助自动生成文档等功能。👇下面我们将介绍介绍JSDoc的注释规范:

JSDoc定义了一系列块级标记,用于说明各种注释内容,基本的写法为:

/**
 * 你的注释内容
 *
 * @param { number } id - 用户id标识
 * @returns { object } 返回查到的用户数据
 */
复制代码

下面看看常见的标记有哪些,以及有哪些作用:

  • @abstract 抽象类、抽象方法
  • @class 描述一个类或构造函数,指明需要new来调用,别名@constructor
  • @extends 描述继承自哪个父类
  • @override 描述覆盖了父类的方法
  • @callback 描述一个回调函数
  • @description 定义描述,如果放在首行,可以省略该标记
/**
 * 人
 * @class
 * @extends Animal
 */
class Person extends Animal {
  private name: string;
  constructor(name: string) {
    super(name);
    this.name = name;
  }
  /**
   * 说出自己的姓名
   * @param { Person~sayCallback } cb - say方法的回调函数
   */
  say(cb) {
    console.log(this.name);
    typeof cb === 'function' && cb(this);
  }
}

/**
 * 这个回调显示为Person类的一部分
 * @callback Person~sayCallback
 * @param {this} cb Person实例
 */
复制代码
  • @constant 描述一个常量
  • @type 描述一个类型,可以与@param、@constant等结合使用
  • @default 定义默认值,如果是简单值,可以让jsdoc自己获取,直接写@default即可,也可以自己定义默认值
 /**
  * @constant
  * @type { string }
  * @default
  */
 const COLORT = '#f00'
复制代码
  • @enum 枚举
  • @readonly
/**
  * 枚举move状态
  * @enum {number}
  * @readonly
  */
enum MoveStatus {
  up = 1,
  right,
  bottom,
  left
}
复制代码
  • @example 描述一个使用示例
/**
 * 格式化数字单位,如果是数字则自动转变为数字加px结尾
 *
 * @example
 * formatUiSize(20) // 返回 '20px'
 * @example
 * formatUiSize('20px') // 返回 '20px'
 * @param {number | string} val - 待格式化的单位
 * @returns {string} 返回格式化后的单位
 */
export const formatUiSize = (val: string | number): string | number => (typeof val === 'number'
  ? `${val}px`
  : val);

复制代码
  • @function、@method表示一个函数或方法
  • @global 描述一个全局变量
  • @mixin 描述一个混入对象
  • @mixes 混入了其他对象
  • @param 对某个函数的参数的各项说明,包括参数名、参数数据类型、描述等
/**
 * @param {string} somebody - Somebody's name.
 */
function sayHello(somebody) {
    alert('Hello ' + somebody);
}

// 如果参数是一个对象,且有很多属性参数
/**
 * Assign the project to an employee.
 * @param {Object} employee - The employee who is responsible for the project.
 * @param {string} employee.name - The name of the employee.
 * @param {string} employee.department - The employee's department.
 */
Project.prototype.assign = function(employee) {
    // ...
};


/**
 * Assign the project to a list of employees.
 * @param {Object[]} employees - The employees who are responsible for the project.
 * @param {string} employees[].name - The name of an employee.
 * @param {string} employees[].department - The employee's department.
 */
Project.prototype.assign = function(employees) {
    // ...
};


// 可选参数和默认值
/**
 * @param {string} [somebody=John Doe] - Somebody's name.
 */
function sayHello(somebody) {
    if (!somebody) {
        somebody = 'John Doe';
    }
    alert('Hello ' + somebody);
}
复制代码
  • @returns 描述一个返回值,用法和@param类似
  • @throws 描述函数可能抛出一个错误
/**
 * @throws Will throw an error if the argument is null.
 */
function bar(x) {}
复制代码

🌟🌟校验注释是否规范

通过eslint-plugin-jsdoc插件检测jsdoc类型的注释是否符合规范。

  • 基本使用
module.exports = {
    plugins: [
        'jsdoc'
    ],
    extends: [
        // 'plugin:vue/essential',
        // '@vue/airbnb',
        // '@vue/typescript/recommended',
        
        // 开启所有recommended的规则
        'plugin:jsdoc/recommended',
    ],
    rules: {
        // 检测无效的填充块
        'jsdoc/check-indentation': 'warn',
    }
}
复制代码
  • 所有检测规则
"rules": {
    // *号必须按标准对齐
    "jsdoc/check-alignment": 1, // Recommended
    // 检测example中的js代码是否符合eslint规则
    "jsdoc/check-examples": 1,
    // 检测无效的填充块
    "jsdoc/check-indentation": 1
    // 检测param的名称是否与函数中的参数名称一致
    "jsdoc/check-param-names": 1, // Recommended
    "jsdoc/check-syntax": 1,
    // 检测块标记名称是否正确,例如@paramsss是错误的
    "jsdoc/check-tag-names": 1, // Recommended
    // 检测参数类型是否正确,例如{object},可自动修复大小写
    "jsdoc/check-types": 1, // Recommended
    "jsdoc/implements-on-classes": 1, // Recommended
    // 通过正则表达式限制描述的内容
    // 比如限制只能使用英文,首字母大写等场景
    "jsdoc/match-description": 1,
    // 强制描述的内容后面必须有一个空行,也可以配置没有
    "jsdoc/newline-after-description": 1, // Recommended
    "jsdoc/no-types": 1,
    // 不允许出现未定义的参数类型,例如{strrrrr}
    "jsdoc/no-undefined-types": 1, // Recommended
    // 要求所有函数都要有描述
    "jsdoc/require-description": 1,
    // 要求完整的标记,例如描述必须使用@description,而不是直接文字描述
    "jsdoc/require-description-complete-sentence": 1,
    // 要求所有函数必须要有example
    "jsdoc/require-example": 1,
    "jsdoc/require-hyphen-before-param-description": 1,
    // 检查类声明和函数上是否有jsdoc注释
    "jsdoc/require-jsdoc": 1, // Recommended
    // 要求函数所有参数写param
    "jsdoc/require-param": 1, // Recommended
    "jsdoc/require-param-description": 1, // Recommended
    // 要求每个@param标签都要有description
    "jsdoc/require-param-name": 1, // Recommended
    // 要求每一个@param标签都要有type类型参数
    "jsdoc/require-param-type": 1, // Recommended
    "jsdoc/require-returns": 1, // Recommended
    // 当使用@returns后,检测函数中是否有符合条件的返回值
    "jsdoc/require-returns-check": 1, // Recommended
    // @returns必须要有description
    "jsdoc/require-returns-description": 1, // Recommended
    // @returns必须要有返回类型参数
    "jsdoc/require-returns-type": 1, // Recommended
    // 验证类型的有效性
    "jsdoc/valid-types": 1 // Recommended
}
复制代码

注意,eslintrc.js配置文件中的'plugin:jsdoc/recommended'会自动开启上述lint规则中的所有Recommended规则。

eslint-plugin-jsdoc文档地址

最后

百尺竿头、日进一步,我是你们的老朋友愣锤!!!更多内容推荐,记得关注、点赞不迷路哦~~~