Om-nextでServer side renderingその後
Om-nextは、1.0.0-alpha45でサーバーサイドレンダリングに対応していますが、前はなぜかうまく動かせず、cellophaneというライブラリを使って実現させていました。今回はその続き
Clojure Advent Calendar 2016 - Qiitaの10日目。
今日はサーバーサイドレンダリングの話です。
Clojureのサーバーサイドレンダリングについては、以前Lisp meetupというイベントで話しました。
これが、その時の発表資料です。その時はOmやOm-next、Reagent、Rumなどの各種Reactラッパー系のサーバーサイドレンダリングの現状について紹介しました。
また、サーバーサイドレンダリングでどういう実装スタイルが存在しているかという話もしました。なので、ClojureのReact系ラッパーライブラリのサーバーサイドレンダリングの情報は上記スライドを見ることで把握できると思います。
さて、その時にちゃんと話せなかったことがあります。
Om-nextでは、1.0.0-alpha45でサーバーサイドレンダリングに対応していますが、その時はなぜかうまく動かせず、cellophaneというライブラリを使って実現させていました。
ということで、今回はOm-next自体のサーバーサイドレンダリングを試していきたいと思います。

今回作るもの
今回作るものは、Om-nextのWikiにも掲載されている、素朴なカウンターをSSRに対応させていくということをやります。
まず最初はcellophaneを使ったバージョンを作成し、その後、Om-nextベースに書き換えます。
では、cellophaneベースでSSRを実装していきましょう。
1. Counterコンポーネントを作る
まず、Counterコンポーネントを作ります。
src/om_tutorial/component.cljc
に作っていきましょう。今回は、cellophaneベースとOm-nextベース両方のサンプルを収録したGitHubリポジトリを用意しましたので、クローンして気楽に遊んでみてください(そしてよかったらスターをください)
SSRコンテンツを作っていくにあたって一番大事なことは、ClojureからもClojureScriptからも利用できるようにコンポーネントやロジックを作っていくことです。
ということで、component.cljc
は、cljcとして作ります。下記のような感じです。
(ns om-tutorial.component
(:require
#?@(:cljs [[om.next :as om :refer-macros [defui]]
[om.dom :as h]]
:clj [[cellophane.next :as om :refer [defui]]
[cellophane.dom :as h]])))
(defui Counter
static om/IQuery
(query [this] [:count])
Object
(render [this]
(let [props (om/props this)
count (:count props)]
(h/div nil
(h/div nil
(h/span nil (str "Count by om: " count)))
(h/div nil
(h/button #js {:onClick
#(om/transact! this `[(app/increment {:e ~%})])}
"Click me!"))))))
注目ポイントは、requireのリーダーコンディショナル部分です。
ClojureScriptだと、
[om.next :as om :refer-macros [defui]]
[om.dom :as h]
がrequireされ、Clojureだと、
[cellophane.next :as om :refer [defui]]
[cellophane.dom :as h]
がrequireされることになります。これでClojureとClojureScriptどちらからも使えるコンポーネントが出来ました。
2. アプリケーションステートを用意
続いてアプリケーションステートを作成します。こちらもcljcで作ります。
src/om_tutorial/model.cljc
に作成しましょう。
(ns om-tutorial.model)
(def app-state (atom {:count 0}))
まあ、ここは、初期アプリケーションステートの定義のみです。
3. リコンシラーを用意
続いて、アプリケーションステートを管理していくリコンシラー(reconciler)を用意します。リコンシラーというのは調整者という意味で、アプリケーションステートの読み込みと変更をうまい具合にやってくれる存在になります。読み込みのための処理read
と変更のための処理mutate
を定義して、om/parser
に渡し、リコンシラーを作ります。
src/om_tutorial/reconciler.cljc
として作っていきます。こちらもcljcです。
コードは、下記のようになります。
(ns om-tutorial.reconciler
(:require
#?@(:cljs [[om.next :as om :refer-macros [defui]]
[om.dom :as h]]
:clj [[cellophane.next :as om :refer [defui]]
[cellophane.dom :as h]])
[om-tutorial.model :as model]))
;;; read
(defmulti read om/dispatch)
(defmethod read :count
[env key params]
(let [state (:state env)]
{:value (:count @state)}))
;;; mutate
(defmulti mutate om/dispatch)
(defmethod mutate 'app/increment
[env key params]
(let [state (:state env)]
;; (js/console.log (:e params))
{:action #(swap! state update-in [:count] inc)}))
;;; reconciler
(def reconciler
(om/reconciler
{:state model/app-state
:parser (om/parser {:read read
:mutate mutate})}))
4. SSR用のコンポーネントラッパーを用意
ここが、SSRのポイントなのですが、SSRをするときにサーバー側のテンプレートとしてレンダリングするための処理をcljcとして記述しておきます。これはサーバー側でしか使わないので、cljでもいいような気もしていますが。
src/om_tutorial/ui.cljc
として下記のように作成します。
(ns om-tutorial.ui
(:require
#?@(:cljs [[om.next :as om :refer-macros [defui]]
[om.dom :as h]]
:clj [[cellophane.next :as om :refer [defui]]
[cellophane.dom :as h]])
[om-tutorial.component :as c]
[om-tutorial.reconciler :as reconciler]))
;; for ssr
(def counter
(om/add-root! reconciler/reconciler
c/Counter nil))
(defn get-counter [] (h/render-to-str counter))
5. ClojureScriptで定義するSPA本体を作成
チュートリアルとほとんど同じですが、ClojureScriptで実際に動作する部分をsrc/om_tutorial/script.cljs
に書いていきます。
websocket-url
の部分は各自書き換えて使ってください。筆者はvagrantを使っているため、localhostではなくリモートで接続しています。
(ns om-tutorial.script
(:require [goog.dom :as gdom]
[om.next :as om :refer-macros [defui]]
[om-tutorial.component :as c]
[om-tutorial.reconciler :as reconciler]
[figwheel.client :as figwheel :include-macros true]))
(declare mount)
(enable-console-print!)
(figwheel/watch-and-reload
:websocket-url "ws://localhost:3449/figwheel-ws"
:jsload-callback mount)
(defn mount []
(om/add-root! reconciler/reconciler
c/Counter (gdom/getElement "app")))
(mount)
6. Ringでサーバサイドレンダリングするルーティングを定義
最後に、Clojureで動作するサーバにて、サーバーサイドレンダリングする処理を書きます。今回は2種類のルーティングを用意しました。
/ssr-1/
は、サーバーサイドレンダリングをしないもの/ssr-2/
は、サーバーサイドレンダリングをしたもの
なので、アクセスして、動作の違いを確認することが出来ます。
src/om_tutorial/core.clj
としてコードを書いていきます。ルーティングにはcompojureを使いました。
(ns om-tutorial.core
(:require [compojure.core :refer :all]
[compojure.route :as route]
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]
[hiccup.page :as page]
[om-tutorial.ui :as ui]))
(defroutes app
(GET "/" [] (page/html5 [:head]
[:body nil
[:div#app]
(page/include-js "/js/main.js")]))
(GET "/ssr-1/" [] (page/html5 [:head]
[:body nil
[:div#app nil]
(page/include-js "/js/main.js")]))
(GET "/ssr-2/" [] (let [component (ui/get-counter)]
(page/html5 [:head]
[:body nil
[:div#app nil component]
(page/include-js "/js/main.js")])))
(route/resources "/")
(route/not-found "<h1>Page not found</h1>"))
7. 実行してみる
以上で、実装コードは終わりです。実行してみましょう。
figwheelの起動
lein figwheel dev
サーバーの起動
lein ring server-headless
上記をそれぞれの別ターミナルで実行させてみましょう。そしてブラウザにて、起動サーバの3000番ポートを開くと確認ができます。
8. HTMLはどう出力されるのか、その違い
Om-nextのサーバーサイドレンダリングでは、ReactでSSRした場合と同様にdata-reactid
とdata-react-checksum
がしっかりと出力されます。
普通にOm-nextで作成した場合は以下な感じですが、
<!DOCTYPE html>
<html>
<head></head>
<body>
<div id="app"></div>
<script src="/js/main.js" type="text/javascript"></script>
</body>
</html>
Om-nextでサーバーサイドレンダリングした場合は以下ようにサーバーから出力されているのがわかります。
<!DOCTYPE html>
<html>
<head></head>
<body>
<div id="app">
<div data-reactroot="" data-reactid="1" data-react-checksum="2018787055"><div data-reactid="2"><span data-reactid="3">Count by om: 0</span></div><div data-reactid="4"><button data-reactid="5">Click me!</button></div></div>
</div>
<script src="/js/main.js" type="text/javascript"></script>
</body>
</html>
9. Om-nextのSSRを使ってみる
で、ここまでで出来ているのは、cellophaneによるSSRです。
これをOm-nextベースで動作させてみましょう。
Om-nextベースにするのは実に簡単で下記のように、cellophaneを使っていた箇所を変えるだけです。
(ns om-tutorial.component
(:require
#?@(:cljs [[om.next :as om :refer-macros [defui]]
[om.dom :as h]]
:clj [[om.next :as om :refer [defui]]
[om.dom :as h]])))
ポイントとしては、:as
を使って別名で読み込むところは変えないというところです。
そうすればコードの変更を行わなくて済みます。
また、[om.next :as om :refer [defui]]
のところが、cljsの時と違って、refer-macros
でなく、refer
となる点にも注意です。
このサンプルのGitHubのリポジトリ
om_next_ssr_with_lib
が、cellophaneベースのSSR、om_next_ssr_only
が、Om-nextベースのSSRのサンプルです。
Om-nextで楽しいサーバーサイドレンダリングライフを!
Colophon
- 編集長
- Greative GK. 原一浩 ( kara_d )
- 製版システム
- Clojure / Compojure / Ring / Enlive / markdown-clj / Jetty / MySQL
- Share this magazine!
- Follow designudge
- Follow @designudge