在日常开发中,我们会根据经验沉淀一些项目模板,可以在不同的项目中复用。如果是每次都把代码copy到一个新的项目中,会比较麻烦和容易出错。这时候我们会想,能不能把一些模板集成到脚手架中(类似vue-cli,create-react-app),让我们初次创建后就可以使用了?比如我有以下两套开发模板。移动端开发模板vue-cli4-vant基于vue-cli4和vant。简单模板vue-cli4-vant基于vue-cli4和ant-design-vue。希望能实现类似aaainitbbbccc等命令,快速初始化一个项目,而不用从头开始一步步配置,大大提高开发效率。脚手架源码地址点此使用npminstallcm-vcli-gcm-h安装为什么需要脚手架?减少重复性工作,不再从头开始创建项目,或复制粘贴另一个项目的代码。基于动态交互生成项目结构和配置文件,具有更高的灵活性和人性化的定制能力。有利于多人开发协作,避免手动传输文件的繁琐。可集成多套开发模板,可根据项目需要选择合适的模板。第三方库的支持实现脚手架通常需要以下工具,后面我们会一一介绍。commander:命令行工具download-git-repo:用于下载远程模板inquirer:交互式命令行工具ora:显示加载动画chalk:修改控制台输出内容样式log-symbols:显示√或×handlebars等图标。js用户提交的信息动态填充到文件中。构建步骤新建一个文件夹,命名为cm-cli(以我的脚手架命名),在该目录下执行npminit-y进行初始化,此时会产生一个package.json文件。安装第三方工具库npminstallchalkcommanderdownload-git-repoinquireroralog-symbols在根目录下创建一个bin文件夹,在bin目录下创建一个不带后缀的cm文件,写入:#!/usr/bin/envnodeconsole.log('helloworld')这个文件是整个脚手架的入口文件,我们用node./bin/cm运行,控制台会打印出helloworld。当然,每次都要输入命令节点./bin/cm有点麻烦。我们可以在package.json中配置命令"bin":{"cm":"bin/cm"}此时我们执行npmlink将命令挂载到全局,然后输入cm即可达到node的效果./bin/cm刚才。定义多条命令我们在bin下的cm文件夹下定义多条命令,此时使用commander。首先我们看一下commander的用法usage():设置用法值command():定义一个命令名description():设置描述值option():定义参数,需要设置“keyword”和“描述”,关键字包括“shorthand”和“fullcase”,以“、”、“|”、“空格”分隔。parse():解析命令行参数argvaction():注册一个回调函数version():终端输出版本号根据日常开发需要,我们创建如下脚手架命令addaddaddanewprojecttemplatedeletedeleteaprojecttemplatelistlistso项目模板init初始化一个项目模板先写一个cm文件#!/usr/bin/envnodeconstprogram=require('commander')program.usage('')program.version(require('../package').version)program.command('add').description('addanewtemplate').action(()=>{require('../commands/add')})program.command('delete').description('删除模板').action(()=>{require('../commands/delete')})program.command('list').description('列出模板列表').action(()=>{require('../commands/list')})program.command('init').description('initaproject').action(()=>{require('../commands/init')})program.parse(process.argv)然后执行cm-h,你会看到如下effect这个时候我们来更改package.json的配置"bin":{"cm-add":"bin/cm-add","cm-delete":"bin/cm-delete","cm-list":"bin/cm-list","cm-init":"bin/cm-init"}然后执行npmunlink解绑全局命令,然后执行npmlink将命令重新绑定到全局,这样就可以直接使用cmadd等命令写指令了。在这里,查询器将用于命令行交互。我们先看看查询器的用法。它有以下几个参数来配置type:表示问题的类型,包括:input,confirm,list,rawlist,expand,checkbox,password,editor;name:存储当前问题答案的变量;message:问题的描述;default:默认值;choices:列出选项,在某些类型下可用,并包含分隔符;validate:验证用户的回答;filter:过滤用户的回答并返回处理后的值;when:根据上一题的答案判断当前题是否需要回答;prefix:修改消息的默认前缀;后缀:修改消息的默认后缀。语法结构如下:constinquirer=require('inquirer')constquestion=[//具体交互内容]inquirer.prompt(question).then((answers)=>{console.log(answers)//returnedresult})cmadd添加一个新的项目模板通过命令行交互,让用户输入模板名称和模板地址添加用户输入的模板信息,并写入到template.json文件中打印出所有项目模板看一下代码#!/usr/bin/envnodeconstinquirer=require('inquirer')constfs=require('fs')consttemplateList=require(`${__dirname}/../template`)const{showTable}=require(`${__dirname}/../util/showTable`)constsymbols=require('log-symbols')constchalk=require('chalk')chalk.level=1letquestion=[{name:'name',type:'input',message:'Pleaseenteratemplatename',validate(val){if(!val){return'名称是必需的!'}elseif(templateList[val]){return'模板已经存在!'}else{returntrue}}},{name:'url',type:'input',message:'请输入模板地址',validate(val){if(val==='')return'Theurl是必须的!'返回true}}]查询r.prompt(question).then((answers)=>{let{name,url}=answerstemplateList[name]=url.replace(/[\u0000-\u0019]/g,'')//过滤unicodecharfs.writeFile(`${__dirname}/../template.json`,JSON.stringify(templateList),'utf-8',(err)=>{if(err)console.log(chalk.red(symbols.error),chalk.red(err))console.log('\n')console.log(chalk.green(symbols.success),chalk.green('添加模板成功!\n'))console.log(chalk.green('最新的templateList是:\n'))showTable(templateList)})})这里还使用了下面两个第三方库来美化交互效果:chalk:用于修改控制台输出内容样式,如颜色日志符号:显示√或×等图标。这时执行cmadd,输入项目模板的名称和地址,可以看到如下效果。cmdelete删除一个项目模板,这个很容易理解,步骤如下通过命令行交互,让用户输入要删除的项目模板名称,删除用户输入的模板数据,然后写入更新数据到template.json文件中,打印出所有项目模板代码如下#!/usr/bin/envnodeconstinquirer=require('inquirer')constfs=require('fs')consttemplateList=require(`${__dirname}/../template`)const{showTable}=require(`${__dirname}/../util/showTable`)constsymbols=require('log-symbols')constchalk=require('chalk')chalk.level=1letquestion=[{name:'name',message:'请输入要删除的模板名称',validate(val){if(!val){return'名字是必需的!'}elseif(!templateList[val]){return'模板不存在!'}else{returntrue}}}]inquirer.prompt(question).then((answers)=>{let{name}=answersdeletetemplateList[name]fs.writeFile(`${__dirname}/../template.json`,JSON.stringify(templateList),'utf-8',(err)=>{if(err)console.log(chalk.red(symbols.error),chalk.red(err))console.log('\n')console.log(chalk.green(symbols.success),chalk.green('删除成功!\n'))console.log(chalk.green('最新的templateList为:\n'))showTable(templateList)})})此时,我们执行cmdelete,输入要删除的模板,可以看到如下效果。cmlist列出所有工程模板,这个更简单,直接输入代码#!/usr/bin/envnodeconst{showTable}=require(`${__dirname}/../util/showTable`)consttemplateList=require(`${__dirname}/../template`)showTable(templateList)此时我们执行cmlist,输入要删除的模板,可以看到如下效果。cminit初始化一个项目模板,这是最重要的部分,步骤如下通过命令行交互,让用户模板名和项目名验证模板是否存在,项目名是否为填写完毕,开始下载模板,显示loading图标完成模板下载,隐藏loading图标来看看代码#!/usr/bin/envnodeconstprogram=require('commander')constora=要求('ora')constdownload=require('download-git-repo')consttemplateList=require(`${__dirname}/../template`)constsymbols=require('log-symbols')constchalk=require('chalk')chalk.level=1program.usage('[project-name]')program.parse(process.argv)//没有输入参数时给出提示if(program.args.length<1)returnprogram.help()//第一个参数是webpack,第二个参数是project-namelettemplateName=program.args[0]letprojectName=program.args[1]if(!templateList[templateName]){console.log(chalk.red('\n模板没有退出!\n'))return}if(!projectName){console.log(chalk.red('\nProjectshouldnotbeempty!\n'))return}leturl=templateList[templateName]console.log(url)缺点ole.log(chalk.green('\nStartgenerating...\n'))//出现加载图标constspinner=ora('Downloading...')spinner.start()download(`direct:${url}`,`./${projectName}`,{clone:true},(err)=>{if(err){spinner.fail()console.log(chalk.red(symbols.error),chalk.red(`Generationfailed.${err}`))return}//结束加载图标spinner.succeed()console.log(chalk.green(symbols.success),chalk.green('Generationcompleted!'))console.log('\n开始')console.log(`\ncd${projectName}\n`)})这里使用download-git-repo下载远程模板,用法如下后面constdownload=require('download-git-repo')download(repository,destination,options,callback)repository是远程仓库地址destination是存放下载的文件路径,也可以直接写文件名,default是当前目录options是一些选项,比如{clone:boolean}表示以http下载或gitclone的形式下载。callback是一个回调函数。此时我们执行cminitappdemo,可以看到如下效果。根目录下多了一个demo文件夹,就是新建的Pulledprojecttemplates。至此,一个前端脚手架正式搭建完成。让我们在下面的npm上发布它。发布npm发布流程执行npmlogin登录npm账号。如果没有账号,先注册,然后执行npmpublish发布。发布到npm的脚手架名称是package.json的名称值。需要注意的是发布名称不能重复。发布后,我们来验证一下。执行npmunlink解绑全局命令执行npminstallcm-vcli-g全局安装脚手架执行cm-h这时候如果看到如下效果,说明脚手架已经释放安装成功。