Cycle.jsとDOM DriverでWebアプリケーションを書く際に,sinkに流す仮想DOMをJSXで記述したい.公式を読むと普通にやり方が書いてあったが,ググって解決しようとしていると,古い手順に当たってしまい,少し引っかかってしまった.加えて,「最近のフロントエンドのエコシステムはこういう感じなのか」と思うところもあったので,同じような立場の人のために記録を残しておくことにする.なお,同じ轍を踏まぬよう,読者諸賢におかれましては,あくまで2017年1月30日現在の情報であることにご注意願いたい.
以下では,Cycle.js (w/ xstream)でウェブアプリケーションを構築する.また,アセットのバンドルにはWebpackを用い,babel-loaderを通じてES2015+およびJSXのトランスパイルを行うこととする.
なお,今回のプロジェクトはGitHub上で公開しているので参考にされたい.
create-cycle-appを使ってもいいが,より深く理解するため,スクラッチでプロジェクトを作ることにする.
とりあえずプロジェクトの初期化を行う.
$ npm init
# 雑なプロジェクトの場合は`package.json`に`{ "private": true }`とだけ書いておけばいいと思う.
続いて,必要なパッケージをインストールする.
$ yarn add @cycle/xstream-run @cycle/dom@^14.3.0 xstream
$ yarn add -D babel-core babel-loader babel-plugin-transform-react-jsx babel-preset-latest snabbdom-jsx webpack
1行目のdependenciesが,アプリケーションを動かすために必要なパッケージで,2行目のdev dependenciesは,記述したスクリプトをブラウザが解釈できるようなコードにトランスパイルするために必要なパッケージである.
なお,本稿執筆時点では,@cycle/domを普通にyarn addで入れると15.0.0-rc1が入るが,このヴァージョンを用いると,webpackでビルドする際に,以下のようなエラーが発生するので,使用するヴァージョンを陽に指定してある.
ERROR in ./~/@cycle/dom/lib/MainDOMSource.js
Module not found: Error: Cannot resolve module '@cycle/run/lib/adapt' in /Users/Ryota/dev/cycle-jsx-example/node_modules/@cycle/dom/lib
@ ./~/@cycle/dom/lib/MainDOMSource.js 2:14-45
ERROR in ./~/@cycle/dom/lib/HTMLSource.js
Module not found: Error: Cannot resolve module '@cycle/run/lib/adapt' in /Users/Ryota/dev/cycle-jsx-example/node_modules/@cycle/dom/lib
@ ./~/@cycle/dom/lib/HTMLSource.js 3:14-45
ERROR in ./~/@cycle/dom/lib/mockDOMSource.js
Module not found: Error: Cannot resolve module '@cycle/run/lib/adapt' in /Users/Ryota/dev/cycle-jsx-example/node_modules/@cycle/dom/lib
@ ./~/@cycle/dom/lib/mockDOMSource.js 3:14-45
ERROR in ./~/@cycle/dom/lib/DocumentDOMSource.js
Module not found: Error: Cannot resolve module '@cycle/run/lib/adapt' in /Users/Ryota/dev/cycle-jsx-example/node_modules/@cycle/dom/lib
@ ./~/@cycle/dom/lib/DocumentDOMSource.js 3:14-45
ERROR in ./~/@cycle/dom/lib/BodyDOMSource.js
Module not found: Error: Cannot resolve module '@cycle/run/lib/adapt' in /Users/Ryota/dev/cycle-jsx-example/node_modules/@cycle/dom/lib
@ ./~/@cycle/dom/lib/BodyDOMSource.js 3:14-45
次に,webpack.config.jsを記述する.
src/app.jsを読み込み,Babelを挟んでdest/app.jsに出力している.
presetにはlatestを指定しておくと,es2015, es2016, es2017が含まれていて便利である.実際,Babel公式も"All you need to compile what's in ES2015+"と謳っているくらいだから,使用が推奨されているのだと思う(個人の見解です).
プラグインとしてtransform-react-jsxを噛ませていて,こいつがJSXをJavaScriptに変換してくれる.プラグインの名前に"react"と入っているくらいだから,デフォルトではReact.createElementに変換するのだが,オプションとしてpragmaを指定することで,好きな関数を選ぶことができる.今回はsnabbdom-jsxのhtml関数を指定している.Snabbdomはシンプルさ・高い性能・高い拡張性を掲げている仮想DOMライブラリで,どうも最近アツいらしい.
古い情報だと,@cycle/domからhJSXという関数をimportして,これを使えという風に書いていたが,どうもこれはoutdatedらしい.実際,@cycle/dom@14.3.0ではhJSXはexportされていなかった.
上記のwebpack.config.jsを用意しておけば,以下のコマンドで,ファイルを監視しつつリアルタイムでビルドしてくれる.
$(npm bin)/webpack --watch
とはいえ,まだsrc/app.jsがないので,実際にアプリケーションのコードを書く.
前述の通り,JSXはhtml関数に変換されるため,snabbdom-jsxからimportしておく.HTMLからdest/app.jsを読み込めば,<div id="app">の要素が置換されて,"Hello, world!"と表示されるはずである.