Edited at

[日本語訳]Yeoman App Generatorsの書き方

More than 5 years have passed since last update.


はじめに

 Yeoman用の独自のgenerator作りたいなーと思っていたら丁度WRITING CUSTOM YEOMAN APP GENERATORSていうドキュメントがあったので訳します。適当に端折ったり言い換えているので、完全な詳細は原文を読んで下さい。

 とりあえず感想だけ言うと「Yeomanはじまったな」です。あとでFirefox OSのアプリ(Open Web Apps)用のgenerator作るで。generatorはNode.jsで動くのでNode.jsで何か作った事ある場合は割と簡単に色々出来ると思う。



目次

 このガイドはYeoman generatorシステムを使おうとしてる人を助ける為のものです。説明を飛ばしてもgeneratorの利用は出来ます。



導入

 Yo generatorのテンプレートはプロジェクトで使うボイラープレートコードやフレームワークや依存ライブラリなどのカスタムセットアップを提供します。手動でボイラープレートコードを書いたりしなくても新しいプロジェクトのキックオフを簡単に出来ます。

yeoman

 yo webappを呼び出した時に生成される基本的なアプリケーションは、実際にgeneratorを使っています。

generatorは既存のボイラープレートなファイルを単純にコピーする事が出来る他に、ユニットテストの利用やドキュメントの出力などのカスタマイズを選択したりも出来ます。

 generatorはYeomanだけが提供していると考えるかもしれませんが、違います。多くのコミュニティがgeneratorを作成し、既にnpm(訳注:Node.jsのパッケージマネージャ)でHTML5のボイラープレートやTwitterのBootstrapやKarmaなどの為のgeneratorを公開しています。



generatorのタイプ

 Yeomanのgeneratorには2つのタイプがあります。「ボイラープレート(boilerplate)」と「アプリケーションの土台(application

scaffolders)」です。ボイラープレート用generatorは通常、既存のボイラープレートプロジェクトを、yoコマンドを実行したディレクトリに持ってきます。アプリケーションの土台用generatorはビルドシステムやサブgeneratorや依存性管理やワークフローの自動化などをカバーする事が出来ます。

NOTE:もし、自分のワークフローに合わせて既存のgeneratorをカスタムしたい場合は、公式のgeneratorを自由にforkして追加や削除などして下さい。BackboneAngularEmberなどの為のgeneratorがあります。



ボイラーテンプレートなgeneratorを作る

(訳注:元のやり方でやっても全く動かなかったので、動く様に書き換えました。)

rewrite begin

 generator-boilerplateというgeneratorのボイラープレートコードをcloneし、HTML5のボイラープレートコードをgeneratorが生成するコードとしてコピーしています。

# boilerplate generator projectを持ってくる

git clone git://github.com/addyosmani/generator-boilerplate.git generator-awesome
cd generator-awesome #訳注:なかったので追記
# app/templatesディレクトリにHTML5のボイラープレートを追加
git submodule add git://github.com/h5bp/html5-boilerplate.git app/templates

# yeoman-generatorをインストールする
npm install

cd ../
mkdir node_modules
mv generator-awesome node_modules

yo

 generator-awesomeを作成したあと、yoコマンドを実行すると、以下の様にgenerator一覧にawesome表示されます。

The webapp generator is bundled, while others can be installed with npm install <generator-name>

Officially supported generators:
webapp angular backbone bbb ember chromeapp chrome-extension bootstrap mocha karma

See a list of all available generators with npm search yeoman-generator

Usage: yo GENERATOR [args] [options]

General options:
-h, --help # Print generator's options and usage
-f, --force # Overwrite files that already exist

Please choose a generator below.

Awesome
awesome:app

Generator
generator

Mocha
mocha:app
mocha:generator

Webapp
webapp:app

 この状態でyo awesomeを実行すると、generator-awecome/app/templatesの中身が、コマンドを実行したディレクトリにコピーされます。

rewrite end

 既存のボイラープレートのtarballのURLを使ってプロジェクトを作る事も出来ます(訳注:予めnpm install generator-boilertemplateをしとく必要があります。上のyo awesomeと同等の動作となります)。

yo boilerplate https://github.com/h5bp/html5-boilerplate/archive/master.tar.gz

 基本的に以下の様な使い方になるでしょう。(訳注:npm installすると実行したディレクトリのnode_moduleディレクトリ内に指定のモジュールがインストールされます。)

npm install generator-boilerplate &amp;&amp; yo boilerplate [tarball]

NOTE: 通常、yoはgeneratorのapp/index.jsを実行します。



アプリケーションの土台を生成するgeneratorを作る





Passy generator-generatorを使う

 generator-generatorは完全な「アプリケーションの土台用generator」を作成するのに使う事が出来るだけでなく、editorconfigpackage.jsonの作成、テストやその他の設定ができます。


generator-generatorを使って自分用のgeneratorを書く

# Make sure you have yo installed

npm install -g yo

# Install the generator
npm install -g generator-generator

# Run the generator
yo generator:app

# Be sure to include :app as generator alone is part of yeoman itself.


組み込みのgeneratorとどの様に違うか?

 組み込みのgeneratorコマンドはindex.jsだけをジェネレートします。generator-generatorは以下の様なgeneratorを作るためのプロジェクトのフルセットを提供します。

├── app

│ ├── index.js
│ └── templates
│ ├── editorconfig
│ └── jshintrc
├── .editorconfig
├── .gitattributes
├── .gitignore
├── .jshintrc
├── LICENSE
├── package.json
├── README.md
└── test
├── test-creation.js
└── test-load.js

 yo generator:appは新しいgeneratorを生成するウィザードを表示します、一方yo generator:subgenerator NAMEはNAMEに指定した名前のsub-generatorを生成します。



組み込みのgeneratorジェネレータ

 Yoはまた、特に新しいツール無しでも、コマンドライン上で新しいgeneratorを作成する機能があります。以下のステップで、自分用のgeneratorプロジェクトを作成出来ます。

yoがインストールされている事を確認する

npm install -g yo

yoに何らかのgeneratorがインストールされているか確認する

 yoコマンドを実行すると、利用可能なgeneratorがリストアップされます。

yo generator generatorNameで新しいgeneratorを作る

yo generator generator

> create generator/index.js
> create generator/templates/readme.md

 index.jsはgeneratorのエントリポイントとなるファイルです。templatesディレクトリはgeneratorがプロジェクト生成などに利用するファイルを置きます。例えば、generatorを呼び出した時にいつでも自分の持っているボイラープレートコードを書き出したいなら、それらのコードをtemplatesディレクトリに入れて下さい。単純な静的サイトのgeneratorの出来上がりです。

index.jsの中身

var generator = require('yeoman-generator');

var util = require('util');

// Documentation: https://github.com/yeoman/generator/wiki/base

var Generator = module.exports = function Generator() {
generator.Base.apply(this, arguments);
// this.option('flag', { desc: 'Desc for flag', ...})
// this.argument('filename', { desc: 'Desc for filename argument', ...})
};

util.inherits(Generator, generator.Base);

// Copies the entire template directory (with `.`, meaning the
// templates/ root) to the specified location
Generator.prototype.scaffold = function scaffold() {
this.directory('.', 'place/to/generate');
};

 Baseクラスを仕様すると、generatorが期待するオプションを指定する事が出来ます。generatorで一般的に使われています。

 コードの最後の方で、"scaffold"メソッドを定義しています。これはユーザのプロジェクトのどこにファイルを生成するかを定義しています。第一引数がgeneratorのtemplatesをrootとしたパスを指定します。第二引数に生成先のパスを指定します。第二引数に"."だとか"/views"をセットしたりすればソースの生成先を簡単に変更する事が出来ます。

 現在、yo generatorコマンドは2つのファイルしか生成しません。もっと色々やりたいですよね。



Bootstrapのgenerator

 generatorの書き方をより理解する為に、もうちょっと詳しく見ていきましょう。

 TwitterのBootstrapを使ったプロジェクト生成するgeneratorであるgenerator-bootstrapをレビューしてみる事にしましょう。沢山のファイルがありますね、しかし一番重要なのはapp/index.jsです。

https://github.com/yeoman/generator-bootstrap

さっきのgeneratorと何が違うか?


  • 'use strict'を書いている

  • generatorが必要とするモジュールを'require'している

  • 'yeoman-generator'がrequireされている。

  • module.exportsに特段なにもしていないが、Yeoman generatorを継承している

  • プロンプトを使ってユーザにカスタムする部分について聞いている

  • ユーザからのレスポンスによってインストールするパッケージを変えている

(訳注:yoのバージョンの問題なのか、最新のyoではgenerator-bootstrapは動きません。下の方のthis.install(packages[this.format]);this.bowerInstall(packages[this.format]);に書き換えれば動きます。)


app/index.js

'use strict';

var path = require('path');
var util = require('util');
var yeoman = require('yeoman-generator');

var Generator = module.exports = function Generator() {
yeoman.generators.Base.apply(this, arguments);
};

util.inherits(Generator, yeoman.generators.Base);

Generator.prototype.askFor = function askFor(argument) {
var cb = this.async();

var formats = ['css', 'sass', 'less'];

var prompts = [{
name: 'format',
message: 'In what format would you like the Twitter Bootstrap
stylesheets?
',
default: formats.join('/')
}];

this.format = formats[0];

this.prompt(prompts, function(err, props) {
if (err) {
return this.emit('error', err);
}

formats.forEach(function(opt) {
if ((new RegExp(opt, 'i')).test(props.format)) {
this.format = opt;
}
}, this);

cb();
}.bind(this));
};

Generator.prototype.bootstrapFiles = function bootstrapFiles() {

// map format -> package name
var packages = {
css: 'bootstrap.css',
sass: 'sass-bootstrap',
less: 'bootstrap'
};

this.install(packages[this.format]); //(訳注:bowerInstallにすれば動く)
};


 より完全なサンプルはコチラを参照して下さい。

https://github.com/yeoman/generator-webapp/blob/master/app/index.js



generatorのスニペット

 以下のセクションはgeneratorの一般的な操作に慣れるのに役立ちます。

Prompts

 Promptsはgeneratorがプロジェクトを生成する時に、ユーザに確認を取る為に利用する事が出来ます。Promptsは以下のフォーマットの配列を受け取り、順次表示していきます。


  • name: プロンプト名。ユーザがプロンプトに対して入力した値を取り出す時に使います。

  • message: プロンプトに表示する文字列。

  • default: プロンプトに答える為に使用出来るオプション

  • warning: 特定のオプションを続行する前に表示する警告

var prompts = [{

name: 'compassBootstrap',
message: 'Would you like to include Twitter Bootstrap for Sass?',
default: 'Y/n',
warning: 'Yes: All Twitter Bootstrap files will be placed into the styles directory.'
},
{
name: 'includeRequireJS',
message: 'Would you like to include RequireJS (for AMD support)?',
default: 'Y/n',
warning: 'Yes: RequireJS will be placed into the JavaScript vendor directory.'
}];

this.prompt(prompts, function (err, props) {
if (err) {
return this.emit('error', err);
}

// manually deal with the response, get back and store the results.
// we change a bit this way of doing to automatically do this in the self.prompt() method.
this.compassBootstrap = (/y/i).test(props.compassBootstrap);
this.includeRequireJS = (/y/i).test(props.includeRequireJS);

cb();
}.bind(this));
};

テンプレートの特定のファイルコピー:

 this.copy()かthis.template()を使って、特定のファイルのコピーを行う事が出来ます。this.template()はgeneratorのtemplatesディレクトリを、ユーザのカレントディレクトリにコピーします。第二引数にコピー後のファイル名を指定する事が出来ます。

AppGenerator.prototype.bootstrapJs = function bootstrapJs() {

if (this.includeRequireJS) {
this.copy('bootstrap.js', 'app/scripts/vendor/bootstrap.js');
}
};

ファイルの書き込み:

 時々、generatorのワークフローのどこかでカスタムしたファイルを書き出したい事があるかもしれません。this.write()で任意のファイルを書きだす事が出来ます。

AppGenerator.prototype.mainStylesheet = function mainStylesheet() {

this.write('app/styles/main.scss',
'MY SASS STYLESHEET CONTENT');
};

ディレクトリとファイルの書き込み:

 this.write()とthis.mkdir()でディレクトリやサブディレクトリを作って、ファイルを書き出す事が出来ます。 

AppGenerator.prototype.app = function app() {

this.mkdir('app');
this.mkdir('app/scripts');
this.mkdir('app/styles');
this.mkdir('app/images');
this.write('app/index.html', this.indexFile);
this.write('app/scripts/main.js', this.mainJsFile);
this.write('app/scripts/hello.coffee', this.mainCoffeeFile);
};

静的なファイルを読み込んで書きだす

(訳注:this.readFileAsString()でtemplatesなどに置いておいたファイルを読み込めます。読み込んだデータを加工してthis.write()などで書きだす事が出来るわけです)

var indexFile = this.readFileAsString(path.join(this.sourceRoot(), 'index.html'));

index.htmlを構築する:

AppGenerator.prototype.writeIndex = function writeIndex() {

// prepare default content text
var defaults = ['HTML5 Boilerplate', 'Twitter Bootstrap'];

var contentText = [
' <div class="container">',
' <div class="hero-unit">',
' <h1>Allo!</h1>',
' <p>You now have</p>',
' <ul>'
];

if (!this.includeRequireJS) {
this.indexFile = this.appendScripts(this.indexFile, 'scripts/main.js', [
'components/jquery/jquery.js',
'scripts/main.js'
]);

this.indexFile = this.appendFiles({
html: this.indexFile,
fileType: 'js',
optimizedPath: 'scripts/coffee.js',
sourceFileList: ['scripts/hello.js'],
searchPath: '.tmp'
});
}

if (this.includeRequireJS) {
defaults.push('RequireJS');
} else {
this.mainJsFile = "console.log('Allo!');";
}

// iterate over defaults and create content string
defaults.forEach(function (el) {
contentText.push(' <li>' + el +'</li>');
});

contentText = contentText.concat([
' </ul>',
' <p>installed.</p>',
' <h3>Enjoy coding! - Yeoman</h3>',
' </div>',
' </div>',
''
]);

// append the default content
this.indexFile = this.indexFile.replace('<body>', '<body>\n' + contentText.join('\n'));
};

Bowerのdependenciesのインストールを実行する事が出来る

 this.on('end', function () {

this.installDependencies({ skipInstall: options['skip-install'] });
});

sub-generatorをフックする

 時折、generatorのワークフローの中でsub-generatorを使いたくなるでしょう。sub-generatorはviewやmodelなどアプリケーションの特定の部分を構築する役割を担います。アプリケーションの生成時にthis.hookFor()を使う事でsub-generatorを呼び出す事が出来ます。もちろん、初期化後にもユーザがyo mygenerator:mysubgeneratorといった具合にsub-generatorを呼び出して新しいviewを作るといった操作を行う事も出来ます。

this.hookFor('angular:common', {

args: args
});

this.hookFor('angular:main', {
args: args
});

this.hookFor('angular:controller', {
args: args
});

リモートからファイルをpullする

Generator.prototype.bootstrapFiles = function bootstrapFiles() {

var appPath = this.appPath;
if (this.compassBootstrap) {
var cb = this.async();

this.write(path.join(appPath, 'styles/main.scss'),
'@import "compass_twitter_bootstrap";');
this.remote('vwall', 'compass-twitter-bootstrap', 'v2.2.2.2', function (err, remote) {
if (err) {
return cb(err);
}
remote.directory('stylesheets', path.join(appPath, 'styles'));
cb();
});
} else if (this.bootstrap) {
this.log.writeln('Writing compiled Bootstrap');
this.copy('bootstrap.css', path.join(appPath, 'styles/bootstrap.css'));
}
};



generatorの内部

 もし、Yo generatorがどのように動くのかさらに知りたい場合は、以下のドキュメントを読むといいでしょう。

 generatorの書き方を学ぶベストな方法は他のデベロッパが書いたgeneratorを見る事です。HTML5ボイラープレートやモバイルボイラープレートのプロジェクトはシンプルで、良い例となるでしょう。一方AngularJS generatorなどはよりアドバンスなものとなります。



FAQ


  • Bowerを使った依存モジュールの取得はどうしればいいですか?

     templatesディレクトリに"_component.json"を配置して、this.installDependenciesをapp/index.js内で呼び出して下さい。その他、this.bowerInstall(['jquery',

    'underscore'], { save: true });
    といったやり方もあります。


  • generatorのユニットテストの作り方は?

     generator-generatorを使うなら、基本的なMochaのユニットテストを作る事が出来ます。あとはgenerator-webapp用に書かれたunit testsを参考にしたらいいでしょう。


  • sub-generatorの作り方は?

     generator-generatorはsub-generatorの生成をサポートしています。generating

    sub-generators
    を見て下さい。


  • がんがん拡張するにはどうしたらええの?

     generatorはNode.jsで動くんで、Node.jsで使えるAPIとかはnpmjs.orgとか見て下さい。


  • 自作generatorをnpmに登録するにはどうしたらええの?

     packageにyeoman-generatorみたいなキーワード入れて、あとはnpm publishしたらええんちゃう。詳細はnpmの公式ドキュメントみれば?