前言和大家熟悉的vue-cli、react-native-cli等脚手架一样,只需要输入一个简单的命令vueinitwebpackproject,就可以快速为我们生成一个初始项目。在实际工作中,我们可以定制一个属于自己的脚手架来提高我们的工作效率。为什么需要脚手架?减少重复性工作,不再需要复制其他项目和删除不相关的代码,或者从头开始创建项目和文件。根据交互动态生成项目结构和配置文件等。多人协作更方便,无需来回传递文件??。思路开发脚手架,首先要理清思路。脚手架是如何工作的?我们可以借鉴vue-cli的基本思想。vue-cli将项目模板放到git上,然后运行时根据用户交互下载不同的模板,通过模板引擎渲染生成项目。这样模板和脚手架就分开了,可以分开维护。即使模板发生变化,也只需要上传最新的模板,即可生成最新的工程,无需用户更新脚手架。那么就可以按照这个思路来开发了。第三方库首先,让我们看看将使用哪些库。Commander.js可以自动解析命令和参数,用于处理用户输入的命令。download-git-repo,下载并解压git仓库,用于下载项目模板。Inquirer.js,一组用于与用户交互的通用命令行用户界面。handlebars.js是一个模板引擎,它使用用户提交的信息动态填充文件。ora,如果下载过程较长,可以用来显示下载时的动画效果。粉笔,您可以为终端的字体添加颜色。log-symbols,可以在终端显示√或×等图标。初始化项目首先创建一个空项目,暂命名为okii-cli,然后新建一个index.js文件,然后执行npminit生成一个package.json文件。最后,安装上面需要的依赖。npminstallcommanderdownload-git-repoinquirerhandlebarsorachalklog-symbols-S处理命令行node.js内置了对命令行操作的支持,package.json中的bin字段可以定义命令名和关联执行文件。所以现在在package.json中加入bin的内容:{"name":"okii-cli","version":"1.0.0","description":"node-basedscaffoldingtool","bin":{"okii":"index.js"},...}然后在index.js中定义init命令:#!/usr/bin/envnodeconstprogram=require('commander');program.version('1.0.0','-v,--version').command('init').action((name)=>{console.log(name);});program.parse(process.argv);调用version('1.0.0','-v,--version')会将-v和--version添加到命令中,可以通过这些选项打印出版本号。调用command('init')定义init命令,name为必填参数,即项目名称。action()是执行init命令时将发生的行为。这里执行生成工程的过程,这里暂时只打印名称。其实此时,init命令已经可以执行了。我们来测试一下,在okii-cli同目录下执行:node./okii-cli/index.jsinitHelloWorld可以看到命令行工具也打印了HelloWorld,所以很清楚,action((name)=>{})这里的参数名就是我们执行init命令时输入的项目名。命令已经完成,接下来就是下载模板生成项目结构了。下载模板download-git-repo支持从Github、Gitlab、Bitbucket下载仓库。具体使用请参考官方文档。由于是公司项目,模板仓库放在Gitlab上,那么在action()中执行下载模板的操作:#!/usr/bin/envnodeconstprogram=require('commander');constdownload=require('download-git-repo');program.version('1.0.0','-v,--version').command('init').action((name)=>{下载('http://xxxxxx:9999:HTML5/H5Template#master',name,{clone:true},(err)=>{console.log(err?'Error':'Success')})});program.parse(process.argv);down??load()第一个参数是仓库地址,但有一点区别。实际仓库地址为http://xxxxxx:9999/HTML5/H5Template#master。可以看到参数中端口号后面的'/'应该写成':',#master代表分支名。不同的模板可以放在不同的分支,改变分支下载不同的模板文件。第二个参数是路径。上面我们直接在当前路径下创建一个名为name的文件夹存放模板,或者使用二级目录如test/${name}命令行交互。用户执行init命令后,即可执行命令行交互功能,向用户提问,接收用户输入并进行相应的处理。这里我们使用inquirer.js来实现。constinquirer=require('inquirer');inquirer.prompt([{type:'input',name:'author',message:'请输入作者姓名'}]).then((answers)=>{console.log(answers.author);})从这个例子可以看出问题是放在prompt()中的,问题的类型是input,也就是输入类型,name是answer对象中的key,message是问题,用户输入答案就在答案中,使用起来就这么简单。更多参数设置请参考官方文档。通过命令行交互,获取用户输入,从而将答案渲染到模板中。渲染模板这里使用handlebars语法对HTML5/H5Template仓库模板中的package.json文件做一些修改{"name":"{{name}}","version":"1.0.0","description":"{{description}}","scripts":{"test":"echo\"Error:notestspecified\"&&exit1"},"author":"{{author}}","license":"ISC"}并在下载模板后将用户输入的答案呈现到package.json中program.version('1.0.0','-v,--version').command('init').action((name)=>{inquirer.prompt([{name:'description',message:'请输入物品描述'},{name:'author',message:'请输入作者姓名'}]).then((answers)=>{download('xxxxx#master',name,{clone:true},(err)=>{constmeta={name,description:answers.description,author:answers.author}constfileName=`${name}/package.json`;constcontent=fs.readFileSync(fileName).toString();constresult=handlebars.compile(content)(meta);fs.writeFileSync(文件名,结果);})})});这里使用了node.js的文件模块fs,将handlebars渲染的模板改写成文件,在用户输入答案后进行视觉美化,开始下载模板,此时使用ora提示用户是下载。constora=require('ora');//开始下载constspinner=ora('正在下载模板...');spinner.start();//下载失败调用spinner.fail();//下载成功调用spinner.succeed();然后用粉笔给打印的信息加上样式,比如成功信息用绿色,失败信息用红色,这样更容易让用户区分,也让终端展示更吸引人。constchalk=require('chalk');console.log(chalk.green('项目创建成功'));console.log(chalk.red('项目创建失败'));除了给打印信息添加颜色之外,还可以使用log-symbols在信息前面添加√或×等图标constchalk=require('chalk');constsymbols=require('log-symbols');console.log(symbols.success,chalk.green('项目创建成功'));console.log(symbols.error,chalk.red('项目创建失败'));完整的例子handlebars');constinquirer=require('inquirer');constora=require('ora');constchalk=require('粉笔');constsymbols=require('log-symbols');program.version('1.0.0','-v,--version').command('init').action((name)=>{if(!fs.existsSync(name)){inquirer.prompt([{name:'description',message:'Pleaseenteradescription'},{name:'author',message:'请输入作者姓名'}]).then((answers)=>{constspinner=ora('正在下载模板...');spinner.start();download('http://xxxxxx:9999:HTML5/H5Template#master',name,{clone:true},(err)=>{if(err){spinner.fail();console.log(symbols.error,chalk.red(err));}else{spinner.succeed();constfileName=`${name}/package.json`;constmeta={name,description:answers.description,author:answers.author}if(fs.existsSync(fileName)){constcontent=fs.readFileSync(fileName).toString();constresult=handlebars.compile(content)(meta);fs.writeFileSync(文件名,结果);}console.log(symbols.success,chalk.green('项目初始化完成'));}})})}else{//错误提示项目已经存在,避免覆盖原来的Projectconsole.log(symbols.error,chalk.red('Theprojectalreadyexists'));}})program.parse(process.argv);效果如下:完成后可以将脚手架发布到npm,通过-g进行全局安装,可以在自己的机器上执行okiiinit[name]来初始化项目,这样就完成了一个简单的脚手架工具。更多文章:lin-xin/blog