字 数:3621, 阅读时间:14分钟, 阅读原文前端
原本早就想写这篇文章的,因为有其余事情耽搁了(可能仍是由于太懒),就拖到了如今,若是再不记下来,估计会抛到九霄云外了。vue
NodeJs的出现,让前端工程化的理念不断深刻,正在向正规军靠近。先是带来了Gulp、Webpack等强大的构建工具,随后又出现了vue-cli和create-react-app等完善的脚手架,提供了完整的项目架构,让咱们能够更多的关注业务,而没必要在项目基础设施上花费大量时间。node
可是,这些现成的脚手架未必就能知足咱们的业务需求,也未必是最佳实践,这时咱们就能够本身来开发一个脚手架。固然,这其实很简单,利用npm上现成的轮子就能够搞定,这里作个记录,仅当备忘,以抛砖引玉。react
在上半年的一个项目中须要自定义一个脚手架,来帮助小伙伴们提升开发效率,统一代码输出质量,并解决一些使用上的问题,固然也是为了装装逼。
在使用脚手架方式构建以前,咱们遇到了这几个问题:git
每一个项目建立的时候,须要去Git仓库拉取项目模板或者拷贝以前的项目,这样作有两个问题程序员
因此咱们来解决这些问题,思路以下:github
固然,除了上述需求,咱们还能够再作些额外工做:vue-cli
首先,咱们须要建立一个项目,这里就叫yncms-template-cli
, 项目结构以下:shell
- commands // 此文件夹用于放置自定义命令 - utils - index.js // 项目入口 - readme.md
为了测试,咱们先在index.js
放点内容:npm
#!/usr/bin/env node // 必须在文件头添加如上内容指定运行环境为node console.log('hello cli');
对于通常的nodejs项目,咱们直接使用node index.js
就能够了,可是这里是脚手架,确定不能这样。咱们须要把项目发布到npm,用户进行全局安装,而后就能够直接使用咱们自定义的命令,相似yncms-template
这样。
因此,咱们须要将咱们的项目作下改动,首先在packge.json
中添加以下内容:
"bin": { "yncms-template": "index.js" },
这样就能够将yncms-template
定义为一个命令了,但此时仅仅只能在项目中使用,还不能做为全局命令使用,这里咱们须要使用npm link
将其连接到全局命令,执行成功后在你的全局node_modules
目录下能够找到相应文件。而后输入命令测试一下,若是出现以下内容说明第一步已经成功一大半了:
PS E:\WorkSpace\yncms-template-cli> yncms-template hello cli
可是,目前这个命令只有咱们本身电脑能够用,要想其余人也能安装使用,须要将它发布到npm,大体流程以下:
npm login
登陆,须要输入username
、password
、email
npm public
发布这一步比较简单,很少说,可是请注意以下几点:
nrm
的须要先将源切换到npm
官方源package.json
中有几个字段须要完善:
name
为发布的包名,不能和npm已有的包重复version
为版本信息,每次发布都必需要比线上的版本高homepage
、bugs
、repository
也能够添加上,对应以下页面发布成功后,须要等待一下子才能够在npm仓库搜索到。
既然是脚手架,确定不能只让它输出一段文字吧,咱们还须要定义一些命令,用户在命令行输入这些命令和参数,脚手架会作出对应的操做。这里不须要咱们本身去解析这些输入的命令和参数,有现成的轮子(commander)可使用,彻底能够知足咱们的须要。
安装好commander
后,咱们将index.js
中内容改成以下:
#!/usr/bin/env node const commander = require('commander'); // 利用commander解析命令行输入,必须写在全部内容最后面 commander.parse(process.argv);
这时,虽然咱们没有定义任何命令,可是commander内部给咱们定义了一个帮助命令--help
(简写-h
):
PS E:\WorkSpace\yncms-template-cli> yncms-template -h Usage: index [options] Options: -h, --help output usage information
接下来,咱们再建立一个查询版本的命令参数,在index.js
增长以下内容:
// 查看版本号 commander.version(require('./package.json').version);
这样,咱们在命令行就能够查看版本号了:
PS E:\WorkSpace\yncms-template-cli> yncms-template -V 1.0.10 PS E:\WorkSpace\yncms-template-cli> yncms-template --version 1.0.10
默认参数是大写V
,若是须要改为小写,将上面内容作以下改动便可:
// 查看版本号 commander .version(require('./package.json').version) .option('-v,--version', '查看版本号');
PS E:\WorkSpace\yncms-template-cli> yncms-template -h Usage: index [options] Options: -V, --version output the version number -h, --help output usage information
接下来,咱们来定义一个init
命令,如yncms-template init test
。
在index.js
中增长以下内容:
commander .command('init <name>') // 定义init子命令,<name>为必需参数可在action的function中接收,如需设置非必需参数,可以使用中括号 .option('-d, --dev', '获取开发版') // 配置参数,简写和全写中使用,分割 .description('建立项目') // 命令描述说明 .action(function (name, option) { // 命令执行操做,参数对应上面的设置的参数 // 咱们须要执行的全部操做,都在这里完成 console.log(name); console.log(option.dev); });
如今测试一下:
PS E:\WorkSpace\yncms-template-cli> yncms-template init test -d test true
commander
具体的用法,请自行查看官方文档。
如此,一个自定义命令雏形就算完成了,然还有几件事情要作:
init
命令具体执行的操做,下面会有单独部分来讲。action
拆分到commands
文件夹中上面,咱们定义了init
命令,可是并无达到初始化项目的目的,接下来咱们就实现一下。
通常来讲,项目模板有两种处理方式:
首先,咱们利用download-git-repo封装一个clone
方法,用于从git拉取项目。
// utils/clone.js const download = require('download-git-repo'); const symbols = require('log-symbols'); // 用于输出图标 const ora = require('ora'); // 用于输出loading const chalk = require('chalk'); // 用于改变文字颜色 module.exports = function (remote, name, option) { const downSpinner = ora('正在下载模板...').start(); return new Promise((resolve, reject) => { download(remote, name, option, err => { if (err) { downSpinner.fail(); console.log(symbols.error, chalk.red(err)); reject(err); return; }; downSpinner.succeed(chalk.green('模板下载成功!')); resolve(); }); }); };
// commands/init.js const shell = require('shelljs'); const symbols = require('log-symbols'); const clone = require('../utils/clone.js'); const remote = 'https://gitee.com/letwrong/cli-demo.git'; let branch = 'master'; const initAction = async (name, option) => { // 0. 检查控制台是否能够运行`git `, if (!shell.which('git')) { console.log(symbols.error, '对不起,git命令不可用!'); shell.exit(1); } // 1. 验证输入name是否合法 if (fs.existsSync(name)) { console.log(symbols.warning,`已存在项目文件夹${name}!`); return; } if (name.match(/[^A-Za-z0-9\u4e00-\u9fa5_-]/g)) { console.log(symbols.error, '项目名称存在非法字符!'); return; } // 2. 获取option,肯定模板类型(分支) if (option.dev) branch = 'develop'; // 4. 下载模板 await clone(`direct:${remote}#${branch}`, name, { clone: true }); }; module.exports = initAction;
测试一下,不出意外就能够成功拉取项目了。
这里拉取的项目是和远程仓库关联的,咱们须要将其删掉(因为咱们项目是svn管理,因此直接把.git
文件夹删掉,若是使用git的话,能够git init
初始化便可),清理掉一些多余文件:
// commands/init.js // 5. 清理文件 const deleteDir = ['.git', '.gitignore', 'README.md', 'docs']; // 须要清理的文件 const pwd = shell.pwd(); deleteDir.map(item => shell.rm('-rf', pwd + `/${name}/${item}`));
在上述过程当中,咱们实现了一个脚手架的基本功能,大体分为三个流程(拉取模板->建立项目->收尾清理),也解决了上面我项目中遇到的第一个问题。接下来,咱们就来看下第二个问题如何解决。
解决的思路就是在建立项目的时候,就经过命令行强制要求开发人员输入对应的配置,而后自动写入配置文件,这样就能够有效避免忘记填写的尴尬。固然经过这种方式也能够实现根据用户的输入来动态初始化项目,达到个性化的目的。
这里咱们直接使用现成的轮子inquirer就能够搞定,效果和VueCli建立项目同样,支持不少类型,比较强大,也比较简单,具体用法看官方文档就能够了。这里我直接上代码,在第4步(下载模板)前面增长以下:
// init.js const inquirer = require('inquirer'); // 定义须要询问的问题 const questions = [ { type: 'input', message: '请输入模板名称:', name: 'name', validate(val) { if (!val) return '模板名称不能为空!'; if (val.match(/[^A-Za-z0-9\u4e00-\u9fa5_-]/g)) return '模板名称包含非法字符,请从新输入'; return true; } }, { type: 'input', message: '请输入模板关键词(;分割):', name: 'keywords' }, { type: 'input', message: '请输入模板简介:', name: 'description' }, { type: 'list', message: '请选择模板类型:', choices: ['响应式', '桌面端', '移动端'], name: 'type' }, { type: 'list', message: '请选择模板分类:', choices: ['整站', '单页', '专题'], name: 'category' }, { type: 'input', message: '请输入模板风格:', name: 'style' }, { type: 'input', message: '请输入模板色系:', name: 'color' }, { type: 'input', message: '请输入您的名字:', name: 'author' } ]; // 经过inquirer获取到用户输入的内容 const answers = await inquirer.prompt(questions); // 将用户的配置打印,确认一下是否正确 console.log('------------------------'); console.log(answers); let confirm = await inquirer.prompt([ { type: 'confirm', message: '确认建立?', default: 'Y', name: 'isConfirm' } ]); if (!confirm.isConfirm) return false;
获取到用户输入的配置之后,就能够写入配置文件或者作个性化的处理了,这个太简单,我这里就不赘述了。
到这里,一个彻底知足需求的脚手架就完成了,可是做为一个有追求的程序员,咱们能够在界面和易用性上面再作点什么:
const installSpinner = ora('正在安装依赖...').start(); if (shell.exec('npm install').code !== 0) { console.log(symbols.warning, chalk.yellow('自动安装失败,请手动安装!')); installSpinner.fail(); // 安装失败 shell.exit(1); } installSpinner.succeed(chalk.green('依赖安装成功!'));
notifier.notify({ title: 'YNCMS-template-cli', icon: path.join(__dirname, 'coulson.png'), message: ' ♪(^∀^●)ノ 恭喜,项目建立成功!' });
vscode
并退出终端// 8. 打开编辑器 if (shell.which('code')) shell.exec('code ./'); shell.exit(1);
到这里,会发现开发一个脚手架其实很简单,都是使用现成的轮子就能够搞定,不晓得哪位大牛说过玩NodeJS就是玩轮子。
除了上述方法,咱们也能够直接经过大名鼎鼎的Yeoman来建立,不过我的以为不必,毕竟这玩意也不难。
一个好的脚手架应该是可以解决工做中遇到的问题,提升开发效率的。