在项目开发中,我们都会有这样的经历,当我们要写一个新的控制器/组件/指令,通常有两种方式:
无论是第一种还是第二种,平均都要花费10~15分钟的时间。如果一个项目中只发生几次这样的情况,那么这个时间成本我们还是勉强可以接受的,但是如果遇到中大型项目,有上百个组件、指令、控制器的时候,累积起来花费的时间成本是非常可怕的。
所以,我们现在有了第三种选择——plop。
简要来说,我们可以使用plop来自动生成具有同一结构的代码文件。
所谓同构文件,就是那些具有相同的书写格式或页面结构的文件,典型地,我们在编辑器里新建html
文件,在初始状态,它们都是以下样子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
</body>
</html>
或者,我们的每一个vue
组件,可能都具有以下结构:
<template>
<div>
//...
</div>
</template>
<script>
import { ... } from '...'
export default {
name: '...',
props: {
//...
},
computed: {
},
data(){
return {
//...
}
},
mounted(){
//...
},
created(){},
components:{
//...
}
}
</script>
<style scoped>
//...
</style>
再或者,我们的angular
指令可能都具有这样的结构:
angular.module('xxxModule', []).directive('xxx', [...,function() {
// Runs during compile
return {
restrict: 'E',
replace: true,
template: "...",
scope: {
...
},
link: function(scope, iElement, iAttrs) {
...
}
};
}]);
像这样所有具有类似相同结构的文件,我们称之为同构文件
plop通过命令行提问的方式获取必要信息,然后帮助我们生成我们想要的文件。
项目中加入了plop
和进行了相关配置后,如果想要生成某一种类型的文件(可以是组件、指令、控制器等等任何具有同构模式的文件),直接在项目根目录下运行命令行工具,输入启动命令:
$ plop
然后plop
就会输出提前设置好的问题:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a3AHGkuV-1607591681872)(D:\document\个人文章\Seafile\doc\tech\plop\plop.png)]
如上图所示,问题可能会是一个选择题(提供选项,使用键盘上下方向键选择),也可能是一个填空题(需要你输入答案),如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nncwAF79-1607591681875)(D:\document\个人文章\Seafile\doc\tech\plop\input.png)]
我们也可以看到,上面的选择题让我们选择一种生成器,一个生成器对应一种同构模式,我们选择了第二项component
,根据我们的配置,这种生成器会生成一个vue
的组件文件,选择了生成器后,会让我们输入组件的名称(即上图所示的填空题), 我们输入组件名称后,又会继续问我们这个组件文件中要包含哪些部分:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jyba585c-1607591681877)(D:\document\个人文章\Seafile\doc\tech\plop\select.png)]
这是一个多选题,如果我们的组件只需要script
部分,那么我们可以使用空格将其它两项的星号处置空,如果我们要的是一个包含以上三对标签的标准组件文件,那么我们默认将它们全部选中(即前面的括号中有星号)。选择结束,按下回车键,plop
就会帮助我们生成我们要的文件了,它会告诉我们生成的文件所在的位置:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iKFvrDYb-1607591681881)(D:\document\个人文章\Seafile\doc\tech\plop\position.png)]
我们根据命令行的提示,找到生成的文件,看看其中的内容:
// src/components/TestCom/index.vue
<template>
<div />
</template>
<script>
export default {
name: 'TestCom',
props: {},
data() {
return {}
},
created() {},
mounted() {},
methods: {}
}
</script>
<style lang="scss" scoped>
</style>
这就是plop
帮我们生成的组件文件,而以上回答问题的过程总共花费时间不超过30秒。
当然,要达到以上的效果,需要我们提前对plop
进行配置,我们看一下我们以上使用的示例是如何在项目中配置的。
这一步比较简单,就是使用npm install
命令将plop
分别安装到全局环境和项目工程的开发依赖目录中。
在项目根目录中创建 plopfile.js
文件,内容如下:
const viewGenerator = require('./plop-templates/view/prompt')
const componentGenerator = require('./plop-templates/component/prompt')
module.exports = function(plop) {
plop.setGenerator('view', viewGenerator) // setGenerator(生成器名称,生成器配置)
plop.setGenerator('component', componentGenerator) // 生成器名称会在命令行中提问时显示,见上文图
}
我们看到,在这个配置中,我们实际上做的工作就是在plop
对象中注册了两种类型的生成器,一种是生成 vue
的页面视图组件的(viewGenerator
), 另一种是生成普通组件的(componentGenerator
)
我们以我们上文中用到的 componentGenerator
为例,来看看它们的配置:
这里就不再具体解释了,看以下代码中的注释就可以了解生成器的各项配置说明:
// ./plop-templates/component/prompt
const { notEmpty } = require('../utils.js') // 引入工具方法 notEmpty,用来判断变量是否为空
module.exports = {
description: 'generate vue component', //生成器描述,会在提问时缀在生成器名称后面,以中划线连接,见上文图
prompts: [{ // 第一个问题
type: 'input', // 提问类型, input代表需要用户在命令行中输入
name: 'name', // 答案字段名称,这里为‘name’,代表组件名称
message: 'component name please', // 答案提示,会在命令行等待用户输入前显示
validate: notEmpty('name') // 验证器,用户输入的名称不能为空
},
//第二个问题
{
type: 'checkbox', //提问类型为多选
name: 'blocks', // 用户输入的答案会被存入 `blocks` 字段
message: 'Blocks:', // 提示语
choices: [{ // 可选项,这里有三种标签,默认都为选中
name: '<template>',
value: 'template',
checked: true
},
{
name: '<script>',
value: 'script',
checked: true
},
{
name: 'style',
value: 'style',
checked: true
}
],
validate(value) { // 验证用户的答案,这里vue组件至少需要script或template标签中的一种
if (value.indexOf('script') === -1 && value.indexOf('template') === -1) {
return 'Components require at least a <script> or <template> tag.'
}
return true
}
}
], // 动作配置,即用户输入答案之后执行操作
actions: data => {
const name = '{{properCase name}}' // 定义name字段值,这里是将用户输入的组件名称转为驼峰格式
const actions = [{ // 要执行的操作列表,这里只有一种,就是 add,代表新增文件
type: 'add',
path: `src/components/${name}/index.vue`, // 生成文件的路径,name为变量【此处使用handlebars模板语法】
templateFile: 'plop-templates/component/index.hbs', // 模板路径,新生成的文件内容就是基于这个模板,该模板指定使用 handlebars 模板语法
data: { //要传给模板的数据
name: name, // 名称
template: data.blocks.includes('template'), // template 布尔值,是否生成template内容
script: data.blocks.includes('script'), // script 布尔值,是否生成 script 内容
style: data.blocks.includes('style') // style 布尔值,是否生成 style 内容
}
}]
return actions
}
}
我们看到,生活器做的工作就是对用户输入的答案就行一系列处理后,封装成数据传递给模板文件,然后模板文件根据传入的数据来决定生成什么及哪些内容。
我们看看以上配置中配置的模板文件的内容:
// plop-templates/component/index.hbs
{{#if template}}
<template>
<div />
</template>
{{/if}}
{{#if script}}
<script>
export default {
name: '{{ properCase name }}',
props: {},
data() {
return {}
},
created() {},
mounted() {},
methods: {}
}
</script>
{{/if}}
{{#if style}}
<style lang="scss" scoped>
</style>
{{/if}}
在模板文件中,我们定义了该类型生成器对应的同构模式的基本模板,并且根据传入的数据来决定生成的文件中包含什么内容和哪些内容。
以上示例中,模板会根据三个标签类型的布尔值决定要不要生成这个标签内容,也会将传入的名称变量直接赋值给组件的 name
属性,当然,我们还可以在模板中定义更多的变量,然后由用户输入,再由生成器配置传递给模板来渲染。
至此,我们就了解了plop
的基本用法和基本流程以及原理,以上只是一个简单示例,在实际业务中,我们还可以写出更加复杂的生成器,来服务于各种不同类型复杂同构模式的文件生成,具体请参考我翻译的polp
文档中文版。