How to get started with recipe.js

#いままでの料理の仕方
あなたは今、必要に迫られて、なんやかんやあって、fettuccine.alfredo.jsを作ることになりました。
各モジュールは再利用をするため、細かくモジュール化しました。

```html
<script src="salt.js"></script>
<script src="milk.js"></script>
<script src="butter.js"></script>
<script src="parmigianoReggiano.js"></script>
<script src="fettuccine.js"></script>
<script src="fettuccine.alfredo.js"></script>
```

各モジュールの依存関係はこんな感じ。

![Information Systems Help Desk.png](https://qiita-image-store.s3.amazonaws.com/0/33653/9d957563-58eb-ba32-037b-2dc78e2ed860.png)

|Module|Dependencies|
|:-----|:-----------|
|salt|なし|
|milk|なし|
|butter|salt, milk|
|parmigianoReggiano|salt, milk|
|fettuccine|なし|
|fettuccine.alfredo|salt, butter, parmigianoReggiano, fettuccine|


フィットチーネ・アルフレッドを食べるためには、あなたは塩とバターとパルミジャーノ・レッジャーノとフェットチーネが必要です。
またバターとパルミジャーノ・レッジャーノは牛乳と塩で構成されるため、牛乳も別途用意する必要があります。

ここで面倒なのは牛乳が直接的にフィットチーネ・アルフレッドに必要ないのにも関わらず、呼び出す必要が有ることです。

依存関係を自力で解決するのはモジュールかを進める上で大きな問題になっていきます。
また、牛乳に新たな依存関係が生まれた場合、フェットチーネ・アルフレッドを呼び出している各ページのHTMLに新たなスクリプトタグを追加する必要が有ります。

上記の問題に対してrecipe.jsは大きく分けて、２つの解決方法を提供しています。

- 依存関係をすべて解決し、結合したスクリプトの生成(Concatenation方式)
- 依存関係を定義したファイルから、関連するモジュールを解決し、動的にロードする(Dynamic Loading方式)

#料理の仕方を変えましょう

##recipe.jsとは？

https://github.com/sideroad/recipe

- 各モジュールの依存関係を一元管理
- head.jsを用いた並列ダウンロード
- <b>ブラウザキャッシュのコントロール</b>
- <b>ページの文字エンコードと違うスクリプトの読み込み機能（一部）</b>

require.jsと似てますが、太字の部分において機能差が存在します

##プロジェクトのディレクトリ構成

<pre>
├── Gruntfile.js (後で説明)
├── package.json (後で説明)
├── recipe.json (後で説明)
├── dist (directory)
└── src
    ├── acqua.pazza.js
    ├── butter.js
    ├── fettuccine.alfredo.js
    ├── fettuccine.js
    ├── milk.js
    ├── parmigianoReggiano.js
    ├── pomodorini.js
    ├── salt.js
    └── whitefish.js
</pre>

##node.jsのインストール
割愛ってやつ？してやりますよ！


##recipe.jsonの作成
各モジュールの依存関係を定義します

```json
{
  "${module_namespace}": {
    "path": "${module_source_path}",
    "dest": "${dist_directory_path}",
    "url": "${module_url}",
    "amd": {
      "dest": "${amd_dist_directory_path}",
      "url": "${module_amd_url}"
    },
    "dependencies": [ "${depended_module_namespace}", "${depended_module_namespace}..." ]
  }
}
```

フェットチーネ・アルフレッドの依存関係を定義すると以下の通り。

```json
{
  "fettuccine.alfredo": {
    "path": "src/fettuccine.alfredo.js",
    "dest": "dist",
    "url": "/dist/fettuccine.alfredo.js",
    "dependencies": ["fettuccine", "butter", "parmigianoReggiano", "salt"]
  },
  "fettuccine": {
    "path": "src/fettuccine.js",
    "dest": "dist",
    "url": "/dist/fettuccine.js",
    "dependencies": []
  },
  "butter": {
    "path": "src/butter.js",
    "dest": "dist",
    "url": "/dist/butter.js",
    "dependencies": ["salt", "milk"]
  },
  "parmigianoReggiano": {
    "path": "src/parmigianoReggiano.js",
    "dest": "dist",
    "url": "/dist/parmigianoReggiano.js",
    "dependencies": ["salt", "milk"]
  },
  "salt": {
    "path": "src/salt.js",
    "dest": "dist",
    "url": "/dist/salt.js",
    "dependencies": []
  },
  "milk": {
    "path": "src/milk.js",
    "dest": "dist",
    "url": "/dist/milk.js",
    "dependencies": []
  }
}
```

##Gruntの準備
プロジェクトのフォルダにコンソールで移動し、
package.jsonがなければ

```bash
npm init
```
で適宜ごにょってください。

###Gruntと関連モジュールのインストール

```bash
npm install -g grunt-cli
npm install grunt --save-dev
npm install grunt-recipe --save-dev
npm install grunt-contrib-concat --save-dev
npm install grunt-contrib-uglify --save-dev
```

#Concatenation方式の場合

##Gruntfile.jsの用意

```js
module.exports = function(grunt) {
  grunt.initConfig({
    recipe: {
      main: {
        files: {
          'dist': ['recipe.json']
        }
      }
    },
    concat: {},
    uglify: {}
  });
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-recipe');

  // By default, lint and run all tests.
  grunt.registerTask('default', ['recipe', 'concat', 'uglify']);
};
```

##Gruntの実行

```bash
grunt
```

distディレクトリにやたらとスクリプト達が出力されますが、どうか平常心を保ってください。
一つのスクリプトに対して、以下のスクリプトが出力されます。

- xxx.js (Minifyされてる)
- xxx.unpack.js (オリジナルのソース)
- xxx.with-dependencies.js (依存するモジュールをすべて結合し、Minifyされたもの)
- xxx.with-dependencies.unpack.js (依存するモジュールをすべて結合されたもの)

また、以下のスクリプトも出力されますが、必要有りません

- recipe.js (recipe本体)
- recipe.version.js (ブラウザキャッシュのコントロール)
- recipe.dependencies.js (依存関係を解決)

##HTMLの修正

```html
<script src="fettuccine.alfredo.with-dependencies.js"></script>
```

上記を各ページのHTMLに埋め込めば、新たな依存性が増えてもrecipe.jsonを編集して、
gruntを実行し、出力されたファイルに置き換えれば作業は完了です。

##まとめ

- recipe.jsonに依存関係を定義する
- gruntで依存するモジュール群を解決したモジュールを生成する
- HTMLには生成されたモジュールをスクリプトタグで読み込む

この方法であれば、recipe.js自体は必要なく、手軽な方法ですが以下の問題点を含んでいます。

- ファイルを更新しても、ユーザのブラウザキャッシュが使われてしまう可能性がある
- 複数のスクリプト内に同じ依存関係が存在する場合、スクリプトを無駄に読み込んでしまう

#Dynamic Loadingの場合

##HTMLの修正
HTMLに以下を記述し、そのページのスクリプトを全部ぶっこ抜く。（document.write使っているようなアド系のJSは抜けません）

```html
<script src="/path_to_recipe_directory/recipe.js" data-menu="foo"></script>
```

data-menu属性にはメニュースクリプトの名前を定義します

##メニュースクリプトを定義する
メニュースクリプトは各ページ単位で作成し、そのページに必要なモジュールの定義とページ固有の処理を記述します。
ぶっこ抜いたスクリプトのうち、recipe.jsonに定義したものは`libraries`に名前空間を記述し、
それ以外のスクリプトは`scripts`にURLを記述します。
recipe関数は`Q`のpromiseを返します。
ページ固有の処理は返り値のthen関数に記述することで、全てのスクリプトがロードされた後に実行されます。
scriptsに指定するURLは末尾に`#Shift_JIS`のように文字エンコードを付け足すと、その文字エンコードでロードされます。

`foo.js`

```js
recipe({
  libraries: [
    "fettuccine.alfredo"
  ],
  scripts: [
    // write script URL
  ]
}).then(function(){
  //write page specific logic
  alert(salt && milk && butter && parmigianoReggiano && fettuccine && 'Digin!!');
});
```

##ディレクトリ構成
メニュースクリプトを定義するため、srcにrecipeディレクトリを掘っております
recipe.version.js, recipe.dependencies.jsはgruntが出力します。
<b>サーバにスクリプトを置く際には、recipe.js, recipe.version.js, recipe.dependencies.js, メニュースクリプトをrecipe.jsと同じディレクトリに配置する必要が有ります。</b>

<pre>
├── Gruntfile.js
├── dist
│   └── recipe (メニュースクリプト, recipe.version.js, recipe.dependencies.jsの出力先)
├── package.json
├── recipe.json
└── src
    ├── acqua.pazza.js
    ├── butter.js
    ├── fettuccine.alfredo.js
    ├── fettuccine.js
    ├── milk.js
    ├── parmigianoReggiano.js
    ├── pomodorini.js
    ├── salt.js
    ├── whitefish.js
    └── recipe (メニュースクリプトの置き場)
        └── foo.js
</pre>

##Gruntfile.jsの用意

```js
module.exports = function(grunt) {
  grunt.initConfig({
    recipe: {
      main: {
        options: {
          concat: false        //結合ファイルを作る必要ないのでfalse
        },
        files: {
          'dist/recipe': ['recipe.json']
        }
      }
    },
    concat: {},
    uglify: {
      menu:{
        files: {
          'dist/recipe/foo.js': ['src/recipe/foo.js']
        }
      }
    }
  });
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-recipe');

  // By default, lint and run all tests.
  grunt.registerTask('default', ['recipe', 'concat', 'uglify']);
};
```

##Gruntの実行

```bash
grunt
```

distディレクトリに
一つのスクリプトに対して、以下のスクリプトが出力されます。

- xxx.js (Minifyされてる)
- xxx.unpack.js (オリジナルのソース)

また、下記ファイルも生成されます。

- recipe/recipe.js (recipe.js本体)
- recipe/recipe.version.js (ブラウザキャッシュのコントロール)
- recipe/recipe.dependencies.js (依存関係を解決)

## 読み込みフロー
- recipe.jsが同ディレクトリのrecipe.version.jsを読み込む
- recipe.jsが同ディレクトリのrecipe.dependencies.jsを読み込む
- recipe.jsが同ディレクトリに存在するdata-menuの名前のメニュースクリプトをロードする
- recipe.jsがlibrariesの依存関係を解決し、scriptsのURLも含めて、スクリプトをロードする
- 全てのスクリプトのロード後にthen関数に与えられたそのページ固有の処理を実行する

##まとめ

- recipe.jsonに依存関係を定義する
- ページに必要なモジュールとページ固有の処理を定義したメニュースクリプトを作成する
- gruntで依存するモジュール群を定義したrecipe.dependencies.jsを生成する
- gruntでブラウザキャッシュ回避用のversionを定義したrecipe.version.jsを生成する
- HTMLにはdata-menu属性にメニュー名を指定したrecipe.jsのスクリプトタグを埋める

この方法であれば、依存性を解決させるだけでなく、ブラウザキャッシュ問題に対しても対応できるうえ、
新たなモジュールをページで利用する際に、HTMLにスクリプトタグを追加する必要はなく、メニュースクリプトに追加するだけでいいというメリットが生まれます。

サンプルのプロジェクトはこちらにあります。
ソース：https://github.com/sideroad/recipe-sample
実行例：http://sideroad.github.io/recipe-sample/

AMDも用意してますが、使い方は疲れたのでまた今度。
ソース：https://github.com/sideroad/recipe-amd-sample-jquery
実行例：http://sideroad.github.io/recipe-amd-sample-jquery/

日本語もちょいちょいおかしいので後で更新します