前端自动化工具plopjs
plopjs是一个基于node js所开发的小工具,主要作用是根据模板代码生成目标代码。
plop对于模板代码的处理选择了 handlebars 作为模板
目的
通过自动化工具减少开发中重复代码的书写
使用举例
新建一容器组件用于展示消息列表,组件名为message
使用plop前
- 在项目的适当目录下新建message/index.js文件,message/index.less文件。
- 在message/index.js文件内写出react组件的基础代码,并在message/index.less内书写默认样式
使用plop之后(需要提前进行相关的配置)
- 项目根目录,终端运行plop,根据提示输入组件名message
- 添加路由配置文件(这步骤应该也可以由plop完成)
对比
- 手动操作:通常我的做法是复制现有的组件,然后重命名,不过有时候涉及到的文件可能更多,比如redux的action与reducer文件。由于是手动操作,有时候也可能忘记了对于某个地方的修改。
- 使用plop:终端操作只需要在命令行进行适当的交互就可以,自动生成对应文件,不会出错并且快速。
应用场景
- 【前端】vue/react框架新建新的页面
- 【前端】根据接口文档,自动生成有关的代码(比如发送请求的函数)?
- 【前端】redux有关代码的自动生成?
- 对于某些不方便抽象的重复代码。比如前端某些类似的表格、表单、页面布局、类似的controller等,可以抽象为模板的代码
- 【小程序】在使用非官方开发工具时,通过脚本来创建组件/页面,从而实现类似于微信开发者工具,一次性创建出wxml,wxss,js,json文件。并且可以自定义这些模板。
- 其他与模板有关的功能。
注:plop.js的终端交互使用了Inquirer.js,所以也可以使用inquirer的插件对其功能进行扩展(比如选择文件夹)
开始
- 安装npm包
npm i plop -g
npm i plop -D
- 在项目的根目录创建配置文件plopfile.js
// 配置文件主要是exports了一个函数
module.exports = function (plop) {
// controller generator
plop.setGenerator('controller', {
description: 'application controller logic',
prompts: [{
type: 'input',
name: 'name',
message: 'controller name please'
}],
actions: [{
type: 'add',
path: 'src/{{name}}.js',
templateFile: 'plop-templates/controller.hbs'
}]
});
};
- 创建 plop-templates/controller.hbs文件。并写一些内容。
- 在项目根目录下,执行 plop,然后根据终端命令提示进行操作即可,最终会发现自动生成了 src/{{name}}.js 文件,文件的内容就是模板的内容。
Plopfile Api
配置文件是支持ts的,具体的使用方法请参考官网
setGenerator
此函数接受两个参数,第一个参数是设置的Generator的名字,第二个config对象需要包括提示和操作(description是可选的)。prompts数组被传递给inquirer。actions数组是要执行的操作的列表。
- 典型的配置如下:
// 示例
module.exports = function (plop) {
plop.setGenerator('fc_page', {
description: 'application fc_page logic', // 可选字段,会显示在控制台
prompts: [{
type: 'input', // 用来在终端输入文本
name: 'name', // 在 actios 内 可以通过{{name}} 取到这里输入的值,在模板内也可以通过{{name}}取到这个值
message: '请输入函数组件的名字(必填)', // 会显示在控制台
validate(val) {
// 如果验证不通过,需要返回字符串,字符串将会被展示在控制台
// 验证通过,则返回true
return true;
},
default: "default", // 默认值
}],
actions:[{
// 添加文件 src/components/{{name}}/index.js 文件内容为templateFile指定的内容
type: 'add',
path: 'src/components/{{name}}/index.js',
templateFile: 'plop-templates/functionComponentWithLessPage.hbs',
// 如果模板文件内容少,可以直接使用template指定
// template:""
// 在将文件输出到硬盘前会调用transform函数
// transform:()=>""
// 当运行这个actions,data会合并到prompt answers。
// data:{}
//...其他不常用参数请查看文档
},{
// 修改src/index.js文件中的内容,将 pattern 匹配到的内容,修改为 template 的内容
type: 'modify',
path: 'src/index.js',
pattern: /(\/\* injectinfo \*\/)/,
template: '$1\nconsole.log(111);',
},{
// 修改src/index.js文件中的内容,在 pattern 匹配到的内容后追加 template 的内容
type: 'append',
path: 'src/route.config/index.js',
pattern: /\/\* inject \*\//,
template: 'console.log(666);',
}],
}
};
prompts
- prompts数组里的type除了可以是input,还可以是
input
,number
,confirm
,list
,rawlist
,expand
,checkbox
,password
,editor
。当然也是支持inquirer插件的。
prompts数组会直接传递给inquirer,所以更多配置请参考 https://github.com/SBoudrias/Inquirer.js#objects
actions
- actions数组内的type,plop默认提供了4中Add,AddMany,Modify,Append。
- 如果actions需要根据prompts的answer来决定,那么可以使用动态actions,示例如下。
// 示例
module.exports = function (plop) {
plop.setGenerator('fc_page', {
description: 'application fc_page logic',
prompts: [{
type: 'confirm',
name: 'needLess',
message: '是否需要样式文件(默认需要)',
default:true,
}],
actions(data) {
if (data.needLess) {
return [];
}
return [];
},
});
};
setPrompt
用于使用inquirer插件来扩展prompts
// 为 prompt 增加目录选择功能
const promptDirectory = require('inquirer-directory');
module.exports = function (plop) {
plop.setPrompt('directory', promptDirectory);
plop.setGenerator('test', {
prompts: [{
type: 'directory',
setActionType
在plopfile中添加自定义的ActionType(类似于add,addMany)
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'
}]
});
};
setHelper
module.exports = function (plop) {
plop.setHelper('upperCase', function (text) {
return text.toUpperCase();
});
// or in es6/es2015
plop.setHelper('upperCase', (txt) => txt.toUpperCase());
};
setPartial
module.exports = function (plop) {
plop.setPartial('myTitlePartial', '<h1>{{titleCase name}}</h1>');
// used in template as {{> myTitlePartial }}
};
load
loads generators, helpers and/or partials from another plopfile or npm module
https://github.com/plopjs/plop/blob/master/plop-load.md
other-methods
- setWelcomeMessage:自定义WelcomeMessage
- ...其它的请去官网查看
高级
prompts的提示
https://plopjs.com/documentation/#3rd-party-prompt-bypass
将plop集合到自己的CLI项目中
https://plopjs.com/documentation/#wrapping-plop
思考
通过内置的action和自己扩展的action,加上前端的一切周边工具,比如nodejs、babel,基本可以使得plop可以实现任何的功能,不过具体是先到什么程度,怎样去用还需要根据具体的项目去权衡。
如果将toB的前端项目的业务逻辑抽离出代码模板,那么我想应该是可以实现如下功能的。
比如:
- 从一个表格页的模板自动生成一个纯展示的表单页。我们只需要配置好接口和需要展示的内容,比如页面的逻辑代码,ajax请求有关代码的自动生成,路由配置文件的自动修改等?之后在针对于代码做修改,即可实现需求
- 我们需要创建一个表单页,是否可以直接通过命令行指定下,与表单相关的接口,然后就将表单有关的代码自动生成出来,之后再在模板代码上做修改以实现具体的需求
这样可以将更多的工作去交给自动化去处理。大量提升开发效率。不过,事情没有面面俱到的,可能做之前还要结合实际的场景来决定适用到什么程度。
比如:
- 项目的功能模块的相似程度,比如toB的业务往往更容易抽象出模板,且模板也更加通用。
- 对于原有项目的代码侵入性,因为如果涉及到修改现有文件,可能需要对现有文件做标记,比如在js中添加特殊的注释。
- 项目处于的阶段,比如是高速迭代,还是维护。在实际的开发中是否会有大量的类似代码对我们的开发工作造成困扰。
- 业务逻辑是否复杂多变。
对于这个工具,我感觉做一些类似于页面的代码模板生成还是挺好的,尤其时对于大多数toB项目来说,每个页面的逻辑其实差距不大,主要也就是列表、表单、表格、之类的东西。通过命令行交互来代替之前的复制、粘贴、手动修改,我想还是会提高一定的效率的。