创建 CLI 来自动创建文件
您是否发现自己需要同时为多个文件复制粘贴相同的样板代码?每次需要构建ISO 8601格式的日期时,您是否都会停下来思考?🤔
如果您无法再将模板代码从一个文件复制粘贴到另一个文件,那该有多令人沮丧?
本文将介绍如何快速创建一个用于生成文本文件的命令行界面 (CLI) 工具。具体来说,本文中的示例将演示如何创建模板,以便.jsx
在Gatsby博客中生成包含测试的新页面,以及如何生成包含博客文章初始结构的 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 init
或npm init
并按照步骤安装来创建一个plop
。
一旦plop
将 作为包依赖项安装,我们就应该更新文件scripts
中的package.json
,以便能够运行或yarn plop
。例如,您可以随意命名命令,它的行为将相同。npm plop
plop
"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.608Z
和shortDate
: 2020-01-14
。ISOStringDate
将在 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.hbs
在src/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 的替代方案,请查看我之前的演讲。
@malweene在我的 React Girls Conf 演讲中做的速写笔记
这里有一些额外的资源,当您越来越熟悉使用 Plop 生成文件时可能会有所帮助。
- https://github.com/plopjs/plop
- https://github.com/react-boilerplate/-react-boilerplate/tree/master/internals/generators
- https://github.com/SBoudrias/Inquirer.js/
- https://handlebarsjs.com/
- https://prettier.io/
- https://github.com/M0nica/generate-kawaii-components
本文最初发表于www.aboutmonica.com
文章来源:https://dev.to/m0nica/automating-file-creation-with-javascript-8j5