Edited at
Riot.jsDay 8

ES6で書くRiot - Rollup編

More than 1 year has passed since last update.

Advent Calendarの何日目かです。近年、JavaScriptでプログラムを書くのに避けて通れない「プリコンパイル」について、Riotの文脈で3本ほど書いてみたいシリーズの2本目です


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

Artboard Copy.png

riot.config.js編では、riotコマンド経由でトランスパイルする方法を扱いました。具体的なツールとしては、babelbubléが「トランスパイラ」にあたります。要は、この手の(↓)変換です。


  • 変換前: 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

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

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

Artboard.png


Rollup

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


  • 機能ごとにファイルを分けられる

  • 先人の作ったライブラリに乗っかれる

  • 1ファイルにまとめてリクエスト数と帯域を圧縮

1480739321-5F6A33D5-90BC-4121-A245-68EA61869962.png

ツールそのものについては、以前に書いた記事もあるので、そちらも参考にしてください。


メモ: バンドラとしての選択肢は3つほどあります。


  • webpack: 利用例多し。多機能すぎ。HMR(hot module loading)が魅力

  • Rollup: シンプル、高速、生成コードが小さい

  • Browserify: 新規プロジェクトではもう使わない

好みで言えば圧倒的にRollup推しですが、周りの環境(と人)に合わせて選択して幸せになりましょう。



準備

Riotの公式Examplesに、Rollupの例も用意しました。これを使いましょう。次のコマンドで、クローンしてきて該当ディレクトリに進みます。

$ git clone https://github.com/riot/examples

$ cd example/rollup


メモ: まだ、Gitをインストールしていない場合は、ここからダウンロードするのでも構いません。


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



  • src/



    • main.js - JavaScriptのエントリーポイント


    • app.tag - メインビューのタグ


    • md.tag - Markdown表示のためのタグ



  • index.html

  • package.json


  • rollup.config.js - Rollupの設定ファイル


設定ファイル

上記それぞれのファイル内容は、適宜調べてもらうとして、ここで重要なのはrollup.config.jsです。このうちの、基本部分のみを抜き出すと次のようになります。

// 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のライブラリを使うため、もう二つほどプラグインが必要です。こうなります。↓

// 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' // えーっと、とりあえずこう書いてください
}


実行

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

$ npm install

$ npm run build

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


  • main.js

  • app.tag

  • md.tag

  • riot.js

  • marked.js

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


メモ: 上記、npm run buildにはrollup -cコマンドが割り当てられています。このように、npmスクリプトに書いておけば、rollupをグローバル環境に入れる必要はありません。

複数人で作業している場合も、npm installするだけで全員の環境を揃えられるのは大きなメリットです。



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

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


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

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

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


どう組み込む?

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

Artboard Copy 3.png


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

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

Riotでは、HTML, JavaScript, CSSに関して、それぞれカスタムパーサを設定できます。Rollupから利用する場合、先ほどはプラグイン設定でシンプルにriot()としていたところ、次のようにstyleparsersを指定します。

riot({

style: 'cssnext',
parsers: {
css: { cssnext }
}
})

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

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
}


: 実は、本稿のために(ということもないのですが)、これを実現するコミットをRollupプラグインに数日前入れました。



まとめ

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


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界隈だと信じています。