Nodejs实战:批量更新npm包

通过本文你可以学习到

1.npm update的简单使用

2.npm-check-updates包的使用

3.github管理代码简单使用

4.fs模块相关api使用

5.nodejs下操作console控制台显示

6.child_process多进程简单使用

7.console.time和console.timeEnd记录代码运行时间

8.如何发布一个npm包

9.制作一个支持cli功能的npm包

【前言】

由于有封装代码的习惯,所以发布了很多npm库,见npm

每一个npm库对应一个git代码仓库,问题就来了,如果想更新每一个npm库,比较繁琐,需要手动遍历每一个代码库进行更新,想解决一下这个问题

繁多的npm库:

【npm update】

说到更新npm库,具体来说是更新某一个npm库下依赖的npm包,可能第一时间会想到npm update这个命令,做一下试验,建一个新项目,依赖两个npm包,比如express和axios,

在一个空项目下,npm i这两个包,并且指定了比较旧的版本

到这个目录下执行npm update看下效果,发现只是更新了node_modules下的npm包,但是package.json中的依赖没有更新,这个并不是预期的效果

预期的效果大概是

1.可以检测目前依赖的npm包们是否有最新版本

2.可以一次性更新依赖的npm包们到最新版本,并修改package.json的内容

这时就需要这个npm包了:npm-check-updates - npm

具体用法就不赘述了,可以看文档或者搜索一些文章

【目标】

有了这个npm包后,可以达到更新依赖npm包们的目的,但本文的目标是

1.一个文件夹下有n个代码仓库

2.每个代码仓库下依赖n个npm包

3.期望把这个文件夹下所有的代码仓库依赖的npm包都更新一遍

如果手动操作大概类似这样,进入每一个代码仓库,执行ncu -u npm i

【思路】

有了目标后,就是具体实现了,大概如下

1.获取目标文件夹下所有的子文件夹数组

2.遍历这个子文件夹数组

3.在每一个文件夹下执行ncu -u

4.执行npm i

5.控制台打印对应的信息

6.最好可以多进程执行以上操作

【github】

开始写代码发现没有版本控制有些不习惯,将代码放到github上做一下版本控制,

添加了这些信息,mit协议,readme文档,lib源码文件夹,test测试文件夹等

【获取子文件夹数组】

1.判断目标文件夹是否存在

// is exists
function isExists(dir){
	try{
		fs.accessSync(dir);
		return true;
	}catch(e){
		return false;
	}
}

2.获取子文件夹数组

var fs = require('fs');

// vars
var subFolders = [];

/**
 * mult ncu
 */
exports.multiNCU = function(folderName){
	// check folder name
	if(!folderName) return 'need folder name';

	// check folder name is folder
	var isExist = isExists(folderName);
	if(!isExist) return 'folder is not exists';

	// get sub folders
	lsdir(folderName);
	console.log(subFolders);
};

// is exists
function isExists(dir){
	try{
		fs.accessSync(dir);
		return true;
	}catch(e){
		return false;
	}
}

// ls dir
function lsdir(dir){
	fs.readdirSync(dir).forEach(function(name){
		var stat = fs.statSync(dir + name);
		if(!stat.isDirectory()) return;

		subFolders.push(dir + name);
	});
}

【文件操作代码处理】

将文件文件夹相关操作抽离一个单独的文件util/fs.js,修改相关引用

1.抽离后的fs.js

'use strict';

// fs
var fs = require('fs');

// sub folders
exports.subFolders = [];

/**
 * is exists
 * @param {string} dir 
 */
exports.isExists = function(dir){
	try{
		fs.accessSync(dir);
		return true;
	}catch(e){
		return false;
	}
};

/**
 * ls dir
 * @param {string} dir 
 */
exports.lsdir = function(dir){
	fs.readdirSync(dir).forEach(function(name){
		var stat = fs.statSync(dir + name);
		if(!stat.isDirectory()) return;

		exports.subFolders.push(dir + name);
	});
};

2.抽离后的multi-ncu.js

/**
 * multi-ncu
 *  1.判断输入的目标文件夹是否存在且是文件夹
 * 	2.获取目标文件夹下的子文件夹数组
 * 	3.遍历该数组
 * 	4.每个子文件夹下执行ncu -u
 * 	5.每个子文件夹下执行npm i
 * 	6.以上要在控制台及时显示回馈
 * 	7.最好是多进程执行以上操作
 */

// fs
var fs = require('./util/fs.js');

/**
 * mult ncu
 */
exports.multiNCU = function(folderName){
	// check folder name
	if(!folderName) return 'need folder name';

	// check folder name is folder
	var isExist = fs.isExists(folderName);
	if(!isExist) return 'folder is not exists';

	// get sub folders
	fs.lsdir(folderName);
	console.log(fs.subFolders);
};

【遍历进行ncu】

0.文件地址拼接

由于ncu这个包需要传入package.json的path,而目前获取到的是代码库的文件夹地址,要做一个拼接,

比较简单,使用path.resolve即可,封装到fs.js中

// path
var path = require('path');

/**
 * resolve
 * @param  {...string} paths 
 */
exports.resolve = function(...paths){
	return path.resolve(...paths);
};

1.遍历子文件夹执行ncu

/**
 * multi-ncu
 *  1.判断输入的目标文件夹是否存在且是文件夹
 * 	2.获取目标文件夹下的子文件夹数组
 * 	3.遍历该数组
 * 	4.每个子文件夹下执行ncu -u
 * 	5.每个子文件夹下执行npm i
 * 	6.以上要在控制台及时显示回馈
 * 	7.最好是多进程执行以上操作
 */

// fs
var fs = require('./util/fs.js');

// ncu
var ncu = require('npm-check-updates');

/**
 * mult ncu
 */
exports.multiNCU = function(folderName){
	// check folder name
	if(!folderName) return 'need folder name';

	// check folder name is folder
	var isExist = fs.isExists(folderName);
	if(!isExist) return 'folder is not exists';

	// get sub folders
	fs.lsdir(folderName);
	if(!fs.subFolders || !fs.subFolders.length) return 'empty folder';

	// ncu
	var subFolders = fs.subFolders;
	for(var i=0; i<subFolders.length; i++){
		var item = subFolders[i];
		ncuSubFolders(item);
	}
};

// ncu
async function ncuSubFolders(dir){
	var packageFile = fs.resolve(dir, 'package.json');
	if(!fs.isExists(packageFile)) return 'package.json not exists';

	var upgraded = await ncu.run({
		packageFile: packageFile,
		upgrade: false
	});
	
	console.log(upgraded);
}

【抽离ncu相关代码】

将ncu相关代码抽离到util/ncu.js,并修改相关文件的引用

'use strict';

// fs
var fs = require('./fs.js');

// ncu
var ncu = require('npm-check-updates');

/**
 * ncu sub folders
 * @param {string} dir 
 */
exports.ncuSubFolders = async function(dir){
    var packageFile = fs.resolve(dir, 'package.json');
    if(!fs.isExists(packageFile)) return 'package.json not exists';
    
    var upgraded = await ncu.run({
        packageFile: packageFile,
        upgrade: false
    });
    
    console.log(upgraded);
};

目前的效果,演示下:

【优化控制台显示】

目前控制台显示比较简单,做下优化

1.显示当前代码库的路径

2.显示当前执行的步骤

代码较多,主要用了一个操作console的库,详见:qiao.util.console:nodejs下清空控制台

在看下效果:

【多进程】

目前的效果已经基本实现功能了,但是每个子文件夹的操作是串行的,其实相互之间没有依赖,修改为并行后速度会倍增,添加个多进程实现效果

0.qiao.util.process

基于child_process做了一些简单的封装,比如fork子进程,父子进程之间的通信,有兴趣的可以看下:qiao.util.process - npm

1.对每一个子文件夹fork一个子进程

没有用进程池之类的,简单粗暴的这么做了,如下

'use strict';

// fs
var fs = require('./fs.js');

// q
var q = {};
q.console = require('qiao.util.console');
q.process = require('qiao.util.process');

// line
var line = 2;

/**
 * mult ncu
 */
exports.multiNCU = async function(folderName){
	// clear
	q.console.clear();

	// start
	q.console.writeLine(0, `start operating folder: ${folderName}`);
	var res = await handlerFolder(folderName);

	// check
	if(res) q.console.writeLine(line, res);
};

// handler folder
async function handlerFolder(folderName){
	// check folder name
	if(!folderName) return 'need folder name';

	// check folder name is folder
	var isExist = fs.isExists(folderName);
	if(!isExist) return 'folder is not exists';

	// get sub folders
	fs.lsdir(folderName);
	if(!fs.subFolders || !fs.subFolders.length) return 'empty folder';

	// ncu
	var subFolders = fs.subFolders;
	for(var i=0; i<subFolders.length; i++){
		(function(item, i, l){
            handlerIt(item, i, l);
        })(subFolders[i], i, subFolders.length);
	}

	return;
}

// handler it
function handlerIt(item, i, l){
	var jsPath = fs.resolve(__dirname, './handler-fork.js');
	var args = [item];
	q.process.fork(jsPath, args, function(msg){
		q.console.writeLine(line + i, msg);
	}, function(){
		if(++i == l){
			q.console.writeLine(line + l, '');
	
			q.console.writeLine(line + l + 1, 'multi update npm packages end');
		}
	});
}

2.子进程执行ncu相关操作,并传回执行结果

'use strict';

// ncu
var ncu = require('./ncu.js');

// q
var q = {};
q.process = require('qiao.util.process');

// handler folder
async function handlerFolder(folderName){
	var folderName = process && process.argv && process.argv.length > 2 && process.argv[2];
	if(!folderName){
        q.process.send('need folder name');
        return;
	}

	q.process.send(`${folderName} ...`);
	
	var res 	= await ncu.ncuSubFolders(folderName);
	var json	= getJson(res);
	var str		= `${folderName} : ${json}`;

	q.process.send(str);
}

// get json
function getJson(s){
	try{
		return JSON.stringify(s);
	}catch(e){
		return s;
	}
}

handlerFolder();

添加多进程之后的效果,加快了不少:

【数据】

从感官上来看,多进程版本的执行速度好像还没有之前最初的版本快,到底是快慢需要一些数据,

先看下最初版本的效果

1.使用console.time和console.timeEnd计算一下两个版本的时间

多进程版本,执行时间计算:

实际执行时间:9.72s

非多进程版本,执行时间计算:

实际执行时间:38s

【几种方案对比】

1.单进程并行

方法:使用for执行async函数,且没有使用await

效果:并行执行,没有按数组内的顺序执行

结论:适合对顺序无要求的场景,应该是最快的一种方案,用时间戳算了下时间大概是7-8s

代码:

	for(var i=0; i<subFolders.length; i++){
		var item = subFolders[i];

		// async func
		ncu.ncuSubFolders(item);
	}

2.单进程串行

方法:使用for of + await async函数

效果:串行执行,按数组内的顺序执行

结论:最慢的一种方案,优点是比较简单,且保序,缺点是比较慢,适合对执行时间没有要求,但对顺序有要求的需求,执行时间38s

代码:

    for(var item of subFolders){
		await ncu.ncuSubFolders(item);
	}

3.多进程并行

方法:使用for + 多进程

效果:多进程,并行执行

结论:速度也比较快,问题是实现比较复杂,代码详见上述【多进程】,执行时间9.3s

【相对路径】

原来的代码扫描的路径是写死的,如下

var dir = '/Users/vincent/Data/qiao/';
m.multiNCU(dir);

现在期望添加一个相对路径,和代码执行目录相对的路径,修改后如下

主要的改动点是通过process.cwd()获取目前代码的执行路径,如下

带动后,入参即支持相对路径,也支持绝对路径了

【发布npm】

一般当一个功能趋于完善的时候就可以发布一个npm包给其他人使用了,

参考之前的一篇文章:手把手教你玩转npm包_uikoo9的博客-CSDN博客_npm包

将目前实现的功能发布为npm包mult-ncu:multi-ncu - npm

【添加cli功能】

本文展示的功能更多的是在控制台上使用,所以发布npm包后还需要添加下cli功能,

添加cli文章展位:todo

全局安装后即可使用

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

uikoo9

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值