Open9

各種JSフレームワークでgeneratorがどう実装されてるか比較

赤座久樹 (Akaza Hisaki)赤座久樹 (Akaza Hisaki)

Blitz@0.35.0

この配下のコードがCLI系
https://github.com/blitz-js/blitz/tree/v0.35.0/packages/cli/src

index.ts
require("v8-compile-cache")
const cacheFile = require("path").join(__dirname, ".blitzjs-cli-cache")
const lazyLoad = require("@salesforce/lazy-require").default.create(cacheFile)
lazyLoad.start()
import {buildConfig} from "@blitzjs/config"
import {run as oclifRun} from "@oclif/command"

// Load the .env environment variable so it's available for all commands
require("dotenv-expand")(require("dotenv-flow").config({silent: true}))

export function run() {
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
  buildConfig().then(() => {
    oclifRun()
      .then(require("@oclif/command/flush"))
      // @ts-ignore (TS complains about using `catch`)
      .catch(require("@oclif/errors/handle"))
  })
}

CLI Frameworkのoclifが使われてる。開発元はHerokuで信頼できる。
https://github.com/oclif/oclif

oclifからは、なんとYeomanが呼ばれているっぽい!
https://github.com/oclif/oclif/blob/v1.17.1/package.json#L29-L31

yoコマンドは入ってないので、yo相当の機能はこの辺でラップしてるのかな。
https://github.com/oclif/oclif/blob/v1.17.1/src/command-base.ts

でもYeomanが使われるのは、一番最初にアプリをgenerateするときだけで、
npx oclif single mynewcliとかで生成されたアプリのpackage.jsonには@oclif/oclifは含まれない。
@oclif/command あたりの各パッケージは、Yeomanに依存していない。
https://github.com/oclif/command/blob/v1.8.0/package.json#L7-L36

・・・と色々書いたけども、上記はあくまで「CLI」であって、
ジェネレータ本体はまた別パッケージだった。こちら。
https://github.com/blitz-js/blitz/blob/v0.35.0/packages/generator

EJSとかでもなく、かなり独自実装されてますね。
https://github.com/blitz-js/blitz/blob/v0.35.0/packages/generator/src/generator.ts

赤座久樹 (Akaza Hisaki)赤座久樹 (Akaza Hisaki)

他に調べたいもの、このあたり。

  • vue-cli
  • firebase-cli
  • create-react-app
  • create-next-app
  • create-nuxt-app
  • create-frourio-app
  • create-bison-app

create-xxx-app という名前がデファクトスタンダードな感じですね。

赤座久樹 (Akaza Hisaki)赤座久樹 (Akaza Hisaki)

create-react-app@4.0.3

この1本でほぼ全て完結。
かなりゴリゴリ独自実装されている印象。
https://github.com/facebook/create-react-app/blob/v4.0.3/packages/create-react-app/createReactApp.js

Commander.jsを使ってるくらいで、scaffold用のフレームワークは特になし。
https://github.com/tj/commander.js

dependenciesのインストール以外は、
こんな感じのテンプレート一式をそのままコピーしているっぽい。
テンプレート1つずつが独自packageになってる。

デフォルト
https://github.com/facebook/create-react-app/tree/v4.0.3/packages/cra-template/template/src

TypeScript版
https://github.com/facebook/create-react-app/tree/v4.0.3/packages/cra-template-typescript/template/src

赤座久樹 (Akaza Hisaki)赤座久樹 (Akaza Hisaki)

create-next-app@10.2.2

全体的にcreate-react-appと似たような印象。
一番メインがこのコードで、Commander.jsを使ってるのも一緒。
https://github.com/vercel/next.js/blob/v10.2.2/packages/create-next-app/create-app.ts

テンプレートのpackageは分かれていないので、
全体のディレクトリ構成はとても分かりやすかった。
default / typescriptの2種類が用意されてる。
https://github.com/vercel/next.js/tree/v10.2.2/packages/create-next-app/templates

赤座久樹 (Akaza Hisaki)赤座久樹 (Akaza Hisaki)

create-nuxt-app@3.6.0

https://github.com/nuxt/create-nuxt-app/blob/v3.6.0/packages/create-nuxt-app/lib/cli.js#L47-L54

cli.js
    // See https://sao.vercel.app/api.html#standalone-cli
    sao({ generator, outDir, logLevel, answers, cliOptions })
      .run()
      .catch((err) => {
        console.trace(err)
        process.exit(1)
      })
  })

saoなるscaffoldライブラリが使われている。これ初めて知った。
https://sao.vercel.app/

Why not use Yeoman?
Yeoman is great except that writing a Yeoman generator is time-consuming, I created SAO to simplify the way we write and use generators.

「Yeomanは素晴らしいんだけど、もうちょっとサクッとgenerator作りたかったからSAOを作った」とのこと。やはりYeomanはちょいちょい触れられますね。

saoというコマンドを使っても良いし、自前コマンドからrequireしてsao()関数を走らせても良いというハイブリッド仕様。Yeomanではyoコマンドが必須で辛かったので、saoのこの仕様は好きかも!もちろんcreate-nuxt-appでもrequireして使ってる。
https://sao.vercel.app/api.html#standalone-cli

デフォルトのテンプレートはこちら。
Monorepoで単独packageになっている。
https://github.com/nuxt/create-nuxt-app/tree/v3.6.0/packages/cna-template/template

nuxt@2.15.7のサブジェネレータ

https://github.com/nuxt/nuxt.js/blob/v2.15.7/packages/generator/src/generator.js

nuxt generate xxxで走行するサブジェネレーターはSAOは使っておらず、独自実装ですね。SAOのサブジェネレーターは最低限の機能しかなさそうなので、こうなってるのかな。

赤座久樹 (Akaza Hisaki)赤座久樹 (Akaza Hisaki)

vue-cli@4.5.13

boxen, commander, inquirer, minimist,... などなど、
CLI用のライブラリが沢山インストールされていますが、
どれも軽めの奴で、割と独自実装が多めな印象。
https://github.com/vuejs/vue-cli/blob/v4.5.13/packages/@vue/cli/package.json#L34-L62

自作ヘルパーライブラリがこんなに沢山。
DIY派ですねぇ。
https://github.com/vuejs/vue-cli/tree/v4.5.13/packages/%40vue/cli/lib

GitはこれもMonorepoで、サブコマンドごとに、色々なpackageが用意されてる。
vue-cliはジェネレータ以外にも、ビルドだとかサーバ起動だとか、色々な種類の操作ができるので、上に上げたcreate-xxx-app系とは毛色が違いますよね。
https://github.com/vuejs/vue-cli/tree/v4.5.13/packages/%40vue

generatorで使われるテンプレートはこのあたり。
EJS形式ですね。
https://github.com/vuejs/vue-cli/tree/v4.5.13/packages/%40vue/cli-service/generator/template/src

App.vue
<template>
<%_ if (rootOptions.vueVersion === '3') { _%>
  <img alt="Vue logo" src="./assets/logo.png">
  <%_ if (!rootOptions.bare) { _%>
  <HelloWorld msg="Welcome to Your Vue.js App"/>
  <%_ } else { _%>
  <h1>Welcome to Your Vue.js App</h1>
  <%_ } _%>
<%_ } else { _%>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <%_ if (!rootOptions.bare) { _%>
    <HelloWorld msg="Welcome to Your Vue.js App"/>
    <%_ } else { _%>
    <h1>Welcome to Your Vue.js App</h1>
    <%_ } _%>
  </div>
<%_ } _%>
</template>
<%_ if (!rootOptions.bare) { _%>
赤座久樹 (Akaza Hisaki)赤座久樹 (Akaza Hisaki)

create-bison-app@1.11.0-canary.11

https://github.com/echobind/bisonapp

Hygenというツールを使っている。
https://github.com/jondot/hygen

Bison自体はあんまり興味ないのだけど、
Hygenはこの辺読んで気になっていた。
https://zenn.dev/mizchi/articles/b53f539ade1f42#getting-started
https://zenn.dev/takepepe/articles/hygen-template-generator

最初にプロジェクトを生成するcreate-bison-appコマンドでは、Hygenは使われていない。inquirer, yargsあたりの軽めのライブラリだけが使われてる感じ。
https://github.com/echobind/bisonapp/blob/4e58b734f5a0a53c0f3a56bfd2c217eb3cab4651/packages/create-bison-app/cli.js

実際にHygenが活躍し始めるのは、プロジェクト生成後。
Yeomanでいうと「サブジェネレーター」の役割をHygenが担ってる感じですね。

これは生成されるpackage.jsonのテンプレートですが、devDependenciesにhygenが含まれてる。scriptsがかなり沢山定義されていて、g:xxxという名前のものがすべてHygenを使ってますね。

https://github.com/echobind/bisonapp/blob/4e58b734f5a0a53c0f3a56bfd2c217eb3cab4651/packages/create-bison-app/template/package.json.ejs#L25-L33

  "scripts": {
    // 中略
    "g:cell": "hygen cell new --name",
    "g:component": "hygen component new --name",
    "g:graphql": "hygen graphql new --name",
    "g:page": "hygen page new --name",
    "g:migration": "yarn -s prisma migrate dev",
    "g:test:component": "hygen test component --name",
    "g:test:factory": "hygen test factory --name",
    "g:test:request": "hygen test request --name",
    "g:test:util": "hygen test util --name",

cell componentなどは何処にあるかというと、_templatesディレクトリ。
https://github.com/echobind/bisonapp/tree/4e58b734f5a0a53c0f3a56bfd2c217eb3cab4651/packages/create-bison-app/template/_templates

hygen test factoryであれば、_templates/test/factory/内のテンプレートが使われるわけですね。

テンプレートのパスは、デフォルトでは${__dirname}/_templatesだけど、.hygen.jstemplates: xxxと書くことで独自定義できる。

ローカルに置くのが基本だけど、

  templates: `${__dirname}/.hygen`

こうすれば、外部ライブラリのtemplatesも普通に参照できた。

  templates: `${__dirname}/node_modules/create-bison-app/template/_templates`

まだ本当の良さは分らんけど、仕組みは何となくわかった。

赤座久樹 (Akaza Hisaki)赤座久樹 (Akaza Hisaki)

create-frourio-app@0.27.2

https://github.com/frouriojs/create-frourio-app

ジェネレータのためのWebサービスがlocalhostで立ち上がるというシャレオツ仕様。イマドキな感じですねぇ。CLIアプリとしての参考にはあまりならなかった。

ジェネレータはejsで独自にやってる感じかな。
https://github.com/frouriojs/create-frourio-app/blob/159a4e311ecb5827a95fd55f74f5c854ac6c49cc/server/service/generate.ts#L61-L63

テンプレートは相当沢山バリエーション用意されてる。
https://github.com/frouriojs/create-frourio-app/tree/159a4e311ecb5827a95fd55f74f5c854ac6c49cc/server/templates