脚手架拆包策略
1.核心流程: core
2.命令:commands
- 初始化
- 发布
- 清除缓存
3.模型层: models
- Command命令
- Project项目
- Component项目
- Npm模块
- Git仓库
4.支撑模块: utils
- Git操作
- 云构建
- 工具方法
- API请求
- Git API
参考lerna源码的思路,core/command/index.js就完成了完整的脚手架;前期例行检查,然后是脚手架的准备阶段,调用runCommand运行命令;运行命令时,先调用初始化方法this.initialize(),然后调用命令的执行方法this.execute();
core模块技术方案
命令执行流程
1.准备阶段
2.命令注册(本篇文章暂不讲)
3.命令执行(本篇文章暂不讲)
涉及技术点
核心库
- import-local
- commander
工具库
- npmlog
- fs-extra
- semver
- colors
- user-home
- dotenv
- root-check
代码编写
脚手架框架代码搭建
首先在根目录创建core、commands、models、utils四个文件夹; 将我们之前在pakages下面创建的utils和core分别移动到新创建的文件夹中; 在lerna.json加入:
"packages": ["core/*", "models/*", "commands/*", "utils/*"],
进入core/core目录,执行npm link;
core/bin/index.js文件:
#! /usr/bin/env node
const importLocal = require("import-local");
if (importLocal(__filename)) {
require("npmlog").info("cli", "正在使用mj-cli-dev 本地版本");
} else {
require("../lib/index")(process.argv.slice(2));
}
core/lib/index.js文件中加一个log;
"use strict";
module.exports = core;
function core() {
// TODO
console.log("exec core--->");
}
core模块中安装:
cnpm i -S import-local
cnpm i -S npmlog
打印出log
检查版本号功能开发
在core/lib/index.js文件中:
"use strict";
module.exports = core;
const pkg = require("../package.json");
function core() {
// TODO
console.log("exec core--->");
checkPkgVersion();
}
// 检查版本号
function checkPkgVersion() {
console.log("version--->", pkg.version);
}
输入mj-cli-dev命令;
require支持加载资源的类型是.js、.json、.node三种;
- .js时,通过module.exports/exports输出模块;
- .json文件,通过JSON.parse解析,输出一个对象;
- .node文件,C++的插件AddOns,process.dlopen去打开一个C++插件;(不常用)
- 其他文件,默认像.js一样解析
log更加复杂的信息;
lerna create @mj-cli-dev/log
lerna add npmlog utils/log/
在log/lib/index.js中修改:
"use strict";
module.exports = index;
const log = require("npmlog");
function index() {
log.info("cli", "test");
}
然后在core模块中使用log模块; 在core的packages.json中加一个依赖:
"@mj-cli-dev/log": "file:../../utils/log"
接着去utils/log目录下执行npm link;
再回到core目录下执行npm link @mj-cli-dev/log;
此时执行mj-cli-dev命令,就可以打印出log日志;
阅读一下npmlog的源码: 最上面定义了一个log对象,在log上增加一些像useColor等属性,再到最后调用addLevel,addLevel中加的就是我们直接可以调用的方法,比如info;如果我们想调用的方法addLevel中没有,那就不能成功调用了;addLevel中还可以对命令的颜色进行定制;
在utils/log/lib/index.js中增加一个success方法:
log.addLevel("success", 2000);
在core/core/lib/index.js中调用success方法:
log.success("test", "gogogog");
我们的log就变得五颜六色了
log中level的设置;
// level支持自定义,支持从环境变量中取
log.level = process.env.LOG_LEVEL ? process.env.LOG_LEVEL : "info";
// 添加一个前缀
log.heading = "mj";
最低Node版本检查
// 检查node版本号
function checkNodeVersion() {
// 1.获取当前Node版本号
console.log(process.version);
// 2.比对最低版本号
}
新建core/lib/const.js文件;
const LOWEST_NODE_VERSION = "12.0.0";
module.exports = {
LOWEST_NODE_VERSION,
};
安装semver包;
lerna add semver@7.3.4 core/core/
lerna add colors@1.4.0 core/core/
修改core/lib/index.js代码;
"use strict";
module.exports = core;
const semver = require("semver");
const colors = require("colors");
const pkg = require("../package.json");
const log = require("@mj-cli-dev/log");
const constant = require("./const");
function core() {
try {
checkPkgVersion();
checkNodeVersion();
} catch (e) {
log.error(e.message);
}
}
// 检查版本号省略
// 检查node版本号
function checkNodeVersion() {
// 1.获取当前Node版本号
const currentVersion = process.version;
// 2.比对最低版本号
const lowestVersion = constant.LOWEST_NODE_VERSION;
// 当前版本号 < 最低版本号
if (!semver.gte(currentVersion, lowestVersion)) {
throw new Error(
colors.red(`mj-cli-dev需要安装 v${lowestVersion}以上版本的Node.js`)
);
}
}
最后报错信息显示;
检查root启动
root-check可以检查当前是否是root账户登录;如果是的话会自动帮我们降级;
lerna add root-check@1.0.0 core/core/
修改core/lib/index.js文件:
// 检查root
// uid是0就是root账户
function checkRoot() {
const rootCheck = require("root-check");
rootCheck();
console.log(process.geteuid());
}
使用root-check前:
使用root-check后:
root-check的原理就是判断当前账户的uid,然后调用setuid方法修改uid,它还会获取当前默认账户的uid,比如502,降级时会修改成502,也可自定义;
检查用户主目录
使用user-home的库,用它帮我们跨操作系统获取用户主目录;还有path-exists这个库,用来判断文件是否存在;
lerna add user-home@2.0.0 core/core/
lerna add path-exists@4.0.0 core/core/
在core/lib/index.js增加检查主目录方法:
//检查主目录
function checkUserHome() {
// userHome不存在
if (!userHome || !pathExists(userHome)) {
throw new Error(colors.red("当前登录用户主目录不存在!"));
}
}
如果主目录不存在会抛出异常;
user-home的原理:首先获取环境变量process.env,判断当前的环境是win32还是darwin(也就是MacOs),或者是linux;在darwin下会/Users/' + user拼接成主目录;
检查入参
为什么要检查入参呢?主要是看当前是否进使用调试模式,如果要打印debug日志默认是关闭,需要在命令行加上--debug去打印,所以得去全局监听是否有--debug命令,如果存在就需要对log进行设置;是要设置环境变量的LOG_LEVEL,让它等于verbose,这样才能输出verbose日志;
安装minimist库,用它来帮我们检查入参;
lerna add minimist@1.2.5 core/core/
require是一个同步方法;import不是这样,需要放最上面;
core/lib/index.js中增加检查入参的方法;
// 检查入参
// log日志要在checkInputArgs之后
// 第一种改法:checkInputArgs得在require(log)之前执行,才能生效
function checkInputArgs() {
const minimist = require("minimist");
args = minimist(process.argv.slice(2));
console.log("argv", args);
checkArgs();
}
// 判断参数中是否存在--debug
function checkArgs() {
// 如果参数中存在--debug
if (args.debug) {
process.env.LOG_LEVEL = "verbose";
} else {
process.env.LOG_LEVEL = "info";
}
// 第二种改法
log.level = process.env.LOG_LEVEL;
}
检查环境变量
可以在操作系统中配置一些环境变量,将用户名、密码等敏感信息保存在用户本地,不用集成在代码当中,需要的时候就可以实时进行读取;
安装dotenv库
lerna add dotenv@8.2.0 core/core/
修改core/lib/index.js的代码
// 检查环境变量
function checkEnv() {
// 这一段好像没发挥啥作用
// const dotenv = require("dotenv");
// const dotenvPath = path.resolve(userHome, ".env");
// // 读取环境变量
// if (pathExists(dotenvPath)) {
// config = dotenv.config({
// path: dotenvPath,
// });
// }
createDefaultCliConfig();
log.verbose("环境变量", config, process.env.CLI_HOME_PATH);
}
// CLI_HOME的默认配置
function createDefaultCliConfig() {
// 脚手架的主目录
const cliConfig = {
home: userHome,
};
if (process.env.CLI_HOME) {
cliConfig["cliHome"] = path.join(userHome, process.env.CLI_HOME);
} else {
cliConfig["cliHome"] = path.join(userHome, constant.DEFAULT_CLI_HOME);
}
process.env.CLI_HOME_PATH = cliConfig.cliHome;
}
在core/lib/const.js中增加一个变量:
const DEFAULT_CLI_HOME = "mj-cli-dev";
最后打印出来prcocess.env.CLI_HOME_PATH;
检查是否是最新版本
如果别人下载脚手架到本地,运行时有可能不是最新的版本,这时候就需要提醒用户下载最新的版本;
首先在utils/新建一个包,叫做get-npm-info
lerna create @mj-cli-dev/get-npm-info
lerna add axios@0.21.0 utils/get-npm-info
// 拼接url的库
lerna add url-join@4.0.1 utils/get-npm-info
// semver
lerna add semver@7.3.4 utils/get-npm-info
get-npm-info/lib/index.js
"use strict";
const axios = require("axios");
const urlJoin = require("url-join");
const semver = require("semver");
// 发请求查询线上包信息
function getNpmInfo(npmName, registry) {
if (!npmName) {
return null;
}
const registryNew = registry || getDefaultRegistry();
const npmInfoUrl = urlJoin(registryNew, npmName);
console.log("npmName", npmInfoUrl);
return axios
.get(npmInfoUrl)
.then((res) => {
console.log("res", res.status);
if (res.status == 200) {
return res.data;
} else {
return null;
}
})
.catch((err) => {
return Promise.reject(err);
});
}
// 获取默认的registry
function getDefaultRegistry(isOriginal = false) {
return isOriginal
? "https://registry.npmjs.org/"
: "https://registry.npm.taobao.org/";
}
async function getNpmVersions(npmName, registry) {
const data = await getNpmInfo(npmName, registry);
if (data) {
return Object.keys(data.versions);
} else {
return [];
}
}
// 查询满足条件的版本号
function getSemverVersions(baseVersion, versions) {
// 版本号排序
return versions
.filter((version) => {
return semver.satisfies(version, `^${baseVersion}`);
})
.sort((a, b) => semver.gt(b, a));
}
async function getNpmserverVersion(baseVersion, npmName, registry) {
const versions = await getNpmVersions(npmName, registry);
const newVersions = getSemverVersions(baseVersion, versions);
if (newVersions && newVersions.length > 0) {
return newVersions[0];
} else {
return [];
}
}
module.exports = {
getNpmInfo,
getNpmVersions,
getNpmserverVersion,
};
core/lib/index.js增加检查是否是最新版本的方法:
// 检查是否是最新版本
async function checkGlobalUptate() {
// 1.获取当前的版本号和模块名
// const currentVersion = pkg.version;
const currentVersion = "0.0.1";
// const npmName = pkg.name;
const npmName = "mj-cli-dev";
// 2.调用npm API,获取所有的版本号 registry.npms.org+你的模块名称
const { getNpmserverVersion } = require("@mj-cli-dev/get-npm-info");
// 3.提取所有的版本号,比对哪些版本号是大于当前版本号的
const lastestVersion = await getNpmserverVersion(currentVersion, npmName);
// 4.获取最新的版本号,提示用户更新到该版本
if (lastestVersion && semver.gt(lastestVersion, currentVersion)) {
log.warn(
colors.yellow(
`更新提示,请手动更新${npmName}的版本号!当前的版本是${currentVersion},最新版本是${lastestVersion}!`
)
);
}
}