中大型公司中前端项目往往不止一个,前端开发人员多加上前端项目众多,为了维持一定的项目团队风格往往十分艰难。这篇文章主要是在公司中针对组内现状问题进行问题收集、调研、开发、落地的总结。
前端组内项目众多,但是在代码质量检测方面一直不统一。比如像xx系统和移动端项目都有简单 lintrc 配置、但都是重复复制的配置;像 node 方向的项目几乎只是简单配置了几个规则;而内部yy系统的项目全部都没有配置代码检测、导致 CR 的时候需要重复性提出相关代码风格问题;除此之外针对新开的项目大部分是手动新建文件并把配置复制一份过来,重复冗余且效率低下。
鉴于以上现状,各个项目相关检测配置不统一也不便于管理。结合现有的前端代码检测工具及组内项目现状,考虑配置一份通用性规则,里面集合了不同规则集,不同项目引入不同的规则集扩展。我们只需要维护这个包项目及规则本身即可,每个项目 install 下载即可使用。这样的做法可以带来多种好处:
JSLint 的核心是 Top Down Operator Precedence(自顶向下的运算符优先级)技术实现的 Pratt 解析器。在不断发展过程中,由于 JSLint 中的所有规则都由开发者定义个人风格过于明显,若要使用就要接受其定义的所有规则,这就导致新的 Lint/Linter 工具诞生。
JSHint 基于 JSLint、其核心实现仍然基于 Pratt 解析器的基础上,但解决了很多 JSLint 暴露出来的缺点,比较明显的就是可添加可配置的规则,用参考文章中的一句话描述:“ JSHint 的出现几乎是必然的,前端开发者日益增长的代码质量诉求和落后偏执的工具之间出现了不可调和的矛盾”。
ESLint 和 JSCS 几乎在同一时段发布,且两者都是利用 AST 处理规则,用 Esprima 解析代码,因为需要将源码转成 AST,执行速度上其实是输给了 JSHint;ESLint 真正能够火的原因其实是 ES6 的出现,ES6 新增许多新语法,但是 JSHint 本身的一些设计原因无法兼容新语法,而 ESlint 由于其高扩展性,既能扩展自定义规则又能通过babel-eslint 更换掉默认解析器,对新框架的语法也能很好的兼容,所以最终 ESlint 被广泛使用;又由于 JSCS 和 ESLint 实现原理大同小异,再维护也没有太大的意义,最终 JSCS 与 ESLint 合并。
image.png
Prettier 也是一个代码样式检查优化的工具,但其可配置的规则很少,除了少部分可配置的规则外,其他都不可更改以此来达到强制性统一代码风格的目的。Prettier 与 ESlint 不同,如下图所示,它不止适用于 JS 代码,还适用于其他前端常用语言,不过它的规则只针对代码样式(Formatting) 而不针对于代码质量(Code-quality) 。Prettier可以单独在项目中使用,也可以与 ESlint 一起搭配使用。
image.png
执行命令 npm install eslint \--save-dev
安装之后,可以直接在项目源码中针对对应的代码块开启规则:
/* eslint console: "error" */
console.log('66666')
复制代码
也可以在代码块中直接禁用相关规则:
1. 被包裹的代码块中取消eslint检查,也可以带上对应的规则表示仅禁用对应规则
/* eslint-disable */
alert(‘foo’);
/* eslint-enable */
2. 针对某一行禁用eslint检查:
alert(‘foo’); // eslint-disable-line
// eslint-disable-next-line no-alert
alert(‘foo’);
复制代码
执行 npm install eslint \--D
全局安装ESlint之后,使用命令 eslint \--init
初始化 eslint 配置文件,执行过程中会询问一些配置化的问题,可以根据自己的需求去进行选择:
选择结束之后最终会生成一份初始化 .eslintrc 文件如下所示:
module.exports = {
env: {
browser: true,
es6: true,
},
extends: [
'plugin:vue/essential',
'airbnb-base',
],
globals: {
Atomics: 'readonly',
SharedArrayBuffer: 'readonly',
},
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
},
plugins: [
'vue',
],
rules: {},
};
复制代码
Tips:建议还是直接看英文翻译,有的查阅中文意思根本就翻译错了
ESLint配置参数文档:eslint.org/docs/user-g…[1]
eslintrc文件中的配置参数非常多,大部分参数官方文档都有相关的说明,这里只针对此次使用中一些比较重要的参数进行罗列:
整个项目中开启严格模式
"off" 或 0:关闭规则
"warn" 或 1:开启规则,黄色警告 (不会导致程序退出)
"error" 或 2:开启规则,红色错误 (程序执行结果为0表示检测通过;执行结果为1表示有错误待修复)
复制代码
有的规则开启后还可以配置额外的参数项,可以根据不同的情况开启规则后再添加额外的参数项。
ESLint命令行参数文档:eslint.cn/docs/user-g…[2]
eslint 相关的命令行设置也可以直接参考官方文档,一般是在 package.json 文件中对 eslint 进行特定的命令行设置,用来达到执行对应命令呈现不同效果的目的。命令行中主要用到的是 global 语法,这里也只针对此次使用过程中个别参数进行介绍:
当我们知道了怎么在项目中简单配置相关的检测规则之后,就会碰到开头所说的问题,重复性的配置、重复的复制粘贴这样会导致效率低下且代码冗余不便于统一管理。eslint 的高扩展性可以让我们很方便的做很多事:比如把通用性的配置整合成一个可共享配置包,这样我们只需要把通用性的一些规则分类整理并发布成为一个 npm 包,其他项目只需 install 下载后配置好规则即可生效。
共享配置包主要是将一些配置导出,使用方直接引入即可,在此参考了一些比较出名的 eslint-config 类似 airbnb 和 alloy。
airbnb源码:自行搜索 github 寻找源码
最开始看的是目前前端社区中使用最火的 airbnb,airbnb 主要是在 package 文件中分别建了两份配置,eslint-config-airbnb 中主要是 react 的一些规则配置,eslint-config-airbnb-base 主要是基本规则配置分类。
image.png
由于 eslint 对于 config 配置包没有提供对应的模版,所以在参照了 airbnb 目录结构之后开始手动新建不同分类的文件,将一些内置规则按照 eslint 中 rules 模块介绍中不同功能块进行划分,然后在 .eslintrc.js 文件中通过 extends 相对路径引入。但是由于 airbnb 他们项目本身使用的技术栈比较简单所以按照这样发布两个包也没什么问题,如果多项目多技术栈按照这样去发包的话则需要管理发布多个包。
image.png
alloy源码:github.com/AlloyTeam/e…[3]
由于 airbnb 其分开发布的结构方式用在我们的设计上不是很适合,于是看了一下 alloy 部分的源码。alloy 中将一些样式相关的规则给了 Prettier 管理,内部主要是 React/Vue/TypeScript 插件中的一些规则分别列在跟目录的 base.js、vue.js、react.js、typescript.js 文件中,通过文档、示例、测试、网站等方便大家参考 alloy 的规则,并在此基础上做出自己的个性化,且其内部利用 travis-ci 高度的自动化,将一切能自动化管理的过程都交给脚本处理,其通过自动化的脚本将成很多个 ESLint 配置文件分而治之,每个规则在一个单独的目录下管理。参考了 alloy 的目录结构之后,我们将 eslint-config-zly 项目结构进行了调整,主要是 base 规则和 vue 规则放在同一集目录下:
image.png
因为最终是要发布到npm上,所以结合之前的一些参考项目的源码结构、对于如何发包管理进行了一部分的讨论,主要是针对于最终的包怎么发布以及项目使用时怎么引入,其中主要发包设计有如下几种:
主要也是不同规则单独发一个包,需要组合使用的放在 extends 中一起使用即可。这个主要是发包管理过多,组合使用也不够直接方便,故弃用。
"off" 或 0:关闭规则
"warn" 或 1:开启规则,黄色警告 (不会导致程序退出)
"error" 或 2:开启规则,红色错误 (程序执行结果为0表示检测通过;执行结果为1表示有错误待修复)
复制代码
类似于 airbnb,不同功能的规则单独作为一个 npm 包发布,需要的时候单独下载引入。这个可以满足让业务方想用哪种类型的规则就引入哪个包的期望,但是这个包管理发布的话需要一个个维护,比较麻烦、也不符合我们的预期。
参考:https://github.com/airbnb/javascript/blob/master/packages/eslint-config-airbnb-base/README.md
extends: airbnb/base
extends: airbnb/vue
一个项目中根据不同分类发布不同的包,如 airbnb 中项目 base 集模块单独发包 eslint-config-airbnb-base,
需要 base 的单独引入这个包即可
复制代码
类似于上述 alloy,将通过不同文件夹将功能划分清楚,例如 base 只装基础规则、vue 只装 vue 规则,如果两者规则都需要的就组合使用,但是由于我们希望使用的业务方可以直接使用一个集中的规则集而不需要手动组合,所以这个方案有点不符合我们的预期。
参考:https://github.com/AlloyTeam/eslint-config-alloy/blob/HEAD/README.zh-CN.md
extends: alloy/base,
extends: alloy/vue,
extends: alloy/typescript
需要组合使用的话就 extends: [alloy/base, alloy/vue]
复制代码
综合考虑前面两种发包设计的优劣,再结合我们的需求,于是选择第三种方案,所有规则都在一个包里面管理,base 集作为基础规则集会在每个其他规则集中引入,其他类似 vue 集会在引入 base 集后叠加 vue 的规则,业务方需要使用的话直接引入 vue 集时基础规则和 vue 规则都会生效。
extends: zly/vue
extends: zly/react
extends: zly/tina
extends: zly/node
vue 的项目就单独引入 zly 包的 vue 集, node的项目就单独引入 zly 包的 node 集
复制代码
定好大概的项目架构之后相当于架子搭建好,下一步就是内容填充了。主要是针对于不同的框架语法要用不同的规则集进行检测,结合公司现有项目制定了一下规则集:
- 'best-practices.js'
- 'custom.js'
- 'errors.js'
- 'es6.js'
- 'import.js'
- 'tyle.js'
- 'variables.js'
复制代码
最后在 _base 文件下面 index.js 将上述文件与 airbnb-base 包一起作为 extends 引入:
Tips:在整理 node 集的时候发现 node 项目需要在严格模式下开发,ecmaFeatures 中 impliedStrict 设置为 true 即项目都处在严格模式下,无需写上 'use strict',刚开始只想把这个配置写在 node 集中,最后商讨后还是放在 base 集中,确定所有用到的项目都严格模式下。
module.exports = {
parserOptions: {
ecmaFeatures: {
impliedStrict: true,
}
},
extends: [
'airbnb-base',
join(__dirname, './rules/best-practices'),
join(__dirname, './rules/errors'),
join(__dirname, './rules/es6'),
join(__dirname, './rules/import'),
join(__dirname, './rules/style'),
join(__dirname, './rules/variables'),
join(__dirname, './rules/custom'),
],
}
复制代码
vue 规则集中主要是引入了 eslint-plugin-vue 中 vue3-recommended,_base集,再针对特定的规则进行配置:
module.exports = {
env: {
browser: true,
},
extends: [
'plugin:vue/vue3-recommended',
join(__dirname, '../_base/index'),
join(__dirname, './rules/vue'),
],
parserOptions: {
parser: 'babel-eslint',
},
plugins: ['vue'],
}
复制代码
tina 规则主要是针对小程序项目,用的也是 vue 语法,这里针对于是引入 vue 规则集还是直接引入 eslint-plugin-vue 做了一部分讨论,最终还是决定直接引入 vue 集针对不必要的规则手动在 tina 集中关闭:
module.exports = {
globals: {
App: true,
Page: true,
wx: true,
getApp: true,
getPage: true,
getCurrentPages: true,
},
extends: [
join(__dirname, '../vue/index'),
join(__dirname, './rules/tina'),
],
parserOptions: {
parser: 'babel-eslint',
},
}
复制代码
node 集制定过程中,看了 eslint-config-node、eslint-config-egg、eslint-plugin-eggache 和 eslint-plugin-node,经过对比发现 eslint-plugin-node 插件中制定的node规则最多最纯粹,且社区中 eslint-plugin-node 使用较多提供的解决方案也比较多,所以经多方调研最后决定 eslint-plugin-node。node 集中用了 eslint-plugin-node 中 recommended,再引入 _base 集,最后针对特定的 node 规则在 rules/node/index.js 中做特殊处理。
module.exports = {
env: {
node: true,
},
extends: [
'plugin:node/recommended',
join(__dirname, '../_base/index'),
join(__dirname, './rules/node')
],
plugins: ['node'],
}
复制代码
针对上述除 _base 以外的规则集中,其他 extends 的顺序都是先引入第三方插件规则,再引入 _base 规则,然后最后引入自己针对性规则修改过的文件。
ELSint自定义规则及源码解析juejin.cn/editor/draf…[4]
定好项目架构、发包设计、规则集之后,想要整合浩哥的自定义规则 "_.get禁止第三参数 " 的时候,发现 eslint 自定义配置包中不能集成自定义规则,只有自定义插件才可以集成自定义规则
,于是整体项目从 config 过渡到 plugin。
Yeoman generator-eslint:yeoman.io/authoring/[5]
ESLint 官方为了方便开发者开发插件有提供 Yeoman 模版 generator-eslint,基于官方提供的模板可以快速创建 ESLint Plugin 项目, 按照如下步骤进行初始化:
npm i -g yo
npm i -g generator-eslint
// 创建一个plugin
yo eslint:plugin
// 创建一个规则
yo eslint:rule
复制代码
初始化的项目目录结构如下:
image.png
lib 文件夹中的 config 下放之前定义好的自定义规则集分类,rules 文件夹下放相关的自定义规则分类,把下篇文中针对性写的 _.get() 禁止第三参数的自定义规则放在 rules 中的 lodash-get.js 文件下且在 index.js 中引入:
image.png
如果你的 npm 包还没发布,需要进行本地调试,可使用 npm link 本地调试。在 eslint-plugin 项目中执行 npm link 命令,就可以全局使用 eslint-plugin 命令了,然后可以在你的测试项目中执行 npm link eslint-plugin,引入测试即可。在 npm 包文件夹下执行 npm link 命令,会创建一个符号链接,链接全局文件夹 {prefix}/lib/node_modules/ 和你执行 npm link 的包文件夹。
注意:package-name 是 package.json 中的 name, 而不是文件夹名。
git hooks 即 git 在执行过程中的钩子,其可以触发自定义脚本。每个含有 repository 的项目下都存在一个.git 文件、每个.git 文件都包含一个 hooks 文件,你可以在此文件夹根据钩子的名称来创建相应的钩子文件
,当 git 执行某些操作时相应的钩子文件就会被执行
。
下图中就是各个钩子的案例脚本,可以把 sample 去掉,直接编写 shell 脚本执行:
image.png
通常情况下我们会使用的钩子有:
Q:.git 文件下相应的 hook 文件配置可以直接上传到远程仓库,以备其他人拉取达到配置共享的效果吗?
A:查询了相关资料,在 .git 文件下的 hook 文件配置不能提交到远程仓库,但是可以通过复制配置给其他同事,只是这样并不是我们想要的结果。
husky 是前端工程化时一个必不可少的工具,由于直接修改 .git/hooks 文件不方便,使用 husky 可以让我们向项目中方便添加 git hooks。
项目最开始的时候普遍是通过配置 pre-commit 库起到提交前检测的作用,npm install pre-commit \--save-dev
下载安装之后在项目中按下方配置,当我们在 commit 提交之前就会执行。
{
"scripts": {
"lint": "eslint . --fix",
},
"pre-commit" : ["lint"],
}
复制代码
除了只使用 pre-commit 库进行提交前代码检测以外,目前最常用的就是使用 husky,npm install husky \--save-dev
安装之后在 package.json 中 配置,可以搭配使用 pre-commit 钩子,这样当提交 commit 的时候就会执行脚本 npm run lint 进行全项目文件检测。如果只是目前配置的话。
注意:如果某个项目之前是通过 pre-commit 库进行预检测的话,后面想换成 husky + pre-commit 钩子 的模式检测的话,需要删除 node_modules 之后再 install 才正常生效,因为本地 node_modules 中的 pre-commit 库还在,两者会产生冲突导致执行 husky 失效。
{
"husky": {
"hooks": {
"pre-commit": "npm run lint", // 在commit之前先执行npm run lint命令
}
}
"scripts": {
"lint": "eslint . --ext .js,.vue",
}
}
复制代码
husky 配置已经可以轻松的实现提交前代码检测,但是直接使用检测也会带来了很多弊端,比如每次提交都会检测项目所有文件。如果想要只检测提交过的代码的话我们就可以使用 lint-staged,lint-staged 只读取暂存区的文件、并运行配置好的脚本,避免了对未提交到暂存区的代码造成影响。lint-staged 过滤文件采用 glob 语法,它可以配合 husky 使用,由 husky 触发 lint-staged,再由 lint-staged 执行脚本,配置好之后既可以减少 eslint 全量检测时消耗时间过长的问题、又可以减少修复问题占用的时间!
// package.json 文件
"husky": {
"hooks": {
"pre-commit": "lint-staged",
}
},
"lint-staged": {
"*.vue": [
"eslint --cache --fix"
],
"*.js": [
"eslint --cache --fix"
]
},
复制代码
有一些项目他不是单独的 vue 项目或 node 项目,类似xx系统有用 node 做中间层,这时候一个项目中就会有两个环境,为了针对不同环境的文件夹下用不同的规则集进行检查,我们这里准备配置两份 .eslintrc 文件,主要配置如下:
// package.json 文件
"scripts": {
"lint": "npm run lint-web && npm run lint-node",
"lint-web": "eslint . -c ./.eslintrc-web.js --ext .js,.vue --ignore-pattern server/ --cache --fix",
"lint-node": "eslint server/ -c ./.eslintrc-node.js --ext .js --cache --fix",
}
复制代码
// .eslintrc-web.js 文件
module.exports = {
root: true,
plugins: [
'@zly'
],
extends: [
'plugin:@zly/vue',
],
}
复制代码
// .eslintrc-node.js 文件
module.exports = {
root: true,
plugins: [
'@zly'
],
extends: [
'plugin:@zly/node',
],
}
复制代码
配置完之后运行可以正常抛错 error,但是自动 autofix 的功能全部失效了。后续发现可能是因为我们把 .eslintrc.js 改成了 .eslintrc-node.js、.eslintrc-web.js 导致的问题,更改之后 ide 无法动态识别 eslint 的配置。不过在编辑器中可以手动配置,但是每个人的编译器都要手动配置真的比较麻烦,于是我们就想到了之前的参数 root。
前面介绍 root 参数的时候,有对着官方文档及网上看相关的翻译,总觉得好像不同的翻译都不一样,而且我自己单独对这个概念也模棱两可的,所以就做了一个 root 的一个测试实验。主要是项目 eslint-root-test 根目录下新建一个.eslintrc.js文件、在根目录的 server 文件夹下也新建一个.eslintrc.js文件,针对两个.eslintrc.js 中 root 配置不通过的情况进行测试,测试结果如下:
image.png
根据以上测试结果发现,root 恰好能解决我们想要两份 rc 的需求,于是针对智能系统在跟目录下设置了 .eslintrc.js 文件,在 server 文件下设置了 .eslintrc.js 文件,两份 rc 文件 root 都设置为 true,最终配置为:
// package.json 文件
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"commit-msg": "commitlint -e $HUSKY_GIT_PARAMS"
}
},
"lint-staged": {
"*.vue": [
"stylelint --config /.stylelintrc.short.js",
"stylelint --config /.stylelintrc.js --fix",
"eslint --cache --fix"
],
"*.css": [
"stylelint --config /.stylelintrc.short.js",
"stylelint --config /.stylelintrc.js --fix"
],
"server/**/*.js": [
"eslint -c ./server/.eslintrc.js --cache --fix"
],
"!(server)/**/*.js": [
"eslint --cache --fix"
]
},
"scripts": {
"lint": "npm run lint-web && npm run lint-node",
"lint-web": "eslint . --ext .js,.vue --ignore-pattern server/ --cache --fix",
"lint-node": "eslint server/ -c ./server/.eslintrc.js --ext .js --cache --fix",
},
复制代码
执行 npm run lint 可以全量跑所有文件的检测,如果单独检测可以单独执行 npm run lint-web 和 npm run lint-node,lint-staged 中配置也针对 server 文件及非 server 文件进行暂存区文件检测。
check-dependencies库:www.npmjs.com/package/che…[6]
我们常常会碰到这样的问题,当 pull 拉的最新的代码有个依赖升版了、但 node_modules 本地安装的依赖版本还是旧的时候,这时候运行是没有问题的但是两边依赖能够保持同步一致才是最好的。
问题一:
Q:如果需要每次 package.json 中我们自己的私有 npm 包依赖更新了,但是本地还是旧版本,怎么让针对私有 npm 远程和本地版本不一致的问题给出提示呢?
解决一:
A:找了一下没有找到特别合适的解决办法。
解决二:
A:针对自己发布的包做这种提示的方法没有找到很合适的,但是在寻找的过程中发现了
check-dependencies
这个库,这个库不会检测某个依赖,它是针对 package.json 中所有的依赖进行远程和本地的比对,但凡有一个不同步就会给出提示。
npm script 是记录在 package.json 中的 scripts 字段中的一些自定义脚本,使用自定义脚本,用户可以将一些项目中常用的命令行记录在 package.json 而不需要每次都要敲一遍。npm 脚本中有提供 pre 和 post 两个钩子,自定义的脚本命令也可以加上 pre 和 post 钩子。例如 dev 脚本命令的钩子就是 predev 和 postdev,用户在执行 npm run dev 的时候,会自动按照下面的顺序执行:
npm run predev && npm run dev && npm run postdev
复制代码
下载安装 check-dependencies 之后,于是打算在 predev 钩子中执行 node check.js 脚本,当执行 npm run dev 的时候会预先执行 predev 中的命令,如果发现两边依赖不同时就进行抛错中断,当两边一致之后才能正常运行:
// package.json 文件
"scripts": {
"predev": "node check.js"
}
复制代码
const output = require('check-dependencies')
.sync({
verbose: true,
})
if (output.status === 1) {
throw new Error('本地依赖与package.json中不同步,请先npm install再执行')
}
复制代码
image.png
由于每个项目需要额外放一个 js 文件这样不是很友好,后面尝试看看能不能直接一个命令执行检测报错就可以,发现 check-dependencies 库本身其实已经支持直接执行报错:
// package.json 文件
"scripts": {
"predev": "check-dependencies"
}
复制代码
image.png
目前已经替换了大部分项目且测试正常,对新项目接入检测工具只需要安装 npm 包,在 .eslintrc.js 中配置即可生效、简单又方便,后续也只需要针对其中某些规则进行统一就可以了。
测试过程中其实还是发现了很多问题的,比如刚开始我自己但是比较习惯用 webstrom 的,但是它测试的时候真的问题太多,遵循单一变量原则就使用 vscode来进行测试,其中问题搜了很多地方都没有解决办法,问题主要如下:
命令行工具调试:segmentfault.com/a/119000001…[7]
ESLint 在中大型团队的应用实践:tech.meituan.com/2019/08/01/…[8]
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8