くらげになりたい。

くらげのようにふわふわ生きたい日曜プログラマなブログ。趣味の備忘録です。

Hygenでテンプレート管理/コード生成して開発を楽にする

今までコンポーネントの雛形やスニペットは、
VSCodeのUser Snippetsを使ってたけど、
複数ファイルの生成やリッチなプロンプト、Gitでの管理などは難しい。。

そのあたりの問題を解決してくれる
リッチなコード生成ツールを見つけたので、試してみたときの備忘録(*´ω`*)

www.hygen.io

インストール

npmやHomebrewでインストール

# npm
$ npm i -g hygen
# or
$ npx hygen ...

# Homebrew
$ brew tap jondot/tap
$ brew install hygen

とりあえず実行(Quick Start)

# 用意されてる初期設定用のテンプレートを実行
$ npx hygen init self

# ./_templatesに作成される
$ tree _templates/
_templates/
├── generator
│   ├── help
│   │   └── index.ejs.t
│   ├── new
│   │   └── hello.ejs.t
│   └── with-prompt
│       ├── hello.ejs.t
│       └── prompt.ejs.t
└── init
    └── repo
        └── new-repo.ejs.t

実行時のオプションとテンプレートディレクトリの構成

実行は、GENERATORACTION(と、追加のデータdata-options)を指定するだけ。

$ npx hygen --help

Usage:
  hygen [option] GENERATOR ACTION [--name NAME] [data-options]

Options:
  -h, --help # Show this message and quit
  --dry      # Perform a dry run.  Files will be generated but not saved.

テンプレートディレクトリの構成はこんな感じになっているので、
指定した_templates/<GENERATOR>/<ACTION>にあるファイルが生成される。

_templates/
└── <GENERATOR>
    └── <ACTION>
        └── index.ejs.t

テンプレートファイルの構成(.ejs.t)

テンプレートファイル(.ejs.t)は「frontmatter」と「body」の2部構成。
対象のディレクトリにあるすべての.ejs.tが生成される。

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

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

frontmatter部分

指定した変数がbody部分のパラメタとして渡される。

いくつか特別はプロパティがあり、
to:はファイルを生成する配置先を指定するプロパティ。

body部分

こっちがテンプレートの本体。

EJS(Embedded JavaScript templates)を使っていて、この書き方に従っている。

インタラクティブなコード生成(prompt.js)

.ejs.tだけだと、即コード生成してくれるけど、
指定するパラメタを忘れるので、対話的にしたい。

その場合は、prompt.jsを配置すればOK

(index.[js|ts]prompt.[js|ts|cjs]でもOK)
(tsの場合はts-nodeが必要) - hygen/prompt.ts at v6.2.11 · jondot/hygen

_templates/
└── init
    └── new
        ├── hello.ejs.t
        └── prompt.js
// prompt.js
module.exports = [
  {
    type: "input",
    name: "message",
    message: "What's your message?",
  },
];
// hello.ejs.t
---
to: hello.js
---

console.log("<%= message %>")

実行するとこんな感じで対話的にできる。

$ npx hygen init new
✔ What's your message? · hello

Loaded templates: _templates
       added: hello.js
$ cat hello.js 
console.log("hello")

プロンプト自体は、Enquirerが使われているので、
Prompt Options | Enquirerをみるとよい。

より高度なプロンプト

prompterを使って、回答に応じた追加の質問とかもできる。

// prompt.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:の返り値がbody部分(ejs)で使えるパラメタになるので、
パラメタの追加や加工がしたい場合は、ここでできる。

// prompt.ts
module.exports = {
  prompt: ({ prompter, args }) => {
    return prompter
      .prompt({
        type: "input",
        name: "email",
        message: "What's your email?",
      })
      .then((answer) => {
        return { ...answer, addParam: "ex-param" };
      });
  },
};

おまけ

モノレポでも楽にするプロンプト

モノレポのときは、複数配置先があるけど、
いろいろ選びたくないなと思ったけど、
ディレクトリを読み込んで、プロンプトを構成するといい感じらしい。

.
└── src
    ├── apps
    │   ├── customer
    │   ├── notification
    │   ├── order
    │   ├── payment
    │   └── product
    ├── clients
    │   └── frontend
    └── packages
       ├── payment-clients
       ├── test-data
       └── test-utils
const fs = require('fs');

const exclude = ['things-to-ignore', '.DS_Store'];
const dirs = fs.readdirSync('./src/apps');
const apps = dirs.reduce((acc, d) => {
  if (!exclude.includes(d)) acc.push({ name: d, value: d });
  return acc;
}, []);

module.exports = [
  {
    type: 'input',
    name: 'name',
    message: 'Package name',
  },
  {
    type: 'multiselect',
    name: 'apps',
    message: 'Which App to add to?',
    limit: 5,
    choices: apps,
  },
];

以上!! これでテンプレートも柔軟でバージョン管理できる。。(*´ω`*)

参考にしたサイト様