情弱がCycle.jsでHello Worldを表示してみた[入門]

世の中にはCycle.jsという便利なものがあるらしい。Cycle.jsがどんなものかについては、たぶん公式サイトの説明が一番わかりやすいと思う。簡単に説明すると、仮想DOMとStream(Observable)をうまく組み合わせて、いい感じに動的なwebページを作れるらしい。
ということで、本記事ではCycle.jsとxstreamを使って、公式ページにある「Hello hogehoge(hogehogeの部分が動的に表示される)」というサンプルを実装しようと思う。

対象

  • RxJsやxstreamの最低限の知識があり、初めてCycle.jsに触れる人。ここでいう最低限は、「streamがどんなものか」が漠然とわかる程度でいいはず(たぶん)。

注意

  • 私自身、Cycle.jsの初心者で間違った表現などあるかもしれないので、もしあれば教えていただけると嬉しいです。
  • javascriptを直接書きたくないので、typescriptを使います(公式でも推奨されています)。
  • 本記事ではRxJsではなくxstreamを使って実装します。

参考にしたサイト

実行環境

では、実際に実装していこう。

1.環境を整える

まずは、諸々の環境を整える。適当にディレクトリを作ってそこに移動してください。ここでは、cyclejsディレクトリを作ったとしよう。ここに全てのファイルをぶち込んでもいいのだが、やや見栄えが良くないので元のソースを入れるsrcディレクトリと、jsファイルなどを入れるcodegenディレクトリを作ろう。以降のファイルも全て含めると、最終的に下のようなディレクトリ構造になると思う。

cyclejs
|__src       //**.tsファイルを入れる
|   |____test.ts
|__codegen       //**.jsファイル、**.htmlファイルを入れる
|   |____test.js
|   |____index.html
|__nodemodules
|
|__bs-config.js
|__package.json
|__tsconfig.json
|__watch.sh

cyclejsディレクトリ内でパッケージ管理用のpackage.jsonを作りたいので、次のコマンドを叩く。なんか色々聞かれますが、Enter連打で大丈夫。

$ yarn init

これでpackage.jsonができるはず。ここに色々とライブラリが追加されていき、管理してくれる(便利)。
では、必要なライブラリを入れていこう。

$ yarn add  xstream @cycle/dom @cycle/run @types/node

これで必要なライブラリが一通り依存関係を解決した状態で入ってくれる。わぁい。 さて、これで準備は整ったので実際にコードを書いていこう。

2.コードを書く

上でライブラリを用意できたので、実際にコードを書いていく。公式サイトにあるやつを参考にすると、次のようになると思う。

//test.ts

import {Stream} from 'xstream';
import {run} from '@cycle/run';
import {div, label, input, hr, h1, makeDOMDriver, DOMSource, VNode } from '@cycle/dom';

type Sources = {
    DOM: DOMSource;
}

type Sinks = {
    DOM: Stream<VNode>;
}

console.log("test.js readed");

function main(sources: Sources) : Sinks {

    const input$: Stream<Event> = sources.DOM.select('.field').events('input');

    const name$: Stream<string> = Stream.from(input$)
        .map((ev: Event) => (ev.target as HTMLInputElement).value)
        .startWith('');

    const vdom$: Stream<VNode> = name$.map(name =>
        div([
            label('Name:'),
            input('.field', {attrs: {type: 'text'}}),
            hr(),
            h1('Hello ' + name),
        ])
    )

    return { DOM: vdom$};
};

const drivers = {
    DOM: makeDOMDriver('#app-container')
};

run(main, drivers);

公式サイトにも書いてあるが、簡単にCycle.jsの仕組みを説明する。Cycle.jsにはmaindriverという2つの部分があり、mainが内部の処理を、driverがDOMの操作などを担当している(たぶん)。で、maindriverstream(Observable)を授受することでやりとりしており、これをmain関数ではsourceとしてdriverから受け取り、sinkとしてdriverに渡す。上のコードでもそれをやっている。
さて、Javascriptをブラウザ上で動かしたいので、HTMLも用意してやる必要がある。これをindex.htmlとして、次のように書く。

<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>Hello Cycle.js</title>
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
    </head>
    <body>
        <div id="app-container"></div>
        <script src="test.js"></script>
    </body>
</html>

実際の処理はtest.jsが全てやってくれるので、ここでは特に何かを書いてやる必要はない。
これで必要なソースはできたので、あとはtest.tsコンパイルして、サーバーを立ててアクセスできるようにしよう。

3.ブラウザに表示させる

ここの部分は正直なところ私もよく分かっていない…が、上の参考サイト様の言う通りに色々とやればいい。
まず、サーバを利用するために以下のものをyarnする。

$ yarn add -D typescript tsify browserify watchify browser-sync

そして、サーバを実行するためのシェルファイルwatch.shを作る。

#!/usr/bin/env bash
 
# Compile TypeScript sources
nohup watchify -d src/test.ts -p [ tsify ] -o codegen/test.js &
browserify_pid=$!
trap "kill -15 $browserify_pid $>/dev/null" 2 15
 
# Run Server
nohup browser-sync start --config bs-config.js &
browserSync_pid=$!
trap "kill -15 $browserSync_pid &>/dev/null" 2 15
 
tail -f nohup.out

よくわからないけどこれでtypescriptのコンパイルとサーバの起動ができて、ファイル変更があったら自動でリコンパイルしてくれるらしい(めっちゃ便利)。
で、このシェルをyarnから呼び出したいので、package.jsonに次の記述を足す。

  "scripts": {
    "watch": "bash ./watch.sh"
  }

これで、ターミナル上で、

$ yarn watch

と叩くと、シェルファイルが走ってくれます。
さて、これでOKだ!と思いきやそんなことはなく。typescriptをコンパイルするにはtsconfig.jsonとかいうものが必要なので、こいつもディレクトリ内に作る。内容は以下の通り。

{
    "compilerOptions": {
        "target": "es2017",
        "lib": ["es5", "es6", "dom"],
        "module": "commonjs",
        "sourceMap": true,
        "outDir": "./codegen"
    },
    "include": [
        "src/**/*"
    ]
}

これで最適なのかはわからないけど、少なくとも動いているので間違ってはないのでしょう(誰か、tsconfigの正しい書き方教えてください)。
そして、残念なことにまだ用意しなければならないものがある。browser-syncとかいうやつを使ってブラウザ上に表示をするわけだが、これの設定ファイルであるbs-config.jsを作らなければならないらしい。そこで、公式サイトを見ると、次のようなコマンドを叩くとbs-config.jsが自動で生成される。

$ yarn browser-sync init

これでbrowser-syncが生成されるので、そこの、filesserverを次のように変更する。

    "files": ["codegen/index.html", "codegen/test.js"],
    "server": {
        baseDir: "codegen",
        serveStaticOptions: {
            extensions: ["html"]
        }
    }

これで、index.htmlが読み込まれる他、"files"に指定したファイルが変更された時に自動でリコンパイルされるようになった。 さて、以上で全ての準備は整った。あとは、

$ yarn watch 

でサーバーの起動を確認後、localhost:3000にアクセスすればサンプルと同じページが見られる。やったぜ!

最後に

コンパイルとかbrowser-syncとかはなんも理解せずに行き当たりばったりで使っているので、もっと理解しなきゃなあというお気持ち。あと、Cycle.jsそれ自体もそんなに理解できてないので、誰か教えてください。