当前版本:v2.5.0
Plop是什么
Plop是一个微生成器框架
,之所以这样叫它 ,是因为它是一个可以允许你方便地创建具有一致性(通用性)的代码模板或文本文件的小工具。我们在代码中会经常创建具有同一种结构或模式的代码文件(路由、控制器、组件、辅助类等等)。这些结构和模式会经常演进,导致你无法很容易地在这些代码中找到可以代表当前最佳实践的代码去创建出一个新的类似模式的文件。这就是Plop要解决的问题。使用Plop,你可以很方便地使用代表最佳实践的文件模板去生成同样模式的新文件。使用Plop可以在命令行中方便地生成代码文件,这免去了我们在代码库中寻找和复制那些具有通用模式和结构的代码,这是创建新文件最简单的方法。
用一句话总结它的核心:基本上就是使用命令行提示器和文件模板来粘合代码。
安装
1.添加plop到项目
$ npm install --save-dev plop
2.全局安装plop(可选,但推荐)
$ npm install -g plop
3.在项目根目录创建plopfile.js
文件
文件基本内容如下:
module.exports = function(plop){
//创建你自己的生成器
plop.setGenerator('basics',{
description:'this is a skeleton plopfile',
prompts:[],// 提示器数组
actions:[] // 动作数组
});
};
你的第一个plopfile
配置
plopfile
从低级节点模块开始,它导出一个函数,该函数接受plop
对象作为它的第一个参数
module.exports = function (plop) {};
plop对象公开包含setGenerator(name, config)
函数的plop api
对象。这是您用来为这个plopfile
创建生成器的函数。当plop
从这个目录(或任何子目录)中的终端运行时,将显示这些生成器的列表 。
我们试着建立一个基本的生成器,看看它是什么样:
module.exports = function (plop) {
// 控制器生成器
plop.setGenerator('controller', {
description: 'application controller logic', //生成器说明
prompts: [{
type: 'input', // 接受类型,这里为接受终端输入
name: 'name', // 输入的内容赋给变量name
message: 'controller name please' // 提示内容:输入控制器名称
}],
actions: [{
type: 'add', // 动作类型:新增
path: 'src/{{name}}.js', // 创建路径
templateFile: 'plop-templates/controller.hbs' // 模板,将根据此模板内容生成新文件
}]
});
};
上面创建的生成器将向我们提一个问题(在终端中),并根据我们的回答(终端输入)创建一个文件。可以将其扩展为问更多的问题,并根据需要创建尽可能多的文件。还有一些其它操作可以用来以不同方式更改我们的代码库。
使用提示器(Prompts)
Plop使用inquier .js库收集用户数据。提示类型列表可以在询问者官方网站上找到。
CLI使用方法
安装了plop
并创建了生成器之后,就可以在终端里运行plop
了。直接运行$ plop
命令,不带任何参数,命令行会给你一个生成器列表供你选择。你也可以运行 $ plop generatorname
来直接触发指定的生成器。
如果你没有全局安装plop
,你需要在设置一个 npm
脚本命令来为你运行polp
:
//package.json
{
...,
"scripts":{
"plop":"plop"
},
...
}
避开提示器(绕行)
一旦你已经熟悉了项目以及其中的生成器,你可能希望在运行生成器时就为它提供答案,而不是等它提出问题后才给出答案。比如,我有一个名为component
的生成器,它会向我提一个问题要求我输入一个名字,我就可以使用下面这样的命令:$ plop componnet "componentname"
,它就会立即执行就像我在提示器中输入答案一样。如果这个生成器还有第二个问题,那用户在上面命令中可以输入第二个值作为第二个问题的答案。
像confirm
和list
这样的提示确保你的输入时有含义的。比如输入y/yes/t/true
中的一个作为一个需要一个布尔值的确认性提问的答案。你可以从一个列表来选择使用他们的值、索引、键或者名称。Checkbox
类型的提示器可以接受一个用逗号分割开的值列表来实现多选。
如果你想为第二个提问(而非第一个问题)提供答案,你可以使用下划线来跳过。
比如:
$ plop component _ "input for second prompt"
Plop为标准查询提示提供了内置的绕行逻辑,但是也有一些方法可以为如何处理指定提示器的用户输入提供定制逻辑 。
绕行提示器(使用名称【name】)
您还可以使用--
按名称绕过提示,然后为每个要绕过的提示提供参数。下面是例子:
# 绕过第一个和第二个提问
$ plop component "my component" react
$ plop component -- --name "my component" --type react
# 只绕过第二个提问
$ plop component _ react
$ plop component -- --type react
强制运行生成器
默认情况下,Plop操作会在事情可疑时失败,从而保证文件的安全 , 最明显的例子就是不允许新增
动作覆盖已经存在的文件。 Plop操作单独支持force属性,但是您也可以在从终端运行Plop时使用——force标志。使用—force标志将告诉每个动作都要强制运行 。
为什么要使用生成器
- 因为当您创建与代码分离的样板文件时,您自然会投入更多的时间和精力
- 因为在创建每个路由、组件、控制器、辅助类、测试文件、视图等时,可以节省您的团队(或您自己)5-15分钟的时间……
- 因为上下文切换是昂贵的,节省时间并不是自动化工作流的唯一好处
Plofile API
plopfile api是由plop对象公开的方法的集合。 大部分工作都是由setGenerator完成的,但是本节将介绍您可能在plopfile中发现的其他有用的方法 。
TypeScript 声明
plop捆绑了TypeScript声明。无论您是否在TypeScript中编写plopfile,许多编辑器都会通过这些声明提供代码帮助 。
// plopfile.ts
import {NodePlopAPI} from 'plop';
export default function (plop: NodePlopAPI) {
// plop generator code
};
// plopfile.js
module.exports = function (
/** @type {import('plop').NodePlopAPI} */
plop
) {
// plop generator code
};
主要方法
这些是创建plopfile时通常使用的方法。其他主要供内部使用的方法在其他方法一节中列出
方法名 | 参数 | 返回值 | 说明 |
---|---|---|---|
setGenerator | String,GeneratorConfig | GeneratorConfig | 设定一个生成器 |
setHelper | String,Function | 设定一个辅助方法 | |
setPartial | String,String | 设定一个片段 | |
setActionType | String,CuscomAction | 注册一个自定义动作类型 | |
setPrompt | String,InquirePrompt | 注册一个自定义的提示器类型 | |
load | Array[String],Object,Object | 从另一个plopfile或npm模块加载生成器、辅助类或片段 |
setHelper
setHelper
直接对应于handlebars
方法registerHelper
。如果你熟悉 handlebars helpers,(handlebars是一款模板引擎,也是plop默认使用的模板引擎),那么你已经知道它是如何工作的了 .
module.exports = function (plop) {
plop.setHelper('upperCase', function (text) {
return text.toUpperCase();
});
// or in es6/es2015
plop.setHelper('upperCase', (txt) => txt.toUpperCase());
};
setPartial
setPartial直接对应于handlebars的方法方法registerPartial。所以如果你熟悉handlebars的片段(就是模板片段),那么你已经知道这是如何工作的
module.exports = function (plop) {
plop.setPartial('myTitlePartial', '<h1>{{titleCase name}}</h1>');
// used in template as {{> myTitlePartial }}
};
setActionType
setActionType允许您创建可以在plopfile中使用的自己的操作(类似于添加或修改)。这些基本上是高度可重用的自定义操作函数。
FunctionSignature Custom Action(自定义动作的函数原型)
参数 | 类型 | 说明 |
---|---|---|
answer | Object | 对提问的回答 |
config | Action Config | 生成器的“动作”数组中的对象 |
plop | PlopfileApi | 运行此动作的plopfile的plop api |
module.exports = function (plop) {
plop.setActionType('doTheThing', function (answers, config, plop) {
// do something
doSomething(config.configProp);
// if something went wrong
throw 'error message';
// otherwise
return 'success status message';
});
// or do async things inside of an action
plop.setActionType('doTheAsyncThing', function (answers, config, plop) {
// do something
return new Promise((resolve, reject) => {
if (success) {
resolve('success status message');
} else {
reject('error message');
}
});
});
// use the custom action
plop.setGenerator('test', {
prompts: [],
actions: [{
type: 'doTheThing',
configProp: 'available from the config param'
}, {
type: 'doTheAsyncThing',
speed: 'slow'
}]
});
}
setPrompt
Inquirer 提供了许多种类型的开箱即用的提示器,但是它也允许开发者创建提示器插件。如果你想使用一个提示器插件,你可以使用setPrompt
来注册它。更多详情可查看 Inquirer 官方文档 。也可以查看plop社区的自定义提示器列表
const promptDirectory = require('inquirer-directory');
module.exports = function (plop) {
plop.setPrompt('directory', promptDirectory);
plop.setGenerator('test', {
prompts: [{
type: 'directory',
...
}]
});
};
setGenerator
配置对象应该包含prompts
与actions
属性(description
属性是可选的)。prompts
数组会被传递给 inquirer。而 actions
数组是要执行的操作的列表(在下面有更详细的描述)
GeneratorConfig 接口
属性 | 类型 | 默认值 | 说明 |
---|---|---|---|
description | [String] | 简短说明生成器的用途 | |
prompts | Array[InquirerQuestion] | 向用户提的问题 | |
actions | Array[ActionConfig] | 可执行的动作 |
如果您需要动态地设置动作,请看看 使用动态动作数组
ActionConfig 接口
属性 | 类型 | 默认值 | 说明 |
---|---|---|---|
type | Striing | 动作类型(如 add、modify、addMany等等) | |
force | Boolean | false | 是否强制执行动作(根据动作表示不同的内容) |
data | Object/Function | {} | 指定在运行此操作时应与用户提示答案混合的数据 |
abortOnFail | Boolean | true | 如果此动作由于任何原因失败,则终止所有未来动作 |
在任何ActionConfig里面,data属性都可以是一个函数,这个函数返回一个对象,或者返回一个Promise对象,这个对象resolve一个对象。
其它方法
方法 | 参数 | 返回值 | 说明 |
---|---|---|---|
getHelper | String | Function | 获取辅助类 |
getHelperList | Array[String] | 获取辅助类名称列表 | |
getPartial | String | String | 根据名称获取handlebars模板片段 |
getPartialList | Array[String] | 获取模板片段名称列表 | |
getActionType | String | CustomAction | 根据名称获取动作类型 |
getActionTypeList | Array[String] | 获取动作类型名称列表 | |
setWelcomeMessage | String | 自定义在运行$ plop 命令时要求您选择生成器的显示消息 | |
getGenerator | String | GeneratorConfig | 根据名称获取生成器配置 |
getGeneratorList | Array[Object] | 获取生成器数组,每个生成器包含名称和描述 | |
setPlopfilePath | String | 设置plopfilePath 值,该值在内部用于定位模板文件等资源 | |
getPlopfilePath | String | 返回plopfile 的绝对路径 | |
getDestBasePath | String | 返回创建文件时使用的基本路径 | |
setDefaultInclude | Object | Object | 设置plopfile的默认配置,如果它被使用plop.load()的另一个plopfile使用,则该配置将用于那个plopfile |
getDefaultInclude | String | Object | 获取将用于此plopfile的默认配置(另一个plopfile可以使用plop.load()使用该配置) |
renderString | String,Object | String | 使用第二个参数(对象)作为数据,通过handlebars的模板渲染器运行第一个参数(字符串)。返回渲染后的模板 |
内建的动作
您可以在GeneratorConfig
中使用几种类型的内置动作。您可以指定操作的类型(所有路径都基于plopfile
的位置)和要使用的模板 。
Add
add
动作被用来向你的项目中新增一个文件,path
属性指定生成文件的路径,它本身是一个handlebars模板,用户输入的文件名称将作为变量嵌入其中。文件内容将由template
或templateFile
决定。
属性 | 类型 | 默认值 | 说明 |
---|---|---|---|
path | Sring | 一个符合handlebars模板规范的字符串,它是新文件的路径 | |
template | String | 被用来创建新文件的handlebars模板 | |
templateFile | String | 一个包含模板的文件路径 | |
skipIfExists | Boolean | 如果要创建的文件已经存在,则跳过(而非报错) | |
force | Boolean | 继承自ActionConfig(如果文件存在则覆盖) | |
data | Object | 继承自ActionConfig | |
abortOnFail | Boolean | 继承自ActionConfig |
AddMany
addMany
动作可以一步创建多个文件。destination
属性是用来指定生成文件所在目录的,它是一个handlebars模板,意味着该属性中定义的目录路径可以是动态的。base
属性可以用于更改在创建文件时应该忽略模板路径的哪些部分。 如果您希望添加的文件名是唯一的,则templateFiles glob
位于的路径可以在它们的文件/文件夹名称中使用handlebars语法(例如:{{dashCase name}}.spec.js)。
属性 | 类型 | 默认值 | 说明 |
---|---|---|---|
destination | String | 规定新文件要创建到的目录(是一个handlebars模板) | |
base | String | 当文件要被创建到目录中时需要忽略的路径部分 | |
templateFiles | Glob | 匹配要添加的多个模板文件的glob模式 | |
stripExtensions | [String] | [‘hbs’] | 文件扩展名,在将其添加到目标文件时,应该从模板文件名称中删除这些扩展名 |
globOptions | Object | 更改如何与要添加的模板文件匹配的glob选项 | |
verbose | Boolean | true | 打印每个成功添加的文件路径 |
skipIfExisits | Boolean | false | 继承自Add接口 |
force | Boolean | false | 继承自 ActionConfig 对象 |
data | Object | {} | 继承自 ActionConfig 对象 |
abortOnFail | Boolean | true | 继承自 ActionConfig 对象 |
Modify
modify
修改动作将使用pattern
属性在指定路径(path
)下的文件中查找/替换文本。 有关修改的更多详细信息,请参见示例文件夹
属性 | 类型 | 默认值 | 说明 |
---|---|---|---|
path | String | 要修改的文件的路径(是一个handlebars模板) | |
pattern | RegExp | end-of-file | 一个正则表达式,用来匹配需要被替换的文本 |
template | String | 一个handlebars模板,用它取代所匹配的模式。捕获组的值有$1、$2等 | |
templateFile | String | 是一个包含模板的路径 | |
data | Object | {} | 继承自ActionConfig |
abortOnFail | Boolean | true | 继承自ActionConfig |
Append
append
追加操作是一个常用功能,它是modify
接口的子集。它用于将数据附加到文件中的特定位置。
属性 | 类型 | 默认值 | 说明 |
---|---|---|---|
path | String | 需要被修改的文件路径(是一个handlebars模板) | |
pattern | RegExp,String | 一个正则表达式,用于匹配附加文本的正则表达式 | |
unique | Boolean | true | 是否删除相同的项 |
separator | String | new line | 分割符 |
template | String | 项要用的handlebars模板 | |
templateFile | String | 包含模板的文件模板 | |
data | Object | {} | 继承自ActionConfig |
abortOnFail | Boolean | true | 继承自ActionConfig |
Custom(动作函数)
add
和modify
操作将处理plop要处理的几乎所有情况 .但是,plop
也为nodejs和js专家提供一些自定义动作的函数。 一个自定义动作函数是一个在动作数组中提供的函数。
- 自定义动作函数将被
plop
执行,这时会使用相同的 CustomAction 动作签名; - 在执行下一个动作前,
plop
将会等待自定义动作执行完。 - 这个函数必须通过返回值来告诉
plop
,发生了什么。如果您返回的是一个 Promise对象,那plop
不会开始其它动作,直到Promise
达到解决状态。如果您返回一条消息(字符串),plop
将知道动作已经完成,它将在动作的状态中报告消息。 - 当
Promise
变为拒绝态或者函数抛出一个异常,自定义动作将失败。
这里有一些简单的异步的自定义动作的示例
Comments(注释)
可以通过添加字符串代替动作配置对象来将注释行添加到动作数组。 出现注释时,注释将显示在屏幕上,并且没有自身功能。
内建的辅助类
plop中包含了一些非常有用的帮助程序。它们大多是大小写修饰词,下面是完整的列表
大小写修改
- camelCasse:将字符串转为驼峰表示法(changeFormatToThis)
- snakeCase:下划线表示法(change_format_to_this)
- dashCase/kebaCase:短划线表示法(change-format-to-this)
- dotCase: 点语法表示法(change.format.to.this)
- pathCase:路径表示法(change/format/to/this)
- properCase/pascalCase 单词首字母大写表示法(ChangeFormatToThis)
- lowerCase 小写表示法 (change format to this)
- sentenceCase: 整句首字母大写法(Change format to this)
- constantCase: 全部大写下划线连接 (CHANGE_FORMAT_TO_THIS)
- titleCase 标题表示法 (Change Format To This)
其它辅助类
- pkg 在与plopfile同级的文件夹中查找package.json文件的属性
更进一步
在一些基本的生成器上不需要太多的启动和运行。然而,如果你想更进一步 ,请往下读
使用动态的动作数组
或者,GeneratorConfig的actions属性本身可以是一个函数,它将answers数据作为参数并返回actions数组 。
这使您可以根据提供的答案来调整action数组
module.exports = function (plop) {
plop.setGenerator('test', {
prompts: [{
type: 'confirm',
name: 'wantTacos',
message: 'Do you want tacos?'
}],
actions: function(data) {
var actions = [];
if(data.wantTacos) {
actions.push({
type: 'add',
path: 'folder/{{dashCase name}}.txt',
templateFile: 'templates/tacos.txt'
});
} else {
actions.push({
type: 'add',
path: 'folder/{{dashCase name}}.txt',
templateFile: 'templates/burritos.txt'
});
}
return actions;
}
});
};
第三方的提示器绕行插件
如果您已经编写了一个inquirer prompt插件,并且希望支持plop的绕行功能,那么这个过程非常简单。提示导出的插件对象应该有一个bypass
函数。这个bypass
函数将由plop运行,用户的输入作为第一个参数,prompt config对象作为第二个参数。此函数返回的值将被添加到该提示的应答数据对象中.
// My confirmation inquirer plugin
module.exports = MyConfirmPluginConstructor;
function MyConfirmPluginConstructor() {
// ...your main plugin code
this.bypass = (rawValue, promptConfig) => {
const lowerVal = rawValue.toString().toLowerCase();
const trueValues = ['t', 'true', 'y', 'yes'];
const falseValues = ['f', 'false', 'n', 'no'];
if (trueValues.includes(lowerVal)) return true;
if (falseValues.includes(lowerVal)) return false;
throw Error(`"${rawValue}" is not a valid ${promptConfig.type} value`);
};
return this;
}
对于上面的示例,bypass函数接受用户的文本输入并将其转换为一个布尔值,该值将用作提问回答数据
将绕行支持添加到您的Plopfile
如果您使用的第三方提示插件默认情况下不支持绕行,则可以在提示器的配置对象中添加上面的bypass函数,这样plop将使用它来处理该提示的绕行的数据