点击查看脚手架系列文章总览【正在更新】
个人网站:www.dengzhanyong.com
关注公众号【前端筱园】,不错过每一篇文章
脚手架的第三个阶段:执行阶段,本篇文章以脚手架初始化项目命令为例进行讲解。
前言
对于脚手架的每个命令的执行,都拆分成了独立的 npm
包来执行,执行相应的命令时,会将对应的 npm
命令包下载到本地缓存中,若缓存中已存在但非最新版本,也命令的注册阶段会帮我们完成自动更新。然后执行本地缓存中的文件即可。
为了方便调试,支持传入本地路径,如果符合条件,则会执行本地代码,对本地的命令包进行调试。
对于执行不同的命令,在相应的包中去实现自己的逻辑即可,但我们任然可以做一些公共的事情,比如:参数的初始化、node版本的校验、实现统一规范等。
command 类
命令执行的那些公共部分,全部可以抽离到 command
类中,在实现每个命令时,需要继承 command
类。
包名:@steamed/command
位置:models/command
作用:命令执行统一处理,规范化命令的实现
检查node版本
在准备阶段中,已经校检查 node
的版本,校验的目的是为了满足脚手架正常运行的环境。这个步骤可以下沉到执行阶段,主要原因是因为脚手架执行的命令包都是独立的,并且是不断更新扩展的,可能会用到一些更新的语法,这就需要我们提高我们所规定的 node
最低版本要求。
检查和初始化参数
参数不能为空且格式必须为数组。
init 和 exec
为了统一命令包的开发规范,可以通过 Command
类来要求必须实现初始化(init)和执行(exec)两个方法。
'use strict';
const colors = require('colors');
const semver = require('semver');
const log = require('@steamed/log');
const { LOWEST_NODE_VERSION } = require('./constant');
class Command {
constructor(argv) {
try {
if (!argv) {
throw new Error('请输入参数');
}
if (!Array.isArray(argv)) {
throw new Error('参数必须是一个数组');
}
if (argv.length < 1) {
throw new Error('参数内容为空');
}
this._argv = argv;
let runner = new Promise((resolve, reject) => {
let chain = Promise.resolve();
chain.then(() => this.checkNodeVersion());
chain.then(() => this.initArgs());
chain.then(() => this.init());
chain.then(() => this.exec());
chain.catch((error) => {
log.error(error);
})
})
} catch (error) {
log.error(error.message);
if (log.level === 'verbose') {
log.verbose(error);
}
}
}
// 初始化参数
initArgs() {
this._cmd = this._argv[this._argv.length - 1];
}
// 初始化
init() {
throw new Error('请实现init方法');
};
// 执行
exec() {
throw new Error('请实现exec方法');
}
// 检查node版本
checkNodeVersion() {
const currentVersion = process.version;
const lowestVersion = LOWEST_NODE_VERSION;
if (!semver.gte(currentVersion, lowestVersion)) {
throw new Error(colors.red(`steamed-cli 需要node的最低版本为${lowestVersion},当前node.js版本为${currentVersion}`));
}
}
}
module.exports = Command;
初始化命令开发
脚手架一般都有一个 init
或者 create
命令用来初始化项目模板,例如 vue-cli 使用 vue create hello-world
来创建一个项目。
首先来分析一下有哪些要点:
- 应该支持手动传入项目名
- 支持传入
--force
参数强制创建项目 - 需要用户输入或选择一些其他必要信息,应该支持命令行的交互功能
- 需要实现项目模板下载到当前目录下
- 下载后的模板需要根据用户填写的信息,对模板内的关键信息进行替换
要点分析
-
命令行交互
使用
inquirer
包实现,使用方法请参考:https://www.npmjs.com/package/inquirer -
判断当前文件夹是否为空,如果不为空,需要让用户确认是否强制覆盖
// 判断文件 isDirEmpty(localPath) { const fileList = fes.readdirSync(localPath); return fileList.filter(f => !f.startsWith('.')).length === 0; }
if (!this.force) { // 没有--fore参数 isForce = (await inquirer.prompt({ name: "isForce", type: "confirm", default: false, message: "当前文件夹不为空,是否强制创建?" })).isForce; }
-
模板下载
模板存储在哪里?
有两种方案:
-
存到
github
中,使用时通过git clone
命令去动态拉取,好处在于可以很方便的修改模板代码。 -
以
npm
包的形式存在,与动态命令思路相同,首次执行时,将相应的模板下载到本地缓存目录下,初始化项目时,将相应的模板从本地拷贝到当前目录。
我选择的是第二种,主要优势在于可以很方便控制模板的版本,后面会讲到如何控制。
模板选择列表的管理
初始化项目模板不仅是内容可能存在更新,也可能随时新增删除模板,模板选择项是不固定的,不能写死在代码中。可以通过接口请求的方式动态获取模板信息,包含模板的名称、版本号、npm包名称、下载源、模板类型等信息。
-
-
替换关键信息
用户通过命令行交互输入项目名称、版本号、项目描述等信息,这些信息应该替换到初始化模板中的相应位置,可以通过
ejs
模板渲染实现。 使用方法请参考:https://www.npmjs.com/package/ejs替换文件中的信息需要遍历模板中的文件,可以使用
glob
这个包。
完整代码请查看:https://github.com/DengZhanyong/steamed
点击查看脚手架系列文章总览【正在更新】
个人网站:www.dengzhanyong.com
关注公众号【前端筱园】,不错过每一篇文章