個人メモ。
Rearchitecting Wantedly's Frontend | Wantedly Engineer Blog の記事を読んで、副業先でも使えるかもと思い、Hypernova を触っていた。
Hypernova を使う個人的なモチベーション
リリースまで至った、それなりの規模の SPA ではないブラウザナビゲーションで遷移する Rails アプリを想定する。リッチな UI の実現と保守性の観点から JavaScript フレームワークの導入をおこなう。導入自体は Webpacker のような仕組みで簡単にできるようになったが、実装時にビューのロジックがあちこちに存在するという問題が残っている。ロジックとは、erb や haml などのテンプレートに書く(サーバーサイドで一度決定したら変わることはないという意味で)静的なロジックと、JavaScript フレームワークが担うUI操作などのイベントに応じて実行される動的なロジックである。
これらのファイル、あるいはテンプレートの仕様を行き来し( erb, haml <-> react, vue, angular )、頭を切り替えながら実装するのは個人的にはけっこうキツい。
JavaScript フレームワークに静的なロジックを担わせることができるが、SEOや初期表示の観点から、どこまでをクライアントサイドでレンダリングするか、という問題が出てくる。 理想を言えば、JavaScript のコンポーネントのコードを Rails で haml などのテンプレートエンジンと同様に扱えるとよい。Hypernova を使えば、これを実現することができる。
Node.js プロセスの面倒をみるという運用の手間が増えるが、エラーハンドリングの仕組みがよくできているので、最悪 Node.js のプロセスが落ちても、クライアントサイドのレンダリングにうまくフォールバックしてくれる。
Hypernova を Vue.js で使う
Hypernova、React に限らず SSR ができる JavaScript フレームワークならば何でも扱えそうな造りに見えつつ、Vue.js のコンポーネントを扱う話をあまり見かけない。こういう issueがあるということは多分やろうとしている人はいるんだろう、というのと、hypernova-react のコードを見たらファイルが一枚だけあって、これと同じことをするラッパーを用意すればよさそうだな、と思って書いた。こんなかんじ。
import Vue from 'vue' import hypernova, { serialize, load } from 'hypernova'; export const renderVue = (name, component) => { const Component = Vue.extend(component); return hypernova({ server() { return (props) => { const { createRenderer } = __non_webpack_require__('vue-server-renderer'); const renderer = createRenderer(); const vm = new Component({ propsData: props }); return new Promise((resolve, reject) => { renderer.renderToString(vm, (err, contents) => { if (err) { console.error(err); reject(err); return; } resolve(serialize(name, contents, props)); }); }); }; }, client() { const payloads = load(name); if (payloads) { payloads.forEach((payload) => { const { node, data } = payload; const vm = new Component({ propsData: data }); vm.$mount(node); }); } return component; } }); };
サーバーとクライアント両方で、Webpack でビルドする前提。Webpack 依存のコード(__non_webpack_require__
)が入っているのがイケてない。Webpack の設定でクライアントのビルドの場合は vue-server-renderer
を除外してもよさそう。ここらへんの問題が解決できたら、hypernova-vue
として npm に公開したい。
使う時は、Vue コンポーネントのオプションオブジェクトを先のファイルで export している renderVue 関数でラップする。後は、Hypernova の設定で、name に応じて、ラップしたコンポーネントを返すようにする。
import {renderVue} from './hypernova-vue'; import Like from './Like.vue'; export default renderVue('like', Like);
Rails のテンプレートからコンポーネントを呼び出す場合は、以下のようになる。ヘルパー名が react 決め打ちなのがアレだけれども alias か自前で render_vue_component
ヘルパーを定義すればいいと思う。
<%= render_react_component('like', liked: false) %>