前端脚手架开发入门

为什么需要脚手架

脚手架本质上是一个工具,使用脚手架的目的就是摆脱构建工程时重复性的工作,尤其是当一个工程具有一定通用性时,工程脚手架的意义就更为突出。它可以让我们只需要一行命令,就可以初始化好一项工程,而不用费心费力的去做配置环境,安装依赖,解决依赖冲突这样的支线任务,可以直奔主线任务,早早下班~~

概览

开发一个的脚手架,通常需要如下npm包:

commander: 强大的node命令行处理工具。能轻松的获取命令行的参数。

inquirer: 命令行交互工具,让你能以“问答”的交互方式来完成一系列的命令行操作。

download-git-repo: git仓库下载工具,通常用来下载模板代码。

ora: 终端loading美化工具。

chalk: 命令行输入/输出美化工具,想要五彩版本的命令行,选它就对了。

handlebars: 模板引擎

实现的功能:

一条简单的命令初始化项目

提供友好的交互体验

可选择安装不同模板

自动安装项目依赖

开始干活

STEP1: 打开一个终端,在你喜欢的地方新建一个空项目

mkdir meo-clicdmeo-cli复制代码

在项目更目录下执行:

npm init -y复制代码

你会得到一个package.json

{"name":"meo-cli","version":"1.0.0","description":"","main":"index.js","scripts": {"test":"echo \"Error: no test specified\" && exit 1"},"keywords": [],"author":"","license":"ISC"}复制代码

关键点来了,在 package.json中加以一个bin字段,在安装时,npm 会将文件符号链接到 prefix/bin 以进行全局安装或./node_modules/.bin/本地安装。这样,就可以全局使用了。例如,下面的将meo-cli作为命令名称,执行文件是根目录的index.js

{"name":"meo-cli","version":"1.0.0","description":"","main":"index.js","bin": {"meo-cli":"index.js"},"scripts": {"test":"echo \"Error: no test specified\" && exit 1"},"keywords": [],"author":"","license":"ISC"}复制代码

STEP2: 先写一个简单代码试一试吧~。在根目录下创建index.js文件,然后狠狠敲入下面的代码:

#!/usr/bin/env nodeconsole.log('helo, world!')复制代码

然后在命令行执行meo-cli, 不出意外的话,你应该看不到输出hello,world。只会有个报错提示:

zsh:commandnot found: meo-cli复制代码

这是为什呢?因为我们并没有安装对应的包,当然就不会链接到全局啦。一个办法是发包,然后安装到本地,就可以了。但是这样太麻烦,难道每次调试都要发包??

有没有更优雅的方法呢?答案是:有 npm早就想好了对策,就是npm link,它可以把指定的执行文件链接到全局,使用也非常简单,在项目根目录下执行:

npm link复制代码

就可以了。如果你执行命令后,显示类似安装npm包的提示,就说明链接成功了。在执行一下meo-cli,就可以看到令人欣慰的hello world了。这里要注意一下首行的代码

#!/usr/bin/env node复制代码

这段并不是注释,这段代码是告诉你的脚本工具(bash/zsh), 下面的内容是要在node环境下运行的代码。千万不能省略!!!进行下一步之前,让我们下来回顾一下vue-cli脚手架是怎么使用的。

前端脚手架开发入门_第1张图片

可以看出来,脚手架会提供一个问答交互的方式,让使用者输入或选择参数,然后根据获取的参数做出相应的动作(action)。

STEP3: 交互式脚手架的另一个特点是灵活,使用者可以根据自己的需求,可以清晰的去选择让脚手架做什么,不做什么。要实现这样的功能,就是要用到开头提到的插件:

commander

inquirer

commander是可以用来获取命令行的参数,并对参数做响应函数,inquirer则可以为我们提供一个’问答’式的交互体验。我们修改一下index.js的代码:

// index.js#!/usr/bin/env nodeconst{ program } =require('commander');program.version('1.0.0')program.parse(process.argv)复制代码

然后我们在命令行输入:

meo-cli -V  // 1,0,0复制代码

就会得到版本号信息。输入

meo-cli -h复制代码

前端脚手架开发入门_第2张图片

就会得到这样的帮助信息。

这和我们使用其他脚手架的体验是一样的。

这样的体验要归功于commander.js

commander用法如下:.command(‘init [name]’, ‘init a project’, opts)功能:注册一个命令

第一个参数:设置的命令的名称,后面可以跟参数,<> 表示必选参数,[]表示可选参数

第二个参数: 命令的描述,可选,注意,当有第二个参数时,不能显示的调用action作为命令的回调,需要使用独立的可执行文件作为命令

第三个参数:配置参数,如noHelp,isDefault等 .option(’-n, --name | [name]’, ‘desc’, ‘GK’)

功能:定义命令选项,(类似命令的额外参数, 用于辅助命令)

.description(‘this is a command desc’)

功能:命令的描述, 同时会应用到命令的帮助信息中,使用help命令时会显示

.action(cb):命令的回调函数

.parse() 命令行参数解析, 通常用于最后 e.g.

前端脚手架开发入门_第3张图片
前端脚手架开发入门_第4张图片

上面的代码,定义了一个命令init, 对命令做了描述(description), 并对init命令做了额外参数-t, 表示初始化工程的类型。action是init命令的回调,在回调用打印了参数,即我们定义一个工程,名字为demo, 工程类型为vue。这样后续工作中就可以用工程名拼接下载模板到本地的路径,type=vue 代表我们会去拉取vue的模板。STEP4: ok, 我们已经可以通过自定义命令获取到参数,接下来就可以拉取对应模板了。这时我们就需要使用download-git-repo这个包,它可以用来下载github, gitlab等远程仓库的代码。用法如下:

download(repository, destination, options, callback)复制代码

repository: 远程仓库的地址。destination:下载到本地的路径 options: 配置参数 callback: 回调函数 需要注意一下,远程仓库地址可以写成:

direct: http://github.com/xxx复制代码

即需要完成的仓库路径,默认情况下,会下载master分支,如果要下载其他分支,在链接后加入分支名,

direct:http://github.com/name/xxx.git#my-branch复制代码

小技巧我们可以将模板分配到不同的分支,然后通过分支来下载不同模板。回调函数中会返回下载的结果,根据结果可以做出不同状态的处理。例如,根据返回状态判断是否下载成功,下载成功后提示是否要进行其他操作(安装项目依赖,后面会提到哦~)

到此,一个初始化工程的脚手架就完成了。是的,真没骗人,命令行执行指定命令,获取参数,拉取模板代码,就这些~~。

但是, 还可以做更多。

通常我们在初始化一个项目时,会提示填写项目名称(已经做了),项目描述,作者等信息,这些都是使用者输入的,也就是命令行的参数,而且会提供一个更友好的“你问我答” 的方式教你做事,不, 求你填写信息。这时候就是inquirer上场的时候了,它通过极为简单函数方式来提供交互操作。例如,我们要提示使用者输入项目描述和作者信息,就可以这样写,在action回调函数中

// ...inquirer.prompt([        {name:'description',message:'请输入项目描述'},        {name:'author',message:'请输入项目作者',default:'robot'}    ])    .then((res) =>{})复制代码

name表示输入的键名,输入的值为value, 即结果会以键值对的形式返回。default为默认值,当直接回车跳过时,会使用默认值。如果希望默认值是空,可以写成default:''或省略default。

inquirer.prompt() 的参数是一个对象数组,可以这样理解,inquirer是流程化的结构,流程可以是等待输入,列表选择,confirm确认yes or no。例如选择项目模板是,使用者可以从提供的模板列表中选择,而不是自己去输入,就可以这样定义参数:

{name:'type',type:'list',message:'choose a type of project to init',choices: ['react','vue','h5'],default:'react',    }复制代码

type表示类型,choices为列表数组,使用者可以从react,vue,h5中选择模板,默认会是react模板。

>need-to-insert-img

然后根据type的值去拼接git仓库地址,下载对应模板。

更多inquirer的用法,大家可以参考github.com/SBoudrias/I…来使用,这里不再赘述。

STEP5: 我们已经通过交互方式拿到了项目描述,作者等信息,但是我们的目的是将这些信息写入到下载的模板中,也就是package.json中对应的description,author以及项目名称name中。这要怎么做呢?这就需要handlebars.js的帮助了,handlebars是一个强大的模板引擎,它可以解析指定模板,然后根据参数渲染模板。因为我们要将name, description, author写入到package.json中,因此我们要稍微修改一下模板文件:

前端脚手架开发入门_第5张图片

如图,将要填写的字段用{{}} 方式表示,内容就是对应要写入的变量名字,这和inquirer交互时拿到的字段要保持一致。当然,handlebars不止这么简单,更多的用法可以参考官网handlebarsjs.com/guide/

这样,我们就可以在模板下载完成后做写入工作了。

download(url,'./template', {clone:true},(error) =>{if(!error) {constpackagePath = path.join(downloadPath,'package.json');// 判断是否有package.json, 要把输入的数据回填到模板中if(fs.existsSync(packagePath)) {constcontent = fs.readFileSync(packagePath).toString();// handlebars 模板处理引擎consttemplate = handlebars.compile(content);constresult = template(param);        fs.writeFileSync(packagePath, result);            }else{console.log('failed! no package.json');    }  }})复制代码

这样在下载成功后,并且有package.json时才会去写入,否则给出错误提示。

然而…

我们发现,到目前为止,我们的命令行的输入一点也不好看,没有下载中的提示,没有五彩斑斓醒目的文字… ora 和 chalk这时就起作用了。ora可以美化命令行的loading,你是转圈圈,动态的小点点,还是自定义的gif都可以满足你,chalk就可以让你的命令行文字有了颜色,失败的红色告警,成功的绿色提示,都没问题。

下面是拉取仓库模板的部分代码:

constdownloadPath = path.join(process.cwd(), name);constparam = {name, ...parameter};constspinner = ora('正在下载模板, 请稍后...');spinner.start();download(// 直连下载,默认下载master`direct:http://git.code.oa.com/name/xxx.git#${type}-tpl`,    downloadPath,    {clone:true},(error) =>{if(!error) {// success downloadspinner.succeed();constpackagePath = path.join(downloadPath,'package.json');// 判断是否有package.json, 要把输入的数据回填到模板中if(fs.existsSync(packagePath)) {constcontent = fs.readFileSync(packagePath).toString();// handlebars 模板处理引擎consttemplate = handlebars.compile(content);constresult = template(param);                fs.writeFileSync(packagePath, result);console.log(chalk.green('success! 项目初始化成功!'));console.log(                  chalk.greenBright('开启项目') +'\n'+                  chalk.greenBright('cd '+ name) +'\n'+                  chalk.greenBright('start to dvelop~~~!')              )                            }else{                spinner.fail();console.log(chalk.red('failed! no package.json'));return;            }        }else{console.log(chalk.red('failed! 拉取模板失败', error));return;        }    })复制代码

到此为止,一个基础的工程脚手架就完成了,它可以通过简单一条命令,根据输入的参数,拉取不同模板代码,并将用户信息回填到模板中,提供了交互式的友好体验。嗯~,挺香的,收工喽!!

cdvue-demonpm run serve复制代码

但是… 我们会发现一个问题,使用vue-cli的时候,在最后会有个运行提示:

它并没有提示我们要npm install, 这说明下载模板的时候项目的依赖也同时安装。可我们的没有这个功能,不行,搞起!!STEP6: 模板下载好后,我们要进入模板目录,然后根据它的package.json安装依赖,这里我们可以丰富一下,让使用者在安装依赖时有选择:1. 先不安装依赖,稍后自行安装, 2. 选择安装工具,这时最后的提示要给个npm intall的提示才算完美。是否安装依赖。即我们需要从使用者那里得到一个confirm, 根据返回的true或false来决定是否进行下一项安装。选择安装工具。如果使用者选择安装,就要提示他选择安装工具。这两个交互同样可以使用inquirer来完成

constcontinueToInstall  = {type:'confirm',name:'next',message:'continue to install the project',default:true,}constinstallTool = {name:'tool',type:'list',message:'choose the tool to install',choices: ['npm','tnpm','yarn'],default:'npm',}复制代码

这里做了简单的封装:

const{ next } =awaitinquirer.prompt(continueToInstall);if(next) {const{ tool } =awaitinquirer.prompt(installTool);// 安装项目依赖constres =awaitinstallFunc({cwd: downloadPath,command: tool });if(res && res.code ===0) {        processSuccess(name,true, type);    }}else{    processSuccess(name,false, type);}复制代码

至此,一个简单的工程脚手架就完成了。

小结

本篇文章按照step by step的方式,在每一步详细的阐述了开发脚手架过程中使用的工具和注意事项。

本篇文章阐述的方法只实现了基础功能,好的脚手架还可以做更多,如集成单元测试,第三方库的选择安装,项目打包发布等。更多符合项目特点的脚手架还需要根据实际项目去开发。

你可能感兴趣的:(前端脚手架开发入门)