笔者最早接触脚手架要追溯到2020年,当时gitchat这个网站还在,而且有点如日中天的意思。某天我恰好冲浪到这个网站,之后还在上面分享了一些东西,赚了些喝咖啡的钱。不过貌似现在这个网站已经关闭了,哎,还有些许遗憾。
也是在gitchat上我正式开始接触脚手架,犹记得是花钱买的一个小课,作者是快狗打车的架构师。课程的最后就是讲脚手架的实现。
时间慢慢来到现在,最近几年除了开发也会整理一些东西,也想着真正实现一些自己的开发上的想法:其中之一就有脚手架。在我整理的过程中我发现一件有趣的事情:当初gitchat脚手架的源代码其实来自vue-cli的源码,当然不是vue-cli 1.0.0版的源代码,而是差不多2.0.0前后的源代码。
大家可以自行查看vue-cli早期尤其3.0.0的之前的源代码,3.0.0是一个分界线。查看地址:github.com/vuejs/vue-c…,之前是单独一个仓库管理,3.0.0开始使用lerna,也开始变得很复杂。
vue-cli 1.0.0源码是非常简单的,然后随着迭代逐步复杂,或许认识到这可以给予我们信心:万丈高楼平地起,只要敢于开始,坚持下去或许也可以写一些复杂应用。
天下文章一大抄,代码多少有些相似。就开发这件事来说,普通人都是从模仿或者就是从抄开始的,但有想法有进取心的程序员绝不能止步于抄,在我看来要有自己的想法,要能够迭代抄,或者至少要判断出抄来的适不适合自己。
本文这个脚手架是借鉴了或者就是抄了vue-cli的早期源代码,但也根据自己的实际需求做了一些改变:模板位置本地化,模板多样化;可直接选择依赖管理工具,可以选择多种工程模板。
zhen-cli介绍
zhen-cli是脚手架的名字。其核心有几个关键的库:
commandernodejs命令行框架主要用来注册命令行命令chalk设置命令行输出字符串颜色inquirer与命令行界面进行交互操作consolidate模板引擎库引来渲染数据metalsmith静态网站生成器execa执行命令行命令比如 npm install
核心流程
下面逐一介绍
可全局安装体验:
npm install zhen-cli -g
1,首先输入命令行命令
zhen create test-project
通过commander拿到项目名称:test-project
/**
* Help.
*/
program.on('--help', function () {
console.log(' Examples:')
console.log()
console.log(chalk.gray(' # create a new project with a template'))
console.log(' $ zhen create [projectName]')
console.log()
})
/**
* Help.如果只输入命令本身会返回帮助文档。
*/
function help() {
program.parse(process.argv);
if (program.args.length < 1) return program.help();
}
help();
这里如果只输入zhen或zhen create会在命令行显示help内容。
/**
* Settings.
*/
// 项目名叫什么 第二个参数作为目录名
const rawName = program.args[0];
// 是否在当前目录 默认my-project
const inPlace = rawName === ".";
// 模板最终会下载到什么地方
const to = path.resolve(rawName);
process.on("exit", () => {
console.log();
});
// 第一步:询问用户是否是在当前目录下创建项目; 如果要存放的目录已经存在提示已存在
if (inPlace || exists(to)) {
// 异步交互 和命令行工具进行交互 简单交互 如果是真则执行下一步
// inquirer这是一个简单功能
inquirer
.prompt([{
type: "confirm",
message: inPlace ? "在当前目录创建项目?" : "目录已经存在,仍要继续?",
name: "ok"
}])
.then(answers => {
if (answers.ok) {
run();
}
})
.catch(logger.fatal);
} else {
// console.log(home, tmp, to)
run();
}
如果输入zhen create .,会在当前目录生成工程文件
2,选择配置
依赖管理支持:npm,cnpm,yarn。模板支持:vue2-js、react-js、express-js,后期根据实际需要添加
3,下载远程模板/使用本地模板
最早获取模板我也采用的是从github仓库下载的方式,使用download-git-repo。使用这种方式有一些问题。由于网络问题,有时候下载会不成功。模板和脚手架分属不同的git仓库,管理起来比较麻烦。后来我索性直接将模板放在脚手架里面。
download-git-repo下载的实现方式可以参考文末其他相关文章。
4,渲染数据生成文件
渲染数据使用的是metalsmith结合handlebars,metalsmith是静态网站生成器,支持插件对数据进行过滤
metalsmith
.use(askQuestions(opts.prompts))
.use(filterFiles(opts.filters))
.use(renderTemplateFiles)
.clean(false)
.source(".")
.destination(dest)
.build(err => {
done(err);
console.log('\n正在下载依赖...\n')
const PACKAGE_MANAGER_CONFIG = {
npm: ['install', '--loglevel', 'error'],
cnpm: ['install', '--loglevel', 'error'],
yarn: []
}
executeCommand(tool, PACKAGE_MANAGER_CONFIG[tool], path.join(process.cwd(), name)).then(()=>{
// createSuccessInfo(name)
logMessage(opts.completeMessage, data);
})
});
这块使用三个插件:
- askQuestions询问是项目名称、项目作者、项目描述等
- filterFiles过滤掉指定文件,比如不使用vuex的话,会直接过滤掉相关的文件
- renderTemplateFiles渲染模板数据
5,下载依赖
使用execa进行依赖下载,执行选择依赖管理工具对应的命令下载依赖。
function executeCommand (command,args,cwd) {
return new Promise((resolve, reject) => {
const child = execa(command, args, {
cwd,
stdio: ['inherit', 'inherit', command === 'yarn' ? 'pipe' : 'inherit']
})
if (command === 'yarn') {
child.stderr.on('data', buf => {
const str = buf.toString()
if (/warning/.test(str)) {
return
}
// progress bar
const progressBarMatch = str.match(/\[.*\] (\d+)\/(\d+)/)
if (progressBarMatch) {
// since yarn is in a child process, it's unable to get the width of
// the terminal. reimplement the progress bar ourselves!
renderProgressBar(progressBarMatch[1], progressBarMatch[2])
return
}
process.stderr.write(buf)
})
}
child.on('close', code => {
if (code !== 0) {
reject(new Error(`command failed: ${command} ${args.join(' ')}`))
return
}
resolve()
})
})
}
6,仓库地址
最后适合自己的才最好。最新的vue-cli源代码已经不再采用3.0.0之前的模板管理方案:模板是静态的。且最新实现的vue-cli功能多样,对模板的拆分很细,模板根据配置选择动态生成。按照这种方式管理多种模板会很复杂。
目前的这个脚手架可以对多个框架下的模板进行生成管理,且模板可以扩展,已经满足了当初的设计需求,那么这样就不错了。