目录
引入项目模版类型和标准安装逻辑开发
判断是否为自定义模版,通过改动 mongodb 数据库中的数据来进行区分。
const TYPE_PROJECT = 'project';
const TYPE_COMPONENT = 'component';
const TEMPLATE_TYPE_NORMAL = 'normal';
const TEMPLATE_TYPE_CUSTOM = 'custom';
......
async installTemplate() {
log.verbose('templateInfo', this.templateInfo);
if (this.templateInfo) {
if (!this.templateInfo.type) {
this.templateInfo.type = TEMPLATE_TYPE_NORMAL;
}
if (this.templateInfo.type === TEMPLATE_TYPE_NORMAL) {
// 标准安装
await this.installNormalTemplate();
} else if (this.templateInfo.type === TEMPLATE_TYPE_CUSTOM) {
// 自定义安装
await this.installCustomTemplate();
} else {
throw new Error('无法识别项目模板类型!');
}
} else {
throw new Error('项目模板信息不存在!');
}
}
......
拷贝项目模版功能开发
......
async installNormalTemplate() {
log.verbose('templateNpm', this.templateNpm);
// 拷贝模板代码至当前目录
let spinner = spinnerStart('正在安装模板...');
await sleep();
try {
const templatePath = path.resolve(this.templateNpm.cacheFilePath, 'template');
const targetPath = process.cwd();
fse.ensureDirSync(templatePath); // 确保目录存在
fse.ensureDirSync(targetPath);
fse.copySync(templatePath, targetPath); // 拷贝文件内容
} catch (e) {
throw e;
} finally {
spinner.stop(true);
log.success('模板安装成功');
}
// 依赖安装
await this.execCommand(installCommand, '依赖安装失败!'); // 下节讲具体实现
// 启动命令执行
await this.execCommand(startCommand, '启动执行命令失败!');
}
......
项目模版安装依赖和启动命令
// mongodb 数据库中的数据增加两项
{
"installCommand": "npm install",
"startCommand": "npm run serve"
}
// 工具函数
function exec(command, args, options) {
const win32 = process.platform === 'win32';
const cmd = win32 ? 'cmd' : command;
const cmdArgs = win32 ? ['/c'].concat(command, args) : args;
return require('child_process').spawn(cmd, cmdArgs, options || {});
}
function execAsync(command, args, options) {
return new Promise((resolve, reject) => {
const p = exec(command, args, options);
p.on('error', e => {
reject(e);
});
p.on('exit', c => {
resolve(c);
});
});
}
......
async execCommand(command, errMsg) {
let ret;
if (command) {
const cmdArray = command.split(' ');
const cmd = this.checkCommand(cmdArray[0]); // 下节讲具体实现
if (!cmd) {
throw new Error('命令不存在!命令:' + command);
}
const args = cmdArray.slice(1);
ret = await execAsync(cmd, args, {
stdio: 'inherit', // 子进程实现,主进程中打印输出。
cwd: process.cwd(),
});
}
if (ret !== 0) {
throw new Error(errMsg);
}
return ret;
}
......
白名单命令检测功能开发
const WHITE_COMMAND = ['npm', 'cnpm'];
......
checkCommand(cmd) {
if (WHITE_COMMAND.includes(cmd)) {
return cmd;
}
return null;
}
......
项目名称自动格式化功能开发
// 修改项目模版的 package.json
{
"name": "<%= className %>",
"version": "<%= version %>"
}
// npm不能识别 <%= version %>,因此需要把模版放到 template 目录中,外层还包裹一个包。
# kebab-case 可以方便的将 驼峰 转换为 - 形式
# commands/init/ 命令行中执行
npm install -S kebab-case
// 部分代码
// 生成classname
if (projectInfo.projectName) {
projectInfo.name = projectInfo.projectName;
projectInfo.className = require('kebab-case')(projectInfo.projectName).replace(/^-/, '');
}
本章核心:ejs 动态渲染项目模版
......
async installNormalTemplate() {
// 拷贝模板代码至当前目录
// 模版拷贝完成后,npm install以前。
const templateIgnore = this.templateInfo.ignore || [];
// ignore 中的 public 不适合在这里写死,移到 mongodb 中更恰当。
const ignore = ['**/node_modules/**', ...templateIgnore];
await this.ejsRender({ ignore });
// 依赖安装
// 启动命令执行
}
......
......
async ejsRender(options) {
const dir = process.cwd();
const projectInfo = this.projectInfo;
return new Promise((resolve, reject) => {
glob('**', {
cwd: dir,
ignore: options.ignore || '',
nodir: true,
}, function(err, files) {
if (err) {
reject(err);
}
Promise.all(files.map(file => {
const filePath = path.join(dir, file);
return new Promise((resolve1, reject1) => {
ejs.renderFile(filePath, projectInfo, {}, (err, result) => {
if (err) {
reject1(err);
} else {
// ejs.renderFile() 完成渲染,并没有写入。要把渲染结果重新写入。
fse.writeFileSync(filePath, result);
resolve1(result);
}
});
});
})).then(() => {
resolve();
}).catch(err => {
reject(err);
});
});
});
}
......
init 命令直接传入项目名称功能支持
// 部分代码
......
let projectInfo = {};
let isProjectNameValid = false;
if (isValidName(this.projectName)) { // this.projectName为命令行中项目名
isProjectNameValid = true;
projectInfo.projectName = this.projectName;
}
......
if (!isProjectNameValid) {
projectPrompt.push(projectNamePrompt);
}
......