前端代码乱糟糟?是时候引入代码质量检查工具了
为了统一团队的代码规范,除了一纸规范说明之外,还需要引入工具进行限制。虽说工具并不能完全实现规范中的规则,但至少能够在一定程度上缓解代码不统一的局面。
相对于后端,前端代码规范的质量检查涉及到HTML, CSS,Javascript ,如今还涉及到SCSS,ES5,JSX, React,Vue,Angular等,更是复杂。
本文提供了在检查工具方面的规则制定,在编辑器IDE中进行配置,在webpack中进行打包。让开发小伙伴有所参考
相关规则可以在 webpack4项目demo 中看到,里头放了相关的规则链接注释,欢迎围观~
1. 工具选取
笔者对常见的代码检查工具做了一番调研,结合规则支持度,配置方式,在编辑器Sublime于Webstrom这只IDE上的支持度,在webpack打包的支持,最终确立了使用如下方案
HTML / tpl: HTMLHint
CSS / SCSS: StyleLint
JS / JSX: ESLint
对比参考: JavaScript 代码静态质量检查 CSS 代码静态质量检查 HTML代码风格检查工具对比
尽管如此,这三个插件也并不完美,有太多太多的坑踩遍了,如果你有更合适的套件,欢迎建议~
2. 规则制定
选取了工具之后,就需要确立相应的规则。
规则非常多,对我们这种没经验的小白是不可能一条一条自主去选取的,所以需要依据某些参考。但也只能是参考,我们需要把这些通用的设置,结合到我们实际项目中,并一条条去了解规则,最终选出并摘录进我们的规则集中。
ESLint规则
ESLint规则最多,参考自 eslint-config-alloy,再加入我们的自定义
// 自定义的规则 rules: { // 必须使用 === 或 !==,禁止使用 == 或 !=,与 null 比较时除外 // @warn 在异步接口返回时不确定参数是数值还是字符串,有时可利用这个类型转换 \'eqeqeq\': \'warn\', // 禁止在 if 代码块内出现函数声明 // @off 在for循环中会经常使用定义var for(var i = 0; i < 10; ++i) \'no-inner-declarations\': \'off\', // switch 的 case 内有变量定义的时候,必须使用大括号将 case 内变成一个代码块 // @off 太严格 \'no-case-declarations\': \'off\', // 禁止使用 !! ~ 等难以理解的运算符 // @off 有些时候会用到 if (!!abc) \'\' + 100 +new Date() 等 \'no-implicit-coercion\': \'off\', // 禁止在全局作用域下定义变量或申明函数 // @off 太严格 \'no-implicit-globals\': \'off\', // 禁止使用没必要的 {} 作为代码块 // @off 有时候需要用代码块做逻辑区分 \'no-lone-blocks\': \'off\', // 禁止出现 location.href = \'javascript:void(0)\'; // @off 有时候需要用便捷的 javascript:; \'no-script-url\': \'off\', // 对象字面量只有一行时,大括号内的首尾必须有空格 // @off 没有必要限制 \'object-curly-spacing\': \'off\', // 禁止对函数的参数重新赋值 // @warn 警示即可 \'no-param-reassign\': \'warn\', // 文件最后一行必须有一个空行 // @error 应该在文件末尾保持一个换行 \'eol-last\': \'error\', // 代码块嵌套的深度禁止超过 10 层 // @warn 有些特殊情况会出现 警示即可 \'max-depth\': [ \'warn\', 10 ], // 禁止函数的循环复杂度超过 100 // @error 最大值可以宽松点 \'complexity\': [ \'error\', { max: 100 } ], // 定义过的变量必须使用 // @warn 多文件互相引用时 偶尔会出现无引用的情况 \'no-unused-vars\': [ \'warn\', { vars: \'all\', args: \'none\', caughtErrors: \'none\', ignoreRestSiblings: true } ], // 在ES5中需使用var // @off 没有必要限制 \'no-var\': \'off\', // 禁止使用未定义的变量 建议将相关变量在上方 globals 配置项中配置 // @warn 警示即可 \'no-undef\': \'warn\', // 函数的参数禁止超过10个 // @warn 警示即可 \'max-params\': [\'warn\', 10], // 回调函数嵌套禁止超过 5 层 // @warn 警示即可 \'max-nested-callbacks\': [\'warn\', 5], // 循环内的函数中不能出现循环体条件语句中定义的变量 // @warn 警示即可 \'no-loop-func\': \'warn\', // Promise 的 reject 中必须传入 Error 对象 // @off 不需要限制 \'prefer-promise-reject-errors\': \'off\', // 变量声明时尽量使用一个var声明连续的多个 // @warn 警示即可 \'one-var\': [ \'error\', \'consecutive\' ], // 变量申明必须每行一个 // @error 赋值时保证处于一行即可 \'one-var-declaration-per-line\': [ \'error\', \'initializations\' ], // 禁止使用已废弃的 api // @off 不需要限制 \'react/no-deprecated\': \'off\', // 禁止使用字符串 ref // @warn 警告即可 \'react/no-string-refs\': \'warn\', // 必须使用 Class 的形式创建组件 // @warn 警告即可 \'react/prefer-es6-class\': [ \'warn\', \'always\' ], // 禁止在 componentDidUpdate 里面使用 setState // @warn 警告即可 \'react/no-did-update-set-state\': \'warn\', // 组件内方法必须按照一定规则排序 // @off 不需要限制 \'react/sort-comp\': \'off\', // jsx 的 props 缩进必须为四个空格 // @off 不需要限制 // \'react/jsx-indent-props\': \'off\', }
StyleLint规则
ESLint规则也很多,以 stylelint-config-standard 为基础,加入自定义
rules: { // 颜色值避免直接使用颜色名 \'color-named\': [ \'never\', { ignore: [\'inside-function\'] } ], // 使用数字或命名的 (可能的情况下) font-weight 值 \'font-weight-notation\': \'numeric\', // 在函数的逗号之后要求有一个换行符或禁止有空白 \'function-comma-newline-after\': null, // 在函数的括号内要求有一个换行符或禁止有空白 \'function-parentheses-newline-inside\': null, // url使用引号 \'function-url-quotes\': \'always\', // 禁止小于 1 的小数的前导 0 \'number-leading-zero\': \'never\', // 字符串使用双引号 \'string-quotes\': \'double\', // 要求选择器列表的逗号之前有一个换行符 \'selector-list-comma-newline-before\': \'never-multi-line\', // 在媒体查询的逗号之前禁止有一换行 \'media-query-list-comma-newline-before\': \'never-multi-line\', // 缩进 \'indentation\': 4, // 禁止低优先级的选择器出现在高优先级的选择器之后 \'no-descending-specificity\': null, // 禁止空源 \'no-empty-source\': null, // 禁止缺少文件末尾的换行符 \'no-missing-end-of-source-newline\': null }
HtmlHint规则
HtmlHint的规则比较少,可以直接自定义
要注意的是它并不支持JS语法,需要使用JSON格式(在webpack中会强制按这个语法parse)
{ "_comment": [ "自定义的HTMLHint配置项", "规则中文 @see https://segmentfault.com/a/1190000013276858", "规则英文 @see https://github.com/yaniswang/HTMLHint/wiki/Rules", "使用注释自定义规则 @see https://github.com/yaniswang/HTMLHint/wiki/Usage#cli" ], "_comment": "标签名必须小写", "tagname-lowercase": true, "_comment": "属性名必须小写", "attr-lowercase": false, "_comment": "属性值必须放在双引号中", "attr-value-double-quotes": true, "_comment": "属性值一定不可为空", "attr-value-not-empty": false, "_comment": "属性值一定不可重复", "attr-no-duplication": true, "_comment": "Doctype必须是 HTML 文档的第一行", "doctype-first": false, "_comment": "标签必须成对", "tag-pair": true, "_comment": "标签必须自封闭", "tag-self-close": false, "_comment": "特殊字符必须转义", "spec-char-escape": false, "_comment": "ID 属性必须唯一", "id-unique": true, "_comment": "src 属性一定不可为空", "src-not-empty": true, "_comment": "title 属性必须出现在标签中", "title-require": false, "_comment": "img 标签必须包含 alt 属性", "alt-require": true, "_comment": "Doctype 必须是 HTML5", "doctype-html5": true, "_comment": "ID 和 Class 的命名规则必须统一", "id-class-value": false, "_comment": "不该使用样式标签", "style-disabled": false, "_comment": "不该使用行内样式", "inline-style-disabled": false, "_comment": "不该使用行内脚本", "inline-script-disabled": false, "_comment": "空格和制表符一定不可混合在行前", "space-tab-mixed-disabled": "space4", "_comment": "ID 和 Class 一定不可使用广告关键词", "id-class-ad-disabled": false, "_comment": "href 必须是绝对路径或者相对路径", "href-abs-or-rel": false, "_comment": "属性值一定不可使用不安全字符", "attr-unsafe-chars": true, "_comment": "script 标签不该使用在头部", "head-script-disabled": false }
对于页面中嵌入的CSS与JS,也需要进行检查。
在ESlint中提供了 eslint-plugin-html 插件,然而对<style> 与 <script> 造成的缩进处理不当(配置失效的样子),这个是比较难搞的
// 检查html文件(或tpl文件)中的JS plugins: [ \'html\' ], settings: { \'html/html-extensions\': [\'.html\', \'.tpl\'], // \'html/indent\': \'+4\' },
在StyleLint中提供了 stylelint-processor-arbitrary-tags 插件,不过新版似乎内置了支持。然而也并算完美,至少能用就行
在Sublime,Webstorm或其他编辑器IDE中使用这些工具的前提:
安装NodeJS,然后使用NPM在全局安装以下依赖包
npm i -g eslint babel-eslint eslint-config-alloy eslint-plugin-html eslint-plugin-react stylelint stylelint-config-standard htmlhint
在项目根目录下添加三个工具对应的文件 (这三个文件即为对应的检查规则集),以便代码编辑器在任何地方都能找到配置文件,如
ESLint 和 StyleLint 工具提供了自动修复功能,可以修复简单的错误如少了分号,多了空格,缩进不正确等
但要注意的是,自动修复某些时候可能会使代码发生逻辑或语法错误,需谨慎使用(自动修复后一定一定一定记得比对代码,确保无误)
3. 在Sublime中的配置
sublime安装对应的linter工具,以SublimeLinter工具为基础进行配置
Ctrl+Shift+P 调出安装插件层,输入关键字 sublimelinter 进行搜索安装
再安装相应的工具插件,SublimeLinter-eslint , SublimeLinter-stylelint, SublimeLinter-contrib-htmlhint
安装 ESLint-Formatter 以支持自动修复检查的错误
新增一个构建任务,可命名为,StyleLint-Fix.sublime-build 以支持自动修复检查的错误
在其中填入以下内容,保存在相应的位置即可
{ "shell_cmd": "stylelint $folder/node_modules/.bin/eslint --fix $file" }
接下来就是配置 SublimeLinter
打开插件配置,在User部分填入以下内容并保存即可
stylelint配置中的executable全局路径需要设置好
// SublimeLinter Settings - User { "debug": true, // "delay": 0.2, "lint_mode": "manual", // "syntax_map": { // "html (django)": "html", // "html (rails)": "html", // "html 5": "html", // "css": "css", // "javascript (babel)": "javascript", // "magicpython": "python", // "php": "html", // "python django": "python", // "pythonimproved": "python" // }, "styles": [ { "scope": "region.yellowish markup.warning.sublime_linter", "types": ["warning"] }, { "scope": "region.redish markup.error.sublime_linter", "types": ["error"] }, { "priority": 1, "icon": "dot", "mark_style": "outline" } ], "linters": { "eslint": { // 让eslint能够识别html页面中嵌入的JS "selector": "source.js | text.html.basic" }, // 下面三个sublimelinter默认都支持,为防止检查干扰,需要禁用它们 "scsslint": { "disable": true }, "csslint": { // "disable": true }, "htmllint": { "disable": true }, "stylelint": { // 似乎Sublime的stylelint需要手动设置到全局路径 // "executable": "C:\\Users\\e470\\AppData\\Roaming\\npm\\stylelint.cmd" "executable": "/usr/local/bin/stylelint" } } }
可以看到,在sublimelinter的配置中是以手动(manual)模式进行调用检查的,可以防止某些文件代码量太大,频繁检查消耗性能
需要检查的时候,在当前文件打开命令即可,或者使用对应快捷键(如果看不到命令,就采用重启大法吧)
以下命令关键字都是在以 Ctrl+Shift+P打开命令层的前提下进行的
Lint This View ,执行检查
SublimeLinter还支持检查HTML或tpl文件里嵌入的JS和CSS, 但Webstorm不行唷~~
Show All Errors,在底部显示错误列表
使用 ESlint-formatter进行自动修复JS
使用 StyleLint-Fix 进行自动修复CSS
这个需要调出构建任务列表层,或者使用快捷键 Ctrl+Shift+B,选择我们的fix任务执行即可
HTMLHint的不提供自动修复功能
4. 在WebStorm中的配置
打开设置
启用内置的ESLint检查
启用内置的StyleLint检查
本地安装 HTMLHint插件,下载地址,注意 此插件仅可支持检查HTML后缀文件,不支持tpl,有兴趣的可以给作者提PR
安装之后,可能需要重启,在列表中可以看到插件配置入口
因此插件比较特殊,在windows下,bin中请使用 node执行程序的绝对路径 全局 htmlhint的绝对路径
其他环境下就慢慢试吧..
bin: D:\Program Files\nodejs\node.exe C:\Users\e470\AppData\Roaming\npm\node_modules\htmlhint\bin\htmlhint
path: .htmlhintrc
内置的ESLint与StyleLint不支持自动修复功能,所以我们需要手动创建 File Watcher
配置成手动执行可能会更好些
需要执行的时候,执行即可
5. 在webpack中的配置
参考我的webpack项目配置DEMO, 在 webpack.config.js 中传入相应的参数
正式使用时autoFix会按需设置,建议修复。如果选择修复,webpack将按模块的设置进行批量修复,可能会有大量文件被修改,所以需要做好代码比对工作
另外,开启自动修复可能会导致webpack编译无限循环的问题,对于这个我们可以引入一个新的插件 time-fix-plugin ,直接调用即可
new TimeFixPlugin()
在使用 htmlhint-loader的时候,webpack默认无法识别html资源,在以往我们可以直接使用 htmlWebpackPlugin来识别,因为它内置支持了ejs-loader
但现在这个代码检查插入之后,我们就需要手动设置好html语法的loader。
不能使用 html-loader ,使用之后会导致无法识别我们的ejs语法,导致htmlWebpackPlugin的资源插入失效
解决办法也很简单,使用 ejs-loader 即可,见下方配置
另外,在生产模式 npm run build:prod的时候,提供了将检查结果输出到文件的功能(css的不支持),见 lint目录
虽然有点错乱,也够搜索存档用了
而具体在webpack的核心配置文件里面,配置也是挺简单的,虽然也有蛮多不如意
首先相关的npm包需要安装好,使用 htmlhint-loader eslint-loader stylelint-webpack-plugin
new HappyPack({ id: \'js\', use: configs.lint.js.open ? [{ loader: \'babel-loader\', options: { // cacheDirectory: true } }, { enforce: \'pre\', exclude: /node_modules/, loader: \'eslint-loader\', options: { fix: configs.lint.js.autoFix, cache: true, emitWarning: !configs.lint.js.emitAsError, failOnError: configs.lint.js.failOnError, formatter: require(\'eslint-friendly-formatter\'), outputReport: { filePath: cwdRalativeOutputPath + \'/lint/js/[name].xml\', formatter: require(\'eslint-friendly-formatter\') } } }] : [{ loader: \'babel-loader\', options: { // cacheDirectory: true } }] }), new HappyPack({ id: \'html\', use: configs.lint.html.open ? [{ loader: \'ejs-loader\', options: { } }, { loader: \'htmlhint-loader\', enforce: \'pre\', exclude: /node_modules/, options: { configFile: configs.lint.html.configFile, failOnError: configs.lint.html.failOnError, outputReport: { filePath: cwdRalativeOutputPath + \'/lint/html/[name].xml\' } } }] : [{ loader: \'ejs-loader\', options: { } }] }), // stylelint检查 if (configs.lint.css.open) { commonConfig.plugins.push(new StyleLintPlugin({ fix: configs.lint.css.autoFix, emitErrors: configs.lint.css.emitAsError, failOnError: configs.lint.css.failOnError, formatter: require(\'stylelint-formatter-pretty\') })); }