概述
大家好,本篇文章的内容次要分为两局部:
- 开发 multi-dependent-management 工具库,解决在业务上遇到的问题
- 对于开发这个工具库时的一些总结
而 multi-dependent-management
是一个基于 NodeJS 开发的,在命令行中应用的工具库,次要用于批量治理基于 Npm 的 package.json
我的项目依赖。它能够批量对你的我的项目进行依赖降级、移除、查看差别、执行 shell 命令等操作。
首先咱们先来介绍下为什么要开发这样一个工具。
背景
我当初的公司,前端开发只有 3 人,但外部应用的管理系统和 H5 就有 27 个了(大部分都是保护状态),而这些前端利用,都是基于一套组件库去开发的:
现有的 Npm 包会有多个,当咱们呈现 Bug 或有新性能迭代时,要同时更新多个零碎并对立公布上线(因为是对内应用的,而且技术治理比拟松,测试没问题就能够在某个时间段上线),这时候就有上面的流程:
不需更新业务代码,只更新依赖版本,就是下面的流程。
如果同时有多个零碎要更新,这里的操作就很麻烦。有一次因为用户模块出了问题,所有应用该模块的零碎(二十多个)都要更新,那时候十分苦楚。
因为我的项目保护都集中在一两个人,所以我的项目都会在咱们电脑本地。这时候我就想,有没有工具能够批量更新多个我的项目的依赖,而后间接 git commit
提交到 gitlab 上?(公共模块要迭代,比方侧边栏、导航栏、页面初始化等操作,都曾经封装好了,所以大部分时候咱们的组件库更新都是只需更新版本号)
这种操作,有点像 npkill,它会扫描的指标门路,让你抉择对应含有 node_modules
的文件夹,进行抉择删除。
还有 npm-check-updates 能够帮你进行依赖查看并更新的操作。
我本人搜寻了下,没找到能够集成下面我所说的内容的工具库,所以就打算本人搞一个,性能如下:
- 批量依赖降级
- 批量依赖移除
- 批量依赖变更
- 批量执行 shell 命令
- 查看我的项目依赖版本差别
这样就能够解决我下面所说的需要,通过执行命令,帮我把反复相似的工作解决掉。
应用
具体的应用教程在 github 仓库 有具体阐明了,这里次要分享下如何疾速应用,并应用该工具疾速解决下面的问题。
这个工具库是用 NodeJS 进行开发的,在命令行执行操作,和咱们平时应用的一些命令相似,比方:vue create test
。
整个工具库的操作流程,根本如下:
依照下面背景所说的需要,咱们须要通过命令行批量更新依赖版本,并提交到 gitlab。
首先装置工具库:
# 全局装置
npm i multi-dependent-management -g
解决形式一
假如我要批改的我的项目都在 ./demo
上面。
首先进行依赖更新:
# 全局装置完依赖后,应用 mdm 简写去应用
mdm upgrade -p ./demo
mdm
就是multi-dependent-management
这个库的简写upgrade
是这个库能够触发的动作-p ./demo
是一个参数,通知这个工具库要从哪个门路进行查问
首先会递归查问该门路下所有的 package.json
文件,而后应用 npm-check-updates 查看每个我的项目的依赖版本是否最新,将能够更新的依赖一一展示进去,让你抉择哪个依赖须要更新:
当咱们抉择要更新的依赖后,就会通过 fs
间接批改文件的版本号,而不会装置依赖。
接着就要把批改记录提交到 gitlab,这时候用到的是 shell
命令:
mdm shell -p ./project
会依据你选中的我的项目,执行相干的脚本命令,该性能自由度比拟高,能够搭配不同的操作。
选完要解决的我的项目后,须要先输出独特执行的命令,没有的话,不输出保留就行了:
咱们这里输出了 git
提交的命令。
二次确认后,执行后果:
胜利将多个我的项目提交到 gitlab
。
解决形式二
形式一别离用了两个命令去操作:upgrade
+ shell
。但其实咱们能够间接 shell
一次性实现。
同样假如咱们的我的项目在 ./demo
下,当初须要更新 vue
的版本,并提交到 gitlab
。
mdm shell -p ./demo
将依赖降级的命令,也放到 shell
去操作:
装置完依赖后,接着就提交代码到 gitlab:
总结
下面两种形式都能够解决我在“背景”所说的问题,具体应用哪种,看你的需要,应用 upgrade
命令,会主动帮你查找每个依赖能够降级的版本,而 shell
是纯手动模式,让你齐全管制要降级的依赖版本。
除去这两个性能,multi-dependent-management
工具库还有其余的性能,具体的应用大家能够去 github
或者 npm
查看。
对于开发
技术栈
该工具的开发,应用的技术栈:
-
无关命令行操作的工具
- commander
- enquirer(命令行交互)
- ora
- shelljs
- npm-check-updates(查看依赖版本是否须要降级)
-
单元测试
- jest
- memfs(应用内存模仿 fs)
-
工具库
- lodash
- just-diff
- semver
-
其余
- typescript
- commitlint
- husky
- lint-staged
- standard-version
- eslint
整个开发,就是下面所展现的库,像单测、工具库、husky、commitlint 这些都是很罕用的,这里就不一一开展。
开发工作流
应用 eslint
标准代码款式,jest
做单元测试,husky
+ lint-staged
+ git hook
进行相干命令操作。
下图就是我在开发这个工具库时,执行的流程:
上面咱们从零开始,实现下面的工作流程配置,如果嫌麻烦,我这里曾经依照上面的流程,配好了一个现成的模板。
筹备工作
整个配置,大略须要 10 – 15 分钟左右。
咱们首先要建一个我的项目,应用 typescript
进行开发。
mkdir test && cd test # 新建文件
npm init # npm 初始化
git init # 初始化 git
mkdir lib && mkdir tests # 增加文件夹
npm i typescript -S
# 增加疏忽文件:node_modules coverage dist
vim .gitignore
增加文件:
vim lib/a.ts
export function getName() {return 'ok'}
export function getData() {
return {name: getName()
}
}
vim lib/index.ts
import {getData} from './a'
console.log(getData())
因为咱们用的是 typescript
,所以须要先编译能力用 node.js
执行
在 package.json
增加脚本命令:
{
"scripts": {
"tsc": "tsc",
"start": "npm run tsc && node ./dist/index.js"
}
}
tsc
命令是用来编译 .ts
文件,变为 .js
。而后应用 node
执行相干文件。
增加 tsconfig.json
,通知 typscript
要如何进行编译。
vim tsconfig.json
:
{
"compilerOptions": {
"module": "commonjs",
"noImplicitAny": true,
"removeComments": true,
"preserveConstEnums": true,
"outDir": "./dist",
"declaration": true,
},
}
这里次要说下 outDir
和 declaration
。当你执行 tsc
时,会将转译文件放到指定目录,而 declaration
会生成 .d.ts
文件。
配置实现后,咱们执行下命令:npm start
,会有上面的日志显示:
npm start
> test-ddd@1.0.0 start ~/Downloads/test-ddd
> npm run tsc && node ./dist/index.js
> test-ddd@1.0.0 tsc ~/Downloads/test-ddd
> tsc
{name: 'ok'}
看到日志胜利打印,咱们的筹备工作实现了,目录构造是这样的:
.
├── lib
│ ├── a.ts
│ └── index.ts
├── package-lock.json
├── package.json
├── tests
└── tsconfig.json
上面就开始配置开发工作流。
配置 husky
husky 是按官网教程来的,这里用的版本是 7.x,要留神版本号,很多以前的教程是在 package.json
配置,那个是要用 4.x 版本才行。
# 先保障以后我的项目有 .git 文件
# 初始化并装置
npx husky-init && npm install
# 这时候,我的项目根目录会生成一个 .husky 文件,外面蕴含了一个钩子文件:pre-commit
批改 .husky/pre-commit
文件,将外面的 npm test
改为 npm run lint-staged
,前面会用到。
配置 lint-staged
npm i lint-staged -D
在 package.json
增加相干配置:
{
"scripts": {
"lint-staged": "lint-staged",
"lint": "eslint --fix lib/**",
"test:unit": "jest"
},
"lint-staged": {"{lib,tests}/**/*": [
"npm run lint",
"npm run test:unit",
"git add"
]
},
}
这里咱们咱们配置了 lint-staged
和 3 个脚本命令。lint
和 test:unit
是执行 eslint
和 jest
用的,上面咱们持续配置这两个工具。
配置 eslint
装置:
npm i eslint -D
# 初始化
./node_modules/.bin/eslint --init
# 按着批示进行配置即可
# 这是我抉择的配置:✔ How would you like to use ESLint? · style
✔ What type of modules does your project use? · esm
✔ Which framework does your project use? · none
✔ Does your project use TypeScript? · No / Yes
✔ Where does your code run? · browser, node
✔ How would you like to define a style for your project? · guide
✔ Which style guide do you want to follow? · standard
✔ What format do you want your config file to be in? · JavaScript
Checking peerDependencies of eslint-config-standard@latest
The config that you've selected requires the following dependencies:
@typescript-eslint/eslint-plugin@latest eslint-config-standard@latest eslint@^7.12.1 eslint-plugin-import@^2.22.1 eslint-plugin-node@^11.1.0 eslint-plugin-promise@^4.2.1 || ^5.0.0 @typescript-eslint/parser@latest
✔ Would you like to install them now with npm? · No / Yes
增加 .eslintignore
疏忽不必要的文件,vim .eslintignore
:
package.json
package-lock.json
配置实现后,咱们看下相干命令: "lint": "eslint --fix lib/**”
这里是指定要 fix
的文件门路,依据你的我的项目进行相干变动即可。
最初能够试下执行 npm run lint
看看是否胜利。
配置 jest
npm i jest -D
# 初始化配置
./node_modules/.bin/jest --init
# 依照你本身的需要进行配置即可
配置 babel
和 typescript
:
npm i babel-jest @babel/core @babel/preset-env @babel/preset-typescript ts-node @types/jest @types/node -D
批改 tsconfig.json
:
{
...,
// 增加:"compilerOptions": {
...,
"types": [
"jest",
"node"
]
}
}
增加 babel.config.js
文件:
// babel.config.js
module.exports = {
presets: [['@babel/preset-env', {targets: {node: 'current'}}],
'@babel/preset-typescript',
],
};
咱们增加一个测试文件,验证下是否胜利:
vim tests/a.spec.ts
:
import {getName} from '../lib/a'
describe('测试 getName', () => {test('执行 getName,返回字符串"ok" ', () => {expect(getName()).toBe('ok')
})
})
批改在 package.json
的 lint
命令:"lint": "eslint --fix lib/** tests/**”
,增加对 tests
文件的查看。
咱们执行之前增加的脚本命令:npm run test:unit
npm run test:unit
> test-ddd@1.0.0 test:unit ~/Downloads/test-ddd
> jest
PASS tests/a.spec.ts
测试 getName
✓ 执行 getName,返回字符串 "ok" (2 ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 50 | 100 | 50 | 50 |
a.ts | 50 | 100 | 50 | 50 | 5
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 0.914 s
Ran all test suites.
看到执行胜利了,jest
的配置也实现了。
试验
筹备工作都筹备好,咱们来提交下代码试试:
git add ./
git commit -m 'test'
这时候会看到触发钩子,使 lint-staged
开始工作:
lint-staged
⚠ Skipping backup because there’s no initial commit yet.
⚠ Some of your tasks use `git add` command. Please remove it from the config since all modifications made by tasks will be automatically added to the git commit index.
✔ Preparing...
⚠ Running tasks...
❯ Running tasks for {lib,tests}/**/*
✖ npm run lint [FAILED]
◼ npm run test:unit
◼ git add
✔ Applying modifications...
✖ npm run lint:
...
~/Downloads/test-ddd/tests/a.spec.ts
3:1 error 'describe' is not defined no-undef
4:3 error 'test' is not defined no-undef
5:5 error 'expect' is not defined no-undef
有个文件的代码格局没通过,所以整个 commit
操作被拦挡下来,无奈胜利 commit
。同理,如果 lint
命令通过,但 test:unit
命令没通过,也是会被拦挡下来。
咱们来修复下这个问题:
在 ./eslintrc.js
增加上面的配置:
module.exports = {
env: {jest: true,},
}
再次 commit
后,提交胜利,eslint
和单测都胜利。
git commit -m 'test'
> test-ddd@1.0.0 lint-staged ~/Downloads/test-ddd
> lint-staged
⚠ Skipping backup because there’s no initial commit yet.
⚠ Some of your tasks use `git add` command. Please remove it from the config since all modifications made by tasks will be automatically added to the git commit index.
✔ Preparing...
✔ Running tasks...
✔ Applying modifications...
[master (root-commit) 658c2dd] test
12 files changed, 6339 insertions(+)
.....
到这里,曾经实现咱们的配置。整个工程配置,能够当成一个工具库模板,前面有新的工具开发,间接应用该模板,疾速搭建根底性能。
单元测试
这个工具库我是有写单元测试,因为写得不是很多,只能依据覆盖率去写,哪里没有笼罩到,就补用例,一些重点的环节,就尽量测试不同的状况。
整个单测过程,我想总结下两个点:
- 应用
memfs
这个库是 mockfs
- 应用
Jest
的spyOn
办法,mock 模块内的某个函数
这两个 Mock,是我在写单测中,常常要用到的。
应用 memfs
mock fs
因为 fs
是属于 io
操作,而且工具办法波及到对文件的操作,如果不 mock fs
,须要写一些重置办法,重置用于单测的文件。
或者还能够间接 mock 应用了 fs
的办法,但这个我感觉十分麻烦,因为有大量的测试用例须要用到,并且用到的场景有些会不同,所以这个办法我也没采纳。
最初我是看到这篇文章 Testing filesystem in Node.js: The easy way 后,晓得了 memfs
这个库,应用内存模式去模仿 fs
。集体体验十分好,只需简略的配置,就能够解决 fs
mock 问题,并且还能自定义文件目录和内容。
首先增加文件:
tests/__mocks__/fs.ts
文件内容:
import {fs} from 'memfs';
export default fs;
应用:
const packageJson = {...}
describe('test', () => {beforeEach(() => {
// 每次执行用例钱,重置内容
vol.reset();
// 设置门路、目录和相干文件的内容
vol.fromNestedJSON({
p1: {'package.json': JSON.stringify(packageJson),
},
p2: {'package.json': JSON.stringify(packageJson),
},
}, '/abc');
});
describe('test...', () => {it('获取内容', async () => {
// 应用 fs(曾经 mock 解决了)获取对应门路的文件内容
const data = JSON.parse(fs.readFileSync('/abc/p1/package.json', { encoding: 'utf-8'}));
// 判断获取的文件内容是否和开始配置的数据统一
expect(data).toBe(packageJson); // pass
});
});
能够看到配置过程非常简单,而应用成果和 fs
没什么区别。
Mock 模块内的某个函数
咱们看下要测试的这个办法:
// 伪代码
import {getConfirmPrompt,} from './utils';
import * as upgradeUtils from './upgrade';
export async function upgrade(paths: string[]): Promise<void> {await upgradeUtils.getMultiSelectProject(paths);
await.getConfirmPrompt().run();
}
这里只展现了关键点,咱们要 mock 下面的两个函数:
getConfirmPrompt
函数是另一个文件引入的getMultiSelectProject
函数是同一个文件的
要 mock getConfirmPrompt
函数很简略,间接应用 spyOn
就行了:
// 伪代码
import * as utils from '../lib/utils';
describe('test', () => {test('upgrade.js', () => {jest.spyOn(utils, 'getConfirmPrompt').mockImplementation(() => ([{ ...}, {...}
]));
})
})
另一个要 mock 的函数是 getMultiSelectProject
,它是和 upgrade
办法在同一个文件,这里的解决办法有点绕。
首先在 该函数 的文件,增加这样一行代码:
import * as upgradeUtils from './upgrade';
须要 mock 的函数,要这样调用:
upgradeUtils.getMultiSelectProject()
接着在测试文件,同样也是要先引入:
// 伪代码
import * as upgradeUtils from '../lib/upgrade';
describe('test', () => {test('upgrade.js', () => {
// 应用 jest.spyOn 去 mock 函数
// 首先传入该函数的模块,第二个参数是你要 mock 的办法名
// 再应用 mockImplementation 返回你要 mock 的值
jest.spyOn(upgradeUtils, 'getMultiSelectProject').mockImplementation(() => ([{ ...}, {...}
]));
})
})
再应用 spyOn
和 mockImplementation
对 getMultiSelectProject
函数 mock 就行了。
这里我还没搞懂为什么要这样解决,前面再演绎下不同状况的 mock 形式。
总结
这次分享的内容,次要是如何应用 multi-dependent-management
这工具去解决在开发遇到的问题,并总结在开发这个工具时的性能。尽管平时有做一些小工具的开发,利用到工作上,但都很少进行这样的总结,所以这次尝试下,锤炼本人的总结能力和表达能力。
以上就是本文章的全部内容了,如果有不正确的中央,感激斧正~
画图工具:miro
录屏工具:kap
multi-dependent-management 工具仓库地址
参考链接
- Testing filesystem in Node.js: The easy way
- typescript 疾速开始
- husky 文档
- eslint
- jest