Hygen : 简单、快速和可伸缩的代码生成器

2,169 阅读9分钟

介绍 (Introduction)

快速启动 (Quick Start)

您可以有很多种方式去运行 hygen, 选择以下一项: 在 macOS 系统上使用Homebrew:

$ brew tap jondot/tap
$ brew install hygen

全局使用npm(or yarn):

$ npm i -g hygen

或者, 如果您喜欢没有附加条件的方法, 请使用 npx

$ npx hygen ...

如果这些中的任何一个都使您感到困惑,请查看您在每个主要平台上找到二进制的 二进制版本

完成此操作,让我们使用 Hygen Generator。

要使用 generator, 我们提供了一个 GENERATOR ACTION (下面是关于 mailernew 操作的一些例子).

$ hygen mailer new [NAME]
         |      ^----- action
         `------------ generator

[[info]] 新版本 hygen 4.0.0; NAME 参数可以省略, 在使用4.0.0 之前的版本, 您仍然必须使用 --name NAME .

执行下面的命令即可获得自己项目中的generator

$ hygen init self
$ hygen generator new awesome-generator
$ hygen awesome-generator new hello

想要查看 generator 的帮助, 您可以这样做:

$ hygen generator help

Loaded templates: _templates
help:
hygen generator new [NAME] --action [ACTION]
hygen generator with-prompt [NAME] --action [ACTION]

最后, 使用 --dry 对您的generators进行测试运行

恭喜! 您已经制作了一个名为 awesome-generator 的 新的生成器!

让我们浏览一下刚刚做了些什么.

[[info]] 您也可以从Homebrew 中 安装或者下载 独立的二进制文件. 有关更多信息, 请参见 Standalone

Bootstrapping Your Project

使用 hygen init self 命令 开始在您的项目中使用它. hygen 配备了两个预构建的generators, 可帮助您构建自己的generator, - 尽管它的构建是为了减轻对React,Redux,和Node.js 的样板疲劳,但实际上可以用于任何技术.

$ cd your-project
$ hygen init self

Loaded templates: src/templates
       added: _templates/generator/with-prompt/hello.ejs.t
       added: _templates/generator/with-prompt/prompt.js
       added: _templates/generator/new/hello.ejs.t

这将在你本地项目中添加一个_templates的文件夹, 并为您节省时间创建了两个助手生成器:

hygen generator new generatorName - 为您创建了一个new的生成器 hygen generator with-prompt new generatorName 与上一个相同, 只不过该生成器只有prompt 驱动

接下来在您的项目跟路径下, 让我们创建一个 new 的生成器:

$ hygen generator new mygen
Loaded templates: _templates
       added: _templates/mygen/new/hello.ejs.t

现在来使用它:

$ hygen mygen new

Loaded templates: _templates
       added: app/hello.js

[[info]] 您注意到了吗?⚠️ | 我们选择将复制您本地模版中的文件夹来作为创建的命令, 而不是在hygen中用 hygen generator new 来创建模板. || 用这种方式, 我们甚至可以调整hygen生成新的generators的方式. 它扩展到在不同的团队中可以根据自己的喜好来设置.

就是这样! 我们现在已经完成一个基础的hygen, 接下来是 templatesgenerators 的详细概述.

Invoking Generators

下面有多种方式供你去创建新的模板:

# 使用'name'变量生成所有的文件
$ hygen example-prompt new reporter


# 根据文件的名称字符串截取'mailer'生成确定的文件
$ hygen example-prompt new:mailer reporter


# 正则表达式生成对应的资源
$ hygen example-prompt 'new:.*html' reporter

好的! 你做到了最后, 现在可以查看 templatesgenerators 了.

模版 (Templates)

hygen 模版就像是Markdown式的**[frontmatter](#All FrontMatter Properties)**, 他的主体是ejs 的模版引擎

---                            <----- frontmatter section
to: app/emails/<%= name %>.html
---

Hello <%= name %>,
<%= message %>                 <----- body, ejs
(version <%= version %>)

Frontmatter

frontmatter 通过匹配可以划定界限 --- 我们可以在模版的上面和下面用 yaml 来定义模版数据

模版也会被渲染, 如果我们在文件中有下面的: _templates/mailer/campaign/emails.ejs.t:

---
to: app/<%=section%>/emails.js
foo: <%= bar %>
---

用下面的命令:

$ hygen mailer campaign --section weekend --bar ping

它其实是干了这件事:

---
to: app/weekend/emails.js
foo: ping
---

Template Body

让我们回想一下模版的外观. 模版的主体是 ejs:

---
to: app/workers/<%= name %>.js
---

class <%= Name %> {
  work(){
   // your code here!
  }
}

hygen中, 可以用首字母大写的写法 Name 获取到 name 的首字母大写的值, 但是仅有这一个(变量name)可以这样写

如果你想要 对其他的变量进行首字母大写, 建议用以下写法:

---
to: app/workers/<%= name %>.js
---
<% 
  Message = message.toUpperCase()
%>

class <%= Name %> {
  work(){
    return "<%= Message %>"
  }
}

Helpers and Inflections

您也可以通过调用 h 使用内置的方法:

class <%= Name %> {
    work(){
        return "<%= h.capitalize(message) %>"
    }
}

特殊的辅助对象h还托管对象inflection, 可以调用里面的方法:

// example: <%= h.inflection.pluralize(name) %>

pluralize( str, plural )
singularize( str, singular )
inflect( str, count, singular, plural )
camelize( str, low_first_letter )
underscore( str, all_upper_case )
humanize( str, low_first_letter )
capitalize( str )
dasherize( str )
titleize( str )
demodulize( str )
tableize( str )
classify( str )
foreign_key( str, drop_id_ubar )
ordinalize( str )
transform( str, arr )

Change case helpers

hygen 提供了 change-case 库去更改文案的能力, 它易于使用并且非常便于理解:

// example: <%= h.changeCase.camel(name) %>

camel( str )
constant( str )
dot( str )
header( str )
isLower( str )
isUpper( str )
lower( str )
lcFirst( str )
no( str )
param( str )
pascal( str )
path( str )
sentence( str )
snake( str )
swap( str )
title( str )
upper( str )

下面是基于React的组件生成的用力:

---
to: components/<%= name %>/index.jsx
---
import React from 'react'

export const <%= name %> = ({ children }) => (
  <div className="<%= h.changeCase.paramCase(name) %>">{children}</div>"
)

带有名为 HelloWorld 将会编译为:

import React from 'react'

export const HelloWorld = ({ children }) => (
  <div className="hello-world">{children}</div>"
)

Local Variables

正如我们之前看到的, 任何CLI参数或者提示参数都会自动成为模板中的局部变量.

下面有两种方法可以参考变量:

Hello <%= message %>

这种方式以暴露的形式 - 指的是 message CLI 参数或者prompt参数, 这也意味着 此参数不能是可选的(否则会抛出reference error)

Hello <%= locals.message %>

这种方式通过 local 对象来存储 message CLI 参数 或者prompt的参数, 如果您想要在使用之前检查一个存在的变量, 这将会是一个比较好的选择:

<% if (locals.message) { _%>
  message: <%= message %>
<% } _%>

Predefined Variables

如果您查看以下命令:

hygen component new:story

hygen 将会为您分解它, 并将某些之放在模版中自动可用的特殊变量中:

VariableContentExample
templatesTemplates path (absolute)/User/.../project/_templates
actionfolderAction path/.../component/new
generatorGenerator namecomponent
actionAction namenew
subactionSub-action namestory
cwdProcess working directory/User/.../project

例如 用 actionfolder :

<%= actionfolder %>

Addition

默认情况下, 将模版"添加"到您的项目中作为新的目标文件, 通过指定 to: frontmatter属性, 我们告诉 hygen 在哪里去添加 force:true, 将会告诉 hygen 有存在的文件时 重写它而不需要跟用户确认(默认 force: false)

---
to: app/index.js
force: true
---
console.log("this is index!")

如果目标文件已经存在,您不想要去重写它, 您可以使用 unless_exists:

---
to: app/index.js
unless_exists: true
---
will not render if target exists

From & Shared Templates

默认情况下, 模版的主体用作创建目标文件的输入. 通过指定 from : frontmatter 或许, 我们告诉 hygen 从哪个外部文件去加载主体 例如: from: shared/docs/readme.md, 将会告诉 hygen, 从_templates/shared/docs/readme.md 文件中加载主体. 该模版的主体被忽略:

---
to: app/readme.md
from: shared/docs/readme.md
---
THIS BODY IS IGNORED !!!

Injection

您也可以选择将模版注入现有目标文件

为此, 您需要使用 inject: true 与伴随的 注入内容一起使用

---
inject: true
to: package.json
after: dependencies
skip_if: react-native-fs
---
"react-native-fs":"*",

这里要注意的是新的属性 afterskip_if . 该模版将会添加 react-native-fs 依赖到package.json文件中, 但不会被添加两次(因为skip_if)

[[info]] 无处不在的正则表达式提高了使用的灵活性 在after:dependencies, "dependencies" 实际上是一个正则表达式, 因此, 它将会在package.json文件中的块找到 "dependencies": {

下面是 inject:true 的可用模版:

  • before/after 将会定位到包含文本的正则表达式. 注入的文件将会出现在定位线 之前或者之后
  • prepend/append: 当设置为true, 将会在文件的开头或者结束添加一行
  • at_line 将会在具体的某一行添加

几乎在所有情况下, 您都需要确保您不会两次注入内容:

  • skip_if 包含正则表达式/文本, 如果存在, 则会跳过注入

让我们看看这些在**Redux**中如何发挥作用

Shell

shell 选项会让你有能力去启动任何的shell命令, 您可以做任何事例如:

  • 从一个目标文件中copy一个资源或者一个asset
  • 将模板的输出输送到外部的命令中
  • 执行任何其他副作用 - 接触文件, 重启程序 启动 yarn install 或者你想要做的

下面是将模版中的输出放到外层的命令中的方法

---
sh: "mkdir -p <%= cwd %>/given/app/shell && cat > <%= cwd %>/given/app/shell/hello.piped"
---
hello, this was piped!

仅使用属性sh, hygen 将会理解这是一个 shell 的动作. 请注意,您拥有可预见的 cwd 变量, 只想到当前的工作目录.

该生成器将其输出到 shell 命令中, 因此, 您可以假设它发生了, 请注意, cat 期望给它一个STDIN.

有时您想运行一个生成器并调用附加的命令, 这意味着可以将外壳动作添加到您想要执行的任何动作(注入或添加)中。

下面是一个命令的任务: 添加一个依赖并且调用 yarn install.

---
inject: true
to: package.json
after: dependencies
skip_if: lodash
sh: cd <%= cwd %> && yarn install
---
"lodash": "*",

Conditional Rendering

如果您想要通过判断一个值是否存在去渲染一个确定的模版, 您可以这样做:

---
to: "<%= message ? `where/to/render/${name}.js` : null %>"
---
conditionally rendering template

hygen遇到一个to: 值为null时, 将会跳过这个模版的输出, 这就意味着它将不再被渲染

接下来, 我们移步到 generators.

All FrontMatter Properties

PropertyTypeDefaultExample
to:String(url)undefinedmy-project/readme.md
from:String(url)undefinedshared/doc/readme.md
force:Booleanfalsetrue
unless_exists:Booleanfalsetrue
inject:Booleanfalsetrue
after:RegexundefineddevDependencies
skip_if:RegexundefinedmyPackage
sh:Stringundefinedecho: "Hello this is a shell command!"

生成器(Generators)

hygen, 每次您发现重复任务或者正在编辑的文件中有隐藏的结构, 您都可以快速制作一个新的generator.

$ hygen generator new --name mailer
                              `-------- just a name you pick.

Loaded templates: _templates
       added: _templates/mailer/new/hello.ejs.t
                                       `------ your template file.


$ hygen

Error: please specify a generator.

Available actions:
generator: new, with-prompt
mailer: new
    \
     `----------- your new generator is already here!

当我们在_templates下添加一个generator是, 就可以使用, 下面是放在template文件夹中的Hello.ejs.t:

---
to: app/hello.js
---
const hello = `
Hello!
This is your first hygen template.

Learn what it can do here:

https://github.com/jondot/hygen
`

console.log(hello)

要制作一个真实的的mailer, 复制下面的文件重新命名它:

$ mv _templates/mailer/new/{hello.js,html.ejs.t}
$ cp _templates/mailer/new/{html.ejs.t,text.ejs.t}

我们使用 .t 后缀, 因为它会禁掉我们的编辑器试图变得聪明 - 使用您任何喜欢的东西.上面的这个例子中的文件代表 html 和 text

[[info]] ###### 创意自由 | hygen 不在乎文件名称或者文件类型, 它仅关心文件夹结构和文件的内容

您还需要注意每个模版都有一对特殊的's, 在我们的例子中, 我们有一个特殊的 to: 该属性会告诉hygen将生成的文件放到哪里. 我们会在 模版 中更多看到这些

Structure

让我们看一下我们文件夹的结构:

_templates/
  mailer/
    new/
      html.ejs.t
      text.ejs.t
app/
  index.js
package.json

每次您调用它时, hygen mailer new 都会自动化的选择最接近的 _templates文件夹, 并在mailer/new中渲染所有的文件, 在上面例子这种情况下, 它会调用html.ejs.ttext.ejs.t 去生成

PR 102 开始, hygen将递归地走您的模版文件夹, 以便您可以按照自己的方式进行精心的构造

[[info]] ###### hygen 上下文 | hygen 通过断言 “命令结构是文件夹结构” 来简化事物. || |hygen 将在您当前的工作目录中获取 _templates

CLI Arguments

为了给hygen以CLI的方式加入参数, 我们遵循以下模式:

$ hygen mailer new --name foobar --message hello --version 1

任何双键(--)参数都会成为我们以后在模版中可以使用的变量. 在上面的示例中, 我们可以 name, message, version

下面是我们在模版 **(html.ejs.t)**中使用这些变量的例子

---
to: app/emails/<%= name %>.html
---
<h1>Hello <%= name %></h1>
<%= message %>
(version <%= version %>)

尝试通过编辑 text.ejs.t 进行文本变体. 注意: 使用 **to:**将其放在正确的位置

Interactive Prompt

为了创建一个有交互的生成器, 添加 prompt.js 文件到生成器的根目录

_templates/
  mailer/
    new/           <-- the mailer new generator
      prompt.js    <-- your prompt file!
      html.ejs.t
      text.ejs.t

例如: 要求输入变量message, 添加prompt.js:

module.exports = [
  {
    type: 'input',
    name: 'message',
    message: "What's your message?"
  }
]

基于enquirer库的格式, 让我们使用message变量:

---
to: app/emails/<%= name %>.txt
---
<%= message %>

注意 name变量是从CLI 的参数来的, 我们可以用下面的命令:

$ hygen mailer new --name fancy-mailer

它将会向用户询问 message, 并且生成所有内容

Advanced Interactive Prompt

可以在其中提出一些问题, 运行一些计算并提出更多问题的情况下创建一个多步骤提示.

此外, 也可以跳过提示或者重新定义在CLI中或者提示中提供的参数, 以便您可以在中心位置去运行.

您可以在操作中使用index.js 文件来替换prompt.js 文件来"启用"高级参数并提示

my-generator
  my-action/
    index.js
    template1.ejs.t
    template2.ejs.t

下面是在 index.js 中如何去创建两步提示的方法. 您只需要导出一个对象名为prompt的函数对象去替代在 prompt.js中导出问题类型的数组:

// my-generator/my-action/index.js 
module.exports = {
  prompt:({ prompter, args }) =>
    prompter
    .prompt({
       type: 'input',
        name: 'email',
        message: "What's your email?"
    })
    .then(({ email }) =>
      prompter.prompt({
        type: 'input',
        name: 'emailConfirmation',
        message: `Please type your email [${email}] again:`
      })
    )
}

prompt 可以使用提示的字段获得数据结构

您可以使用自定义的逻辑进行条件提示:

// my-generator/my-action/index.js
module.exports = {
  prompt: ({ prompter, args }) => {
    if (args.age > 18) {
      return Promise.resolve({ allow: true })
    }
    return prompter.prompt({
      type: 'input',
      name: 'age',
      message: 'whats your age?'
    })
  }
}

您可以跳过提示并使用从CLI中得到的参数去构建更复杂的参数:

// my-generator/my-action/index.js
module.exports = {
  params: ({ args }) => {
    return { moreConvenientName: args.foobamboozle }
  }
}

[[info]] ##### params和prompt相同 | 如果您考虑一下,prompt中的变量或者CLI中的参数会导致相同的目标: 新参数. 但是, 为了兼容未来的API, 我们将这两个分开为promptparams功能.

Documenting Your Generators

由于您可以在任何模版中使用一个特殊的变量 message, 因此您可以使用它去构造 generator help, 最终, 您的generator应记录自己

查看我们的generator的布局, 我们添加一个 help 的action:

_templates/
  mailer/
    help/
      index.ejs.t
    new/
      prompt.js
      html.ejs.t
      text.ejs.t

我们的index.ejs.t 只是一个空白模版, 只有一个属性message:

---
message: |
  - hygen {bold mailer} new --name [NAME]
---

特别注释 | 是YAML字面快. 如果您需要了解它, 请点击查看YAML

注意 在 message 中 可以使用特殊的color 语法, 可以为您的文档增加一些趣味

下面是一些例子:

{bold mailer}
{red mailer}
{underline mailer}
{green mailer}

有关更多样式, 查看chalk

Selecting Parts of a Generator

到了目前为止我们已经看到的东西外, hygen 还让用户选择声称模版的一部分:

$ hygen GENERATOR ACTION:SUBACTION

SUBACTION 是正则表达式或者简单的字符串用来从generator中截取得到的子集

使用我们的mailer 例子, 该generator指生成text的部分:

$ hygen mailer new:text --name textual-mailer

由于我们已经有文件命名为 text.ejs.t, 字符串textnew:text 中会被匹配上

同样地方式我们可以用下面正则表达式的方式去匹配:

$ hygen mailer new:.*xt --name textual-mailer

可扩展性(Extensibility)

您可以使用特殊的 .hygen.js 文件扩展hygen的这些属性:

  • 模版中Helper的功能
  • 自定义logger, 模版定位以及shell执行命令
  • (WIP) 自定义generator操作除了内置的添加,inject, shell

.hygen.js

Hygen 支持用于搜索**.hygen.js** 文件并加载它的冒泡(bubbling-up)机制, 无论您从哪儿运行hygen, 它都会开始向上去搜索此文件, 直到找到第一个文件 这意味着:

  • 您可以每一个项目配置一个文件
  • 对于某些特殊的子项目, 为不同的行为设置了不同的行为
  • 如果您将其放入到用户的文件夹中, 也可以设置一个全局文件

注意: 当前策略在路径中有两个或者多个 .hygen.js 文件时, 是先向上找到第一个文件之后使用该策略, 而忽略其他的

Helpers

下面是使用辅助工具函数h不存在函数的模版, 由于缺少更好的名称, 该功能先成为extended

---
to: given/hygen-js/new.md
---
this demonstrates hygen loaded up .hygen.js and extended helpers.
<%= h.extended('hello') %>

为了将extended函数添加到标准的hygen helpers的集合中, 我们在根目录下创建一个 .hygen.js的文件

src/
package.json
.hygen.js

.hygen.js文件将会是我们所有扩展方法的位置. 对于此示例, 我们将使用以下内容:

module.exports = {
    helpers: {
        extended: s => s.toUpperCase()
    }
}

请注意, 除了像在此示例中插入使用的程序功能之外, 您可以在此处获取和使用此项目中的任何代码 , 并将其导出以供hygen使用

生态系统 (Ecosystem)

包(Packages)

跨项目和跨团队的共享生成器可以简单地通过复制或者您的团队提出的完全适合您的工作流的任何自定义工具来实现.

hygen-add 工具提供了一种简洁的方法来通过引入package来实现此目的. package包是一组编译的生成器, 该生成器以node module 的形式发布, 您可以和他人安装和共享

Popular Packages

hygen-cra(create-react-app) - 为您的 Create React Project 声称一组component,storybook 和 test

Installing a Package

首先, 你需要去安装 hygen-add 工具, 它是hygen工具箱中的工具之一.

yarn global add hygen-add

现在, 在npm上选择已发布的模块并安装它, 对于命名为hygen-acme-generator的模块, 安装时 不用加上前缀 hygen-

$ hygen-add acme-generators

这将带有嵌入式yarnacme generators packages 安装, 因此将其版本化和锁定, 并将generators添加到您的当前的项目中. 从技术上讲, 它将generator复制到您的本地_templates目录, 因为复制更具有弹性和稳健型, 而不是引用可能意外变化的事物

完成此操作后, 您可以使用yarn删除acme-generators,如果您想要偶尔同步模版, 请留下它

Installing from Github

hygen-add 支持从github上安装, 就像yarn支持它一样, 因为它在引擎下使用yarn, 它将尝试共github repo url 上推断package名称

运行下面的命令:

$ hygen-add https://github.com/butttons/hygen-vuex

将会安装包vuex. 包命名为vuex是从github url中解析出来的

如果处于某种原因, 您的github url没有透露出有关package名称的任何信息, 则可以手动置顶package名称(请记住: package名称是Package Project 的package.json文件中的name属性)

$ hygen-add https://github.com/acme/archive --name acme-generators

Name Clashes

如果您想要从 acme 或者 awesome 中安装 react package, 则可以在其中一个或两者兼而有之以避免名称冲突:

$ hygen-add acme-react
$ hygen-add awesome-react --prefix awsm

创建(Create)

在某些情况下, 随着时间的推移维护代码的生成器可能是一个挑战, 如果您有一个bug在generator中, 则需要去修改您的templates ,运行修改后的生成器, 最后手动将更改重新导入模板中, 同时记住用EJS占位符替换关键字. hygen-create 提供了一种创建新的生成器的方法, 采用现有的代码并自动将其转化为generator.

hygen-create创建一个generator

  • 开始hygen-create会话
  • 选择要包含在generator中的文件
  • 指定一个字符串, 以自动变成结果模版中的EJS摘要
  • 运行 $ hygen-create generate

就是这样! 您的hygen 生成器已经准备就绪, 您可以使用它来创建您概括的代码的新"实例", 如果不正确的话, 您可以直接在生成的代码中解决问题, 然后在修改的代码上运行hygen-create, 以使用修复程序更新generator. 它甚至还记得最初包含哪些文件, 因此您不必再次选择文件.

查看更多详细信息, 请参见 github.com/ronp001/hyg…

Installation

yarn global add hygen-create

or

npm install --global hygen-create

Key Features

  • 字符串变化(UPPERCASED, CamelCased, etc.) 自动识别, 例如:
// if the following 'package.json' is used as a generator source
// with 'MyPackage' as the templatization string:
{
   "name": "MyPackage",
   "index": "my-package/bin/my_package.js"
}
  • 维护目录层次结构
  • 逐渐以类似git的方式将文件添加到generator
  • 与内置有色差异一起查看自动插入的EJS占位符
  • 通过在generator 创建的代码上再次运行hygen-create来迭代作者生成器

独立模式(Standalone)

如果您没有安装Node.js, 则可以在独立模式下 安装hygen二进制文件, 根据您的操作系统, 您有一些选择:

MacOS

您可以通过Homebrew添加 brewtapjondot/tapbrew tap jondot/tap brew install hygen

Linux, Windows, MacOS (Non-homebrew)

您可以在github版本页面上下载操作系统的二进制文件. 每个新版本都会自动更新二进制文件并自动push.

Why Standalone?

有几个原因想把hygen作为独立:

  • 您不熟悉node.js或者不想安装它
  • 您更喜欢全局二进制, 以避免一遍又一遍地安装相同的二进制
  • 您将hygen打包到自己的软件,Docker image 或者重新分发它
  • 您正在建立在hygen上的工具, 需要可延展的二进制去执行它
  • 独立包要快一些(因为它快照代码), 您想要额外的速度

用例 (Use Cases)

Redux

Redux 是 hygen的典型代表, 您为Redux体系结构选择哪个粒度水平都没关系, 您仍然会得到很多样板.

对于我的应用体系结构, 我主要选择ducks, 这有助于删除一些样板, 并加入模块化

我典型的Redux体系结构看起来像这样:

app/
  components/
    icon.js
    avatar.js
  modules/
    boot.js     <---- glues modules together, requires chat, app, and auth.
    app/
      index.js
      view.js
      state.js
    auth/
      index.js
      view.js
      state.js
    chat/
      index.js  <---- the 'connect' bit for Redux.
      view.js   <---- the view, separated, for testing.
      state.js  <---- reducer, actions, types, selectors.

Adding a Module

作为模块化架构, 添加一个模块有一个巨大的优势. 这只是意味着在boot.js中添加一个文件夹, index, view(视图),state(状态), 也许是一个默认组件, 一个storybook故事, 并将所有的内容链接到一起,例如: 一个reducer,导出一个actions等.

Hygen应该轻而易举. 您的模版的外观应该是:

_templates/
  module/
    new/
      index.ejs.t
      view.ejs.t
      state.ejs.t
      inject_boot.ejs.t   <--- adds a 'require' clause to boot.js

这是index文件的内容:

---
to: app/modules/<%= name %>/index.js
---
// 
// require, mapping, etc....
//
export default connect(...)(<%= Name %>)

类似的技巧将永载 viewstate 中.

boot.js像这样的将如何添加一行require?

// ... some bootstrapping code ...

const modules = [
  //     `--- we want to inject "after" this
  require('auth').default,
  require('app').default
]

// ... rest of bootstrapping code ...

inject_boot.ejs.t 打包. 我们必须使用inject:true, 因为这是一个inject 模版, 可以定位到我们要在 const modules = ... 后注入内容.

---
to: app/modules/boot.js
inject:true
skip_if: <%= name %>
after: "const modules = ["
---
require('./<%= name%>).default,

运行之后, 我们最终会得到:

// ... some bootstrapping code ...

const modules = [
  require('chat').default
  require('auth').default,
  require('app').default
]

// ... rest of bootstrapping code ...

我们完成了! 生成一个新模块用下面这个命令:

$ hygen module new --name settings

React Native

虽然Redux 用例是非常适合用于React Native, 但我们具有React Native Packager 的一个唯一(可以说是限制)的属性, 这是: 您不能动态地require任何东西

因此, 所有那些本应该粘贴许多文件的花哨的glob-and-require 代码都无法完成. 这意味着更多的手动工作和记账

// this can't be done
each(m => require(m), glob('modules/**/state'))

Injecting with

使用hygen 我们可以帮助自己维持这些依赖性.

使用hygen生成的每个模块, 组件 或者文件, 我们可以做这样的事情:

_templates/
   component/
     new/
       component.ejs.t
       inject_component.ejs.t <---- let's look at this

下面是inject_component 的样子:

---
inject: true
to: app/globs/all-components.js
skip_if: <%= name %>
after: "const components = {"
---
<%= Name %>: require('../components/<%= name %>').default,

这是假设我们在globs/all-components.js 中有一个核心文件, 我们用来将所有组件引入并重新导出的一个模块

const components = {
  Avatar: require('../components/avatar').default,
  Icon: require('../components/icon').default
}
export default components

运行下面:

$ hygen component new --name intro

将会构建成:

const components = {
  Avatar: require('../components/avatar').default,
  Icon: require('../components/icon').default,
  Intro: require('../components/intro').default
}
export default components

现在我们可以做一些有趣的事情:

import { Intro } from 'app/globs/all-components'

Express

Express.js大概是Node.js中最受欢迎的一个web框架 一个典型的express服务的app 架构 推崇 routeshandlers 的概念, 而 视图和数据则留给 interpretation(可能是因为微服务和客户端应用程序的兴起)

因此一个app的结构应该看起来像这样:

app/
  routes.js
  handlers/
    health.js
    shazam.js

而route.js 将所有的文件粘合到一起:

// ... some code ...
const health = require('./handlers/health')
const shazam = require('./handlers/shazam')
app.get('/health', health)
app.post('/shazam', shazam)

module.exports = app

与React Native不同, 您可以在此处动态导入模块. 但是, 在构建路线时仍需要判断(app.get/post 部分)

使用 hygen 让我们看看如何构建这样的东西:

$ hygen route new --method post --name auth

由于与以前的用例一样, 我们经历了几个模版, 因此我们直接跳到有趣的部分, 注入的部分

因此, 假设我们的generator 的结构是这样的:

_templates
  route/
    new/
      handler.ejs.t
      inject_handler.ejs.t

inject_handler 的模版是这样的:

---
inject: true
to: app/routes.js
skip_if: <%= name %>
before: "module.exports = app"
---
app.<%= method %>('/<%= name %>', <%= name %>)

请注意, 我们将在"module.exports = app" 之前注入. 如果以前的情况,我们将其内容附加到给定行, 我们现在是它的前置