0%

Koa入門 - Part4: ES6でIsomorphicなDeku

Reactのリアクティブとコンポーネント指向の考えはすごく良いのですが、どうも書き方に馴染めません。IsomorphicもJavaScriptで書くよりClojureで書く方が楽しいのでReagentを使っています。Node.jsのES6時代に向けたKoaのIsomorphicを調べていると、horseDekuといったライブラリがありました。今回はDekuの方を試してみます。

Deku

DekuはReactのalternativeとしてUIのコンポーネントを作成するライブラリです。

NIHではない

なぜDekuを開発し始めたかはDeku: How we built our functional alternative to Reactに書いてあります。頭が痛いすぐ内製でやりたがるNIHではないと言っています。彼らはフロントエンドのパッケージマネージャとしてDuoを使っているようです。Reactのような大きなブラックボックス中でたくさんの機能を実装するよりも、小さなモジュールで構成した方が見通しもよく、デバッグしやすいといった考え方が背景にあります。見通しがよく何か問題があったときに原因を特定しやすいというのはフレームワークを選ぶ上でとても重要なことです。

IsomorphicかAPIサーバーか

以前Reagent入門 - Part3: クライアントとサーバーの通信パターンReagent入門 - Part4: SPAとフォームの要件で考察しました。今のところSPAはIsomorphicでなくAPIサーバーにして、毎回面倒ですがルーティングやモデルはクライアントもサーバーも2回書くのがよいと思っています。

Isomorphic初心者なので、Clojure/ClojureScriptで書いていてもサーバーのコードを書いているのかクライアントなのか時々わからなくなります。ルーティングを書いているときなどほとんど同じコードなので混乱してしまいます。

プロジェクト

今回のプロジェクトは以下の構成です。Dekuとサンプルのtodomvcを読みながらインストールとKoaを使ったサーバサイドレンダリングを試してみます。

Dekuのビルド方法

Dekuはクライアントサイドのライブラリです。ES6で書くのでいずれにいてもBabelは必要です。

Browserfify

Browserfifyを使うのが、サーバーサイドとクライアントサイドのどちらのレンダリングを使う場合でも一番簡単なようです。babelifyを使うとJSXのコンパイルも簡単にできます。

$ browserify -t babelify main.js > build.js

Duo

Duoは次世代のフロントエンドのパッケージマネージャです。DuoからBabelを使うときにはduo-babelが必要になります。DekuはDuoを推しているようなので使ってみようと思います。

Duoの考え方は1つのパッケージは1つの機能を提供することです。コード上のインポートは直接GitHubのパッケージを指定してます。

import {element,tree,render} from 'segmentio/deku@0.2.1'

ES6で書いてBabelが必要になるので、duoコマンドには--use duo-babelフラグを付けてコンパイルして使います。

$ duo --use duo-babel main.js > build.js

babel/register

単純なHello Worldなので、今回はindex.jsでbabel/registerのフックからrequireしたapp.es6を自動的にコンパイルします。本来のIsomorphicならばクライアント側のES6コードはbrowserifyでコンパイルして、ES5
にビルド済みのjsファイルをindex.htmlなどから<script src="/build/index.js"></script>として最初にロードする必要があります。次回はクライアント側のbrowserifyを試してみます。

プロジェクト

適当なディレクトリにプロジェクトを作成します。リポジトリはこちらです。

$ cd ~/node_apps/docker-deku-koa
$ tree
.
├── Dockerfile
├── README.md
├── client
│   └── helloworld.es6
├── docker-compose.yml
├── node_modules -> /dist/node_modules
├── package.json
└── server
├── app.es6
└── index.js

Dockerfileとdocker-compose.yml

Dockerfileのベースイメージはiojsを使います。

~/node_apps/docker-deku-koa/Dockerfile
FROM iojs:2.3
MAINTAINER Masato Shimizu <ma6ato@gmail.com>

RUN mkdir -p /app
WORKDIR /app

COPY package.json /app/
RUN mkdir -p /dist/node_modules && \
ln -s /dist/node_modules /app/node_modules && \
npm install

EXPOSE 3000
COPY . /app
ENTRYPOINT ["npm", "start"]
CMD []

docker-compose.ymlではDockerホストのカレントディレクトリごと、コンテナのWORKDIRである/appにマウントします。

~/node_apps/docker-deku-koa/docker-compose.yml
deku:
build: .
volumes:
- .:/app
- /etc/localtime:/etc/localtime:ro
ports:
- "3030:3000"

コンテナにマウントしたときにnode_modulesが隠れないように、カレントディレクトリに予めシムリンクを作っておきます。

$ ln -s /dist/node_modules .

package.json

package.jsonに必要なパッケージをdependenciesに追加します。

~/node_apps/docker-deku-koa/package.json
{
"name": "deku-koa-example",
"description": "deku-koa-example",
"version": "0.0.1",
"private": true,
"dependencies": {
"deku": "^0.4.5",
"koa": "^0.21.0",
"babel": "^5.6.7"
},
"scripts": {
"start": "node server/index.js"
}
}

サーバーサイド

babel/registerのフックでapp.es6をrequireしたときに自動的にES5にコンパイルします。jsxPragmaオプションにelementを指定しています。こうするとJSXの変換にBabelがデフォルトのReact.createElementの代わりに、import {element} from 'deku'のelementを使うようになります。

~/node_apps/docker-deku-koa/server/index.js
'use strict';

// ES6 support
require('babel/register')({ jsxPragma: 'element' });
require('./app');

app.es6はES6で書いたKoaのコードです。IsomorphicなKoaのテストなので、クライアントサイドのコードをimportしてレンダリングしています。

~/node_apps/docker-deku-koa/server/app.es6
'use strict'

import koa from 'koa'
import HelloWorld from '../client/helloworld'
import {element, tree, renderString} from 'deku'

const app = koa()

app.use(function *() {
this.body = renderString(tree(<HelloWorld text="Hello World!" />))
})

app.listen(3000)

クライアントサイド

client/helloworld.es6で、サーバーサイドのserver/app.es6でも使うHelloWorldコンポーネントを定義しています。

~/node_apps/docker-deku-koa/client/helloworld.es6
'use strict'

import {element, render} from 'deku'

var HelloWorld = {
render(component) {
let {props,state} = component
return (
<div>
{props.text}
</div>
)
}
}

export default HelloWorld

Dockerイメージのビルドと起動Docker

$ cd ~/node_apps/docker-deku-koa
$ docker-compose build
$ docker-compose up
Recreating dockerdekukoa_deku_1...
Attaching to dockerdekukoa_deku_1
deku_1 | npm info it worked if it ends with ok
deku_1 | npm info using npm@2.11.1
deku_1 | npm info using node@v2.3.0
deku_1 | npm info prestart deku-koa-example@0.0.1
deku_1 | npm info start deku-koa-example@0.0.1
deku_1 |
deku_1 | > deku-koa-example@0.0.1 start /app
deku_1 | > node server/index.js
deku_1 |

Dockerホストの3030ポートにマップされています。curlコマンドでテストするとHello World!のdiv要素が返りました。

$ curl localhost:3030
<div>Hello World!</div>