ES6で書くRiot - Rollup編


[Advent Calendar](http://qiita.com/advent-calendar/2016/riotjs)の何日目かです。近年、JavaScriptでプログラムを書くのに避けて通れない「プリコンパイル」について、Riotの文脈で3本ほど書いてみたいシリーズの2本目です

- [ES6で書くRiot - riot.config.js編](http://qiita.com/cognitom/items/200a96b86eafc463ed24) - ([サンプルコード](https://github.com/riot/examples/tree/gh-pages/riot-config))
- [ES6で書くRiot - Rollup編](http://qiita.com/cognitom/items/c20c22614560627062cb) - ([サンプルコード](https://github.com/riot/examples/tree/gh-pages/rollup))
- ES6で書くRiot - Felt編 (予定)

## バンドラ? トランスパイラ?

![Artboard Copy.png](https://qiita-image-store.s3.amazonaws.com/0/1858/09a0d334-15fa-8fb8-cbc7-650af124f246.png "Artboard Copy.png")

riot.config.js編では、`riot`コマンド経由でトランスパイルする方法を扱いました。具体的なツールとしては、[babel](https://babeljs.io/)や[bublé](https://buble.surge.sh/)が「トランスパイラ」にあたります。要は、この手の(↓)変換です。

- 変換前: ```this.message = `Hello ${name}!` ```
- 変換後: `this.message = "Hello " + name + "!"`

ただ、タグから`import`文を使って他のライブラリを参照しようとすると、良い方法がありません。そこで必要となるのがバンドラです。シンプルな話としては、トランスパイラの前段に置かれて複数のJavaScriptをまとめる役目を果たします。

- 変換前:
	- `import { add } from './a'; console.log(add(1, 2))`
	- `export add function add (a, b) { return a + b }`
- 変換後:
	- `function add (a, b) { return a + b }; console.log(add(1, 2))`

これも絵にすると、こんな感じでしょうか。

![Artboard Copy 2.png](https://qiita-image-store.s3.amazonaws.com/0/1858/85b45637-17e0-1e7c-b15a-8a9a03438c8c.png "Artboard Copy 2.png")

ただ、実際にはもう少し複雑で、世に公開されているツールの多くはまだブラウザ(ES6)ではなくNode.js(CommonJS)向けに書かれています。それらのツールを使うには、CommonJS→ES6変換が必要です。

本稿の本題である「Riotのタグを一緒に読み込む」場合も、Riotコンパイラを事前に通してJavaScriptに変換しておく必要があります。

![Artboard.png](https://qiita-image-store.s3.amazonaws.com/0/1858/48e95331-e5bf-f953-c509-b0bfd07fe01a.png "Artboard.png")

## Rollup

さて、いよいよRollupです。このツールを使えば、複数のファイル、さらにはnpmで公開されているモジュールたちを、ひとつのファイルにまとめられます。これには、フロントエンドにおいて、3つの大きなメリットがあります。

- 機能ごとにファイルを分けられる
- 先人の作ったライブラリに乗っかれる
- 1ファイルにまとめてリクエスト数と帯域を圧縮

![1480739321-5F6A33D5-90BC-4121-A245-68EA61869962.png](https://qiita-image-store.s3.amazonaws.com/0/1858/633d22bd-740c-bee8-0044-900c69393263.png "1480739321-5F6A33D5-90BC-4121-A245-68EA61869962.png")

ツールそのものについては、[以前に書いた記事](http://qiita.com/cognitom/items/e3ac0da00241f427dad6)もあるので、そちらも参考にしてください。

> **メモ**: バンドラとしての選択肢は3つほどあります。
> 
> - webpack: 利用例多し。多機能すぎ。HMR(hot module loading)が魅力
> - Rollup: シンプル、高速、生成コードが小さい
> - Browserify: 新規プロジェクトではもう使わない
> 
> 好みで言えば圧倒的にRollup推しですが、周りの環境(と人)に合わせて選択して幸せになりましょう。

### 準備

Riotの公式Examplesに、[Rollupの例](https://github.com/riot/examples/tree/gh-pages/rollup)も用意しました。これを使いましょう。次のコマンドで、クローンしてきて該当ディレクトリに進みます。

```bash
$ git clone https://github.com/riot/examples
$ cd example/rollup
```

> **メモ**: まだ、Gitをインストールしていない場合は、[ここからダウンロード](https://github.com/riot/examples/archive/gh-pages.zip)するのでも構いません。

ファイル構成は次の通りです。

- `src/`
	- `main.js` - JavaScriptのエントリーポイント
	- `app.tag` - メインビューのタグ
	- `md.tag` - Markdown表示のためのタグ
- `index.html`
- `package.json`
- `rollup.config.js` - Rollupの設定ファイル

### 設定ファイル

上記それぞれのファイル内容は、適宜調べてもらうとして、ここで重要なのは[rollup.config.js](https://github.com/riot/examples/blob/gh-pages/rollup/rollup.config.js)です。このうちの、基本部分のみを抜き出すと次のようになります。

```javascript
// Riotプラグイン
import riot from 'rollup-plugin-riot'
// ES6トランスパイラ
import buble from 'rollup-plugin-buble' 

export default {
  entry: 'src/main.js', // JavaScriptのエントリーポイント
  dest: 'dist/bundle.js', // 出力先
  plugins: [
    // 以下、プラグイン設定
    riot(),
    buble()
  ],
  format: 'iife' // えーっと、とりあえずこう書いてください
}
```

順番に見ていけば、それほど難しいことはなくて、

- プラグインを読み込み
- 入力ファイルと出力ファイルを設定
- プラグインを適用

をしているだけです。

実際には、Nodeのライブラリを使うため、もう二つほどプラグインが必要です。こうなります。↓


```javascript
// Riotプラグイン
import riot from 'rollup-plugin-riot'
// node_modulesの中から取ってくるためのプラグイン
import nodeResolve from 'rollup-plugin-node-resolve'
// さきほどの図の中の「CommonJSブリッジ」に相当
import commonjs from 'rollup-plugin-commonjs'
// ES6トランスパイラ
import buble from 'rollup-plugin-buble' 

export default {
  entry: 'src/main.js', // JavaScriptのエントリーポイント
  dest: 'dist/bundle.js', // 出力先
  plugins: [
    // 以下、プラグイン設定
    riot(),
    nodeResolve({ jsnext: true }),
    commonjs(),
    buble()
  ],
  format: 'iife' // えーっと、とりあえずこう書いてください
}
```

### 実行

では、依存モジュールをインストールして、実際に実行してみましょう。

```bash
$ npm install
$ npm run build
```

`dist`フォルダに、`bundle.js`が生成されているはずです。確認してみてください。ちょっとごちゃっとして見えるかもしれませんが、

- main.js
- app.tag
- md.tag
- riot.js
- marked.js

の5つ(+依存ファイル)がひとつのコードにまとまったのが見て取れると思います。

> **メモ**: 上記、`npm run build`には`rollup -c`コマンドが[割り当てられています](https://github.com/riot/examples/blob/gh-pages/rollup/package.json#L8)。このように、npmスクリプトに書いておけば、rollupをグローバル環境に入れる必要はありません。
> 複数人で作業している場合も、`npm install`するだけで全員の環境を揃えられるのは大きなメリットです。

## タグ内のCSSどうしよう問題

上記までで済めば、比較的シンプルだったのですが、最後に浮上するのがCSSのプリコンパイルです。ここまで、JavaScriptの文脈で説明してきましたが、同じようなことはCSSでも起きています。

- JavaScript: 次世代標準(ES6・ES7)を現在のブラウザへ
- CSS: 次世代標準(CSS3・CSS4)を現在のブラウザへ

CSSの文脈で使われるのは[PostCSS](http://postcss.org/)で、そのプラグインを束ねて使いやすくしたものが[cssnext](http://cssnext.io/)です。これを使えば、自動でベンダープリフィックスをつけたり、CSS変数を使ったりが可能になります。

### どう組み込む?

単体のCSSであれば、`cssnext`単体で簡単に変換できます。でも、思い出してください。Riotではタグファイルの中に一緒にCSSも書くことで、コンポーネントのメンテナンス性を高めているのでした。...ということは、Riotコンパイラの中で`cssnext`を実行する必要があります。絵にするとこうですね。先ほどの図より詳しくなっているRiotコンパイラ周辺に注目してください。

![Artboard Copy 3.png](https://qiita-image-store.s3.amazonaws.com/0/1858/d24c85ac-02a1-068d-b224-5dc03d15c2ab.png "Artboard Copy 3.png")

### Riotのオプションにカスタムパーサを入れる

そのための方法がRiotにも用意されています。(注)

Riotでは、HTML, JavaScript, CSSに関して、それぞれ[カスタムパーサを設定](http://riotjs.com/api/compiler/#on-server)できます。Rollupから利用する場合、先ほどはプラグイン設定でシンプルに`riot()`としていたところ、次のように`style`と`parsers`を指定します。

```javascript
riot({
  style: 'cssnext',
  parsers: {
    css: { cssnext }
  }
})
```

上記、`cssnext`は別途次のように定義しています。`:scope`をうまく使うために、少しトリックを入れていますが、基本的には`postcss([プラグイン]).process(css).css`が全てです。`cssnext`以外のプラグインを使いたい場合は、配列に足しましょう。

```javascript
function cssnext (tagName, css) {
  // ちょっとだけハックして、:scopeを:rootに置き換えてPostCSSに渡す
  // タグの中でCSS変数を使いやすくするため
  css = css.replace(/:scope/g, ':root')
  css = postcss([postcssCssnext]).process(css).css
  css = css.replace(/:root/g, ':scope')
  return css
}
```

> **注**: 実は、本稿のために(ということもないのですが)、[これを実現するコミット](https://github.com/riot/rollup-plugin-riot/commit/2e30d0b55a57907322e6efa92eaedb43e8ea143c)をRollupプラグインに数日前入れました。

## まとめ

最終的に、全体像としてはこのようになりました。いろいろ説明した割には短いですね w

```javascript:rollup.config.js
import riot from 'rollup-plugin-riot'
import nodeResolve from 'rollup-plugin-node-resolve'
import commonjs from 'rollup-plugin-commonjs'
import buble from 'rollup-plugin-buble'
import postcss from 'postcss'
import postcssCssnext from 'postcss-cssnext'

export default {
  entry: 'src/main.js',
  dest: 'dist/bundle.js',
  plugins: [
    riot({
      style: 'cssnext',
      parsers: {
        css: { cssnext }
      }
    }),
    nodeResolve({ jsnext: true }),
    commonjs(),
    buble()
  ],
  format: 'iife'
}

function cssnext (tagName, css) {
  css = css.replace(/:scope/g, ':root')
  css = postcss([postcssCssnext]).process(css).css
  css = css.replace(/:root/g, ':scope')
  return css
}
```

現在のところ、もっとも柔軟性と拡張性が高く、どっちにでも転べる(?)書き方になったかなと思います。というわけで、RiotとRollup使おうよ!

## APPENDIX: CommonJSなのかES6なのか

結論から書くと、Riotでタグを書くときはES6の記法を使えばOKです。CommonJSのモジュール記法を使う理由は見当たりません。記法については、記事がたくさんあるので、ここでは見分け方だけ。

- **ES6モジュール**: `import`とか`export`があったらES6モジュール
- **CommonJS**: `require`とか`module.exports`があったらCommonJSモジュール

CommonJSは、ES6以前に提唱されていたモジュール読み込みの仕組みで、JavaScriptの文法そのものは拡張せず`require()`という関数と`exports`という変数だけでどうにかしていたのが画期的でした。ただ、`ES6`で文法側で「モジュール」の仕組みが加わったことで、事態が複雑化しています。一部、CommonJSでてきていたことがES6で出来なかったり、その逆があったりと「相互運用」も課題です。

> **メモ**: 両方が「JavaScript」と言っているので初見殺し感があります。

フロントエンドで書く場合、

- 自分のコードはES6で書く
- ES6版のライブラリがない場合は、CommonJSのモジュールをブリッジ経由で使う

を基本としましょう。

> **メモ**: Node.js用に書かれた資産が膨大なので、一朝一夕にすべてES6に変わる...ということはないですが、全体としてはじわじわとES6に軸足を移しているのが今のNode.js界隈だと信じています。