使用 Plop 生成模板

由于本博客文章的特殊编写模式,会有两段类似结构,仅作用不同,每次都要写两遍,使用 plop 提前设置好格式,在命令行仅需补充字段即可。

起因

本博客采用 markdown 为主要编写格式,想要打包后生成的html文件,能够有单独的 title 、description 和 keywords,需要在 markdown 文件头部添加一段 特殊代码,如下:

---
title: 使用 plop 生成模板
meta:
  - name: description
    content: 使用 plop,通过命令行,快速生成博客模版,减少本博客因为特殊性,导致的重复代码。
  - name: keywords
    content: Plop, Generator, Template
---

但是在逻辑中,想要在列表页中,展示出文章的 title 、description、 keywords 和 date 等,则需要在 markdown 文件头部添加一段路由信息,如下:

<route lang="yaml">
meta:
  title: 使用 plop 生成模板
  desc: 使用 plop,通过命令行,快速生成博客模版,减少本博客因为特殊性,导致的重复代码。
  keywords: [Plop,Generator,Template]
  date: 2023-06-30 21:30:59
</route>

这两段代码,仅仅是作用不同,但是结构相同,每次都要写两遍,所以想要通过plop,通过命令行,快速生成博客模版,减少本博客因为特殊性,导致的重复代码。

什么是 Plop

简单来说Plop就是是一个小型的、零依赖的、可编程的代码生成器,可以通过命令行,快速生成代码,减少重复代码。详情介绍可以参考What is Plop?

安装 Plop

pnpm add plop -D

创建 Plop 目录与 npm script

目录结构

以本博客为例,在项目根创建如下目录结构:

├── plop
   ├── blog
   └── index.md.hbs
   └── config.js

其中 config.js 是 plop 的配置文件,blog 目录下 index.md.hbs 是模版文件,.hbshandlebars的后缀,handlebars是一个简单的模版语言,可以通过{{}}来插入变量,{{#if}}来判断条件,{{#each}}来遍历数组等,具体语法可以参考 handlebars.js

npm script

package.json中的scripts添加如下命令:

{
  "scripts": {
    "create:blog": "plop --plopfile ./plop/config.js"
  }
}

配置 Plop config 文件

plop/config.js中,添加如下代码:

import { exec } from 'node:child_process'

export default function (
  /** @type {import('plop').NodePlopAPI} */
  plop,
) {
  const today = '2023-06-30'

  plop.setHelper('date', () => {
    return '2023-06-30 21:30:59'
  })

  // 自定义打开文件action
  plop.setActionType('openFile', (answers, config) => {
    exec(`code ${config.path.replace('fileName', answers.fileName)}`)
  })

  // 创建博客模版命令
  plop.setGenerator('blog', {
    description: '创建一个blog:',
    prompts: [
      {
        type: 'input',
        name: 'fileName',
        message: '请输入blog文件夹名称:',
      },
      {
        type: 'input',
        name: 'title',
        message: '请输入blog标题:',
      },
      {
        type: 'input',
        name: 'desc',
        message: '请输入blog描述:',
      },
      {
        type: 'input',
        name: 'keywords',
        message: '请输入blog关键词:',
      },
    ],
    actions: [
      {
        type: 'add',
        path: `../src/pages/blog/${today}_{{fileName}}/index.md`,
        templateFile: './blog/index.md.hbs',
        data: {
          title: '{{title}}',
          desc: '{{desc}}',
          keywords: '{{keywords}}',
        },
      },
      {
        type: 'openFile',
        path: `./src/pages/blog/${today}_fileName/index.md`,
      }
    ],
  })
}

setHelper

setHelper用来注册一个handlebars的 helper,可以在模版中使用,你也可以进行传参数,进行动态的渲染:

plop.setHelper('upperCase', txt => txt.toUpperCase())

更多请查看:setHelper

setGenerator

setGenerator 方法用于定义生成器(generator)。生成器是 Plop 框架的核心概念之一,它允许您创建可重复使用的代码生成模板。下面是 setGenerator 方法的详细说明:

plop.setGenerator(name, config)
  • name:生成器名称,用于标识生成器。在运行 Plop 时,您将使用这个名称来选择特定的生成器。
  • config:生成器的配置对象,包含以下属性:
    • description:生成器的描述,可以提供一些关于生成器目的的信息。
    • prompts:用户提示的数组,每个提示定义了一个用户输入的信息。提示可以包括以下属性:
      • type:提示类型,可以是 ‘input’、‘confirm’、‘list’ 等,取决于需要的用户输入类型。
      • name:提示名称,用于在模板中引用用户输入的值。
      • message:提示用户的消息,说明用户应该提供什么样的信息。
    • actions:生成器运行时要执行操作任务数组。每个操作可以包括以下属性:
      • type:操作类型,可以是 ‘add’、‘modify’、‘append’ 等,取决于对生成文件进行的操作。
      • path:生成文件的路径,可以是相对路径或绝对路径。
      • templateFile:模板文件的路径,用于根据用户输入和模板生成最终的文件内容。

更多请查看:setgenerator

setActionType

setActionType 方法允许您创建自己的 Action,这些操作可以在plopFiles中使用。这些基本上是高度可重用的自定义操作函数:

import { exec } from 'node:child_process'

// 自定义打开文件action
plop.setActionType('openFile', (answers, config, plop) => {
  exec(`code ${config.path.replace('fileName', answers.fileName)}`)
})

例如在上面的代码中,我们注册了一个openFile的操作类型,目的是能够打开创建的文件,通过VSCode自带的code命令,打开文件。

setActionType 方法的详细说明:

plop.setActionType(name, handler)
  • name:操作类型名称,用于标识操作类型。
  • handler:操作类型的处理函数,用于执行操作。处理函数接收三个参数:
    • answers:用户输入的答案对象,包含用户输入的所有信息。
    • config:操作配置对象,包含操作的所有配置信息。
    • plop:Plop 实例,可以用于访问 Plop 的其他方法。

更多请查看:setactiontype

在上面的代码中,我们注册了一个openFile的操作类型,目的是能够打开创建的文件。

配置模版文件

plop/blog/index.md.hbs中,添加如下代码:

---
title: {{ title}}
meta:
  - name: description
    content: {{ desc }}
  - name: keywords
    content: {{ keywords }}
---

<route lang="yaml">
meta:
  title: {{ title}}
  desc: {{ desc}}
  keywords: [{{ keywords }}]
  date: {{ date }}
</route>

# {{ title}}
  {{ desc }}

可以看到,我们在模版中使用了handlebars的语法,{{ title}}{{ desc }}{{ keywords }}都是通过用户输入的值来进行渲染的,date 是通过setHelper注册的 helper 方法来进行渲染的。

本次 setHelper注册的 helper 方法 未使用到参数,若需要传递参数,可以这样写:

plop.setHelper('fn', (text) => {
  return text.toUpperCase()
})

然后在模版中使用:

{{ fn 'hello world' }}

生成的结果为:

HELLO WORLD

运行 Plop 命令

plop

完整 Plop 文档查阅

官方文档:plopjs