脚手架vue-cli、create-react-app、react-native-cli等都是优秀的脚手架。通过脚手架,我们可以快速初始化一个Projects,无需从头开始一步步配置,有效提升开发体验。这些脚手架虽然很优秀,但不一定适合我们的实际应用。我们可以定制一个自己的脚手架(或者公司通用的脚手架)来提高我们的开发效率。更多优质文章可以戳:https://github.com/YvetteLau/...脚手架的作用是减少重复性工作,不用复制其他项目删除不相关的代码,或者从头开始创建项目和文件。可以根据交互动态生成项目结构和配置文件。多人协作更方便,无需来回传递文件??。实现的功能在开始之前,我们需要明确我们的脚手架需要哪些功能。vueinittemplate-name项目名称,create-react-app项目名称。我们这次写的脚手架(eos-cli)有如下能力(不管脚手架叫什么,我选Eos黎明女神):eosinittemplate-nameproject-name根据远程模板初始化一个项目(remotetemplateConfigurable)eosconfigset修改配置信息eosconfigget[]查看配置信息eos--version查看当前版本号eos-h可以自行扩展其他命令。本文旨在教大家如何实现一个脚手架。效果展示初始化一个项目修改.eosrc文件,从vuejs-template下载模板需要用到的第三方库babel-cli/babel-env:语法转换command:命令行工具download-git-repo:used下载远程模板ini:格式转换查询器:交互式命令行工具ora:显示加载动画粉笔:修改控制台输出内容样式log-symbols:显示√或×等图标对于这些第三方库的说明,可以直接查看npm上的相应说明,这里不一一展开。初始化项目创建一个空项目(eos-cli),使用npminit初始化它。安装依赖npminstallbabel-clibabel-envchalkcommanderdownload-git-repoiniinquirerlog-symbolsora目录结构├──bin│└──www//可执行文件├──dist├──...//Generatefile└──src├──config.js//管理eos配置文件├──index.js//主进程入口文件├──init.js//初始化命令├──main.js//入口File└──utils├──constants.js//定义常量├──get.js//获取模板└──rc.js//配置文件├──.babelrc//babel配置文件├──package.json├──README.mdbabel配置开发使用ES6语法,babel转义,.bablerc{"presets":[["env",{"targets":{"node":"current"}}]]}eos命令Node.js内置了对命令行操作的支持,package.json中的bin字段可以定义命令名和关联的执行文件。在package.json中添加bin字段package.json{"name":"eos-cli","version":"1.0.0","description":"scaffolding","main":"index.js","bin":{"eos":"./bin/www"},"scripts":{"compile":"babelsrc-ddist","watch":"npmruncompile----watch"}}www在文件开头添加一行#!/usr/bin/envnode指定当前脚本由node.js解析#!/usr/bin/envnoderequire('../dist/main.js');链接到全局为了环境开发时方便调试,在当前eos-cli目录下执行npmlink,将eos命令链接到全局环境。启动项目npmrunwatch处理命令行,使用commander处理命令行。mainimportprogramfrom'commander';import{VERSION}from'./utils/constants';importapplyfrom'./index';importchalkfrom'chalk';/***eoscommands*-config*-init*/letactionMap={init:{description:'从模板生成新项目',usages:['eosinittemplateNameprojectName']},config:{alias:'cfg',description:'config.eosrc',usages:['eosconfigset','eosconfigget','eosconfigremove']},//其他命令}//添加init/config命令Object.keys(actionMap).forEach((action)=>{program.command(action).description(actionMap[action].description).alias(actionMap[action].alias)//别名.action(()=>{switch(action){case'config'://配置apply(action,...process.argv.slice(3));休息;case'init':apply(action,...process.argv.slice(3));休息;默认值:中断;}});});functionhelp(){console.log('\r\nUsage:');Object.keys(actionMap).forEach((action)=>{actionMap[action].usages.forEach(usage=>{console.log('-'+usage);});});console.log('\r');}program.usage('[options]');//eos-hprogram.on('-h',help);program.on('--help',help);//eos-VVERSION为package.json中的版本号program.version(VERSION,'-V--version').parse(process.argv);//eos不带参数.argv.slice(2).length){program.outputHelp(make_green);}functionmake_green(txt){returnchalk.green(txt);}下载模板download-git-repo支持从Github和Gitlab仓库远程下载到本地get.jsimport{getAll}来自'./rc';从'download-git-repo'导入downloadGit;导出constdownloadLocal=async(templateName,projectName)=>{letconfig=awaitgetAll();让api=`${config.registry}/${templateName}`;returnnewPromise((resolve,reject)=>{//projectName为下载的本地目录downloadGit(api,projectName,(err)=>{if(err){reject(err);}resolve();});});}initcommand命令行交互用户执行init命令后,向用户提出问题,接收用户的输入并进行相应的处理。查询器实现命令行交互:inquirer.prompt([{name:'description',message:'请输入项目描述:'},{name:'author',message:'请输入作者姓名:'}]).then((answer)=>{//...});用户进入后视觉美化开始下载模板,此时使用ora提示用户正在下载模板,下载完成后也会给出提示。importorafrom'ora';letloading=ora('正在下载模板...');loading.start();//downloadloading.succeed();//orloading.fail();index.jsimport{downloadLocal}from'./utils/get';importorafrom'ora';importinquirerfrom'inquirer';importfsfrom'fs';importchalkfrom'chalk';importsymbolfrom'log-symbols';letinit=async(templateName,projectName)=>{//项目不??存在if(!fs.existsSync(projectName)){//命令行交互inquirer.prompt([{name:'description',message:'请输入项目描述:'},{name:'author',message:'请输入作者姓名:'}]).then(async(answer)=>{//下载模板选择模板//通过配置文件获取模板信息letloading=ora('下载模板...');loading.start();downloadLocal(templateName,projectName).then(()=>{loading.succeed();constfileName=`${projectName}/package.json`;if(fs.existsSync(fileName)){constdata=fs.readFileSync(fileName).toString();让json=JSON.parse(数据);json.name=项目名称;json.author=answer.author;json.description=answer.description;//修改项目文件夹下的package.json文件fs.writeFileSync(fileName,JSON.stringify(json,null,'\t'),'utf-8');console.log(symbol.success,chalk.green('项目初始化完成!'));}},()=>{loading.fail();});});}else{//项目已经存在console.log(symbol.error,chalk.red('项目已经存在'));}}module.exports=init;configconfigureeosconfigsetregistryvuejs-templatesconfig配置支持我们使用其他仓库的模板,比如我们可以使用vuejs-templates中的仓库作为模板这样有一个好处:更新模板不需要重新发布脚手架,用户不需要重新安装,可以自由选择下载目标。config.js//管理.eosrc文件(当前用户目录下)import{get,set,getAll,remove}from'./utils/rc';letconfig=async(action,key,value)=>{switch(action){case'get':if(key){letresult=awaitget(key);控制台日志(结果);}else{让obj=awaitgetAll();Object.keys(obj).forEach(key=>{console.log(`${key}=${obj[key]}`);})}break;案例“设置”:设置(键,值);休息;案例“删除”:删除(键);休息;默认值:中断;}}module.exports=config;rc.js.eosrc文件的修改import{RC,DEFAULTS}from'./constants';import{decode,encode}from'ini';import{promisify}from'util';从'chalk'导入粉笔;从'fs'导入fs;constexits=promisify(fs.exists);constreadFile=promisify(fs.readFile);constwriteFile=promisify(fs.writeFile);//RC是配置文件//DEFAULTS是默认的配置exportconstget=async(key)=>{constexit=awaitexits(RC);让选择;如果(退出){opts=awaitreadFile(RC,'utf8');选择=解码(选择);返回选项[键];}return'';}exportconstgetAll=async()=>{constexit=awaitexits(RC);让选择;如果(退出){opts=awaitreadFile(RC,'utf8');选择=解码(选择);返回选项;}return{};}exportconstset=async(key,value)=>{constexit=awaitexits(RC);让选择;如果(退出){opts=awaitreadFile(RC,'utf8');选择=解码(选择);if(!key){console.log(chalk.red(chalk.bold('Error:')),chalk.red('keyisrequired'));返回;}if(!value){console.log(chalk.red(chalk.bold('Error:')),chalk.red('valueisrequired'));返回;}Object.assign(opts,{[键]:值});}else{opts=Object.assign(DEFAULTS,{[key]:value});}awaitwriteFile(RC,encode(opts),'utf8');}exportconstremove=async(key)=>{constexit=awaitexits(RC);让选择;如果(退出){opts=awaitreadFile(RC,'utf8');选择=解码(选择);删除选项[键];等待writeFile(RC,encode(opts),'utf8');}}发布npmpublish将这个脚手架发布到npm,其他用户可以通过npminstalleos-cli-g全局安装。您可以使用eos命令。项目地址本项目完整代码请点这里:https://github.com/YvetteLau/...虽然写这篇文章花费了一定的时间,但是在这个过程中也收获了很多。感谢您愿意花宝贵的时间阅读本文。如果这篇文章能给你一点帮助或者启发,请不要吝啬你的点赞和star。您的肯定是我前进的最大动力。https://github.com/YvetteLau/...参考文章:[1]npm依赖文档(https://www.npmjs.com/package...感谢指点:补充参考文章:【简单搭建前端-端脚手架ICE】(https://link.juejin.im/?target...推荐关注我公众号