Reactのリアクティブとコンポーネント指向の考えはすごく良いのですが、どうも書き方に馴染めません。IsomorphicもJavaScriptで書くよりClojureで書く方が楽しいのでReagentを使っています。Node.jsのES6時代に向けたKoaのIsomorphicを調べていると、horseやDekuといったライブラリがありました。今回は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を使います。
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
にマウントします。
deku:
build: .
volumes:
- .:/app
- /etc/localtime:/etc/localtime:ro
ports:
- "3030:3000"
コンテナにマウントしたときにnode_modules
が隠れないように、カレントディレクトリに予めシムリンクを作っておきます。
$ ln -s /dist/node_modules .
package.json
package.jsonに必要なパッケージをdependenciesに追加します。
{
"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を使うようになります。
'use strict';
// ES6 support
require('babel/register')({ jsxPragma: 'element' });
require('./app');
app.es6はES6で書いたKoaのコードです。IsomorphicなKoaのテストなので、クライアントサイドのコードをimportしてレンダリングしています。
'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
コンポーネントを定義しています。
'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>