Help us understand the problem. What is going on with this article?

ES6で書くRiot - Rollup編

More than 3 years have 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界隈だと信じています。

cognitom
下北沢オープンソースCafeのマスターで、図書館サービス「リブライズ」のデザイン担当。Riot.jsのコア開発者。
https://github.com/cognitom
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away