内容简介:我们日常中经常使用各种cli来加速我们的工作,你们也一定和我一样想知道这些cli内部都干了什么?接下来我们就以实现一个koa-generator来打开脚手架工具的大门,来跟着我一步一步做吧:首先我们先对我们要实现的工具做一个简单的需求分析:
我们日常中经常使用各种cli来加速我们的工作,你们也一定和我一样想知道这些cli内部都干了什么?接下来我们就以实现一个koa-generator来打开脚手架 工具 的大门,来跟着我一步一步做吧:
为了加快我们的学习进度,更快的理解cli,我们这里会省略一些内容,旨在帮助大家更快建立基本的概念和入门方法
需求分析
首先我们先对我们要实现的工具做一个简单的需求分析:
- 自动化生成koa初始项目结构
- 可以自定义一些内容
- 发布
是不是很简单?没错,真的很简单!
逐步实现
1
想要自动化生成koa初始项目结构的前提,就是要知道我们构建出来的结构是什么样的:
上图就是我们想要生成的项目结构
明确了我们的目的接下来就开始着手吧!
2
2.1
创建文件夹
mkdir koa-simple-generator 复制代码
2.2
进入项目目录
cd koa-simple-generator 复制代码
2.3
初始化npm(等不及实践就一路enter,后面也可以再做修改)
npm init 复制代码
2.4
打开我们的package.json,如下
将下面的代码复制到package.json里
{ "name": "koa-simple-generator", "version": "1.0.0", "description": "", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "main": "bin/wowKoa", "bin": { "koa2": "./bin/wowKoa" }, "dependencies": { "commander": "2.7.1", "mkdirp": "0.5.1", "sorted-object": "1.0.0" }, "devDependencies": { "mocha": "2.2.5", "rimraf": "~2.2.8", "supertest": "1.0.1" }, "engines": { "node": ">= 7.0" } } 复制代码
1. dependencies和devDependencies简单来说就是应用的依赖包,devDependencies只会在开发环境安装 2. 这句话的意思是我们的这个工具需要node7.0及以上的版本才能支持 "engines": { "node": ">= 7.0" } 重点是这两句 "main": "bin/wowKoa", "bin": { "wowKoa": "./bin/wowKoa" }, 意思是默认执行的是bin目录下的wowKoa, 执行wowKoa的命令,执行的也是bin目录下的wowKoa, 复制代码
####2.5 接下来安装我们的依赖吧
npm i 复制代码
2.6
安装完,我们新建一个目录template
mkdir template 复制代码
然后我们可以把我们想要生成的目录结构拷贝进去,这里我就只是把koa2的目录拷贝进去,现在我们的目录长这样:
2.7
新建bin目录,在bin下新建文件wowKoa
2.8
接下来就是关键了,我们的所有工作都是在bin下的wowKoa文件里完成的 直接复制粘贴下面的,然后进入项目目录运行 node bin/wowKoa
就能看到结果了
代码我已经大部分都注释啦
#!/usr/bin/env node // 告诉Unix和 Linux 系统这个文件中的代码用node可执行程序去运行 var program = require('commander'); var mkdirp = require('mkdirp'); var os = require('os'); var fs = require('fs'); var fsm = require('fs-extra') var path = require('path'); var readline = require('readline'); var pkg = require('../package.json'); // 退出node进程 var _exit = process.exit; // s.EOL属性是一个常量,返回当前操作系统的换行符(Windows系统是\r\n,其他系统是\n) var eol = os.EOL; var version = pkg.version; // Re-assign process.exit because of commander // TODO: Switch to a different command framework process.exit = exit program /** * .version('0.0.1', '-v, --version') * 1版本号<必须>, * 2自定义标志<可省略>:默认为 -V 和 --version * * .option('-n, --name<path>', 'name description', 'default name') * 1 自定义标志<必须>:分为长短标识,中间用逗号、竖线或者空格分割;标志后面可跟必须参数或可选参数,前者用 <> 包含,后者用 [] 包含 * 2 选项描述<省略不报错>:在使用 --help 命令时显示标志描述 * 3 默认值<可省略> * * .usage('[options] [dir]') * 作用:只是打印用法说明 * * .parse(process.argv) * 作用:用于解析process.argv,设置options以及触发commands * process.argv获取命令行参数 * * * Commander提供了api来取消未定义的option自动报错机制, .allowUnknownOption() */ .version(version, '-v, --version') .allowUnknownOption() .usage('[options] [dir]') .option('-f, --force', 'force on non-empty directory') .parse(process.argv); // 没有退出时执行主函数 if (!exit.exited) { main(); } /** * 主函数 */ function main() { // 获取当前命令执行路径 var destinationPath = program.args.shift() || '.'; // 根据文件夹名称定义appname // 用于package.json里的name var appName = path.basename(path.resolve(destinationPath)); // 判断当前文件目录是否为空 emptyDirectory(destinationPath, function (empty) { // 如果为空或者强制执行时,就直接生成项目 if (empty || program.force) { createApplication(appName, destinationPath); } else { // 否则询问 confirm('当前文件夹不为空,是否继续?[y/N] ', function (ok) { if (ok) { // 控制台不再输入时销毁 process.stdin.destroy(); createApplication(appName, destinationPath); } else { console.error('aborting'); exit(1); } }); } }) } /** * Check if the given directory `path` is empty. * 判断文件夹是否为空 * @param {String} path * @param {Function} fn */ function emptyDirectory(path, fn) { fs.readdir(path, function (err, files) { if (err && 'ENOENT' != err.code) throw err; fn(!files || !files.length); }); } /** * 在给定路径中创建应用 * @param {String} path */ function createApplication(app_name, path) { // wait的值等于complete函数执行的次数 // 用于选择在哪一次complete函数执行后执行控制台打印引导使用的文案 var wait = 1; console.log(); function complete() { if (--wait) return; var prompt = launchedFromCmd() ? '>' : '$'; console.log(); console.log(' install dependencies:'); console.log(' %s cd %s && npm install', prompt, path); console.log(); console.log(' run the app:'); // 根据控制台的环境不同打印不同文案(linux或者win) if (launchedFromCmd()) { console.log(' %s SET DEBUG=koa* & npm start', prompt, app_name); } else { console.log(' %s DEBUG=%s:* npm start', prompt, app_name); } } copytmp(complete, path,app_name) } // 拷贝模拟里的文件到本地 function copytmp(fn, destinationPath,app_name) { // 获取模板文件的文件目录 tmpPath = path.join(__dirname, '..', 'template') // 创建目录 fsm.ensureDir(destinationPath + '/'+app_name) .then(() => { // 拷贝模板 fsm.copy(tmpPath, destinationPath + '/'+app_name, err => { if (err) return console.log(err) fn() }) }) } /** * Determine if launched from cmd.exe * 判断控制台环境(liux或者win获取其他) */ function launchedFromCmd() { return process.platform === 'win32' && process.env._ === undefined; } /** * node是使用process.stdin和process.stdout来实现标准输入和输出的 * readline 模块提供了一个接口,用于一次一行地读取可读流(例如 process.stdin)中的数据。 它可以使用以下方式访问: */ var rl = readline.createInterface({ input: process.stdin, output: process.stdout }); // 控制台问答 function confirm(msg, callback) { rl.question(msg, function (input) { callback(/^y|yes|ok|true$/i.test(input)); }); } // 控制台问答 function wrieQuestion(msg, callback) { rl.question(msg, function (input) { // rl.close()后就不再监听控制台输入了 rl.close(); callback(input) }); } /** * 通过fs读取模板文件内容 */ function loadTemplate(name) { return fs.readFileSync(path.join(__dirname, '..', 'template', name), 'utf-8'); } /** * echo str > path. * 写入文件 * @param {String} path * @param {String} str */ function write(path, str, mode) { fs.writeFileSync(path, str, { mode: mode || 0666 }); console.log(' \x1b[36mcreate\x1b[0m : ' + path); } /** * 这里是主要解决在winodws上的一些bug,不用卡在这里,核心目的就是为了能让进程优雅退出 * Graceful exit for async STDIO */ function exit(code) { // flush output for Node.js Windows pipe bug // https://github.com/joyent/node/issues/6247 is just one bug example // https://github.com/visionmedia/mocha/issues/333 has a good discussion function done() { if (!(draining--)) _exit(code); } var draining = 0; var streams = [process.stdout, process.stderr]; exit.exited = true; streams.forEach(function (stream) { // submit empty write request and wait for completion draining += 1; stream.write('', done); }); done(); } 复制代码
以上所述就是小编给大家介绍的《手把手教你实现脚手架工具Koa-generator》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 脚手架命令行工具实现揭秘
- 58 金融前端脚手架的设计与实现
- 仿照laravel-artisan实现简易go开发脚手架
- SpringBoot+JWT+SpringSecurity+MybatisPlus实现Restful鉴权脚手架
- SpringBoot+JWT+Shiro+MybatisPlus实现Restful快速开发后端脚手架
- Next.js 脚手架进阶 —— 扩展为全栈脚手架
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
XML、JSON 在线转换
在线XML、JSON转换工具
HEX CMYK 转换工具
HEX CMYK 互转工具