在实际开发过程中,我们经常会使用脚手架搭建前端工程项目。常见的主流框架都有自己的脚手架,比如vue-cli、create-react-app、angular-cli。在大公司,会有内部定制的脚手架开发工具。使用脚手架可以大大提高工程建设的速度。通过命令行交互,您可以选择您需要的配置和集成,快速完成初始项目的创建。既然已经用了那么多脚手架来创建项目,何不自己实现一套属于自己开发习惯的脚手架呢。本文将从零开始搭建一套脚手架开发工具。什么是脚手架脚手架是一种快速形成工程目录的工具(command-line-interface,简称:CLI)。简单的说,脚手架就是一个帮助你减少“为了重复工作而重复工作”的工具。根据我们日常使用的脚手架,我们知道一个脚手架至少需要实现以下功能:有不同的命令执行操作,例如:Commands:create[options]add[options][pluginOptions]invoke[options][pluginOptions]inspect[options][paths...]servebuildui[options]init[options]config[options][value]过时[options]upgrade[options][plugin-name]migrate[options][plugin-name]infohelp[command]可以通过命令行交互执行问答列表吗?请选择一个预设:?Default([Vue3]babel,eslint)Default([Vue3]babel,eslint)Default([Vue2]babel,eslint)手动选择features最后根据用户的问答结果,生成相应的文件。必备知识库在完善脚手架施工之前,有必要介绍一下脚手架施工中必须要用到的一些工具库。Commander可以自定义一些命令行指令。当输入自定义命令行时,会执行相应的操作。查询器可以在命令行向用户提问,并可以记录用户的回答结果。fs-extra是fs的扩展,提供了很多方便的API,并且继承了fs的所有方法,并增加了对fs方法的promise支持。chalk可以美化终端的输出figlet可以在终端输出logoora控制台的加载动画download-git-repo下载远程模板实现过程createscaffolding新建文件夹,执行npminit生成package.json文件。npminit-y创建一个bin文件夹,bin文件夹存放主要命令程序入口文件,并在该文件夹下新建一个index.js文件,目录结构如下:fe├─bin│├─index.js└─package.jsonpackage.json中指定命令的入口文件是新建的index.js,bin下的fe是程序的快速启动命令。{"name":"fe","version":"1.0.0","main":"index.js","bin":{"fe":"./bin/index.js"},...}修改入口文件index.js为如下内容。该文件以#!开头,表示该文件作为可执行文件执行,可以作为脚本运行。下面的/usr/bin/envnode表示这个文件是用node执行的,根据用户安装根目录下的环境变量查找node:#!/usr/bin/envnodeconsole.log('hellofecli')最后将当前命令链接到全局,可以测试npm链接是否正常。这时在命令行输入刚才的快速启动命令fe,打印的日志就输出了。说明index.js中的代码已经执行完毕,接下来就要正式搭建脚手架的功能了。这里我们可以扩展输出样式。当然,这不是必须的。在安装依赖包时,经常会看到不同颜色的输出和LOGO。主要使用粉笔和figlet。示例代码如下:font:'Standard',horizo??ntalLayout:'default',verticalLayout:'default',width:80,whitespaceBreak:true})));console.log(`\r\nRun${chalk.cyan(`fe<命令>--help`)}给定命令的详细用法\r\n`)})最终输出样式如下,其他具体LOGO样式和字体颜色请参考官方文档。脚手架的功能是基于指挥官执行自定义命令而完善的。具体使用请参考相关文档。下面实现一个简单的create命令,传入参数,打印日志。#!/usr/bin/envnodeconst{Command}=require('commander');constprogram=newCommand();program.name('fecli').description('这是描述文本').version('1.0.0');program.command('create').description('Createanewproject').action((name)=>{console.log('[projectname]>',name)});program.parse(process.argv);通过command定义创建命令,要求传入参数名,然后通过action回调获取传入的参数,然后通过输入的名称创建项目用户。接下来进入创建文件的过程。创建文件时,需要检查当前目录是否已经存在,如果存在,是否需要进行覆盖操作。constpath=require('path')constfs=require('fs-extra')//当前命令行执行的目录constcwd=process.cwd();//要创建的目录consttargetPath=path.join(cwd,name)//目录是否存在if(fs.existsSync(targetPath)){//强制创建if(options.force){}else{//询问用户是否强制创建}}else{//目录不存在正常创建}上面代码说明options.force命令参数可以直接进入强制创建过程。如果有其他想法,想通过不同的指令做不同的处理,可以参考其他自定义的强制创建逻辑。上面提到,询问用户是否强制创建,需要通过命令行与用户进行交互,获取用户选择的信息。inquirer包可以在命令行向用户提问,并得到结果用于后续的逻辑交互。let{action}=awaitinquirer.prompt([{name:'action',type:'list',message:'目录已经存在,请选择:',choices:[{name:'overwrite',value:'overwrite'},{name:'Cancel',value:false}]}])if(!action){return;}elseif(action==='overwrite'){//删除现有目录awaitfs.remove(targetPath)}接下来继续询问选择哪个模板。本github提供了相关接口,你也可以维护模板配置的相关接口。核心是让用户选择自己需要的模板。比如模板里面有vue、react、smalltemplates。针对程序或框架模板等不同场景,用户选择模板并获取对应的远程模板地址进行下载。这里将使用两个新的npm包。ora可以在控制台输出加载动画,download-git-repo执行下载远程模板。示例代码如下:constspinner=ora('远程仓库开始下载...');spinner.start();//本地存储地址consttmp=path.join(process.cwd(),'templates','项目目录名',);//删除已有路径if(fs.existsSync(tmp)){awaitfs.remove(tmp)}//下载远程模板download('远程仓库地址',tmp,{clone:true,},(err)=>{if(err){spinner.fail('[远程仓库下载失败]')logger.console.error();}spinner.succeed('[远程仓库下载成功]')})操作过程如下:如果只是做一个简单版的脚手架,下载模板,这里几乎是一样的。选择对应的框架,下载对应的模板。但是,在实际开发过程中往往不够。不同的框架会有不同的配置,比如使用vue框架时是否自动添加vuex、vue-router等。因此,在不同的模板中,我们需要进行下一步的用户查询配置,根据用户的回答结果,创建更适合的项目文件。这部分可以参考Vue-cli的源码,在不同的模板项目中添加相关的查询配置。比如在vue模板中添加如下提示配置:"name":{"type":"string","required":true,"message":"填写项目名称"},"description":{"type":"string","required":false,"message":"填写项目描述","default":"ZZZ前端vue项目"},...继续执行项目查询下载文件后的配置列表,效果如下:最后根据获取到的用户回答结果生成工程文件,比如直接将获取到的工程名填入模板内容中,需要增加使用vuex根据用户的选择。这一块主要使用了以下依赖。Metalsmith静态网站(博客、项目)generatorhandlerbars模板编译器,通过template和json,输出一个htmlconsolidate模板引擎集成库在vue_cli源码中注册了2个渲染器,类似于vue中的v-ifv-else条件渲染,我们可以扩展其他条件根据实际需要绘制。//注册把手helperHandlebars.registerHelper('if_eq',function(a,b,opts){returna===b?opts.fn(this):opts.inverse(this)})Handlebars.registerHelper('unless_eq',function(a,b,opts){returna===b?opts.inverse(this):opts.fn(this)})使用模板文件中对应的标识,结合模板编译器动态生成内容,如果对具体的构建细节感兴趣,可以查看源码或者其他文章进行详细分析。{{#if_equserSelectVuex'vuex'}}importVuexfrom'vuex'Vue.use(Vuex){{/if_eq}}列出这些是为了说明如果我们要修改脚手架生成的模板内容,可以直接找到Add修改对应的配置,在用户查询部分添加需要的场景字段,获取对应的值,进行模板内容的动态处理。综上所述,我们实现了一个简单的开发脚手架,自定义快捷命令,模板选择,根据不同模板选择下一步配置,最后生成工程文件。这只是一个简单的实现。查看vue脚手架的命令,可以看到还有很多功能点可以进一步完善。有兴趣的同学可以深入研究。本文到此结束。如果您看完这篇文章觉得有用,记得点赞支持哦。说不定哪天会用到~专注前端开发,分享前端相关技术。公众号:南城大学:nanchengfe)