规范化标准
规范化介绍
规范化是我们践行前端工程化中重要的一部分。
为什么要有规范化标准
- 大多数情况下,软件开发需要多人协同。
- 不同的开发者有不同的编码习惯和喜好。
- 不同的喜好增加项目维护成本。
- 每个项目或者团队需要明确统一的标准。
哪里需要规范化标准
- 开发过程中编写的代码、文档、甚至是提交日志等
- 也就是凡是开发过程中,人为编写而产生的内容,都应该被规范化操作
- 其中代码标准化规范最为重要
- 代码规范很大程度上决定了项目质量 和 项目可维护性
- 为了便于后期维护 和 团队其他成员的阅读,一般会对代码的编码风格做一个统一的要求。
- 其中一般都会包括:
- 统一关键词和操作符中的空格
- 统一代码缩进方式
- 统一是否使用分号结尾
- 统一变量或函数的命名规范
- 等
实时规范化的方法
最初开发者在落实规范化的操作时,使用提前认为约定标准的方式,然后按照这个标准进行开发工作。
最终在Code review 环节,就会按照之前约定的标准,检查相应的代码。
这种单靠人为约束的方式落实规范化,有很多问题:
- 人为约束不可靠
- 开发者很难记住所有规则
所以需要相应的工具去保障。
相比人为检查,借助工具检查更为严谨。
同时还可以配合自动化工具,自动检测。
这样的规范化就更加容易得到质量的保证。
一般,将通过工具去找到项目中不符合规范的地方的过程 叫做 「Lint」。
来源:
在刚有C语言的时候,有一些常见的代码问题,是不能被编译器打包到的。
所以有人就开发了一个叫做「Lint」的工具,用于在编译之前检查出这些问题。
避免编译之后,带来不必要的问题。
所以后续这种类似功能的工具,就都被称作「Lint」,或者「Linter」。
例如现在前端最常见的 ESLint StyleLint。
常见的规范化实现方式
- ESLint 工具使用
- 定制 ESLint 校验规则
- ESLint 对 TypeScript 的支持
- ESLint 结合自动化工具或者 Webpack
- 基于 ESLint 的衍生工具
- StyleLint 工具的使用
- Git Hooks 配合Lint工具,实现代码提交前进行自动化校验
ESLint 介绍
ESLint 是当下最为主流的 Javascript Lint 工具,专门用于检测 JS 代码质量。
ESLint 很容易统一开发者的编码风格,例如缩进 换行 分号 空格的使用。
ESLint 可以帮助开发者找出代码中不合理的地方,这些问题都是开发过程中的隐患,例如:
- 定义了一个从未使用的变量
- 在变量使用后才对它进行声明
- 在使用比较的时候总会使用双等号
==
ESLint可以帮助开发者提升编码能力。
ESLint 安装
- 初始化项目
- 安装 ESLint 模块为开发依赖
npm i eslint --save-dev
oryarn add eslint --dev
- 通过 CLI 命令验证安装结果
npm run eslint -v
ornpx eslint -v
oryarn eslint -v
ESLint 快速上手
新建js文件,编写“问题”代码
// index.js
const foo=123
function fn(){
console.log("hello");
console.log("eslint")
}
fn(
syy()
完成 eslint 使用配置(ESLint需要一个配置文件,否则会提示Something went wrong!
)
通过npx eslint --init
初始化一个配置文件
How would you like to use ESLint?
如何使用ESLink
How would you like to use ESLint?
To check syntax only // 只检查语法性错误
To check syntax and find problems // 检查语法错误并且发现问题代码
> To check syntax, find problems, and enforce code style // 检查语法错误,发现问题代码,校验代码风格
- 语法错误:代码示例中的
fn(
就是 语法错误。 - 问题代码:代码中不可理的地方,例如:
- 代码示例中定义了一个未被使用的变量
foo
- 以及调用了一个不存在的函数
syy()
- 代码示例中定义了一个未被使用的变量
- 代码风格:eslint默认以及自定义的一些代码风格规范,例如:
- 缩进不统一
- 空行太多等
项目开发中,建议选择第三种。
What type of modules dos your project use?
项目代码中使用哪种模块发方式
What type of modules dos your project use?
JavaScript modules (import/export) // ESM:允许使用import/export
CommonJS (require/exports) // CommonJS:允许使用require/exports
> None of these // 没有用到任何模块化
这个问题决定代码当中是否允许使用或调用指定的语法。
Which framework does you project use?
当前项目使用的那款框架
Which framework does you project use?
React
Vue.js
> None of these
Does your project use TypeScript?
项目是否使用了TypeScript,本例选择No
Does your project use TypeScript? No / Yes
Where does you code run?
代码最终将运行在什么环境中。
根据运行环境,判断是否允许使用相应环境下的API,例如:
- 浏览器环境:window
- node环境:process.cwd()
Where does you code run? // 多选
(*) Browser // 浏览器环境
(*) Node // node环境
How would you like to define a style for your project?
指定怎样定义项目的代码风格
How would you like to define a style for your project?
> Use a popular style guide // 使用一个市面上的主流风格
Answer questions about your style // 通过回答问题,形成一个风格
Inspect your JavaScript file(s) // 根据JS代码文件,推断代码风格
一般选择一个市面上的主流风格,这样项目如果有新的成员加入,他会很快适应这个风格。
Which style guide do you want to follow?
选择上面的Use a popular style guide
,ESLint提供3个风格选项:
Which style guide do you want to follow?
Airbnb: https://github.com/airbnb/javascript
> Standard: https://github.com/standard/standard
Google: https://github.com/google/eslint-config-google
Airbnb和Google分别是这两个公司的具体编码规范。
Standard是开源社区的编码规范,它最大的特点是不用在语句的末尾添加分号;
。
这里选择Standard
What format do you want your config file to be in?
指定配置文件的文件类型。
What format do you want your config file to be in?
> JavaScript // 方便在配置文件中添加一些条件判断
YAML
JSON
Would you like to install them now with npm?
以上功能需要一些npm模块,这里提示是否安装它们。
Would you like to install them now with npm? No / Yes
配置结束
一切完成之后,项目根目录下就会生成一个.eslintrc.js
的配置文件。
执行检测命令
npx eslint index.js
它首先会提示语法错误。因为在JS代码中存在语法错误时,ESLint是没有办法检测 问题代码 和 代码风格的。
修复后再次执行会就报出问题代码错误,从而可以依次解决。
可以使用提示--fix
自动修复大部分问题,报错中会提示有几个问题可以被--fix
修复。
建议手动修复这些问题,已提升自己的代码开发质量。
总结
- ESLint 可以找出代码中的问题,问题包括:
- 语法错误
- 代码不合理
- 风格不统一
- ESLint 可以自动修复代码中的绝大多数的问题(
--fix
)
ESLint 配置
eslint可以通过package.json的eslintConfig
属性配置。
上面使用npx eslint --init
创建的配置文件.eslintrc.js
。
它可以影响当前目录和所有子目录的文件。
ESLint配置文件最终会运行在node环境中,所以它的内容就是用CommonJS的方式,导出了一个配置对象。
在最新版本的ESLint,初始化的配置文件中,默认设置
evn
extends
parseOptions
rules
4个属性
env 运行环境
JavaScript在不同的运行环境,可以使用不同的API,这些API很多时候都是以全局变量的形式提供。例如浏览器环境的window,node环境的process。
配置文件中env
属性用于标记当前代码最终的运行环境。
ESLint根据env
判断哪些全局变量是否是可用的,从而避免代码中使用到了不存在的变量。
env
中的每一个环境,对应着一组预定义的全局变量。
一旦开启了某个环境,这个环境当中所有的全局变量就都能够允许被使用。
注意事项
上例中,将evn
中的browser
设置为false
,理论上ESLint就不允许代码中使用document
等浏览器全局变量。
但是执行校验并没有报出错误。
这是因为上例使用的是 Standard 校验风格,最终ESLint配置结果会继承 Standard 中的配置。
而在 Standard 配置中,它也做了一些额外的配置。
比如 document 和 window 在任意环境中都允许使用。
可以在 node_modules 中寻找 eslint-config-standard 查看源码求证。
校验风格对应的模块名,就是eslint-config-<extends中配置的风格名称>
可以看到这个模块的功能就是导出eslintrc.json
文件的内容。
查看这个文件,可以看到它是通过globals
属性将document
navigator
window
设置为了全局只读(readonly)的成员。
所以此时可以使用alert
来校验配置browser:false
是否成功。
env 可配置的所有环境 及 对应的全局变量
browser
- 浏览器环境中的全局变量node
- Node.js 全局变量和 Node.js 作用域commonjs
- CommonJS 全局变量和 CommonJS 作用域 (用于 Browserify/WebPack 打包的,只在浏览器环境运行的代码)shared-node-browser
- Node.js 和 Browser 通用全局变量es6
- 启用除了 modules 以外的所有 ECMAScript 6 特性 (该选项会自动设置ecmaVersion
解析器选项 为 6)es2017
- 增加所有 ECMAScript 2017 全局变量,以及自动设置ecmaVersion
解析器选项为 8es2020
- 增加所有 ECMAScript 2020 全局变量,以及自动设置ecmaVersion
解析器选项为 11worker
- Web Workers 全局变量amd
- 将require()
和define()
定义为像 AMD 一样的全局变量mocha
- 添加所有 Mocha 测试全局变量jasmine
- 添加所有 Jasmine 1.3 和 2.0 版本的测试全局变量jest
- Jest 全局变量phantomjs
- PhantomJS 全局变量protractor
- Protractor 全局变量qunit
- QUnit 全局变量jquery
- jQuery 全局变量prototypejs
- Prototype.js 全局变量shelljs
- ShellJS 全局变量meteor
- Meteor 全局变量mongo
- MongoDB 全局变量applescript
- AppleScript 全局变量nashorn
- Java 8 Nashorn 全局变量serviceworker
- Service Worker 全局变量atomtest
- Atom 测试全局变量embertest
- Ember 测试全局变量webextensions
- WebExtensions 全局变量greasemonkey
- GreaseMonkey 全局变量
这些环境并不是互斥的,可以同时开启多个环境。
extends 配置继承 / 共享配置
继承一些共享的配置。
实际上就是指定ESLint使用的校验风格,最终ESLint配置会继承这些风格的配置。
它可以配置为:
- String:一个配置文件的路径 或 可共享配置的名称。
- Array[String]:多个配置组合,后面的配置继承并覆盖前面的配置。
如果使用的是配置风格模块的名称,可以在 node_modules 中寻找到名为 eslint-config-<名称>
的校验风格模块。
也可以自定义一些公共配置的文件或模块,使用extends
去继承这些配置。
parserOptions 语法解析器
语法解析器用于控制是否使用某个ES版本的 语法。
sourceType
值为 script
(默认)或 module
,表示使用ESM的方式。
ecmaVersion
指定使用ES的版本。
- 可以是版本号:3,5(默认),6,7,8,9,10,11
- 5版本之后的也可以使用年份:2015,2016,2017,2018,2019,2020
设置parserOptions.ecmaVersion:5
最高解析ES5版本的语法,使const
校验失败。
注意:
当前配置使用了 Standard ,Standard配置中parserOptions.sourceType
为module
。
即使用 ESM 新语法的方式,但ES5不支持,所以此时会报错:
sourceType 'module' is not supported when ecmaVersion < 2015
“当ES版本低于2015时,不支持sourceType为module。”
可以将node_modules/eslint-config-standard
中的sourceType
配置为script
。
也可以修改当前配置中的parserOptions.sourceType
。
执行校验提示:The keyword 'const' is reserved
ecmaFeatures
定义一个对象,指示要使用的其他语言的功能/特性。
例如使用 jsx
module.exports = {
parserOptions: {
ecmaFeature: {
jsx: true
}
}
}
注意事项
parserOptions 中的 ecmaVersion 以及 ecmaFeature,影响的只是「语法检测」,不代表这些ES版本或语言的全局变量可用。
使用某个ES版本的全局变量可用,必须通过环境选项env
配置。
而且,通过环境env
配置 ES 版本,会自动启用对应版本的语法支持。
例如:
{"env": {"es6":true}}
自动启用ES6语法- 但是
{"parserOptions": {"ecmaVersion": 6}}
不会自动启用ES6全局变量
rules
配置 ESLint 配置中具体某个校验的开启 / 关闭。
使用方式就是向 rules 对象添加具体的校验规则属性:
- key:内置的校验规则名称
- value:可以为字符串或数组
- String:设置错误级别(rule ID),
'no-alert':'error'
,值为以下3个之一off
or0
:关闭这个规则warn
or1
:启用这个规则,触发时发出warning警告(不会影响 exit code,即不会退出)error
or2
:启用这个规则,触发时会报错(触发时 exit code 为 1,即会退出)
- Array:元素1为 这个规则的错误级别,后面的元素为这个规则的选项,`‘quotes’:[‘error’, ‘double’]
- String:设置错误级别(rule ID),
Exit codes:当检测代码时,ESLint会在 exit code 为以下值时退出:
0
:检测成功,并且没有错误。1
:检测成功,并且至少存在一个错误,或者警告数量超过--max-warnings
选项允许的数量。2
:由于配置问题或内部错误,导致检测失败。
Rules可以扩展规则集:
- 规则集中如果为使用某个规则,则可以通过rules开启某个规则
- 规则集中如果配置了某个规则,可以通过rules修改这个配置:
- value设置为字符串则只会 覆盖 当前配置的规则集 中 这个规则 的 错误等级。
- value设置为数组,则会直接覆盖这个规则的配置。
开启no-alert
校验规则,使代码中不能使用alert
module.exports = {
rules: {
'no-alert': true
}
}
ESLint 官网给出了所有可以使用的内置校验规则列表:ESLint Rules
globals
no-undef
规则将警告:“使用但未在同一文件中定义的变量”这个行为。
globals 可以额外的声明代码中可以使用的全局变量。
globals 的值是一个对象:
- key:要使用的全局变量的名称
- value:可以配置为以下:
'writable'
:可以访问,可写,允许被覆盖'readonly'
:可以访问,只读,禁止覆盖'off'
:禁用该全局变量
- 由于历史原因,以下value等效于上面对应的值,但不建议使用这些旧值:
false
orread
:等同于readonly
true
orwriteable
:等同于writable
globals 同 rules 一样,会覆盖最终配置结果。
plugins
每一个eslint插件是一个命名格式为eslint-plugin-<plugin-name>
的npm模块,比如eslint-plugin-react
在ESLint中使用插件时,需省略eslint-plugin-
前缀,ESLint在内部只使用没有前缀的名称去定位插件。
在plugins中添加了插件,相当于在配置文件require(插件)。
使用插件的规则需要手动开启才能使用。插件可以这样使用(而不需要在plugins中添加插件):
-
rules属性中启用或配置插件的某个规则:
-
rules: { '<plugin-name>/<rule-name>': 'off' }
-
-
extends属性中启用插件的配置
-
extends: [ 'plugin:<plugin-name>/<config-name>' ]
-
-
env属性中定义,在一个插件中使用某个环境
-
env: { '<plugin-name>/<env-name>': true }
-
-
等
settings
ESLint支持在配置文件添加共享设置。settings对象将提供给每一个将被执行的规则。
例如提供一个指定react版本的共享配置:
settings: {
react: {
version: 'detect' // detect自动检测实际安装的版本
}
}
parser
「语法解析器」。默认使用Expree。常用的与ESLint兼容的解析器:
- babel-eslint:包含babel解析的解析器。
- @typescript-eslint/parser:typescript解析器,将TypeScript转换成与ESTree兼容的形式,以便可以在ESLint中使用它。
ESTree 是一种json风格的AST(抽象语法树)规范,它是社区中的一个非官方的语法表达标准。
当前流行的babel和eslint的实现都是基于ESTree。
ESLint 配置注释
配置注释:将配置直接通过注释的方式写在脚本文件中,然后再去执行代码校验。
配置注释的作用:
在开发中使用 ESLint,难免会遇到一两个违反配置规则的地方。
这种情况下,不能因为这一两个点推翻整个校验规则的配置。
这时就可以使用 ESLint 的配置注释,解决类似的问题。
更多使用查看官方文档。
// 对单行代码禁用规则
// standard 不允许这样使用字符串模板语法
const str1 = '${name} is a coder' // eslint-disable-line no-template-curly-in-string
console.log(str1)
// 对某块代码禁用规则
/* eslint-disable no-unused-vars, no-undef */
const str2 = 'no-unused-vars'
console.log(str3)
/* eslint-enable no-unused-vars, no-undef */
// 声明全局变量,可以在当前脚本任意位置使用
console.log(str4)
/* global str4, str5:readonly */
console.log(str5)
// 临时修改某个规则配置,影响当前脚本
// 不会被修改的变量,应该用const声明
let str6 = 'prefer-const'
// 强制使用“三等号”
if (str6 == 'eqeqeq') {
console.log('eqeqeq')
}
/* eslint prefer-const: 'warn', eqeqeq: [1, 'always'] */
// 启用 node 环境,影响当前脚本
console.log(__dirname)
/* eslint-env node */
ESLint 结合自动化工具
集成 ESLint 到自动化构建工作流中的优点:
- 集成之后,确保ESLint 一定会工作。
- 与项目统一,管理更加方便。
Gulp 集成 ESLint
示例代码参考:notes\note_code\04-ESLint\02-gulp-integrate-eslint。
安装模块 eslint
和 插件 gulp-eslint
在配置文件gulpfile.js中找到编译JS的任务,在babel处理之前将eslint集成进去,因为应该用eslint校验源代码。
// gulpfile.js
const script = () => {
return src('src/assets/scripts/*.js', { base: 'src' })
.pipe(plugins.eslint())
.pipe(plugins.babel({ presets: ['@babel/preset-env'] }))
.pipe(dest('temp'))
.pipe(bs.reload({ stream: true }))
}
记得初始化ESLint的配置文件并安装相应的npm包。
向JS脚本中添加问题代码,执行script任务,任务成功执行,eslint并没有报错。
并没有实现「eslint发现问题,并直接体现出来,同时能够终止后续的编译任务」的效果。
原因是,这里的 gulp-eslint 插件,默认只会检查代码中的问题,并不会根据检查的结果做出任何的反馈。
解决方法是:
- 使用 gulp-eslint 插件的
format()
方法,将结果打印到控制台。 - 使用插件的
failAfterError()
方法,使检测到错误代码exit code
为1
时,退出进程。
const script = () => {
return src('src/assets/scripts/*.js', { base: 'src' })
.pipe(plugins.eslint())
.pipe(plugins.eslint.format())
.pipe(plugins.eslint.failAfterError())
.pipe(plugins.babel({ presets: ['@babel/preset-env'] }))
.pipe(dest('temp'))
.pipe(bs.reload({ stream: true }))
}
Webpack 集成 ESLint
webpack 集成 ESLint ,是通过 Loader 而不是 Plugins 集成的。
这样就可以在实现打包JS代码之前,先通过 eslint 的加载器校验JS代码。
React项目示例代码参考:notes\note_code\04-ESLint\03-webpack-integrate-eslint。
安装eslint
和eslint-loader
,初始化eslint配置文件并安装相应模块。
在babel-loader前添加eslint-loader,或者使用enforce
属性指定eslint-loader的执行顺序。
module.exports = {
// ...
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader',
},
{
test: /\.js$/,
exclude: /node_modules/,
use: 'eslint-loader',
enforce: 'pre',
},
}
执行npx webpack
打包就会执行eslint校验。
配置 ESLint 支持 React
上例执行打包后,eslint 校验到 React 使用有问题:
导入了React和App模块,但是没有被使用到(实际上在JSX中使用了)
这时ESLint就需要额外的插件来支持React的JSX语法。
社区中为React专门提供了一个插件eslint-plugin-react
。
安装这个插件,并配置。
在配置文件中的plugins
数组中添加这个插件。
注意:使用插件时,需省略eslint-plugin-
前缀,ESLint在内部只使用没有前缀的名称去定位插件和规则:
module.exports = {
env: {
browser: true,
es2020: true,
},
extends: [
'standard',
// 可以在extends中开启插件提供的配置
'plugin:react/recommended' // 使用插件提供的配置 recommended
],
parserOptions: {
ecmaVersion: 11,
},
rules: {
// 可以在rules中单独开启插件的规则
// 'react/jsx-uses-react': 2, // 防止React被标记为未使用
// 'react/jsx-uses-vars': 2, // 防止在JSX中使用的变量被标记为未使用
},
plugins: ['react'],
}
现代化项目集成 ESLint
类似 Vue.js 和 React 这种主流框架,它们CLI工具中已经集成了 Webpack 和 ESLint 等工具,不需要单独去配置。
以vue-cli创建的项目为例:
全局安装vue-cli:npm install @vue/cli -g
创建vue项目:vue create vue-app
,vue会询问配置问题。
其中当询问Pick additional lint features
应该在何时使用lint校验?
- 选项1:
Lint on save
真正含义是「在webpack构建时自动校验」,而不是「文件保存时自动校验」。- 因为一般我们使用监视模式,在文件改动后,webpack就会自动执行编译,所以这里就称为保存(save)时校验。
- 选项2:
Lint and fix on commit
,利用git hook钩子,在commit时修复和校验。
一般建议两个都勾选。
ESLint 检查 TypeScript
以前对TypeScript的检查都是使用tslint
工具。
后来由于tslint
官方放弃维护,转而建议使用eslint
配合 TypeScript 插件 实现对代码的校验。
npx eslint --init
初始化eslint配置文件
注意,如果没有先安装typescript
模块,初始化eslint配置时就会提示错误信息。
但是,配置文件依然安装成功,之后安装typescript
模块,也可以正常使用。
Error: An error occurred while generating your JavaScript config file. A config file was still generated, but the config file itself may not follow your linting rules.
// 提示 typescript 解析器(parser)依赖typescript模块
Error: Failed to load parser '@typescript-eslint/parser' declared in 'BaseConfig': Cannot find module 'typescript'
// eslint配置
module.exports = {
"env": {
"browser": true,
"es2020": true
},
"extends": [
"standard"
],
"parser": "@typescript-eslint/parser", // 使用typescript解析器
"parserOptions": {
"ecmaVersion": 11
},
"plugins": [
"@typescript-eslint" // 加载typescript规则,等待被配置后使用
],
"rules": {
}
};
Stylelint 介绍
CSS代码的lint操作,一般使用Stylelint工具。
它与 ESLint 使用基本一致:
- 提供默认的代码检查规则,可以在配置中选择性的开启或关闭某项规则。
- 提供CLI工具,可以在终端中执行校验命令。
- 通过插件支持 Sass Less PostCSS 这些CSS衍生语法的代码检查。
- 支持 Gulp 或 Webpack 集成
快速使用
安装
npm install stylelint -D
配置
与ESLint一样,使用Stylelint,也必须有一个配置文件。
Stylelint 使用 cosmiconfig
工具从当前工作目录查找并加载配置信息。
当找到一个目标后,就停止搜索,也可以使用--config
命令快速指定配置文件。
配置信息来源:
package.json
的stylelint
属性(ESLint是eslintConfig
)- 一个
.stylelintrc
文件- 文件无后缀名,则内容可以为json或yaml
- 也可以指定后缀名:
.json
.js
.yaml
.yml
- 一个导出一个对象的
stylelint.config.js
文件
Stylelint没有提供像ESLint一样的--init
初始化配置文件的命令,所以需要手动创建配置文件。
extends 配置继承 / 共享配置
Stylelint 默认并没有提供一个共享配置,像 ESLint的 eslint:recommended
。
需要安装使用的配置模块,常用的共享配置模块:
stylelint-config-recommended
stylelint-config-standard
与ESLint的区别就是,添加配置的名称,必须是完整的:
- Stylelint 完整名称:
extends: 'stylelint-config-standard'
- ESLint 省略
eslint-config-
前缀:extends:'standard'
module.exports = {
extends: "stylelint-config-standard"
}
使用
npx stylelint ./index.css
注意,Stylelint 检查的文件路径中的目录分隔符必须为/
,使用\
会报错:
Error: No files matching the pattern ".\index.css" were found.
自动修复:
npx stylelint index.css --fix
校验 Sass
校验Sass,需要安装一个配置模块stylelint-config-sass-guidelines
module.exports = {
extends: ['stylelint-config-standard', 'stylelint-config-sass-guidelines'],
}
Prettier
近年来使用频率特别高的通用的前端代码格式化工具。
几乎可以完成所有类型代码的格式化工作。
通过Prettier自动化格式功能,就可以落实前端项目中的规范化标准。
安装:npm install prettier -D
执行格式化命名:npx prettier style.css
这个命令默认会将格式化后的代码输出到控制台中,而不是覆盖修改源文件。
添加--write
使其覆盖源文件:npx prettier style.css --write
也可以使用通配符,格式化当前目录下所有文件及下级文件:npx prettier . --write
Git Hooks
通过lint工具确保代码规范的落地,但是在过程中还有遗漏的问题。
比如,代码提交至仓库之前未执行lint工作(未使用或忘记使用lint工具),直接将有问题的代码提交到了远端仓库。
在后期进行项目集成时,导致项目代码有可能不被CI通过。
这种情况,lint工具就丧失了它的意义。
相比口头要求团队成员在提交代码前执行一次lint命名。
更好的方案是,通过某种方式(Git Hooks) 在代码提交前 强制 lint。
Git Hooks 介绍
Git Hooks 也称为 Git 钩子,每个钩子都对应一个任务或具体的Git操作,例如 push、commit。
通过编写 shell 脚本,定义一些钩子任务触发时,具体要执行的操作。
Git Hooks 使用
新建一个git仓库,查看.git/hooks
目录,里面的每个.sample
文件就是每一个钩子。
当前只需要使用到pre-commit.sample
钩子。
它对应的就是commit
操作。
当执行commit
操作时,就会触发这个钩子中所定义的任务。
复制这个文件,重命名为pre-commit
(也就是去掉.sample
后缀),编写其中的脚本内容:
保留执行环境代码:#!/bin/sh
编写一段测试脚本:echo "before commit"
再次执行commit
操作时,就会执行echo
命令,在终端输出before commit
#!/bin/sh
echo "before commit"
Husky - ESLint 结合 Git Hooks
很多前端开发者并不擅长使用shell。
并且.git/hooks
中的文件不会被提交到远端仓库中,无法自动令团队成员使用。
所有有人开发了一个可以实现 Git Hooks 的使用需求 的工具模块:Husky
使用 Husky 就可以在不编写shell脚本的情况下,使用Git Hooks 钩子。
安装这个模块后,就会自动在.git/hooks
目录下添加Git钩子文件。
注意,使用之前,请确认上例中创建的pre-commit
自定义文件已被删掉,否则会影响 Husky的使用。
安装
npm install husky -d
模块安装完成后,可以看到.git/hooks
目录下多了一些Husky自定义的钩子,如pre-commit
。
它们负责完成一些任务,但不需要关心它们如何实现。
npm uninstall husky -d
卸载Husky后会同步删除Husky创建的 Git hooks 钩子。
注意:使用npm安装Husky,因为yarn无法在终端执行husky命令,commit 触发 husky的git hooks可行(也有说不可行的)。
npx
也无法自动识别本地的husky。
如果node_modules
中已经包含了husky,可以通过npm rebuild
重新安装它的Git Hooks。
配置
在package.json
中添加一个husky
字段并配置。
// package.json
{
// ...
"scripts": {
// 可以使用--fix或prettier --write(慎用)自动修复
"lint": "eslint ."
},
"husky": {
// 在hooks中定义一些钩子任务
"hooks": {
// 当进行commit提交时,就会运行scripts中的test脚本
"pre-commit": "npm run lint"
}
}
}
也可以通过配置文件配置:.huskyrc
, .huskyrc.json
, .huskyrc.js
or husky.config.js
// .husky.json
{
"hooks": {
// 当进行commit提交时,就会运行scripts中的test脚本
"pre-commit": "npm run lint"
}
}
影响范围
如果仓库包含多个package.json(多项目),安装Husky,会直接在根目录的.git/hooks
中添加钩子文件。
并且在提交本仓库的代码时,配置了husky.hooks
的任务,都会触发。
但是仍然建议在根目录的package.json中配置husky。
lint-staged
上例使用husky在代码提交前执行lint校验。
但是校验的是项目全部文件,包括不提交的文件。
lint-staged 是一个在git暂存文件上运行linters的工具。
可以实现提交代码执行 lint 校验时,只 lint 提交的文件代码。
lint-staged包含一个脚本,它可以 运行任意的 shell任务,并将 [由指定的glob模式过滤的] [一系列 暂存(Staged)文件] 作为参数。
Git文件的4种状态:
Untracked
:未跟踪,此文件在文件夹中,但并没有加入到git库中,不参与版本控制。- 通过
git add
将状态变为Staged
- 通过
Unmodify
:文件已经入库,未修改,即版本库中的文件快照内容与文件夹中完全一致。- 这种状态的文件有两种去处:
- 如果被修改会变为
Modified
- 如果使用
git rm
移出版本库,则成为Untracked
文件
- 如果被修改会变为
- 这种状态的文件有两种去处:
Modified
:文件已修改,仅仅是修改,并没有进行其他操作。- 这个文件也有两种去除:
- 通过
git add
可进入暂存状态Staged
- 使用
git checkout
从库中取出文件,覆盖当前文件,状态变为Unmodify
- 通过
- 这个文件也有两种去除:
Staged
:暂存状态。- 执行
git commit
则将修改同步到库中,这时库中的文件和本地文件又变为一直,文件状态变为Unmodify
- 执行
git reset HEAD filename
取消暂存,文件状态为Modified
- 执行
因为暂存文件都是提交的文件,所以仅对它们进行lint,可以省略项目其他未提交文件的lint。
简单使用
在上例husky的基础上,安装npm install lint-staged -D
在package.json中配置任务,并添加执行lint-staged命令的脚本,最终在husky的pre-commit钩子任务中执行这个脚本。
{
// ...
"scripts": {
"lint": "eslint .",
"precommit": "lint-staged"
},
"husky": {
"hooks": {
"pre-commit": "npm run precommit"
}
},
"lint-staged": {
// key是glob匹配字符串
// value是执行的任务,多任务可以使用数组
"./*.js": "eslint"
}
}
快速使用
最快速的方式就是执行npx mrm lint-staged
命令。
它会根据package.js中依赖的lint工具,安装和配置husky和lint-staged。
所以请确认在执行这个命令前,已经安装了package.json中的依赖,并配置好代码质量工具,如Prettier,ESLint。
mrm
模块在尽可能保证配置不变的情况下,添加配置。
它提供很多配置任务,例如
mrm gitignore
:创建一个.gitignore
文件mrm eslint --init
:初始化eslint配置文件,同eslint --init
一样mrm lint-staged
任务:- 根据npm scripts 推断lint命令
- 在package.json中添加lint-staged配置,并将推断的lint命令添加到里面
- 设置一个pre-commit Git钩子,用于执行lint-staged命令
- 安装相关依赖:husky lint-staged
mrm虽然会覆盖规则,但会尽量保留开发者自定义的规则。
npx mrm lint-staged
执行后的package.json
{
// ...
"devDependencies": {
// ...
"husky": "^4.2.5",
"lint-staged": "^10.2.11"
},
"scripts": {
"lint": "eslint ."
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.js": "eslint --cache --fix"
}
}
配置
lint-staged的配置也可以使用配置文件:.lint-stagedrc
lint-staged.config.js