Om-nextでServer side renderingその後

Om-nextは、1.0.0-alpha45でサーバーサイドレンダリングに対応していますが、前はなぜかうまく動かせず、cellophaneというライブラリを使って実現させていました。今回はその続き

Story Permalink

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-reactiddata-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

Magazine Archives

vol.122

2016-Feb-01

Cover

台湾に行ってきました。

今回のカバーは、台湾にて撮影したものです。

自治体Webデザイントレンドこぼれ話 : ここだよマップ

CSS Niteの年末イベントShiftでやっているセッションにデザイントレンドというのがあります。

descjop で遊ぼう day 16 : Windows版のメニューを表示する

2015年の振り返り的な一人レトロスペクティブ

そういえば、まだ2015年の振り返りをマガジンに掲載していませんでした

編集後記

vol.121

2016-Jan-14

Cover

今回のカバーは、、、UFO?

自治体Webデザイントレンドこぼれ話 : 予告

CSS Niteの年末イベントShiftでやっているセッションにデザイントレンドというのがあります。

descjop で遊ぼう day 11 : Omのコンポーネントで遊ぼう

descjop で遊ぼう day 12 : Omのコンポーネントで遊ぼうその2

descjop で遊ぼう day 13 : Clojure ワークショップでTAしてきた話

descjop で遊ぼう day 14 : アプリケーションメニューを作ろう

descjop で遊ぼう day 15 : descjop 0.5.3をリリースしました

編集後記

vol.120

2016-Jan-14

Cover

今回のカバーも、初詣に行ったときのショットです。

descjop で遊ぼう day 6 : Electronがうまくダウンロードできないとき

descjop で遊ぼう day 7 : Hello Worldを出力してみるには

descjop で遊ぼう day 8 : Om basedテンプレートを使ってみる

descjop で遊ぼう day 9 : Om basedテンプレートにボタンをつける

descjop で遊ぼう day 10 : Om basedテンプレートにボタンをつけるその2

編集後記

vol.119

2016-Jan-10

Cover

今回のカバーは、初詣に行ったときのショット。

「descjop で遊ぼう」について

「descjop で遊ぼう」というのは年末にやったアドベントカレンダーのシリーズなのですが、前段としてまずはその解説をば。

descjop で遊ぼう day 1 : はじめに

descjop で遊ぼう day 2 : 3つのテンプレート

descjop で遊ぼう day 3 : 環境構築をしよう

descjop で遊ぼう day 4 : Helpを見てみよう

descjop で遊ぼう day 5 : デフォルトアプリケーションのビルドと起動

編集後記

vol.118

2015-Oct-22

Cover

今回のカバーは、Stack Overflow DevDaysのイベントをお手伝いしたときの窓からのショット。

Vaqum Web Design Review

創刊以来延々と続くWebサイトレビュー

ミカヅキClojure : ゼロからはじめるClojure入門第2回

Clojureっていうじつに面白いプログラミング言語をとりあげていきます

インフォメーション

おすすめのイベント・勉強会情報を紹介

編集後記

vol.117

2015-Oct-07

Cover

今回は散歩道からのショット

Editor’s Picks

designudgeで扱っている内容に近いジャンルのリソースのうち気になったものなど

U.S. Web Design Standardsを見てみる

今話題となっているU.S. Web Design Standardsの周辺情報など見てみました

Bootstrap 4徹底攻略 (3)

Bootstrap 4のアルファ版がでているので使おう

インフォメーション

おすすめのイベント・勉強会情報を紹介

編集後記

vol.116

2015-Sep-28

Cover

今回は帰省した際に通った三島からのショットです

Vaqum Web Design Review

創刊以来延々と続くWebサイトレビュー

みそじ過ぎからの英語再学習

40を目前に今までサボっていた英語学習をはじめました

Bootstrap 4徹底攻略 (2)

Bootstrap 4のアルファ版がでているので使おう

ミカヅキClojure

Clojureっていうじつに面白いプログラミング言語をとりあげていきます

インフォメーション

おすすめのイベント・勉強会情報を紹介

編集後記

vol.115

2015-Sep-13

Cover

前回と同じ開発合宿のショットから一つ

Vaqum Web Design Review

創刊以来延々と続くWebサイトレビュー

開発合宿へ行こう! : Python mini hack-a-thon 夏山合宿 2015

先週末に行ってきた開発合宿について書きました。

ミカヅキClojure

Clojureっていうじつに面白いプログラミング言語をとりあげていきます

インフォメーション

おすすめのイベント・勉強会情報を紹介

編集後記