本篇重在介绍前端工程化中可能会用到的lint工具和配置,不深入原理,阅读大概需要十分钟。
规范的代码可以带来一致、无歧义、高效的阅读体验,也可以在某种程度上避免一些低级的错误,甚至提高代码的执行效率。大部分现代前端项目都会配置Eslint、Stylelint、Prettier等前端代码校验工具,通过预设规则校验代码,检测其是否存在错误和漏洞,并提示修复方案并尽可能依据修复方案格式化出正确代码。
JavaScript 是一个动态的弱类型语言,在带给我们灵活性的同时,也埋下了一些坑,在开发中比较容易出错。比如 == 涉及到的弱类型转换,还有 this 的指向,也是一个让人迷惑的东西。而 Lint 工具就很好的解决了这个问题,干脆禁止你使用 == ,这种做法虽然限制了语言的灵活性,但是带来的收益也是可观的。 代码检查是一种静态的分析,常用于寻找有问题的模式或者代码,并且不依赖于具体的编码风格。对大多数编程语言来说都会有代码检查,一般来说编译程序会内置检查工具。 作为一门动态语言,因为缺少编译过程,有些本可以在编译过程中发现的错误,只能等到运行才发现,这给我们调试工作增加了一些负担,而 Lint 工具相当于为语言增加了编译过程,在代码运行前进行静态分析找到出错的地方。
主要包括了以下一些配置内容。
**安装 : **npm i -D eslint ,这边使用的版本是7.32.0。 **生成: **npx eslint init,初始化lint配置文件。
// .eslintrc.js module.exports = { root: true, // 认为它为根目录,不再向上寻找 // env 表示当前运行环境都支持的API有那些。eslint 会根据当前运行环境的配置去检查代码中所使用的 API 是否匹配当前环境所配置的内容 env: { browser: true, // 表示支持浏览器环境的所有API es2021: true // 表示支持ES2021的所提供的API }, // 第三方插件。继承共享配置,这就是继承了standard风格的标准 extends: [ 'plugin:react/recommended', 'standard' ], // 设置语法解释器的配置,控制是否允许使用某个es版本的新特性 parserOptions: { ecmaFeatures: { jsx: true }, ecmaVersion: 12, sourceType: 'module' // 指定文件来源类型,"script" (默认) 或 "module"(如果你的代码是 ECMAScript 模块) }, plugins: [ 'react' ], // 配置 eslint 具体单条校验规则的开启或是关闭 rules: {} }
支持的配置文件格式如下,我们常用的是前面两种,一般是配置在项目根目录下即可(也支持在要检测的文件目录里单独配置,优先级更高)。
eslint [options] [file|dir|glob]* **Options **提供了更丰富的选项和能力,你可以通过运行 eslint -h 查看所有选项。例如:
为了测试配置效果,用react官方脚手架生成了demo代码,加了一条 lint 的script,同时也增加了 utils.js 文件。
// package.json "scripts": { // ... "lint": "eslint --ext .js,.ts,.tsx src/util.js" }
ESLint会检测所有未声明的变量并发出报错。但是在实际开发中,我们的代码经常会访问运行时环境提供的全局变量。比如node环境中的process,浏览器环境下的全局变量window,或者通过cdn引入的jQuery定义的$等。这时候可以在globals中进行变量声明,如果需要使用场景的很多,比如node或者浏览器中的全局变量很多,如果我们一个个进行声明显得繁琐,因此就需要用到我们的env,这是对环境定义的一组全局变量的预设。更多配置可以参考官网。
// eslintrc.js module.exports = { globals: { '$': true, // true表示该变量可读写,false表示变量是只读 }, env: { browser: true, // 浏览器 }, extends: [ 'eslint:recommended', ] } // util.js var dom = $('content') window.alert('test')
如果没有配置执行 globals 和 env ,会报👇的错误
parser的作用是将我们写的代码转换为 ESTree,ESLint 会对 ESTree 进行校验。 ESLint将代码解析成抽象语法树,遍历抽象语法树并通过预设规则做一些判断和修改,再将新的抽象语法树转换成正确代码。想了解抽象语法树,可阅读babel源码了解其工作原理。 ESTree只是一个 AST 的某一种规范,ESTree 本质上还是 AST。 ESLint 默认的 parser ,只转换 js文件,默认支持 ES5 的语法,可以通过制定 parserOptions 给 Espree 传递如下选项。ESLint 默认使用Espree作为其解析器,可以通过 parser 字段指定一个不同的解析器,常见的解析器如下:
使用场景:因为ESLint默认的解析器只支持已经形成 ES 标准的语法特性,对于处于实验阶段以及非标准的(譬如 Flow、TypeScript等)需要使用Babel转换的语法。所以正常情况下,是不需要指定第三方的解析器的。如果你想使用一些先进的语法,就使用babel-eslint(一个npm包);如果你想使用typescript,就使用@typescript-eslint/parser。 选好了解析器,我们可以通过parserOptions给解析器传入一些配置参数,允许指定想要支持的JS语言选项,默认支持ECMAScript 5语法。可用的选项有:
module.exports = { parserOptions: { ecmaVersion: 5 // es5 } } // util.js let a = 'test' // let是es6的语法
执行脚本,校验代码出错。
module.exports = { env: { es6: true }, parserOptions: { ecmaVersion: 6 // es5 } } // util.js let a = new Proxy({})
虽然开启了ecmaVersion是6,依然会有下面的报错。
ecmaVersion和 env 区别在于, ecmaVersion指定的是ES语法特性,比如支持 ES6 语法并不意味着同时支持新的 ES6 全局变量或API(比如 Map 类型、Proxy对象)。对于 ES6 语法,指定用 { "ecmaVersion": 6 };对于 ES6 全局变量或者API,使用 { "env": { "es6": true } } 。 @babel/eslint-parser @babel/eslint-parser 是允许 ESLint 在由 Babel 转换的源代码上运行的解析器。当指定它作为ESLint的解析器后,它会根据 Babel 的配置把源码转化为 ESLint 默认支持的 AST,并保持住源码的行列数,它的作用只是负责把 ESLint 不能识别的语法特性转化为能识别的,但它本身不包括规则,还需要使用 @babel/eslint-plugin 插件来提供对应的规则,才能正常执行 ESLint 对代码的检测。
// eslintrc.js module.exports = { globals: { '$': true, // true表示该变量可读写,false表示变量是只读 }, env: { browser: true, // 浏览器 }, extends: [ 'eslint:recommended', ], parser: '@babel/eslint-parser', parserOptions: { babelOptions: { "plugins": [ ["@babel/plugin-proposal-decorators", { legacy: true }] ] } } } // util.js @Wrapper class UtilClass{} function Wrapper(fn) { console.log('wrapper') return fn } export default UtilClass
默认不识别装饰器。
配置了 @babel/eslint-parser,还需要配置一些babel的preset
@typescript-eslint 在TypeScript团队发布全面采用 ESLint 之后,发布 typescript-eslint 项目,以集中解决 TypeScript 和 ESLint 兼容性问题。而 ESLint 团队将不再维护 typescript-eslint-parser,TypeScript 解析器转移至Github 的 typescript-eslint/parser。 @typescript-eslint 是一个采用 Lerna 进行设计的 Monorepo 结构仓库,包含以下两个重要的 npm 包:
export enum Type { Agree = 1, DisAgree = 2, } export default Type;
如果不配置parser,不识别ts的语法
module.exports = { root: true, parser: "@typescript-eslint/parser", plugins: ["@typescript-eslint"], extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], // @typescript-eslint/recommended包含了一系列推荐的扩展 rules: { "@typescript-eslint/no-explicit-any": 2, // 默认不返回any 'no-unused-vars': 0 }, }; // util.js const add = (x, y): any => { return x + y; }
rules: ESLint 有大量的规则,可以在配置文件的rules属性自定义需要的规则,可以去官网查看各种规则及其各自的错误级别。规则的报错等级有如下3种:
有些规则没有属性,只需控制开启还是关闭;有些规则可以传入属性,我们通过数组的方式传入参数:
// eslintrc.js module.exports = { rules:{ // 代码缩进,使用tab缩进,switch语句的case缩进级别 "indent": ["error", "tab", { "SwitchCase": 1 }], // 引号,双引号 "quotes": ["error", "double"], } }
extends: 如果每条规则都需要团队协商配置还是比较繁琐的,我们可以使用一些业内已经成熟的、大家普遍遵循的编码规范;扩展就是直接使用别人已经配置好的一套 lint 规则。extends可以看作是集成一个个配置方案的最佳实践。它配置的内容实际就是一份份别人配置好的rules。 ESLint支持三种类型的扩展:
下面是常见的一些扩展项
上面讲扩展时,已经提到了如何加载插件中的扩展配置。既然已经有了这么多扩展可以使用,为什么还需要插件呢?因为 ESLint 只能检查标准的 JavaScript 语法,如果你使用 Vue 单文件组件, ESLint 就束手无策了。这个时候,相应框架就会提供配套的插件来定制特定的规则进行检查。插件和扩展类似,也有固定的前缀 eslint-plugin-,配置时也可以省略前缀。可以使用 plugin 定义自己的规则 eslint-plugin-react, eslint-plugin-vue。引入 plugin 可以理解为引入了额外的 rules,需要在 rules、extends 中定义后才会生效。引入 plugin 可以理解为只是加载了插件,引入了额外的 自定义的rules。需要在 rules、extends 中定义后才会生效,如果没有则不生效。
// eslintrc.js module.exports = { extends: [ 'eslint:recommended', 'plugin:react/recommended', ], plugins: [], rules: { } } // App.js import logo from './logo.svg'; import './App.css'; import React from 'react'; class App extends React.Component { componentWillMount() {} render() { return ( <div className="App"> <header className="App-header"> <p> React App </p> </header> </div> ); } } export default App;
如果没有配置plugin
/recommended,会报👇的错误配置了extends,会命中react/no-deprecated这条规则
plugin 与 extend 的区别:
VSCode 的配置分为两种类型(用户和工作区),针对上述通用的配置主要放在用户里,针对不同项目的不同配置则可以放入工作区进行处理 。settings.json是VSCode的配置文件,用户可通过插件暴露的字段自定义编辑器功能。
// settings.json { "eslint.validate": [ "html", "vue", "javascript", "javascriptreact", "typescript" ], "editor.codeActionsOnSave": { "source.fixAll": false, "source.fixAll.eslint": false, "source.fixAll.stylelint": false }, "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" // prettier }, }
你可以通过在项目根目录创建一个 .eslintignore 文件告诉 ESLint 去忽略特定的文件和目录。.eslintignore 文件是一个纯文本文件,其中的每一行都是一个 glob 模式,表明哪些路径应该忽略检测,更多注释规范参考官网。
# gulp gulpfile.js # eslint .eslintrc.js # commitizen commitlint.config.js # jest jest.config.js # build dist
Prettier 是一个统一团队代码格式风格的工具,能够极大的提高团队执行效率,统一的编码风格能很好的保证代码的可读性。它并非针对一种语言,对 HTML/CSS/JavaScript/Vue/SCSS 都有效果。 ESLint 的规则校验同时包含了 格式规则 和 质量规则(如用===而不是==判断相等),但是大部分情况下只有 格式规则 可以通过 --fix 功能一键修复,而 质量规则 更多的是发现代码可能出现的 Bug 从而防止代码出错,这类规则往往需要手动修复。因此 格式规则 并不是必须的,而 质量规则 则是必须的。Prettier 与 ESLint 的区别在于 Prettier 专注于统一的格式规则,只是代码格式的校验和修正,不会对代码质量进行校验,如单行代码长度、tab 长度、空格、逗号表达式等问题。从而减轻 ESLint 在格式规则上的校验,而对于质量规则 则交给专业的 ESLint 进行处理。
还有一点,在实际项目中,eslint可以检测出代码问题并标红但是并不会自动格式化,需要手动格式化,接入Prettier并配置可以进行自动化。但是需要考虑到的是,prettier和eslint的规则有可能冲突,因此需要考虑到当规则发生冲突时,需要解决冲突,正常是以prettier为准。 **安装 :**npm i -D prettier eslint-config-prettier eslint-plugin-prettier
配置: 在项目根目录下新建.prettierrc.js(同时支持.yaml/.yml/.json/.js格式)。忽略规则是项目根目录下添加.prettierignore。
// .eslintrc.js module.exports = { "extends": [ "plugin:@typescript-eslint/recommended", // 用于关闭 ESLint 相关的格式规则集,具体可查看 https://github.com/prettier/eslint-config-prettier/blob/master/index.js "prettier", // 用于关闭 @typescript-eslint/eslint-plugin 插件相关的格式规则集,具体可查看 https://github.com/prettier/eslint-config-prettier/blob/master/%40typescript-eslint.js "prettier/@typescript-eslint", ] } // .prettierrc.js module.exports = { "printWidth": 80, //一行的字符数,如果超过会进行换行,默认为80 "tabWidth": 2, //一个tab代表几个空格数,默认为80 "useTabs": false, //是否使用tab进行缩进,默认为false,表示用空格进行缩减 "singleQuote": false, //字符串是否使用单引号,默认为false,使用双引号 "semi": true, //行位是否使用分号,默认为true "trailingComma": "none", //是否使用尾逗号,有三个可选值"<none|es5|all>" "bracketSpacing": true, //对象大括号直接是否有空格,默认为true,效果:{ foo: bar } "parser": "babylon" //代码的解析引擎,默认为babylon,与babel相同。 }
用来对 CSS 代码进行规则校验
安装:npm install -D stylelint stylelint-order stylelint-config-standard
**生成:创建stylelint 的配置文件 .stylelintrc.json ** 配置文件:.stylelintrc.js。
// .stylelintrc.js module.exports = { // 与 eslint 不同的是 extends 这里的继承规则是需要填写全名称 extends: [ // css 标准规则 "stylelint-config-standard", // sass 标准规则 "stylelint-config-sass-guidelines" ], plugins: [ // 插件一般是社区提供,对stylelint已有规则进行扩展,一般可以支持一些非标准的css语法检查或者其他特殊用途。一个插件会提供一个或者多个检查规则。 "stylelint-order" ], rules: { // 可以去官网查询所有的规则 "indentation": 2, "order/properties-order": [ // 只有配置了stylelint-order插件才能配置这条规则 "width", "height" ] } }
如果想格式化 sassscss 文件,则需要下载 stylelint-scss 插件。也可以配置vscode插件stylelint-plus。
配置了ESLint之后,我们需要让开发者感知到 ESLint 的约束。开发者可以自己运行 eslint 命令来跑代码检查,但是这不够高效,或者开发者在编辑器中没有装 ESLint 插件或者插件配置不正确,所以我们需要一些自动化手段来做这个事情,并且保证代码提交的时候不会把错误的代码提交到仓库中。我们可以通过以下几种方式做 ESLint 检查:
Git 能在特定动作发生时触发自定义脚本。Git Hook 其实是进行项目约束非常好用的工具,它的作用包括但不限于:
阻止用户进行不规范的 Git 代码提交,其原理就是监听了 Git Hook 的执行脚本(会在特定的 Git 执行命令诸如 commit、push、merge 等触发之前或之后执行相应的脚本钩子)。 Git Hook 的钩子非常多,但是在客户端中可能常用的钩子是以下两个:
Git hook生命周期
Git 生成之后默认会在 .git/hooks 目录下生成所有 Git 钩子的 Shell 示例脚本,这些脚本是可以被定制化的,对于开发而言去更改这些示例脚本适配前端项目非常不友好。此时我们需要借助 husky 来拦截 git 操作。原理:husky会根据 package.json里的配置,在.git/hooks目录生成所有的 hook 脚本(如果你已经自定义了一个hook脚本,husky不会覆盖它) 对于老的项目,可能已经存在很多遗留的风格问题,导致 ESLint 检查通不过,此时不可能把所有问题都修复掉,全盘阻止提交势必会造成影响。另外对于单次提交而言,如果每次都检查所有文件,也是没有必要的。所以我们需要使用 lint-staged 工具只针对当前修改的部分进行检测。
// package.json { "husky": { "hooks": { "pre-commit": "lint-staged" // git提交之前执行 linter 操作,不通过则提交无效。 } }, "lint-staged": { "src/**/*.{js,vue}": [ "eslint --fix", "npm run prettier", "git add" ] }, }
git 规范主要包括两点:分支管理规范(参考git flow规范)和 commit 规范。
现在常见的commit提交信息规范可以分为两大类,一种是angular标准规范,适用于xx库和插件的开发,方便生成CHANGELOG.md。另一种就是自定义规范,适用于自己公司的项目。angular规范的Commit Message 标准格式 包括三个部分:Header,Body,**Footer。**其中,Header 是必需的,Body 和 Footer 可以省略。
// 提交信息 git commit -m <header> // 空一行 <body> // 空一行 <footer>
Body 是对本次 commit 的详细描述,可以分成多行。Footer 部分只用于两种情况,不兼容变动和关闭issue。Header 包含 (?): 。
有的人如果记不住angular书写规范,可以安装下面这两个插件,这两个插件就是在你提交代码时候自动帮你组织commit信息。
npm install -D commitizen cz-conventional-changelog commitizen init cz-conventional-changelog --save --save-exact // 使用commitizen生成符合AngularJS规范的提交说明,初始化cz-conventional-changelog适配器
commitizen 是用来格式化 git commit message 的工具,它提供了一种问询式的方式去获取所需的提交信息。 cz-conventional-changelog 是用来规定提交时需要输入哪些信息,譬如提交的类型是修复问题还是功能开发,提交影响范围等等,cz-conventional-changelog 是官网提供的规则,完全可以根据项目实际情况自已开发适合的规则。
不管是标准还是非标准,提交了commit之后,都需要做相应的检查。这里注意我们需要安装@commitlint/config-conventional和@commitlint/cli
这两个插件。npm install --save-dev @commitlint/config-conventional @commitlint/cli @commitlint/cli 用来执行检查,@commitlint/config-conventional 是检查的标准,即提交的信息是否符合这个标准的要求,只有符合要求才允许提交,它是符合Angular规范的一份校验规则。
commitlint.config.js // commitlint.config.js 配置文件 module.exports = { extends: ['@commitlint/config-conventional'] };
执行提交
如果需要生成版本变动日志可以安装 npm install -g conventional-changelog-cli
使用conventional-changelog -p angular -i CHANGELOG.md -s
命令可生成changelog,不会覆盖以前的 Change log,只会在CHANGELOG.md的头部加上自从上次发布以来的变动。加上-r 0会生成所有发布的changelog。
本文作者:sora
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!