这部分主要讲述了在开发类似于CLI命令中遇到的问题,是一些很值得关注的问题。如果喜欢记得star,如果有任意问题也欢迎issue。
bin目录下的文件名称和package.json中的bin配置的文件名称是配套的,如果bin下的文件有后缀那么package.json中也必须有后缀:
"bin": {
"extract": "./bin/extract.js"
}
const glob = require("glob");
const files = glob
.sync("**", { dot: false,cwd, ignore: "node_modules/**" })
.filter(file => {
return path.extname(file) === ".js" || path.extname(file) === ".jsx";
})
.reduce((prev, cur) => {
return prev.concat(path.join(cwd, cur));
}, []);
比如上面的例子就是抓取所有的js和jsx后缀文件,但是忽略node_modules下的额外文件。需要注意的是ignore的配置。
const chokidar = require("chokidar");
const watchDir = 'xxxx';
// 需要监听的目录
let watcher = chokidar
.watch(watchDir, {
persistent: true,
disableGlobbing: true,
ignoreInitial: true,
// 1.忽略一开始就有的某一个目录下的文件夹
depth: 0
})
.on("addDir", genPath => {
// 2.监听到文件被生成,即hub生成的文件
const relativePath = path.relative(genPath, watchDir);
const index = genPath.indexOf(watchDir);
let genHubPath = genPath.substring(index + watchDir.length + 1);
// 3.获取产生的目录名称
if (relativePath == "..") {
// 4.表示是动态产生的目录,此时获取到产生的目录可以操作了
}
});
const path2Cnpt = '';
// 需要解析的某一个组件的地址
const fakeProps = require("react-fake-props");
const props = fakeProps(path2Cnpt, { optional: true });
// optional: true表示非required的属性也要动态产生
// 产生了props就可以动态构建jsx的template了
下面通过动态产生的props构建jsx的template:
//cntName:组件名称
//props:动态产生的组件props
function generateTestTemplate(cntName, props) {
const template = `
import React from 'react';
import ReactDOM from 'react-dom';
import ${cntName} from './index';
const props = ${JSON.stringify(props)};
class Test extends React.Component {
render() {
return <${cntName} {...props}\/>;
}
}
ReactDOM.render(<Test />, document.getElementById('app'));
`;
return template;
}
有一点需要注意:那就是react-fake-props产生的props是对象,即object,所以动态产生template的时候需要stringify一下:
const props = ${JSON.stringify(props)};
const path = require('path');
const sourcePath = require.resolve(
path.resolve(`${cwd}`, relative[r])
);
let fileName = path.basename(sourcePath);
//通过文件名称引入的,比如require('./util/test')表示util下的test.js
if (fileName != "index.js") {
const arr = destPath.split(path.sep);
arr.pop();
destPath = arr.join(path.sep);
// 去掉文件名就是它的目录
}
如果要兼容低版本可以使用npm上的resolve模块来完成。
很多情况下需要在自己的cli命令中包装别人的cli命令,而别人的cli命令是yoeman开发的,需要支持用户的输入,此时你可能想到的是使用shelljs,但实际上官方明确说了不支持此类用法,可以采用如下的方式来完成。
const child_process = require("child_process");
// 假如原有的cli命令,即包装命令是:et init liangklfangl
child_process.execFileSync("et", ["init", "liangklfangl"], {
stdio: "inherit"
});
const pathRegex = /^\./g;
const isRelative = pathRegex.test(importSource);
// 第一种方法判断是否是相对路径,但是有时候会出现很奇怪的结果,所以我都使用如下的方式
const isRelative = path2Detect.indexOf(".") == 0;
const child_process = require("child_process");
const exec = child_process.exec;
/**
* 执行shell脚本
*/
function execute(cmd, workingDirPath) {
return new Promise((resolve, reject) => {
try {
let child = exec(
cmd,
{ cwd: workingDirPath },
(error, stdout, stderr) => {
if (error) {
reject(error);
} else {
resolve(stdout);
}
}
);
} catch (e) {
reject(e);
}
});
}
/**
* npm配置
*/
function getNpmConfigVariable(varName, workingDirPath) {
return execute(`tnpm get ${varName}`, workingDirPath).catch(err => {
console.error(err);
return undefined;
});
}
/**
* 设置npm配置
*/
function setNpmConfigVariable(varName, varValue, workingDirPath) {
return execute(
`tnpm set ${varName}=${varValue}`,
workingDirPath
).catch(err => {
console.error(err);
return undefined;
});
}
/**
* 安装特定模块
*/
const installPackages = function(pkgNames, workingDirPath) {
let oldProgress;
return getNpmConfigVariable("progress", workingDirPath)
.then(result => {
oldProgress = result;
return setNpmConfigVariable("progress", "false", workingDirPath);
})
.then(() => {
log(chalk.green(`准备开始安装${pkgNames}依赖`));
return execute(`tnpm install ${pkgNames} -S -E`, workingDirPath);
})
.then(() => {
log(chalk.green(`依赖的${pkgNames}模块安装完成!`));
return setNpmConfigVariable("progress", oldProgress, workingDirPath);
});
};
以上方法来自于structor-commons 。
var os;
os = require('os');
exports.execute = function(command, callback) {
var commander;
if (os.platform() === 'win32') {
commander = require('./windows');
} else {
commander = require('./unix');
}
return commander.executeWithPrivileges(command, callback);
};
具体你可以点击这里。
以下所有内容来自于selfupdate模块。
var async, npm, utils, _;
npm = require('npm');
async = require('async');
_ = require('lodash-contrib');
utils = require('./utils');
/**
* 得到一个npm包的信息
*/
exports.getInfo = function(name, callback) {
// https://github.com/liangklfangl/react-article-bucket/tree/master/async-programing/async-js#43-asyncify%E7%94%A8%E4%BA%8Eparallel%E6%96%B9%E6%B3%95
return async.waterfall([
function(callback) {
var options;
options = {
loglevel: 'silent',
global: true
};
return npm.load(options, _.unary(callback));
// unary:Creates a function that accepts up to one argument, ignoring any additional arguments.
}, function(callback) {
//npm view命令
return npm.commands.view([name], true, callback);
}
], callback);
};
/**
* 得到npm包的最新版本
*/
exports.getLatestVersion = function(name, callback) {
return exports.getInfo(name, function(error, data) {
var versions;
if (error != null) {
return callback(error);
}
versions = _.keys(data);
return callback(null, _.first(versions));
});
};
/**
* 判断某一个包是否在最新的V版本上
*/
exports.isUpdated = function(packageJSON, callback) {
return exports.getLatestVersion(packageJSON.name, function(error, latestVersion) {
if (error != null) {
return callback(error);
}
return callback(null, packageJSON.version === latestVersion);
});
};
/**
* 更新一个npm包
*/
exports.update = function(packageJSON, callback) {
return async.waterfall([
function(callback) {
var command;
command = utils.getUpdateCommand(packageJSON.name);
return utils.runCommand(command, callback);
}, function(stdout, stderr, callback) {
if (!_.isEmpty(stderr)) {
return callback(new Error(stderr));
}
return exports.getLatestVersion(packageJSON.name, callback);
}
], callback);
};
下面是utils模块提供的方法包括getUpdateCommand/runCommand:
var child_process, president, _;
president = require('president');
_ = require('lodash-contrib');
child_process = require('child_process');
/**
* 得到更新的命令
*/
exports.getUpdateCommand = function(name) {
if (name == null) {
throw new Error('Missing package');
}
return "npm install --silent --global " + name;
};
/**
* 判断一个错误是否是权限相关的
*/
exports.isPermissionError = function(error) {
if (error == null) {
return false;
}
if (error.message == null) {
error.message = '';
}
return _.any([error.code === 3, error.errno === 3, error.code === 'EPERM', error.code === 'EACCES', error.code === 'ACCES', error.message.indexOf('EACCES') !== -1]);
};
/**
* 执行某一个cli命令,如果执行失败就提升为sudo权限
*/
exports.runCommand = function(command, callback) {
return child_process.exec(command, function(error, stdout, stderr) {
if (_.any([exports.isPermissionError(error), exports.isPermissionError(new Error(stderr))])) {
return president.execute(command, callback);
}
return callback(error, stdout, stderr);
});
};
那么selfupdate模块如何使用呢?下面来自于zan-proxy:
import childProcess from 'child_process';
import { promisify } from 'es6-promisify';
import { prompt } from 'inquirer';
import ora from 'ora';
// https://github.com/sindresorhus/ora
import selfupdate from 'selfupdate';
// 来自于上面分析的selfupdate
const packageInfo = require('../../package');
// 需要检查全局自动更新的package.json
const update = promisify(selfupdate.update);
const isUpdated = promisify(selfupdate.isUpdated);
// update方法和isupdated方
const exec = promisify(childProcess.exec);
// promisify方法
export default async () => {
const checkSpinner = ora('正在检查更新...').start();
const isLatest = await isUpdated(packageInfo);
checkSpinner.stop();
if (isLatest) {
console.log('当前已是最新版本');
return;
}
const ans = await prompt([
{
message: '检测到新版本,是否更新?',
name: 'shouldUpdate',
type: 'confirm',
},
]);
// 用户输入
if (!ans.shouldUpdate) {
return;
}
const updateSpinner = ora('正在更新').start();
//开始更新
return update(packageInfo)
.catch(() => exec(`npm uninstall --global --silent ${packageInfo.name}`))
// 已经安装了删除掉,重新安装一遍~~
.then(() => exec(`npm install --global --silent ${packageInfo.name}`))
.then(() => {
updateSpinner.stop();
console.log(
`更新完成,请重新启动! 如出现命令丢失情况,请手动重新安装:npm install -g ${
packageInfo.name
}`,
);
process.exit(0);
})
.catch(error => {
console.error('更新失败', error);
process.exit(1);
});
};
11.nodejs中阻塞当前进程等待用户输入inquery.js
const ans = await prompt([
{
message: '检测到新版本,是否更新?',
name: 'shouldUpdate',
type: 'confirm',
},
]);
// shouldUpdate表示用户是否选择了自动更新
if (!ans.shouldUpdate) {
return;
}
上面已经简单说了npm的基本使用方法,里面还包括的方法有:
config: { loaded: false, get: [Object], set: [Object] },
commands: {ac,}
// commands里面包含所有的方法
建议自己require它并打印出来。
遇到一个常见的问题:在babel打包的时候需要考虑less/scss等文件,此时可以直接使用如下的命令即可:
rimraf lib && babel src --out-dir lib --copy-files
const semver = require('semver')
var gits = [
"daily/0.0.10",
"daily/0.0.11",
"daily/0.0.12",
"daily/0.0.13",
"daily/0.0.2",
"daily/0.0.5",
"daily/0.0.6",
"daily/0.0.7",
"daily/0.0.8"
]
const sorted = gits.sort(function(a, b) {
var lt = semver.coerce(a);
var gl = semver.coerce(b);
return semver.gt(lt, gl);
})