德胜云资讯,添加一些关于程序相关的内容,仅供大家学习交流(https://www.wxclwl.com)
日期:2023/03/25 11:42作者:陈文婷人气:
作者:子弈
转发链接:
https://juejin.im/post/6856410900577026061从零开始配置 TypeScript 项目的教程(一) 本篇
本文是算法与 TypeScript 实现[5]中 TypeScript 项目整体的环境配置过程介绍。主要包括了以下一些配置内容:
Git Commit MessageTypeScriptESLintPrettierLint StagedJestNpm Script HookVuepressGithub Actions如果你对以上的某些配置非常熟悉,则可以跳过阅读。如果你不清楚是否要继续阅读其中的一些配置信息,则可以通过工程问题来决定是否要继续阅读相关的内容。
算法与 TypeScript 实现[6] 关于当前配置的改造在 feat/framework[7] 分支上,希望刚兴趣的同学可以 star 一波。学习文档[8] 目前仍然是老版本的学习文档,之后会进行持续更新。
温馨提示:如果你希望在项目中制作基于 TypeScript 实现的简单易用的工具函数库,你可以使用一些成熟的 "零配置" 脚手架,例如 tsdx[9]、microbundle[10] 以及 typescript-starter[11] 等。如果功能不能满足你的项目需求,你也可以基于这些工具进行团队的定制化改造,例如 ts-lib-scripts[12]。
希望你读完这篇文章能够了解以下一些问题(很有可能成为工程配置方面的面试题哦,细节决定成败):
在使用 Git 的时候如何规范 Git 的提交说明(Commit 信息)?简述复合 Angular 规范的提交说明的结构组成?Commit 信息如何和 Github Issues 关联?在设计一些库包时如何生成版本日志?TypeScript 如何自动生成库包的声明文件?TypeScript 目前是采用 TSLint 还是 ESLint 进行代码校验,为什么?列举你知道的所有构建工具并说说这些工具的优缺点?这些构建工具在不同的场景下应该如何选型?Babel 对于 TypeScript 的支持有哪些限制?列举你所知道的 ESLint 功能?如何确保构建和上传的代码无 ESLint 错误信息?ESLint 和 Prettier 的区别是什么?两者在一起工作时会产生问题吗?Linters 有哪两种类型的校验规则?如何有效的识别 ESLint 和 Prettier 可能产生冲突的格式规则?如何解决此类规则冲突问题?git hook 在项目中哪些作用?git hook 中客户端和服务端钩子各自用于什么作用?git hook 中常用的钩子有哪些?pre-commit 和 commit-msg 钩子的区别是什么?各自可用于做什么?husky 以及 ghook 等工具制作 git hook 的原理是什么?如何设计一个通用的 git hook ?git hook 可以采用 Node 脚本进行设计吗?如何做到?lint-staged 的功能是什么?VS Code 配置中的用户和工作区有什么区别?VS Code 的插件可以只对当前项目生效吗?谈谈你所理解的 npm scripts,它有哪些功能?你所知道的测试有哪些测试类型?你所知道的测试框架有哪些?什么是 e2e 测试?有哪些 e2e 的测试框架?假设现在有一个插入排序算法,如何对该算法进行单元测试?假设你自己实现的 React 或 Vue 的组件库要设计演示文档,你会如何设计?设计的文档需要实现哪些功能?在设计工具库包的时候你是如何设计 API 文档的?在通常的脚手架项目中进行热更新(hot module replacement)时如何做到 ESLint 实时打印校验错误信息?Vuepress 有哪些功能特点?你所知道的 CI / CD 工具有哪些?在项目中有接触过类似的流程吗?CI 和 CD 的区别是什么?Github Actions 的特点是什么?除此之外如果你对其他相关的知识感兴趣(非本文相关的知识),希望你能额外深入去探索:
TypeScript 的特点?CommonJS 和 ES Module 有哪些区别?Tree Shaking 的作用是什么?什么情况下可以使用 Tree Shaking 的能力?如何引入 ES Module 库包?在构建层面和包描述文件层面需要注意哪些方面?谈谈你对 TypeScript 声明文件的理解?在制作库包时如何对外识别声明文件?在外部使用时有哪些好处?在制作工具包的时候如何考虑按需引入和全量引入的优雅引入设计?你知道哪些制作工具函数库的脚手架?了解 Vue CLI 3.x 的功能特点吗?如何基于 Vue CLI 3.x 定制符合团队项目的脚手架?了解 react-scripts 吗?工程化配置领域的设计可以有哪些设计阶段(例如 react-scripts 和 vue ui 在设计以及使用形态上的区别)?工程化配置监控(使用版本信息、版本兼容性报错信息分析、使用功能分析等)?温馨提示:有些问题在本文中能够得到答案,有些问题需要自己扩展阅读或查看源码才能得到答案(作者同样是工程化配置领域的小白,以上的这些问题同样在问自己)。
需要注意文档中的配置说明可能会省略某些细节步骤(例如某些依赖的 npm 包安装、某些配置文件说明等),如果想要知道更多细节信息,可查看各个配置的 Commit 提交信息:
项目初始化 (afaa458[13])framework: 新增 Git Commit Message 规范提交能力 (d04e259[14])framework: 新增 TypeScript 编译能力 (ebecee9[15])framework: 新增 ESLint 代码校验能力 (dca67d4[16])framework: 新增 Prettier 自动格式化能力 (7f3487a[17])framework: 新增 Lint Staged 上传校验能力 (b440186[18])framework: 新增 Jest 单元测试能力 (6f086f2[19])framework: 新增 Npm Scripts Hook 能力 (93e597a[20])framework: 新增 Vuepress 演示文档能力 (66e38d1[21])framework: 新增 Github Actions 能力 (1cc85a4[22])温馨提示:以上都是使用 npm run changelog 自动生成的版本日志信息,你也可以通过仓库的 CHANGELOG.md[23] 进行查看。
Commitizen[24] 是一个规范 Git 提交说明(Commit Message)的 CLI 工具,具体如何配置可查看 Cz 工具集使用介绍[25](这篇文章对于 Commit Message 的配置介绍已经非常详细清楚,因此这里不再过多介绍)。本项目中主要使用了以下一些工具:
cz-customizable[26]commitlint[27]conventional-changelog[28]配置后会产生以下一些特性:
使用 git cz 代替 git commit 进行复合 Angular 规范的 Commit Message 信息提交代码提交之前会通过 husky[29] 配合 git hook 进行提交信息校验,一旦提交信息不符合 Angular 规范,则提交会失败执行 npm run changelog 会在根目录下自动生成 CHANGELOG.md 版本日志温馨提示:husky 中文的意思是哈士奇,大家可以想象一下为什么这个工具叫哈士奇,是不是咬着你不放的意思(or more woof!),一旦它咬着你的代码提交不放,这将会是非常有趣的一件事情,在后续的工具配置中,我们仍然将于哈士奇见面,看看它会具体咬什么东西!
例如当你提交了一个不符合规范的 Commit Message(此时提交失败):
PS C:\Code\Git\algorithms> git commit -m "这是一个不符合规范的 Commit Message" husky > commit-msg (node v12.13.1) ⧗ input: 这是一个不符合规范的 Commit Message ✖ subject may not be empty [subject-empty] ✖ type may not be empty [type-empty] ✖ found 2 problems, 0 warnings ⓘ Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint husky > commit-msg hook failed (add --no-verify to bypass) 复制代码温馨提示:如果不知道什么是 CLI (命令行接口),可查看 使用 NPM 发布和使用 CLI 工具[30]。
工具函数库的实现采用 TypeScript,除了可以自动生成 ts 声明文件供外部更好的提示使用之外,也可以避免 javascript 动态性所带来的一些无法预料的错误信息(具体可查看Top 10 JavaScript errors from 1000+ projects (and how to avoid them)[31]),从而使算法的设计更加严谨。TypeScript 的构建方式有很多种,除了原生编译器 tsc 以外,还包括 Webpack、Rollup、Babel 以及 Gulp 等(更多构建工具的集成可查看Integrating with Build Tools[32]):
Webpack 主要用于页面应用的模块化构建,使用 Webpack 构建会增加构建库的体积,因此简单工具库的制作使用 Webpack 完全是 "杀鸡用牛刀"。Rollup 是一个构建工具库非常不错的轻量选择,它持有的 Tree Shaking[33] 以及构建 ES Module[34] 的特性使得它被 tsdx、microbundle 甚至 Vue 等广泛使用。Babel 对于 TypeScript 可使用 @babel/preset-typescript[35] 去除 TypeScript 类型标记,但是不做类型编译检查,更多关于 Babel 对于 TypeScript 支持的限制可查看@babel/plugin-transform-typescript - Caveats[36] 或 Babel 7 or TypeScript[37]。Gulp 是一个非常轻量的构建工具,并且也是 TypeScript 官方推荐的构建工具,具体可查看 TypeScript - Building[38],简单的 Gulp 配置可查看 TypeScript 中文网 - Gulp[39]。由于算法的函数工具库功能非常单一简单,因此采用 TypeScript 官方推荐的 Gulp 工具进行构建即可满足需求。
温馨提示:更多构建工具可以了解 esbuild[40]、parcel[41]以及 backpack[42]等。当然如果你想要更多了解这些构建工具的差异以及在什么项目环境下应该做如何选型,可以自行搜索前端构建工具的对比或差异,这里推荐一篇个人觉得总结不错的文章 前端构建:3 类 13 种热门工具的选型参考[43]。
本项目会构建输出 CommonJS 工具包(npm 包)供外部使用,采用 TypeScript 设计并输出声明文件有助于外部更好的使用该资源包进行 API 的提示。TypeScript 编译采用官方文档推荐的 Gulp 工具并配合 gulp-typescript[44] 和 tsconfig.json[45] 配置文件。在根目录下新建 tsconfig.json 文件并新增以下配置:
{ "compilerOptions": { // 指定 ECMAScript 目标版本 "ES3"(默认), "ES5", "ES6" / "ES2015", "ES2016", "ES2017" 或 "ESNext"。 "target": "ES5", // 构建的目标代码删除所有注释,除了以 /!* 开头的版权信息 "removeComments": true, // 可配合 gulp-typescript 生成相应的 .d.ts 文件 "declaration": true, // 启用所有严格类型检查选项。启用 --strict 相当于启用 --noImplicitAny, --noImplicitThis, --alwaysStrict, --strictNullChecks, --strictFunctionTypes 和 --strictPropertyInitialization "strict": true, // 禁止对同一个文件的不一致的引用 "forceConsistentCasingInFileNames": true, // 报错时不生成输出文件 "noEmitOnError": true } } 复制代码温馨提示:这里没有新增 module 配置信息,因为默认输出 CommonJS 规范,更多关于 TypeScript 配置信息可查看TypeScript 官方文档 / 编译选项[46]。如果对于 CommonJS 和 ES modules 的区别不是很清晰,这里有一些非常好的文档可以供大家阅读:ES modules: A cartoon deep-dive[47]、ES6 modules[48]。如果想了解如何对外提供 ES Module 可以查看 pkg.module[49]。
同时在根目录下新建 gulpfile.js 文件:
const gulp = require("gulp"); const ts = require("gulp-typescript"); const tsProject = ts.createProject("tsconfig.json"); // 输出 CommonJS 规范到 dist 目录下 gulp.task("default", function () { const tsResult = tsProject.src().pipe(tsProject()); return tsResult.js.pipe(gulp.dest("dist")); }); 复制代码在 package.json 中新增 npm script 脚本:
"scripts": { "build": "rimraf dist && gulp" }, 复制代码其中 rimfaf[50] 用于在构建之前清除 dist 目录文件内容。此时在 src 目录下新增 TypeScript 源码并使用 npm run build 命令可以进行项目构建并输出 CommonJS 规范的目标代码倒忙dist 目录下。
除此之外,此项目希望可以快速生成声明文件供外部进行代码提示,此时仍然可以借助gulp-typescript 工具自动生成声明文件。在 gulpfile.js 中新增以下配置
const gulp = require("gulp"); const ts = require("gulp-typescript"); const tsProject = ts.createProject("tsconfig.json"); const merge = require("merge2"); // 输出 CommonJS 规范到 dist 目录下 gulp.task("default", function () { const tsResult = tsProject.src().pipe(tsProject()); return merge([ tsResult.dts.pipe(gulp.dest("types")), tsResult.js.pipe(gulp.dest("dist")), ]); }); 复制代码修改 build 命令使其在构建之前同时可以删除 types 目录:
"scripts": { "build": "rimraf dist types && gulp", }, 复制代码再次执行 npm run build 会在项目根目录下生成 types 文件夹,该文件夹主要存放自动生成的 TypeScript 声明文件。
需要注意发布 npm 包时默认会将当前项目的所有文件进行发布处理,但这里希望发布的包只包含使用者需要的编译文件 dist 和 types,因此可以通过package.json中的 `files`[51](用于指定发布的 npm 包包含哪些文件) 字段信息进行控制:
"files": [ "dist", "types" ], 复制代码温馨提示:发布的 npm 表中某些文件将忽视 files 字段信息的配置,包括 package.json、LICENSE、README.md 等。
除此之外,如果希望发布的 npm 包通过 require(algorithms-utils) 或import 形式引入时指向 dist/index.js 文件,需要配置 package.json 中的`main`[52] 字段信息:
"main": "dist/index.js" 复制代码温馨提示:对于工具包使用全量引入的方式并不是一个好的选择,可以通过具体的工具方法进行按需引入。
TypeScript 的代码检查工具主要有 TSLint 和 ESLint 两种。早期的 TypeScript 项目一般采用 TSLint 进行检查。TSLint 和 TypeScript 采用同样的 AST 格式进行编译,但主要问题是对于 JavaScript 生态的项目支持不够友好,因此 TypeScript 团队在 2019 年宣布全面转向 ESLint(具体可查看 TypeScript 官方仓库的 `.eslintrc.json`[53] 配置),更多关于转向 ESLint 的原因可查看:
https://medium.com/palantir/tslint-in-2019-1a144c2317a9https://github.com/microsoft/TypeScript/issues/30553TypeScript 和 ESLint 使用不同的 AST 进行解析,因此为了在 ESLint 中支持 TypeScript 代码检查需要制作额外的自定义解析器[54](Custom Parsers,ESLint 的自定义解析器功能需要基于 ESTree[55]),目的是为了能够解析 TypeScript 语法并转成与 ESLint 兼容的 AST。@typescript-eslint/parser[56] 在这样的背景下诞生,它会处理所有 ESLint 特定的配置并调用
@
typescript-eslint/typescript-estree[57] 生成 ESTree-compatible AST(需要注意不仅仅兼容 ESLint,也能兼容 Prettier)。@typescript-eslint 是一个采用 Lerna[58] 进行设计的 Monorepo 结构仓库,除了上述提到的 npm 包之外,还包含以下两个重要的 npm 包:
@typescript-eslint/eslint-plugin[59]: 配合 @typescript-eslint/parser 一起使用的 ESLint 插件,可以设置 TypeScript 的校验规则。@typescript-eslint/eslint-plugin-tslint[60]: TSLint 向 ESLint 进行迁移的插件。温馨提示:如果你正在使用 TSLint,并且你希望兼容 ESLint 或者向 ESLint 进行过渡(TSLint 和 ESLint 并存), 可查看 Migrating from TSLint to ESLint[61]。除此之外,以上所介绍的这些包发布时版本一致(为了联合使用的兼容性),需要额外注意@typescript-eslint 对于 TypeScript 和 ESLint 的版本支持性,更多可查看 @typescript-eslint/parser 的仓库信息。
从背景的介绍中可以理解,对于全新的 TypeScript 项目(直接抛弃 TSLint)需要包含解析 AST 的解析器 @typescript-eslint/parser 和使用校验规则的插件 @
typescript-eslint/eslint-plugin,这里需要在项目中进行安装: npm i --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin 复制代码在根目录新建 .eslintrc.js 配置文件,并设置以下配置:
module.exports = { root: true, parser: "@typescript-eslint/parser", plugins: ["@typescript-eslint"], extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], }; 复制代码其中:
parser: @typescript-eslint/parser:使用 ESLint 解析 TypeScript 语法plugins: [@typescript-eslint]:在 ESLint 中加载插件 @typescript-eslint/eslint-plugin,该插件可用于配置 TypeScript 校验规则。extends: [ ... ]:在 ESLint 中使用共享规则配置[62],其中 eslint:recommended 是 ESLint 内置的推荐校验规则配置(也被称作最佳规则实践),plugin:@typescript-eslint/recommended 是类似于 eslint:recommended 的 TypeScript 推荐校验规则配置。温馨提示
:如果你稍微阅读一下 recommanded 源码你会发现,其实内部可以理解为推荐校验规则的集合。因此如果想基于 @
typescript-eslint/eslint-plugin 进行自定义规则,则可参考 TypeScript Supported Rules[63]。配置完成后在 package.json 中设置校验命令
"scripts": { "lint": "eslint src", } 复制代码此时如果在 src 目录下书写错误的语法,执行 npm run lint 就会输出错误信息:
> eslint src C:\Code\Git\algorithms\src\greet.ts 2:16 warning Missing return type on function @typescript-eslint/explicit-module-boundary-types ✖ 1 problem (0 errors, 1 warning) 复制代码温馨提示:输出的错误信息是通过 ESLint Formatters[64] 生成,查看 ESLint 源代码并调试可发现默认采用的是 stylish formatter[65] 。
如果不使用插件,很难发现代码可能存在 TypeScript 格式错误,因为在书写代码的时候除了手动执行 npm run lint 以外没有任何的实时提示信息(你当然也可以通过 gulp监听文件的变化并执行 npm run lint)。为了可以实时看到 TypeScript 错误信息,可以通过 VS Code 插件进行处理。安装 ESLint 插件后可进行代码的实时提示,具体如下图所示:
当然为了防止不需要被校验的文件出现校验信息,可以通过 .eslintignore 文件进行配置(例如以下都是一些不需要格式校验的配置文件):
# gulp gulpfile.js # eslint .eslintrc.js # commitizen commitlint.config.js # jest jest.config.js # build dist此时可以发现之前执行 lint 命令的错误通过插件的形式可实时在 VS Code 编辑器中进行显示。除此之外,一些 ESLint 的格式校验错误(例如多余的; 等)可通过配置 Save Auto Fix 进行保存自动格式化处理。具体 VS Code 的配置可参考 ESLint 插件[66] 的文档说明,这边应该需要进行如下配置:
"editor.codeActionsOnSave": { "source.fixAll": true, "source.fixAll.eslint": true }温馨提示:VS Code 的配置分为两种类型(用户和工作区),针对上述通用的配置主要放在用户里,针对不同项目的不同配置则可以放入工作区进行处理。
VS Code 插件并不能确保代码上传或构建前无任何错误信息,此时仍然需要额外的流程能够避免错误。在构建前进行 ESLint 校验能够确保构建时无任何错误信息,一旦 ESLint 校验不通过则不允许进行源码的构建操作:
"scripts": { "lint": "eslint src --max-warnings 0", "build": "npm run lint && rimraf dist types && gulp", }需要注意在构建时进行校验的严格控制,一旦 lint 抛出 warning 或者 error 则立马终止构建(详情可查看 ESLint 退出代码[67])。
温馨提示:需要注意 Shell 中的 && 和 & 是有差异的,&& 主要用于继发执行,只有前一个任务执行成功,才会执行下一个任务,& 主要用于并发执行,表示两个脚本同时执行。这里构建的命令需要等待 lint 命令执行通过才能进行,一旦 lint 失败那么构建命令将不再执行。
尽管可能配置了 ESLint 的校验脚本 以及 VS Code 插件,但是有些 ESLint 的规则校验是无法通过 Save Auto Fix 进行格式化修复的(例如质量规则),因此还需要一层保障能够确保代码提交之前所有的代码能够通过 ESLint 校验,这个配置将在 Lint Staged 中进行讲解。
Prettier 是一个统一代码格式风格的工具,如果你不清楚为什么需要使用 Prettier,可以查看 Why Prettier?[68]。很多人可能疑惑,ESLint 已经能够规范我们的代码风格,为什么还需要 Prettier?在 Prettier vs Linters[69] 中详细说明了两者的区别,Linters 有两种类型的规则:
格式规则(Formatting rules):例如 max-len[70]、keyword-spacing[71] 以及 no-mixed-spaces-and-tabs[72] 等质量规则(Code-quality rules):例如 no-unused-vars[73]、no-implicit-globals[74] 以及 prefer-promise-reject-errors[75] 等ESLint 的规则校验同时包含了 格式规则 和 质量规则,但是大部分情况下只有 格式规则 可以通过 --fix 或 VS Code 插件的 Sava Auto Fix 功能一键修复,而 质量规则 更多的是发现代码可能出现的 Bug 从而防止代码出错,这类规则往往需要手动修复。因此格式规则 并不是必须的,而 质量规则 则是必须的。Prettier 与 ESLint 的区别在于 Prettier 专注于统一的格式规则,从而减轻 ESLint 在格式规则上的校验,而对于质量规则 则交给专业的 ESLint 进行处理。总结一句话就是:Prettier for formatting and linters for catching bugs!(ESLint 是必须的,Prettier 是可选的!)
需要注意如果 ESLint(TSLint) 和 Prettier 配合使用时格式规则有重复且产生了冲突,那么在编辑器中使用 Sava Auto Fix 时会让你的一键格式化哭笑不得。此时应该让两者把各自注重的规则功能区分开,使用 ESLint 校验质量规则,使用 Prettier 校验格式规则,更多信息可查看 Integrating with Linters[76]。
温馨提示:在 VS Code 中使用 ESLint 匹配到相应的规则时会产生黄色波浪线以及红色文件名进行错误提醒。Prettier 更希望你对格式规则无感知,从而不会让你觉得有任何使用的负担。如果想要了解更多 Prettier,还可以查看 Prettier 的背后思想 Option Philosophy[77],个人认为了解一个产品设计的哲学能更好的指导你使用该产品。
首先安装 Prettier 所需要的依赖:
npm i prettier eslint-config-prettier --save-dev其中:
eslint-config-prettier[78]: 用于解决 ESLint 和 Prettier 配合使用时容易产生的格式规则冲突问题,其作用就是关闭 ESLint 中配置的一些格式规则,除此之外还包括关闭 @typescript-eslint/eslint-plugin、eslint-plugin-babel、eslint-plugin-react、eslint-plugin-vue、eslint-plugin-standard 等格式规则。理论上而言,在项目中开启 ESLint 的 extends 中设置的带有格式规则校验的规则集,那么就需要通过 eslint-config-prettier 插件关闭可能产生冲突的格式规则:
{ "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", ] }配置完成后,可以通过命令行接口[79]运行 Prettier:
"scripts": { "prettier": "prettier src test --write", },--write 参数类似于 ESLint 中的 --fix(在 ESLint 中使用该参数还是需要谨慎哈,建议还是使用 VS Code 的 Save Auto Fix 功能),主要用于自动修复格式错误。此时书写格式的错误代码:
import great from "@/greet"; // 中间这么多空行 export default { great, };执行 npm run prettier 进行格式修复:
PS C:\Code\Git\algorithms> npm run prettier > algorithms-utils@1.0.0 prettier C:\Code\Git\algorithms > prettier src test --write src\greet.ts 149ms src\index.ts 5ms test\greet.spec.ts 11ms修复之后的的文件格式如下:
import great from "@/greet"; export default { great, };需要注意如果某些规则集没有对应的 eslint-config-prettier 关闭配置,那么可以先通过 CLI helper tool[80] 检测是否有重复的格式规则集在生效,然后可以通过手动配置 eslintrc.js 的形式进行关闭:
PS C:\Code\Git\algorithms> npx eslint --print-config src/index.ts | npx eslint-config-prettier-check No rules that are unnecessary or conflict with Prettier were found.例如把 eslint-config-prettier 的配置去除,此时进行检查重复规则:
PS C:\Code\Git\algorithms> npx eslint --print-config src/index.ts | npx eslint-config-prettier-check The following rules are unnecessary or might conflict with Prettier: - @typescript-eslint/no-extra-semi - no-mixed-spaces-and-tabs The following rules are enabled but cannot be automatically checked. See: https://github.com/prettier/eslint-config-prettier#special-rules - no-unexpected-multiline此时假设 eslint-config-prettier 没有类似的关闭格式规则集(例如本项目中配置的 plugin:jest/recommended 可能存在规则冲突),那么可以通过配置 .eslintrc.js 的形式自己手动关闭相应冲突的格式规则。
温馨提示:ESLint 可以对不同的文件支持不同的规则校验, 因此 --print-config 只能对应单个文件的冲突格式规则检查。由于通常的项目是一套规则对应一整个项目,因此对于整个项目所有的规则只需要校验一个文件是否有格式规则冲突即可。
通过命令行接口 --write 的形式可以进行格式自动修复,但是类似 ESLint,我们更希望项目在实时编辑时可以通过保存就能自动格式化代码(鬼知道 --fix 以及 --write 格式了什么文件,当然更希望通过肉眼的形式立即感知代码的格式化变化),此时可以通过配置 VS Code 的 Prettier - Code formatter[81] 插件进行 Save Auto Fix,具体的配置查看插件文档。
和 ESLint 一样,尽管可能配置了 Prettier 的自动修复格式脚本以及 VS Code 插件,但是无法确保格式遗漏的情况,因此还需要一层保障能够确保代码提交之前能够进行 Prettier 格式化,这个配置将在 Lint Staged 中讲解,更多配置方案也可以查看 Prettier - Pre-commit Hook[82]。
在 Git Commit Message 中使用了 commitlint[83] 工具配合 husky 可以防止生成不规范的 Git Commit Message,从而阻止用户进行不规范的 Git 代码提交,其原理就是监听了 Git Hook 的执行脚本(会在特定的 Git 执行命令诸如 commit、push、merge 等触发之前或之后执行相应的脚本钩子)。Git Hook 其实是进行项目约束非常好用的工具,它的作用包括但不限于:
Git Commit Message 规范强制统一ESLint 规则统一,防止不符合规范的代码提交Prettier 自动格式化(类似的还包括 Style 样式格式等)代码稳定性提交,提交之前确保测试用例全部通过发送邮件通知CI 集成(服务端钩子)Git Hook 的钩子非常多,但是在客户端中可能常用的钩子是以下两个:
pre-commit:Git 中 pre 系列钩子允许终止即将发生的 Git 操作,而post 系列往往用作通知行为。pre-commit 钩子在键入提交信息(运行 git commit或 git cz)前运行,主要用于检查当前即将被提交的代码快照,例如提交遗漏、测试用例以及代码等。该钩子如果以非零值退出则 Git 将放弃本次提交。当然你也可以通过配置命令行参数 git commit --no-verify 绕过钩子的运行。commit-msg:该钩子在用户输入 Commit Message 后被调用,接收存有当前Commit Message 信息的临时文件路径作为唯一参数,因此可以利用该钩子来核对 Commit Meesage 信息(在 Git Commit Message 中使用了该钩子对提交信息进行了是否符合 Angular 规范的校验)。该钩子和 pre-commit 类似,一旦以非零值退出 Git 将放弃本次提交。除了上述常用的客户端钩子,还有两个常用的服务端钩子:
pre-receive:该钩子会在远程仓库接收 git push 推送的代码时执行(注意不是本地仓库),该钩子会比 pre-commit 更加有约束力(总会有这样或那样的开发人员不喜欢提交代码时所做的一堆检测,他们可能会选择绕过这些钩子)。pre-receive 钩子可用于接收代码时的强制规范校验,如果某个开发人员采用了绕过 pre-commit 钩子的方式提交了一堆 一样的代码,那么通过设置该钩子可以拒绝代码提交。当然该钩子最常用的操作还是用于检查是否有权限推送代码、非快速向前合并等。post-receive:该钩子在推送代码成功后执行,适合用于发送邮件通知或者触发 CI 。温馨提示:想了解更多 Git Hook 信息可以查看 Git Hook 官方文档[84] 或 Git 钩子:自定义你的工作流[85]。
需要注意初始化 Git 之后默认会在 .git/hooks 目录下生成所有 Git 钩子的 Shell 示例脚本,这些脚本是可以被定制化的。对于前端开发而言去更改这些示例脚本适配前端项目非常不友好(大多数前端开发同学压根不会设计 Shell 脚本,尽管这个对于制作工具是一件非常高效的事情),因此社区就出现了类似的增强工具,它们对外抛出的是简单的钩子配置(例如 ghooks[86] 在 package.json 中只需要进行简单的钩子属性配置[87]),而在内部则通过替换 Git 钩子示例脚本的形式使得外部配置的钩子可以被执行,例如 husky[88]、ghooks 以及 pre-commit[89] 等。
温馨提示:Git Hook 还可以定制脚本执行的语言环境,例如对于前端而言当然希望使用熟悉的 Node 进行脚本设计,此时可以通过在脚本文件的头部设置 #! /usr/bin/env node 将 Node 作为可执行文件的环境解释器,如果你之前看过 使用 NPM 发布和使用 CLI 工具[90] 可能会对这个环境解析器相对熟悉,这里也给出一个使用 Node 解释器的示例:ghooks - hook.template.raw[91],ghooks 的实现非常简单,感兴趣的同学可以仔细阅读一些源码的实现。
介绍 Git Hook 是为了让大家清晰的认知到使用 Hook 可以在前端的工程化项目中做很多事情(本来应该放在 Git Commit Message 中介绍相对合适,但是鉴于那个小节引用了另外一篇文章,因此将这个信息放在本小节进行科普)。
之前提到使用 Git Hook 可以进行 ESLint 规范约束,因此大家其实应该能够猜到使用 pre-commit 钩子(当然需要借助 Git Hook 增强工具,本项目中一律选择 husky)配合 ESLint 可以进行提交说明前的项目代码规则校验,但是如果项目越来越大,ESLint 校验的时间可能越来越长,这对于频繁的代码提交者而言可能是一件相对痛苦的事情,因此可以借助 lint-staged 工具(听这个工具的名字就能够猜测 lint 的是已经放入 Git Stage 暂存区中的代码,ed 在英文中表明已经做过)减少代码的检测量。
使用 commitlint[92] 工具可以防止生成不规范的 Git Commit Message,从而阻止用户进行 Git 代码提交。但是如果想要防止团队协作时开发者提交不符合 ESLint 规则的代码则可以通过 lint-staged[93] 工具来实现。lint-staged 可以在用户提交代码之前(生成 Git Commit Message 信息之前)使用 ESLint 检查 Git 暂存区中的代码信息(git add 之后的修改代码),一旦存在 一样不符合校验规则的代码,则可以终止提交行为。需要注意的是 lint-staged 不会检查项目的全量代码(全量使用 ESLint 校验对于较大的项目可能会是一个相对耗时的过程),而只会检查添加到 Git 暂存区中的代码。根据官方文档执行以下命令自动生成配置项信息:
npx mrm lint-staged需要注意默认生成的配置文件是针对 JavaScript 环境的,手动修改 package.json 中的配置信息进行 TypeScript 适配:
// 我们的哈士奇再次上场,这次它是要咬着你的 ESLint 不放了,这里我简称它的动作为 "咬 " ~~~ "husky": { "hooks": { "pre-commit": "lint-staged" } }, "lint-staged": { // 这里需要注意 ESLint 脚本的 --max-warnings 0 // 否则就算存在 warning 也不会终止提交行为 // 这里追加了 Prettier 的自动格式化,确保代码提交之前所有的格式能够修复 "*.ts": ["npm run lint", "npm run prettier"] }此时如果将要提交的代码有 , 则提交时会提示错误信息且提交会被强制终止:
husky > pre-commit (node v12.13.1) [STARTED] Preparing... [SUCCESS] Preparing... [STARTED] Running tasks... [STARTED] Running tasks for *.ts [STARTED] npm run lint-strict [FAILED] npm run lint-strict [FAILED] [FAILED] npm run lint-strict [FAILED] [SUCCESS] Running tasks... [STARTED] Applying modifications... [SKIPPED] Skipped because of errors from tasks. [STARTED] Reverting to original state because of errors... [SUCCESS] Reverting to original state because of errors... [STARTED] Cleaning up... [SUCCESS] Cleaning up... × npm run lint-strict: ESLint found too many warnings (maximum: 0). npm ERR! code ELIFECYCLE npm ERR! errno 1 npm ERR! algorithms-utils@1.0.0 lint-strict: `eslint src --max-warnings 0 "C:/Code/Git/algorithms/src/greet.ts"` npm ERR! Exit status 1 npm ERR! npm ERR! Failed at the algorithms-utils@1.0.0 lint-strict script. npm ERR! This is probably not a problem with npm. There is likely additional logging output above. npm ERR! A complete log of this run can be found in: npm ERR! C:\Users\子弈\AppData\Roaming\npm-cache\_logs\2020-07-11T07_25_39_102Z-debug.log > algorithms-utils@1.0.0 lint-strict C:\Code\Git\algorithms > eslint src --max-warnings 0 "C:/Code/Git/algorithms/src/greet.ts" C:\Code\Git\algorithms\src\greet.ts 2:16 warning Missing return type on function @typescript-eslint/explicit-module-boundary-types 2:34 warning Argument name should be typed @typescript-eslint/explicit-module-boundary-types ✖ 2 problems (0 errors, 2 warnings) husky > pre-commit hook failed (add --no-verify to bypass)husky 在 package.json 中配置了 pre-commit 和 commit-msg 两个 Git 钩子[94],优先使用 pre-commit 钩子执行 ESLint 校验,如果校验失败则终止运行。如果校验成功则会继续执行 commit-msg 校验 Git Commit Message,例如以下是 ESLint 校验通过但是 Commit Message 校验失败的例子:
PS C:\Code\Git\algorithms> git commit -m "这是一个不符合规范的 Commit Message" // pre-commit 钩子 ESLint 校验通过 husky > pre-commit (node v12.13.1) [STARTED] Preparing... [SUCCESS] Preparing... [STARTED] Running tasks... [STARTED] Running tasks for *.ts [STARTED] npm run lint-strict [SUCCESS] npm run lint-strict [SUCCESS] Running tasks for *.ts [SUCCESS] Running tasks... [STARTED] Applying modifications... [SUCCESS] Applying modifications... [STARTED] Cleaning up... [SUCCESS] Cleaning up... // commit-msg 钩子 Git Commit Message 校验失败 husky > commit-msg (node v12.13.1) ⧗ input: 这是一个不符合规范的 Commit Message ✖ subject may not be empty [subject-empty] ✖ type may not be empty [type-empty] ✖ found 2 problems, 0 warnings ⓘ Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint husky > commit-msg hook failed (add --no-verify to bypass)如果对于测试的概念和框架不是特别清楚,这里推荐一些可查看的文章:
JavaScript 程序测试[95] - 全面的测试基础知识New to front-end testing? Start from the top of the pyramid![96] - 重点可以了解一下测试金字塔和测试置信度[译] JavaScript 单元测试框架:Jasmine, Mocha, AVA, Tape 和 Jest 的比较[97] - 单元测试框架对比中文版(2018)JavaScript unit testing frameworks in 2020: A comparison[98] - 单元测试框架对比英文版(2020)除此之外,如果想了解一些额外的测试技巧,这里推荐一些社区的最佳实践:
javascript-testing-best-practices[99]ui-testing-best-practices[100]由于这里只是 Node 环境工具库包的单元测试,在对比了各个测试框架之后决定采用Jest[101] 进行单元测试:
内置断言库可实现开箱即用(从 it 到 expect, Jest 将整个工具包放在一个地方)Jest 可以可靠地并行运行测试,并且为了让加速测试进程,Jest 会优先运行之前失败的测试用例内置覆盖率报告,无需额外进行配置优秀的报错信息温馨提示:前端测试框架很多,相比简单的单元测试,e2e 测试会更复杂一些(不管是测试框架的支持以及测试用例的设计)。之前使用过 Karma 测试管理工具配合 Mocha 进行浏览器环境测试,也使用过 PhantomJS 以及 Nightwatch(使用的都是皮毛),印象最深刻的是使用 testcafe[102] 测试框架(复杂的 API 官方文档),除此之外如果还感兴趣,也可以了解一下 cypress[103] 测试框架。
本项目的单元测试主要采用了 Jest[104] 测试框架。Jest 如果需要对 TypeScript 进行支持,可以通过配合 Babel 的形式,具体可查看 Jest - Using TypeScript[105],但是采用 Babel 会产生一些限制(具体可查看 Babel 7 or TypeScript[106])。由于本项目没有采用 Babel 进行转译,并且希望能够完美支持类型检查,因此采用 ts-jest[107] 进行单元测试。按照官方教程进行依赖安装和项目初始化:
npm install --save-dev jest typescript ts-jest @types/jest npx ts-jest config:init子啊根目录的 ject.config.js 文件中进行 Jest 配置修改:
module.exports = { preset: "ts-jest", testEnvironment: "node", // 输出覆盖信息文件的目录 coverageDirectory: "./coverage/", // 覆盖信息的忽略文件模式 testPathIgnorePatterns: ["<rootDir>/node_modules/"], // 如果测试覆盖率未达到 100%,则测试失败 // 这里可用于预防代码构建和提交 coverageThreshold: { global: { branches: 100, functions: 100, lines: 100, statements: 100, }, }, // 路径映射配置,具体可查看 https://kulshekhar.github.io/ts-jest/user/config/#paths-mapping // 需要配合 TypeScript 路径映射,具体可查看:https://www.tslang.cn/docs/handbook/module-resolution.html moduleNameMapper: { "^@/(.*)$": "<rootDir>/src/$1", }, };需要注意路径映射也需要配置 tsconfig.json 中的 paths 信息,同时注意将测试代码包含到 TypeScript 的编译目录中。配置完成后在 package.json 中配置测试命令:
"scripts": { "lint": "eslint src --max-warnings 0", "test": "jest --bail --coverage", "build": "npm run lint && npm run jest && rimraf dist types && gulp", }需要注意 Jest 中的这些配置信息(更多配置信息可查看 Jest CLI Options[108]):
bail 的配置作用相对类似于 ESLint 中的 max-warnings,设置为 true 则表明一旦发现单元测试用例错误则停止运行其余测试用例,从而可以防止运行用例过多时需要一直等待用例全部运行完毕的情况。coverage 主要用于在当前根目录下生成 coverage 代码的测试覆盖率报告,该报告还可以上传 coveralls[109] 进行 Github 项目的 Badges 显示。温馨提示:Jest CLI Options 中的 findRelatedTests 可用于配合 pre-commit 钩子去运行最少量的单元测试用例,可配合 lint-staged 实现类似于 ESLint 的作用,更多细节可查看 `lint-staged - Use environment variables with linting commands`[110]。
在当前根目录的 test 目录下新建 greet.spec.ts 文件,并设计以下测试代码:
import greet from "@/greet"; describe("src/greet.ts", () => { it("name param test", () => { expect(greet("world")).toBe("Hello from world 1"); }); });温馨提示:测试文件有两种放置风格,一种是新建 test 文件夹,然后将所有的测试代码集中在 test 目录下进行管理,另外一种是在各个源码文件的同级目录下新建 __test__ 目录,进行就近测试。大部分的项目可能都会倾向于采用第一种目录结构(可以随便找一些 github 上的开源项目进行查看,这里 ts-test则是采用了第二种测试结构)。除此之外,需要注意 Jest 通过配置`testMatch`[111] 或 `testRegex`[112] 可以使得项目识别特定格式文件作为测试文件进行运行(本项目采用默认配置可识别后缀为 .spec 的文件进行单元测试)。
本篇文章未完结,请见下一篇
作者:子弈
转发链接:
https://juejin.im/post/6856410900577026061