jshitaの日記

勉強したことを書いていきます。

Yeoman でカスタムジェネレータを作る

yeoman について

アプリケーションの構成(ファイルやディレクトリ)をコマンド一発で作るツール。

簡単なジェネレーターを作る

以下のようなジェネレータを作る。
ジェネレータ名: sample
コマンド:
1)yo sample
2)yo sample:files

実行例は以下の通り。

# myappディレクトリを作成し、index.html, index.js, index.css を作成するコマンド
$ yo sample myapp

# 作成できたか確認
$ tree myapp
myapp
└── public
    ├── css
    │   └── index.css
    ├── index.html
    └── js
        └── index.js

# home.html, home.js, home.css を作成するコマンド
$ yo sample:files home

# 作成できたか確認
$ tree .
.
└── public
    ├── css
    │   ├── home.css
    │   └── index.css
    ├── home.html
    ├── index.html
    └── js
        ├── home.js
        └── index.js

yeomanのインストール

始めに、yeoman の動作可能を準備する。
yeoman は node.js で動作するため、最初に node.js を以下からインストール。

https://nodejs.org/ja/

node.js を準備できたら必要なパッケージをインストールする。

npm install -g yo generator-generator

yo は yeoman ジェネレータを実行するツールで、generator-generator はジェネレータの雛形を作成するツール。

ジェネレータの雛形作成

準備が整ったので、早速ジェネレータを作成する。

# generator-sample のディレクトリを作成
mkdir generator-sample
cd generator-sample
# generator-generator で雛形作成、いくつか質問されるので、適宜入力する。
yo generator

generator-sample の雛形が完成するので、ディレクトリ構成を確認してみます。

generator-sample/
├── LICENSE
├── README.md
├── __tests__
│   └── app.js
├── generators
│   └── app
│       ├── index.js
│       └── templates
├── node_modules
│   └── (大量のディレクトリ) 
├── package-lock.json
└── package.json

雛形のうち、特に重要なのは generator/ ディレクトリ配下です。

ジェネレータは yo コマンドで呼び出された際、 generators/ 配下のディレクトリ名に基づいて動作します。
例えば "yo sample" コマンドを実行すると、 generator-sample/generators/app/index.js が実行されます。

ただし、app ディレクトリはデフォルトジェネレータと呼ばれる特殊なケースです。
普通は "yo sample:" とする事で、 generator-sample/generators//index.js が実行されます。

メインジェネレータを実装する

では雛形が完成したので、早速ジェネレータを作ります。
まず、generator-sample/generators/app/index.js を以下のように修正します。

'use strict';
const Generator = require('yeoman-generator');
const makeDir = require('make-dir');
const path = require('path');

module.exports = class extends Generator {

  constructor(args, opts){
    super(args, opts);
    // (1) コマンドライン引数を取得する
    this.argument('projectname', {type:String, required: true});
  };

  writing() {
    // (2) ディレクトリを作成する
    const root = this.options.projectname;
    makeDir.sync(root);
    makeDir.sync(root + '/public');
    makeDir.sync(root + '/public/js');
    makeDir.sync(root + '/public/css');


    // (3) テンプレートを作成する
    const name = "index";

    this.fs.copyTpl(
      this.templatePath('template.html'),
      this.destinationPath(root + '/public/' + name + '.html'),
      {name:name}
    );
    this.fs.copyTpl(
      this.templatePath('template.js'),
      this.destinationPath(root + '/public/js/' + name + '.js'),
      {name:name}
    );
    this.fs.copyTpl(
      this.templatePath('template.css'),
      this.destinationPath(root + '/public/css/' + name + '.css'),
      {name:name}
    );

    // (4) .yo-rc.json をプロジェクトのルートフォルダに作成する
    this.destinationRoot(path.join(this.destinationRoot(), '/' + root));
    this.config.save();
  }
};

順にコードを説明します。

(1) では、コマンドライン引数を取得します。
例えば "yo sample myapp" とすると、引数 projectname には "myapp" が代入されます。

(2) では、代入されたコマンドライン引数 projectname と同名のディレクトリを makeDir.sync(root) で作成しています。
さらに、その配下に "public", "public/js", "public/css" ディレクトリを作成しています。

(3) では、 this.fs.copyTpl コマンドでテンプレートファイル template.html を ejs形式で展開してコピーします。
現段階では、テンプレートファイルが存在しないため、 app/ 配下に templates ディレクトリを作成し、以下ファイルを配置して下さい。

<!-- generator-sample/generators/app/templates/templates.html -->
<!DOCTYPE html>
<html>
  <head>
    <title><%= name %></title>
    <meta charset='UTF-8'>
    <script src='./js/<%= name %>.js'></script>
    <link rel='stylesheet' href='./css/<%= name %>.css'></script>
  </head>

  <body>
    <h2><%= name %></h2>
  </body>
</html>
// generator-sample/generators/app/templates/templates.js
// <%= name %>.js
/* generator-sample/generators/app/templates/templates.css */
/* <%= name %>.css */

(4) では、 .yo-rc.json をプロジェクトのルートに作成しています。
.yo-rc.json はプロジェクトのルートを yeoman に記憶させるためのファイルです。
このファイルを作成しておけば、例えば public フォルダ内で "yo sample:files home としても正しい場所にファイルが作成されます。

これでメインジェネレーター機能は完成です。

最後に generator-sample/ に移動し、以下コマンドを実行して下さい。

$ npm link

これで generator-sample がローカルインストールされました。
ちなみに解除する場合は以下のコマンドで解除できます。

$ npm -g unlink generator-sample

では、任意のディレクトリに移動し、yo コマンドを確認して見て下さい。プロジェクト構成が "yo generate sample" と打つだけで作成されるはずです。

サブジェネレータを実装する

では次に "yo sample:files" を作成します。

まず、generators/files ディレクトリを作成し index.js を以下の内容で作成しましょう。

// generator-sample/generators/files/index.js
'use strict';
const Generator = require('yeoman-generator');

module.exports = class extends Generator {

  constructor(args, opts){
    super(args, opts);
    this.argument('filename', {type:String, required: true});
  };

  writing() {
    const name = this.options.filename;

    this.fs.copyTpl(
      this.templatePath('template.html'),
      this.destinationPath('public/' + name + '.html'),
      {name:name}
    );

    this.fs.copyTpl(
      this.templatePath('template.js'),
      this.destinationPath('public/js/' + name + '.js'),
      {name:name}
    );

    this.fs.copyTpl(
      this.templatePath('template.css'),
      this.destinationPath('public/css/' + name + '.css'),
      {name:name}
    );

  }

ディレクトリを作成する以外は app/index.js と一緒の内容ですので解説は省略します。
では、 templates/ ディレクトリを作成し、テンプレートを以下内容で作成しましょう。

<!-- generator-sample/generators/files/templates/templates.html -->
<!DOCTYPE html>
<html>
  <head>
    <title><%= name %></title>
    <meta charset='UTF-8'>
    <script src='./js/<%= name %>.js'></script>
    <link rel='stylesheet' href='./css/<%= name %>.css'></script>
  </head>

  <body>
    <h2><%= name %></h2>
  </body>
</html>
// generator-sample/generators/files/templates/templates.js
// <%= name %>.js
/* generator-sample/generators/files/templates/templates.css */
/* <%= name %>.css */

完成

これでジェネレーター完成です。

# myappディレクトリを作成し、index.html, index.js, index.css を作成
$ yo sample myapp

# 作成できたか確認
$ tree myapp
myapp
└── public
    ├── css
    │   └── index.css
    ├── index.html
    └── js
        └── index.js

# home.html, home.js, home.css を作成
$ yo sample:files home

# 作成できたか確認
$ tree .
.
└── public
    ├── css
    │   ├── home.css
    │   └── index.css
    ├── home.html
    ├── index.html
    └── js
        ├── home.js
        └── index.js