文章输出主要来源:拉勾大前端高新训练营(链接) 与 各技术官网。小哥哥小姐姐请不要嫌弃啰嗦,下面肯定都是干货。
1. 脚手架工具
脚手架工具可以方便地帮助我们进行项目基本结构的生成。
它是对具有相同的代码组织结构、相同开发范式、相同模块依赖、相同工具配置、相同基础代码的一类项目的快速生成工具。
常用脚手架工具:
专用型项目脚手架工具
- Vue: vue-cli
- React: create-react-app
- Angular: angular-cli
通用性脚手架工具
- Yeoman:可以通过yeoman自定义自己需要的脚手架工具
创建局部文件类型的工具
- plop: 根据具体的模板生成相应的代码文件,使用场景为快速生成新组件或模块等场景。
2. Yeoman
Yeoman是一款通用的脚手架系统,允许创建任何类型的应用程序。它是与语言无关的脚手架工具,Web、Java、Python、C#等类型的项目都可以使用Yeoman作为脚手架。
我们可以使用其他人制作的Yeoman脚手架工具,也可以定制自己的脚手架工具。
2.1 Yeoman 脚手架的使用
通过Yeoman创建的脚手架使用需要先安Yeoman,然后再安装具体的脚手架工具。最终按照具体的脚手架文档进行使用。
- 安装Yeoman:
npm install -g yo - 安装使用Yeoman编写的脚手架工具:
npm install -g generator-name,例如npm install -g generator-webapp - 使用脚手架工具创建具体的项目:
yo name例如:yo webapp
sub-generator: 除了创建工程的脚手架,Yeoman还提供了sub-generator用于创建模块或组件,例如yo angular:controller MyNewController可以为项目添加新的controller
2.2 自定义Generator
官网说,一个generator的核心就是一个node.js模块。
它一般以一个Npm包的形式出现,在创建一个generator的时候,项目名称必须以generator-name的格式进命名,generator-前缀必须有,后面name才是生成器的名称。
generator的基本项目结构为:
├───package.json
└───generators/ -------------生成器目录
├───app/ -----------------默认生成器目录
│ └───index.js ---------默认生成器实现
└───router/ --------------其他(sub-generator)生成器目录
└───index.js ---------生成器实现
在package.json中,除了项目名称必须为gererator-name的形式,keywords字段里面也必须包含yeoman-generator关键词:
{
"name": "generator-name",
"version": "0.1.0",
"description": "",
"files": [
"generators"
],
"keywords": ["yeoman-generator"],
"dependencies": {
"yeoman-generator": "^1.0.0"
}
}
创建基础generator流程与示例:
-
创建项目目录
mkdir generator-sample&cd generator-sample -
初始化项目
yarn initornpm init -
安装
yeoman-generatoryarn add yeoman-generatorornpm install yeoman-generator -
创建默认生成器目录
mkdir -p generators/app&touch generators/app/index.js,该文件将作为默认生成器的入口文件 -
Yeoman提供了一个基础的Generator类,我们可以继承它实现自己的一些行为,每次调用生成器,被添加到类prototype中的方法也都会被当做任务,执行一次,通常是按顺序执行,某些特殊的方法可能会触发特定的顺序
//generators/app/index.js const Generator = require('yeoman-generator'); module.exports = class extends Generator { // 重写构造函数,构造函数中可能会运行一些特殊的方法,例如设置重要的状态控件,而它们无法再构造函数外部执行 constructor(args, opts) { super(args, opts); } sayHello() { // 在使用脚手架生成项目时会先打印hello world,prototype中的普通方法会依次按顺序执行 console.log('hello world') } // 简单的写入文件操作 writing() { this.fs.write( this.destinationPath('temp.txt'), Math.random().toString() ) } } -
创建Npm包链接
yarn linkornpm link -
使用
yo sample即可创建项目,生成temp.txt文件。
2.3 Yeoman中与文件系统交互
Yeoman中文件操作方法基于磁盘上的两个位置上下文location contexts,它们是generator用于读取和写入操作的两个文件夹位置。分别为Destination context 目标上下文与Template context 模板上下文
2.3.1 Destination context 目标位置上下文
-
目标位置上下文中的目标是 Yeoman 脚手架生成新应用的位置。
-
目标上下文被定义在当前工作目录中的
.yo-rc.json或最近的父文件夹中的.yo-rc.json中。 -
.yo-rc.json中定义了Yeoman工程的根目录.(其中还可以配置其他选项)
通过this.destinationRoot()可以获取目标位置,通过this.destinationPath('index.js')可以自定义目标位置,但官方表明为了确保一致性,不推荐修改目标位置。
// yeoman官方例子
// Given destination root is ~/projects
class extends Generator {
paths() {
this.destinationRoot();
// returns '~/projects'
this.destinationPath('index.js');
// returns '~/projects/index.js'
}
}
2.3.2 Template context 模板位置上下文
- 模板位置上下文中的模板位置默认为generator同级目录下的
./templates/,例如your-project/generators/app/templates - 通过
this.sourceRoot()可以获取到模板位置的绝对路径 - 通过
this.templatePath('app/index.js')将子目录与模板目录拼接,获取到模板目录/子目录
// yeoman官方例子
class extends Generator {
paths() {
this.sourceRoot();
// returns './templates'
this.templatePath('index.js');
// returns './templates/index.js'
}
};
2.3.3 文件的读写
-
Yeoman在创建项目时,对预先已经存在的文件处理会比较小心,默认不会进行覆盖,而是通过命令行报出冲突并询问是否进行覆盖。
-
由于涉及到用户的交互,可能需要等待用户的输入,这就意味着对文件的写入操作需要设计为异步模式。
-
由于异步API难以使用,Yeoman提供了一个同步文件系统API,其中每个文件都被写到内存中的文件系统in-memory file system中,并且在Yeoman运行完后只写到磁盘一次
2.3.4 文件操作方法
generator中操作文件的方法都通过this.fs进行暴露,this.fs是mem-fs editor的一个对象实例,点击可查看其所有方法说明。
其中复制模板apithis.fs.copyTpl可以处理模板文件,其使用了ejs模板语法,因此在定制模板时可以采用 ejs template syntax方案
2.3.5 通过模板复制文件案例
- 定义脚手架模板
<!--generators/app/templates/index.html-->
<html>
<head>
<title><%= title %></title>
</head>
<body>
hello <%= name %>
</body>
</html>
-
使用
copyTpl复制模板并传入变量,使用方式:copyTpl(from, to, context[, templateOptions [, copyOptions]]),详情见mem-fs editor// generators/app/index.js const Generator = require('yeoman-generator'); module.exports = class extends Generator { writing() { this.fs.copyTpl( this.templatePath('index.html'), this.destinationPath('public/index.html'), // 设置目标上下文位置 { title: 'Say Hello', name: 'Tom' } ); } } -
npm linkoryarn link生成全局软链接 -
在自己的项目中使用脚手架
yo sample, 输出create public/index.html,成功创建public/index.html,内容为:<html> <head> <title>Say Hello</title> </head> <body> hello Tom </body> </html>
通过以上案例,我们已经明确如何使用模板进行创建文件,这在普通的脚手架工程里面也是非常常用的创建文件方式。
2.4 Generator运行时上下文
当前小结为在写2.5时候进行插入的,在了解命令行中与用户交互之前,我们还需要了解一下generator中的一些特定的方法。
在之前说到过添加到Generator的prototype中的方法都会作为任务,也就是说只要能通过Object.getPrototypeOf(Generator)获取到的方法都是generator的任务,在使用generator时候自动执行。其中普通的方法会按顺序依次执行,但有些特殊的方法会有特定的执行时机。
2.4.1 不会被当做任务的方法
-
私有方法不会被当做任务: 上面提到能被
Object.getPrototypeOf(Generator)获取到的方法都是generator的任务,其中有个特例为私有方法以_开头的方法不会被当做任务// yeoman官方给出的示例代码 class extends Generator { // 会被当做任务自动执行 method1() { console.log('hey 1'); } // 不会被当做任务自动执行 _private_method() { console.log('private hey'); } } -
实例方法不会被当做任务执行:
// yeoman官方给出的示例代码 class extends Generator { constructor(args, opts) { // Calling the super constructor is important so our generator is correctly set up super(args, opts) // 实例方法不会被当做任务执行 this.helperMethod = function () { console.log('won\'t be called automatically'); }; } } -
继承自父Generator的方法不会被当做任务:
// yeoman官方给出的示例代码 class MyBase extends Generator { helper() { console.log('methods on the parent generator won\'t be called automatically'); } } module.exports = class extends MyBase { // exec不会被当做任务自动执行 exec() { this.helper(); } };
2.4.2 run loop队列系统
由于Yeoman脚手架项目中可以存在多个generator,并可以进行组合compose,详情可查阅文档。在单个generator的情况下顺序执行任务是可以的,但使用组合生成器就不行了,因此使用了run loop方式。
run loop是一个具有优先级支持的队列系统,Yeoman采用了 Grouped-queue 模块进行run loop的实现。
优先级可以通过我们自行指定,如果prototype中的方法命中优先级的方法名,则会被放入到特殊的队列special queue.如果没有命中,则会被push进默认组default group。
这也解释了之前我们说的普通的任务会按顺序依次执行,特殊的任务会打破这个规则。
例:
// Yeoman官方给出的示例代码
// 定义单个单优先级任务
class extends Generator {
priorityName() {}
}
// 成组定义多个优先级任务,注意它不能使用js的class语法定义gererator
Generator.extend({
priorityName: {
method() {},
method2() {}
}
});
2.4.3 运行时默认的优先级任务
initializing: 初始化方法(检查当前项目状态,获取配置,等等)prompting: 与用户进行propt方式交互的的方法,在这个方法中可以调用this.prompt(), 下面的交互中会提及。configuring: 保存配置并配置项目(创建.editorconfig文件和其他元数据文件)default::如果方法名与优先级不匹配,它将被推到这个组。writing: 在这里编写生成器的特定文件(路由、控制器等). 由此可以知道上面内容中的writing方法也不是乱写的😂😂conflicts:处理冲突的地方(内部使用)install: 安装运行的位置(npm, bower)end: 最后的调用,可以进行清理,输出结束语等。
2.4.4 异步任务介绍
Yeoman中也支持异步任务,执行异步任务也会暂停run loop,直到异步任务结束后再继续进行。
异步任务可以通过promise的方式实现,如果promise成功则任务圆满结束,如果失败则任务也失败。
// 成功的任务
asyncTask() {
return Promise.resolve(1)
}
// 失败的任务
asyncTask() {
return Promise.reject(new Error('err'))
}
除此之外还可以借助this.async()生成回调,在任务结束时候执行回调。
asyncTask() {
var done = this.async();
getUserEmail(function (err, name) {
if(err) {
done(err); // 失败回调
}
done(); // 成功回调
});
}
2.5 命令行与用户交互
Yeoman提供了命令行中与用户交互的能力,在Yeoman中需要避免使用console.log()或process.stdout.write()进行内容的输出,这样可能会造成用户无法再在命令行进行输入的问题,导致无法进行继续交互。替换方法为this.log(),this是当前generator的上下文。
2.5.1 Prompts类型交互
Prompts是generator与用户交互的主要方式,它是由Inquirer.js提供
在generator中可以使用this.prompt()方法进行使用,prompt方法是异步方法,返回结果为promise,因此需要注意使用await等待结果后方可进行后续输出,避免同步代码预先执行。
通过2.4中的内容介绍,我们可以知道,prompts类型的交互需要写在prompting方法中,它也是个异步任务。
使用示例:
const Generator = require('yeoman-generator');
module.exports = class extends Generator {
async prompting() {
const answers = await this.prompt([
{
type: "input",
name: "name",
message: "Your project name",
default: this.appname // Default to current folder name
},
{
type: "confirm",
name: "cool",
message: "Would you like to enable the Cool feature?"
}
]);
this.log("app name", answers.name);
this.log("cool feature", answers.cool);
}
writing() {
...
}
}
运行yo example结果:
2.5.2 使用用户交互中的回答
我们可以将用户的回答缓存在实例属性中,之后在其他任务中(如writing任务)使用。
例:
const Generator = require('yeoman-generator');
module.exports = class extends Generator {
async prompting() {
// 将用户回答缓存在this.answsers中
this.answers = await this.prompt([
{
type: "input",
name: "projectName",
message: "Your project name",
default: this.appname // Default to current folder name
},
{
type: "input",
name: "target",
message: "Say Hello To ",
default: "Tom"
},
{
type: "confirm",
name: "cool",
message: "Would you like to enable the Cool feature?"
}
]);
this.log("app name", this.answers.projectName);
this.log("cool feature", this.answers.cool);
}
writing() {
this.fs.copyTpl(
this.templatePath('index.html'),
// 使用this.answsers中的回答
this.destinationPath(`${this.answers.projectName}/public/index.html`),
{ title: 'Say Hello', name: this.answers.target }
);
}
}
结果:
? Your project name hello
? Say Hello To Jerry
? Would you like to enable the Cool feature? Yes
app name hello
cool feature true
create hello/public/index.html
可以成功使用用户的答案。
2.5.3 缓存用户回答
如果创建的generator某些问题用户每次都需要输入相同的内容,则可以将用户的回答进行缓存,以便下次不用输入直接使用。
由于yeoman继承了Inquirer.js,其中用户回答可以通过store属性确定是否进行存储,将其作为未来该问题的默认答案。
使用方式:
this.prompt({
type: "input",
name: "username",
message: "What's your GitHub username",
store: true
});
2.5.4 插播: Yeoman中的缓存机制
以上我们可以看到Yeoman可以缓存用户的回答,那它是怎么做到的呢?
Yeoman中缓存用户的回答后也会将其分享给sub-generators,官网描述,这是一个通用的任务common task.
这些用户的回答最终会被存储在.yo-rc.json文件中。
例:这里将上面sayHello的对象问题缓存了下来
{
"generator-sample": {
"promptValues": {
"target": "Jerry"
}
}
}
缓存的这些内容,还提供了相应的api方法供我们去操作他们,以使我们能做出更加灵活的脚手架。
操作API列表: 详细描述见官网文档
this.config.save(): 将配置信息保存到.yo-rc.json,如果没有该文件则会进行创建this.config.set(): 可以接受key 和相关的 value,或者多个键值对的对象,但值必须是可以被JSON序列化的(字符串、数字或非递归对象)。this.config.get(key):返回对应的值this.config.getAll(): 返回所有可用的配置项this.config.delete(key): 删除对应keythis.config.defaults(defaultValue): 设置默认值,如果对应的key有值,则保持不变,如果key缺省,则添加key与默认value
.yo-rc.json 的数据格式:
{
"generator-backbone": {
"requirejs": true,
"coffee": true
},
"generator-gruntfile": {
"compass": false
}
}
可以看到每个generator都会有一个自己的命名空间,其中各自的配置项都保存在自己的命名空间中,不会与其他的generator共享。因此上述提及的store保存的内容只能在generator于自己的sub-generator之间共享,不能在多个generator之间共享。
我们也可以预先在该文件中保存默认的配置,同时用户也可以通过该配置文件去修改相关配置。
2.5.5 命令行参数Arguments
我们可以通过yo sample --help查看一些帮助信息,其中提供了一些参数的用法,我们也可以添加自定义的参数。
注意:
yo sample中的sample是上面我们自己写的脚手架的名字。
yo sample my-project
其中my-project就是第一个参数
例:
constructor(args, opts) {
super(args, opts);
// 参数1
this.argument("test1", { type: String, required: true, desc: "this is an test1 argument", default: 'hello' });
// 参数2
this.argument("test2", { type: Number, required: true, desc: "this is an test2 argument", default: 100 });
this.log(this.options.test1);
this.log(this.options.test2);
}
在generator的构造函数constructor中,可以根据this.argument(paramName, paramConfig)进行参数的配置,参数的顺序就是yo sample param1 param2从param1开始依次去匹配this.argument设置的顺序。
this.argument第一个形参为参数名称,第二个形参为参数的信息,其包含四个选项:
desc: 参数的描述信息required:Boolean类型,参数是否必须type:参数类型,可以为String, Number, Array,也可以是一个自定义函数接收行内字符串值并解析它default: 参数默认值
以上设置的结果,通过yo sample --help可以看到:
hello # 通过this.log打印的第一个值
100 # 通过this.log打印的第二个值
Usage:
yo sample:app <test1> <test2> [options]
Options:
-h, --help # Print the generator's options and usage
--skip-cache # Do not remember prompt answers Default: false
--skip-install # Do not automatically install dependencies Default: false
--force-install # Fail on install dependencies error Default: false
--ask-answered # Show prompts for already configured options Default: false
Arguments:
test1 # this is an test1 argument Type: String Required: true
test2 # this is an test2 argument Type: Number Required: true
2.5.6 Options选项
上方通过yo sample --help中的--help就是一种选项,它与参数相似但书写格式略有不同。
它的定义与使用也与参数差不多,参数的定义用了this.argument(),options需要使用this.option(optionName, optionConfig)
this.option的第一个参数为选项的名称,--help的名称就是help,第二个参数包含五个选项:
desc: 选项的描述信息alias: 选项民惠城更的短格式,例如--help的段格式为-htype:选项类型,可以为String, Number, Array,也可以是一个自定义函数接收行内字符串值并解析它default: 默认值hide: Boolean类型,是否隐藏帮助信息
示例:
constructor(args, opts) {
super(args, opts);
...
this.option("coffee", {
desc: '是否使用CoffeeJs',
alias: 'c',
type: Boolean,
default: false,
hide: false
});
this.option("typescript", {
desc: '是否使用typescript',
alias: 'ts',
type: Boolean,
default: false,
hide: false
});
this.log(this.options.coffee);
this.log(this.options.typescript);
}
以上设置的结果,通过yo sample --help可以看到:
false # 通过this.log打印的coffee的值
false # 通过this.log打印的typescript的值
Usage:
yo sample:app <test1> <test2> [options]
Options:
-h, --help # Print the generator's options and usage
--skip-cache # Do not remember prompt answers Default: false
--skip-install # Do not automatically install dependencies Default: false
--force-install # Fail on install dependencies error Default: false
--ask-answered # Show prompts for already configured options Default: false
-c, --coffee # 是否使用CoffeeJs Default: false
-ts, --typescript # 是否使用typescript Default: false
Arguments:
test1 # this is an test1 argument Type: String Required: true
test2 # this is an test2 argument Type: Number Required: true
注意:
通过以上使用,我们发现,无论是参数还是选项,我们都可以通过this.options.name的方式获取到相应的值,从而进行判断是否需要做其他操作。
2.6 通过流stream输出文件
Yeoman中的Generator允许我们对每个文件的写入进行拦截过滤,并可以使用自定义的过滤方式。例如对代码进行美化,对空格进行格式化等操作。
我们可以通过注册transformStream对文件或文件路径等的修改,相应的api为registerTransformStream()
例:
// Yeoman官方示例代码
var beautify = require("gulp-beautify");
this.registerTransformStream(beautify({ indent_size: 2 }));
由于Yeoman使用了gulp一致的vinyl处理对象流,因此很多gulp的插件可以在Yeoman中进行使用。
不过需要注意的是,每个文件的写入都会通过以上注册的stream进行,因此,可以采用 gulp-if or gulp-filter 进行类型判断。
2.7 小结
以上介绍了一些Yeoman中的常用内容,关于Yeoman的更深入使用,请查看官方文档
3. Plop.js
3.1 plop.js介绍
对于plop.js,官方的描述为:Plop is a little tool that saves you time and helps your team build new files with consistency.(Plop是一个小工具,可以节省您的时间,并帮助您的团队构建具有一致性的新文件).
相比于Yeoman这种大型的脚手架,plop.js确实比较小型,通常用于在项目中帮我们创建一些包含通用代码的routes, controllers, components等类型的代码片段。(在vue-element-admin项目中我们也可以看到plop的身影,笔者第一次接触它也是看vue-element-admin源码时候发现的,然后就将其应用在了自己的项目中)
plop.js也是通过 inquirer.js实现命令行与用户进行交互,模板文件采用了handlebars 模板引擎。
3.2 plop.js的安装与基本使用
1. 安装:
yarn add plop -D
or
npm install --save-dev plop
2. 在项目根目录创建 plopfile.js文件
plopfile.js需导出一个函数,该函数接收一个plop参数,plop对象暴露了plop相关api,其中包含的setGenerator(name, config)方法可以创建一个generator,当然它也可以多次使用,创建多个generator。
setGenerator第一个代表generator的名称,第二个参数为配置项,包含:
description:String, generator所做事情的描述prompts:Array<InquirerQuestion>InquirerQuestion,由于plop也是基于inquirer.js实现的命令行交互,因此这里可以参考inquirer.js的数据结构,其与Yeoman基本一致actions:Array<ActionConfig>动作的执行,点击查看ActionConfig数据结构
例:
module.exports = function (plop) {
// 创建generator
plop.setGenerator('component', {
description: '创建react组件',
prompts: [], // array of inquirer prompts
actions: [] // array of actions
});
};
3. 创建模板文件
polp的模板一般放在plop-templates目录中,模板语法为handlebars,可以点击链接查看其基本语法
// your-project/plop-templates/component/components.jsx.hbs
import React from 'react';
import './{{componentName}}.css';
function {{componentName}}() {
return (
<div className="{{componentName}}">
</div>
);
}
export default {{componentName}};
// your-project/plop-templates/component/components.css.hbs
.{{componentName}} {
text-align: center;
}
// your-project/plop-templates/component/components.test.js.hbs
import React from 'react';
import { render } from '@testing-library/react';
import {{componentName}} from './{{componentName}}';
test('renders learn react link', () => {
const { getByText } = render(<{{componentName}} />);
const linkElement = getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});
4. 完善plopfile.js,完成generator编写
以下actions中共有三个action,且每个type都为add,代表添加文件,path为目标文件,templateFile为模板文件。
module.exports = function(plop) {
plop.setGenerator('component', {
description: "创建react组件",
prompts: [
{
type: 'input',
name: 'componentName',
message: '请输入组件名称: ',
default: 'MyComponent'
}
],
actions: [
{
type: 'add',
path: 'components/{{componentName}}/{{componentName}}.jsx',
templateFile: 'plop-templates/component/component.jsx.hbs'
},
{
type: 'add',
path: 'components/{{componentName}}/{{componentName}}.css',
templateFile: 'plop-templates/component/component.css.hbs'
},
{
type: 'add',
path: 'components/{{componentName}}/{{componentName}}.test.js',
templateFile: 'plop-templates/component/component.test.js.hbs'
}
]
})
}
5. 运行yarn plopornpx plop使用generator
结果为:
? 请输入组件名称: HelloWorld
✔ ++ /components/HelloWorld/HelloWorld.jsx
✔ ++ /components/HelloWorld/HelloWorld.css
✔ ++ /components/HelloWorld/HelloWorld.test.js
✨ Done in 18.45s.
就可以轻松创建基础的组件。如果需要创建其他类型的基础代码文件,例如路由、store、controller等可以继续 plop.setGenerator继续编写需要的generator并准备好相关的模板文件。
3.3 小结
本节介绍了plop.js创建基础代码文件的例子,plop.js是一个比较简单的小工具,以上介绍已可以满足基本的需求,如需了解其他api可以到plop.js官网进行深入了解。
4. 窥探脚手架原理,使用Node原生实现
4.1 node cli应用
脚手架本身就是一个node的cli应用,我们可以通过node cli应用来实现自己的脚手架。
通过以上对Yeoman与Plop.js的介绍,我们可以发现它们在实现时都使用了 inquirer.js 与用户进行交互。
使用node cli应用实现脚手架,我们可以选择inquirer.js结合结合commander.js,并搭配各种小工具例如chalk.js, log-symbols等小工具自定义一些输出信息。
node cli应用的核心就是在package.json中添加一个"bin"字段,指向js cli脚本,js脚本的书写方式必须由以下代码开头,其他代码则为正常的js代码
#!/usr/bin/env node
console.log('hello')
4.2 通过node实现脚手架的方案
-
创建一个项目
xxx-cli,yarn init初始化项目 -
添加
bin/cli.js文件,并填写如下代码#!/usr/bin/env node console.log('hello world') -
在
package.json中添加bin字段{ "name": "node-cli", "version": "1.0.0", "main": "index.js", "bin": { "node-cli": "./bin/cli.js" }, "license": "MIT", "dependencies": { "ejs": "^3.1.5", "inquirer": "^7.3.3" } } -
在项目根目录通过
npm link或者yarn link创建全局软连接 -
如果在mac或linux下编写代码还需要给cli脚本添加执行权限
chmod 755 ./bin/cli.js -
然后随便进入一个目录,执行
node-cli,注意你自己在bin字段下填写的key是什么,命令就是什么,这里是node-cli,然后输出hello world
至此使用node实现了cli的应用,脚手架所需的基本知识已经具备。
除此之外,脚手架本质上就是通过cli与用户进行各种交互,收集回答后根据用户回答选择性地创建项目模板。所以再结合 inquirer.js 或commander.js 可以进行与用户的交互,并收集回答。
结合 ejs template syntax 等模板方案,根据收集的回答,为用户生成相应的基础代码。