通过本文你可以学习到
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
全局安装后即可使用