从零开始搭建前端脚手架


一、功能设计

每个前端小组都会有自己的独特的业务场景,从这些业务场景从提取公共部分,并打造一个前端项目模版,是非常有必要的

为了能够基于这个项目模版快速创建一个新项目,就需要脚手架工具登场

所以这里至少有两个项目仓库:前端模版项目、脚手架工具

而对于脚手架工具,它应当具备这样的功能:输入一个命令和项目名称,创建对应的项目目录,其内容就是模版项目

my-cli create my-project

基于此,脚手架工具的内部逻辑也就很清晰了:

创建一个 create 命令,其行为是拉取模版项目的代码(用 git clone 即可实现)

为了实现这个命令,我们需要借助一个神奇的工具:commander

 

 

二、Commander

Commander.js 是完整的 node.js 命令行解决方案

可以通过它创建一个 program 对象,作为程序的主体

const { Command } = require('commander'); const program = new Command(); program.version('0.0.1');

commander 常用的功能有创建选项和命令


选项 option 需要基于短线 " - " 声明,常见的  -h 、 -V  都是选项

每个选项可以定义一个短选项名称(" - " 后面接单个字符)和一个长选项名称(" -- "后面接一个或多个单词)

解析后的选项可以通过 .opts() 方法获取,从而执行对应的操作

比如创建一个 -d 选项:

program .option('-d, --debug', 'output extra debugging') program.parse(process.argv); const options = program.opts(); if (options.debug) console.log(options);

还可以对选项定义参数及默认值:

program .option('-d, --debug <type>', 'output extra debugging', 'hard')

这里就对 debug 选项定义了 type 参数,并设置其默认值为 hard

用尖括号   <>  定义的参数为必填,用中括号   []  创建的参数为选填


命令 command 可以通过  .command()  创建,并通过链式调用 .description() 和 .action() 定义该命令的描述和具体行为

program .command('create <name> [type]') .description('create a new project') .action((name, type) => { console.info('project', name); });

和选项类似,命令也可以通过  <>  和  []  定义参数

除了像上面那样通过链式调用创建命令行为(description、action)之外,还可以用单文件的形式描述命令,不过我没有跑通


commander 还有更多更强大的功能,这里就不逐一介绍了,详情可以参考中文文档

 

 

三、正式开始

学习了 commander 之后,就可以着手脚手架的开发了 

首先创建脚手架工具的目录,如 my-cli,然后在目录下创建一个 .gitignore 文件

# Dependency directories node_modules/

接着通过  npm init 创建 package.json

创建之后可以删除其中的 main 和 scripts,然后安装 commander

npm install commander --save

然后创建 bin 目录及 bin/cli.js 文件:

#!/usr/bin/env node const { Command } = require('commander'); const { name, version } = require('../package.json'); const program = new Command(); program.name(name).version(version); program .command("create <project-name>") .description("create a new project") .action((name) => { console.info('project', name); }); program.parse();

顶部的  #!/usr/bin/env node  是告诉操作系统,用 /usr/bin 下的 node 来执行这个脚本

 

一个简单的 cli 命令就创建好了,可以回到根目录,用 node 执行 cli.js 试试:

node bin/cli.js create my-project

可以看到 create 命令正常执行 

但直接通过 node + 路径 的形式运行代码还是太死板了,可以通过  npm link 将项目挂到全局,就能像正常的脚手架工具那样用了

首先需要在 package.json 里添加 bin 命令:

{ ... "bin": { "my-cli": "bin/cli.js" } }

这里的 my-cli 是包的名称,后面的路径需要写全后缀

然后在根目录执行  npm link 就能将 my-cli 链接到全局

npm link 是一个很好的本地测试的手段,调试完成后可以通过 npm unlink 卸载

 

 

四、完善 create 命令 

据文档介绍, commander 支持将命令拆成单文件进行维护,但我没有搞出来,最后只好加了一层 map 来拆文件

在根目录下新建一个 command 目录,并创建 command/create.js 和 command/index.js 两个文件:

然后将 create 命令的相关逻辑移到 command/create.js 中

// create.js
 function createAction(name) { console.log('project', name); } const create = { alias: 'c', params: '<project-name>', description: 'create a new project', action: createAction, } module.exports = create;

并在 command/index.js 中导出

// index.js

const create = require('./create.js'); module.exports = { create, };

最后来改造 bin/cli.js

#!/usr/bin/env node const { Command } = require('commander'); const { name, version } = require('../package.json'); const commands = require('../command/index.js'); const program = new Command(); program.name(name).version(version); // 创建命令
Reflect.ownKeys(commands).map((name) => { const { params, alias, action, description } = commands[name] || {}; program.command(`${name} ${params || ''}`) .alias(alias) .description(description) .action((...args) => { typeof action === 'function' && action(...args); }) }); program.parse(process.argv);

项目的结构基本成型,接下来完善 create 命令

在文章的开头就已经分析过了,create 命令只需要做一个事情,就是将项目 clone 到当前目录

const { exec } = require("child_process"); function createAction(name) { // 这是模板项目的仓库地址
  const url = "http://github.com/xxx/template.git"; // 克隆项目
  exec(`git clone ${url} ${name}`, (error, stdout, stderr) => { if (error) { console.log(error); process.exit(); } console.log("Success"); process.exit(); }); }

如果模板项目是 github 上的项目,应该没什么大问题

而如果模板项目是小组内部的 gitlab 项目,就需要放开模板项目的访问权限,至少让小组人员都能够访问

 

 

五、优化体验

在完成了 create 命令之后,我们的脚手架工具就可以算是开发完成

但为了更好的体验,可以借助这些工具加以改造:chalkinquirer

 

1. chalk

它可以在命令行打印彩色文字:

import chalk from 'chalk'; const error = chalk.bold.red; const warning = chalk.hex('#FFA500'); console.log(chalk.blue('Hello world!')); console.log(error('Error!')); console.log(warning('Warning!'));

 

2. inquirer

这是一个让用户与命令行交互的工具

它提供了很多 api,让用户可以在程序运行的过程中输入内容,从而影响程序运行的结果

const inquirer = require('inquirer'); inquirer .prompt([ /* Pass your questions in here */ ]) .then((answers) => { console.log('success', answers); }) .catch((error) => { console.log('error'); });

在上面的 prompt 中配置需要用户输入/选择的(表单)内容

比如让用户输入项目名称:

.prompt([ { type: "input", message: "项目名称:", name: "projectName", validate: (val) => { // 对输入的值做判断
      if (!val || !val.trim()) { return chalk.red("项目名不能为空,请重新输入"); } else if (val.includes(" ")) { return chalk.red("项目名不能包含空格,请重新输入"); } return true; }, }, ])

除了这里的 input 类型外,inquirer 还提供了很多交互类型,如单选列表 list、多选 checkbox、确认项 confirm 等

 


在了解了 chalk 和 inquirer 之后,我们就可以进一步改造 create 命令

npm install inquirer chalk --save
// create.js
const inquirer = require("inquirer"); const chalk = require("chalk"); const { exec } = require("child_process"); function createProject(name) { // 这是模板项目的仓库地址
  const url = "git@github.com:wisewrong/chart-admin.git"; exec(`git clone ${url} ${name}`, (error, stdout, stderr) => { if (error) { console.log(chalk.red(error)); process.exit(); } console.log(chalk.green("Success")); process.exit(); }); } const create = { alias: "c", params: "[project-name]", description: "create a new project", action: (project) => { project ? createProject(project) : inquirer .prompt([ { type: "input", message: "项目名称:", name: "projectName", validate: (val) => { // 对输入的值做判断
                if (!val || !val.trim()) { return chalk.red("项目名不能为空,请重新输入"); } else if (val.includes(" ")) { return chalk.red("项目名不能包含空格,请重新输入"); } return true; }, }, ]) .then((answer) => { createProject(answer.projectName); }); }, }; module.exports = create;

麻雀虽小五脏俱全,这样一个简单的脚手架就开发完了

如果需要发布到 npm,可以参考我之前的文章《vue-cli 3.x 开发插件并发布到 npm》

后面我也会继续完善这个脚手架,添加更多的交互,以及展示进度条(专业挖坑,从来不填...)


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM