创建 CLI 来自动创建文件

2025-06-07

创建 CLI 来自动创建文件

您是否发现自己需要同时为多个文件复制粘贴相同的样板代码?每次需要构建ISO 8601格式的日期时,您是否都会停下来思考?🤔
如果您无法再将模板代码从一个文件复制粘贴到另一个文件,那该有多令人沮丧?

本文将介绍如何快速创建一个用于生成文本文件的命令行界面 (CLI) 工具。具体来说,本文中的示例将演示如何创建模板,以便.jsxGatsby博客中生成包含测试的新页面,以及如何生成包含博客文章初始结构的 Markdown 文件。这些示例应该能给您一些启发,因为根据特定开发人员或项目的需求,可以生成的文本文件类型是无限的。

为什么要使用脚手架?

我们将要创建的 CLI 是一种脚手架软件,因为它基于模板生成启动代码。需要注意的是,脚手架生成的启动代码通常无法用于生产环境,但它仍然有其优势,可以提升开发者体验,更容易实现标准化,并加快软件交付速度。

脚手架的一些好处包括:

  • 涉及更少的工作 - 不再复制和粘贴样板代码(即从一个文件到另一个文件的相对导入)
  • 自动实施设计模式和最佳实践
  • 减少生成新项目或组件的时间
  • 比手动复制、粘贴和编辑更不容易出错
  • 鼓励一致性和设计模式的实施

脚手架可以回答以下问题:

  • 这个 React 组件的翻译应该放在哪里?
  • 在哪里可以找到我们当前的代码标准?
  • 这种类型的文件应该放在哪个目录中?
  • 我应该使用 cameCase、snake_case、kebab-case、PascalCase 还是 UPPERCASE_SNAKE_CASE?

脚手架“值得”吗?

实施脚手架需要时间。应该评估搭建特定软件的潜在收益与开发所需时间,以确定是否值得投入时间和精力。如果我们分析的是预计投入的时间与节省的时间,而不是其他无形的收益,例如一致性或减少上下文切换,您可以使用下面的漫画来评估是否值得实施。

值得花时间看漫画吗

至于其他潜在的缺点,代码需求通常会随着时间的推移而变化,而脚手架模板可能需要在未来随着新需求的出现而进行维护。理想情况下,重构脚手架模板应该感觉像是工作流程的自然延伸,而不是维护会带来额外的开销并拖慢流程。脚手架可以隐藏一些实现细节或决策,从而减少上下文信息,具体取决于所使用的工具,用于生成文件的实际逻辑是否易于访问。

微型生成器工具:PlopJS

如果您正在寻找一种轻量级的方式将脚手架引入您的工作流程,请考虑使用微型生成器Plop。Plop允许开发人员通过命令行界面 (CLI) 根据用户输入生成文件,且只需极少的设置。

Plop 如何工作?

PlopJS 结合了 Handlebars 模板语言和Inquirer.js。Inquirer.js是一个通过 CLI 收集用户输入的工具。您可以使用 Inquirer 以不同的格式呈现问题(也称为 CLI 提示)。Handlebars是一种您可能熟悉的模板语言。模板语言可用于各种场景,从显示 React 属性、创建电子邮件模板,甚至简化您的工作流程(正如我们今天将要看到的)。之前,我.jsx在 React 中使用Jekyll中的Liquid模板语言,以及 Foundation 中用于电子邮件的Handlebars 。

通过将 Inquirer.js 的功能与 Handlebars 相结合,plop 允许用户以最少的设置快速创建模板。如果您不熟悉软件模板,可以将其视为文字处理器中的邮件合并。在邮件合并中,通常会有一个包含数据的电子表格,然后将其与包含占位符变量的模板文档合并。当数据和模板与邮件合并结合时,结果是一个文档版本,其中包含位于正确位置(由占位符变量确定)的数据。该文件中的数据在邮件合并过程中填充,并根据收件人的需要进行定制。在我们的例子中,在 CLI 中输入的数据将被填充到模板中,并在运行时生成一个新文件plop

扑通设置

package.json如果您已经有一个包含要生成文件的目录,那么可以使用yarn以下命令安装 Plop:

yarn add -D plop

npm使用:

 npm install —save-dev plop

如果您还没有,package.json您可以通过键入yarn initnpm init并按照步骤安装来创建一个plop

一旦plop将 作为包依赖项安装,我们就应该更新文件scripts中的package.json,以便能够运行yarn plop例如,您可以随意命名命令,它的行为将相同。npm plopplop"plop""generate": "plop"

"scripts": {
 "plop": "plop"
}

与代码片段不同,plop 无需额外设置即可在计算机之间或开发人员之间共享,并且可进行版本控制。此外,plop 还允许一次生成多个文件。

plopfile.js现在我们可以在目录的根级别创建第一个,这就是 plop 魔法发生的地方。

plopfile.js

module.exports = function(plop) {
  /* welcome messag that will display in CLI */
  plop.setWelcomeMessage(
    "Welcome to plop! What type of file would you like to generate?"
  ),
    /* name and description of our template */
    plop.setGenerator("generate blog post ✏️", {
      description: "Template for generating blog posts",

      prompts: [
        /* inquirer prompts */
        /* questions we want to ask in CLI and save questions for*/
      ],

      actions: [
        /* what should be generated based off of the above prompts */
      ],
    })
}

现在我们有了基础,plopfile.js让我们添加一些功能。首先,我们将添加生成前置内容或元数据的功能,这些内容需要出现在每篇博客文章的草稿中,以便 Gatsby 能够正确生成它们。

sample frontmatter

---
title: Automating File Creation With JavaScript
date: 2020-01-14T12:40:44.608Z
template: "post"
draft: true
slug: 2020-01-14-automating-file-creation-with-javascript
category:
  - tutorial
description: This article walks through how to use plop a micro-generator to generate new text-based files.
---

我们应该更新 plop 文件,添加内置功能,将今天的日期格式化为 ISOString,并使用 Inquirer(内置于 Plop)创建 CLI 提示符并收集输入。
我们将使用new Date(Date.now())来获取当前日期。我们将把日期格式化为ISOStringDate:2020-01-14T12:40:44.608ZshortDate: 2020-01-14ISOStringDate
将在 frontmatter 中使用,而shortDate将在新生成文件的文件路径中使用。日期实用程序将通过设置返回,plop.setHelper()以便.hbs通过写入{{ISOStringDate}}或 来公开模板中的值{{shortDate}}

在收集提示中的输入方面,提示的基本结构是

{
  // example inquirer types:
  // input, list, raw list, expandable list, checkbox, password, editor
  // learn more here: https://github.com/SBoudrias/Inquirer.js#prompt-types
  type: "input",

  name: "description",

  message: "Description of post:",

  }

本例中最复杂的提示符是这个列表提示符,它允许用户使用箭头键选择博客文章的类别,然后我们将该值转换为小写字符串。该filter提示符可用于将用户友好的值(例如“yellow”)转换为可插入到模板中的值#ffff00

 {
          type: "list",
          name: "category",
          message: "Category:",
          choices: ["Tutorial", "Reflection"],
          filter: function(val) {
            return val.toLowerCase()
          },
        },

一旦所有提示都处理完毕,我们就需要通过添加操作来对输入进行一些处理:

{
          type: "add",
          path: `content/blog/${shortDate}-{{dashCase title}}.md`,
          templateFile: "src/plop-templates/blog-post.hbs",
        },

一种操作类型add在 处创建一个新文件,path并将来自 的响应prompts和来自 plop 助手的值插入到 中templateFile

plopfile.js此时的完整内容应类似于以下内容:

module.exports = function(plop) {
  // highlight-start
  const today = new Date(Date.now())
  const shortDate = today.toISOString().split("T")[0]
  plop.setHelper("shortDate", () => shortDate),
    plop.setHelper("ISOStringDate", () => today.toISOString()),
    // optional welcome message

    // highlight-end
    plop.setWelcomeMessage(
      "Welcome to plop! What type of file would you like to generate?"
    ),
    plop.setGenerator("blog post ✏️", {
      // highlight-start
      description: "template for generating blog posts",
      prompts: [
        {
          type: "input",
          name: "title",
          message: "Title of post:",
        },
        {
          type: "input",
          name: "description",
          message: "Description of post:",
        },

        {
          type: "list",
          name: "category",
          message: "Category:",
          choices: ["Tutorial", "Reflection"],
          filter: function(val) {
            return val.toLowerCase()
          },
        },
      ],
      actions: [
        {
          type: "add",
          path: `content/blog/${shortDate}-{{dashCase title}}.md`,
          templateFile: "src/plop-templates/blog-post.hbs",
        },
      ],
      // highlight-end
    })
}

为了实际使用它,我们需要blog-post.hbssrc/plop-templates/目录中创建模板。在这个.hbs文件中,我们对代码进行参数化,以便只保留文件之间需要的部分,并根据生成内容的名称或类型设置占位符。Plop 内置了大小写辅助函数,例如titleCase或 ,dashCase用于格式化输入(查看内置的大小写修饰符:https: //plopjs.com/documentation/#case-modifiers)。

博客文章.hbs

---

title: {{titleCase title}} # from title prompt

date: {{ISOStringDate}} # from plopHelper

template: “post”

draft: true

slug: {{shortDate}}-{{dashCase title}} # from plop helper and title prompt

category:

- {{category}} # from category prompt

description: {{description}} # from description prompt

---
## Intro
{{description}}
<!— The blog post starts here >

现在运行yarn plop应该会引导您完成我们添加的提示,并根据提示的响应和 Handlebars 模板生成一个新文件。生成的文件将
位于content/blog/${shortDate}-{{dashCase title}}.md(或您在 中设置的路径action)。

生成 JSX 页面的 Plop 示例

下面是用于生成Page.jsx和的更新 plopfile 和示例 handlebars 模板Page.test.jsx

Page.hbs

import React from "react"



// Components

import { Helmet } from "react-helmet"

import { graphql } from "gatsby"

import Layout from "../components/page/layout"



const {{properCase pageName}} = ({

data: {

site: {

siteMetadata: { title },

},

},}) => (<Layout>

<div>

<Helmet title={title} />

</div>

</Layout>)





export default {{properCase pageName}}



export const pageQuery = graphql`

query {

site {

siteMetadata {

title

}

}

}

pageTest.hbs

import React from "react"

import { shallow } from "enzyme"

import Layout from "../components/page/layout"

import {{properCase pageName}} from "./{{properCase pageName}}"

import { Helmet } from "react-helmet"



const data = {

site: {

siteMetadata: {

title: “monica*dev”,

},

}

}



describe(“{{properCase pageName}}”, () => {

const component = shallow(

<{{properCase pageName}} data={data} />)



it(“renders page layout”, () => {

expect(component.find(Layout)).toHaveLength(1)

})



it(“renders helmet with site title from site metadata”, () => {

expect(component.find(Helmet).props().title).toBe(“monica*dev”)

})

})

plopfile.js

module.exports = function(plop) {

const today = new Date(Date.now())

const shortDate = today.toISOString().split("T")[0]

plop.setHelper("shortDate", () => shortDate),

plop.setHelper("ISOStringDate", () => today.toISOString()),

plop.setGenerator("generate blog post ✏️", {

 /*...*/

}),

plop.setGenerator("Create new page 📃", {

description: "template for creating a new page",

prompts: [

{

type: "input",

name: "pageName",

message: "Page name:",

},

],

actions: [

{

type: add,

path: src/pages/{{properCase pageName}}.jsx,

templateFile: src/plop-templates/page.hbs,

},

{

type: add,

path: src/pages/{{camelCase pageName}}.test.jsx,

templateFile: src/plop-templates/pageTest.hbs,

},

],

})

}

格式化输出

我遇到了一个问题,由于初始模板文件是.hbs文件,因此生成的文件不一定是像.md或 这样的格式.jsx。我的项目中已经设置了 Prettier,因此为了解决格式问题,我最终更新了plop脚本简写,使其在运行 plop 后格式化所有文件。但是,这应该重构为仅格式化相关的、刚刚生成的文件。

"scripts": {
  ...
  "format": "prettier —write \"**/*.{js,jsx,json,md}\"",
  "plop": “plop && yarn format”
}

结论

回顾一下,我们过去常常plop生成跨特定类型文件共享的样板代码。理想情况下,实现某种类型的文件创建自动化应该能够减少创建功能文件的时间,比复制+粘贴+编辑更不容易出错,并鼓励一致性和设计模式的实施。

创建您自己的模板

关于模板中应包含的内容的一些想法:

  • 根据 React 组件的类型创建不同的模板(React-Boilerplate 的生成器中的示例)
  • 生成注释或基础文档
  • 生成包含索引文件(和相关测试)、package.json 和 README.md 的自包含目录或包

其他资源

去年,我有机会通过 CLI 提示符简化了新包的创建(这启发了我在伦敦 React Girls Conf 上关于生成 React 组件的演讲),并促使我进一步了解了 Plop。如果您有兴趣了解更多关于 Plop 在 React 环境中的应用,或者 Plop 的替代方案,请查看我之前的演讲。

Monica 在伦敦 React Girls Conf 上关于自动化 React 工作流的演讲速写
@malweene在我的 React Girls Conf 演讲中做的速写笔记

这里有一些额外的资源,当您越来越熟悉使用 Plop 生成文件时可能会有所帮助。

本文最初发表于www.aboutmonica.com

文章来源:https://dev.to/m0nica/automating-file-creation-with-javascript-8j5
PREV
ReactJS 中常见的错误
NEXT
工程白板面试:是还是不是?白板面试是评估工程候选人的最佳方式吗?白板面试是评估工程候选人的最佳方式吗?白板面试是评估工程候选人的最佳方式吗?