ReactとRuby on Railsを連携させる時によくやる方法
既存のRails製プロダクトのフロントエンドを徐々にReactベースに置き換えていったり、一部のUIコンポーネントだけReactで動かしたりする際に使っているヘルパーをメモしておきます。
Rails側にView用のヘルパーを設置する。
# app/helpers/react_helper.rb module ReactHelper # Reactコンポーネントを表示します # props: Propsに渡すJSONエンコード可能なデータ def render_component(component, props = {}) raw render(partial: 'helpers/react/component', locals: { component: component, props: props }) end end
/ app/views/helpers/react/_component.html.slim div.react-component[ data-component=(component) data-props=(props.to_json) ]
次に、webpackerのエントリーポイント配下で以下のスクリプトを読み込ませる。
// app/javascript/react-bridge.tsx import React from "react" import ReactDOM from "react-dom" const selector = ".react-component" /** RailsのviewからReactComponentを探して描画 */ const mountComponents = () => { document.querySelectorAll<HTMLDivElement>(selector).forEach(async host => { try { const componentName = host.dataset["component"] const Component: React.ComponentType<any> = _componentStore.get( componentName ) assert( Component, `bridge.react: component not defined with name: ${componentName}` ) if (!Component) return let props: object | undefined = undefined try { const rawProps = host.dataset["props"] props = rawProps && JSON.parse(rawProps) } catch (e) {} let options: MountOption | undefined = undefined try { const rawOptions = host.dataset["options"] options = rawOptions && JSON.parse(rawOptions) } catch (e) {} render( <ComponentWrapper> <Component {...props} /> </ComponentWrapper>, host ) } catch (e) { assert(false, e) } }) } /** ページ遷移時に古いコンポーネントをGC */ const cleanupComponents = () => { const nodes = document.querySelector(selector) nodes && ReactDOM.unmountComponentAtNode(nodes) } const ComponentWrapper: React.FC<{ noTheme?: boolean }> = props => { // コンテキスト等の挿入はここで return <>{props.children}</> } /** コンポーネントの読み込み機構を初期化して監視を開始 */ export const start = () => { document.addEventListener("turbolinks:load", mountComponents) document.addEventListener("turbolinks:visit", cleanupComponents) } /** コンポーネントをローダーに追加 */ export const registerComponent = (component: any, name?: string) => { name = name || component?.displayName || component?.constructor?.name _componentStore.set(name, component) } function assert(value: any, message?: string | Error): asserts value { if (!value) { if (process.env.NODE_ENV === "production") { if (message) { console.log(message) } } else { throw message || "assertion error" } } } const _componentStore = new Map<string, any>()
次にapplication.jsに以下を挿入
// app/javascript/packs/application.ts require("../react-bridge").start()
コンポーネントをViewから呼び出せるように登録する。(application.js等で実行)
const SomeComponent = () => (<div>Hello</div>) registerComponent(SomeComponent, 'SomeComponent');
RailsのViewから以下の様に呼び出す。
= render_component 'SomeComponent', { foo: 'bar' }
まとめ
screen.so: Linux client (i3wm), macOS host時に右のWindowsキーを⌘として使用する
状況が特殊すぎて自分以外に参考にならないと思うので簡単なメモ
i3にて$mod Mod4
して普段はWindowsキーを使って画面移動等を行なっている。
そのままだとscreen.soでホスト側を操作する場合にコピペ等ができない。
追記: 2020-04-20
xmodmapだとキーボードを再接続した時や、スリープからの復帰時に内容がリセットされてしまうためxkbで設定できないか模索中。
setxkbmap -option "lv3:lwin_switch"
これを設定すると、左の⌘がISO_Level3_Shift
に変換される。これはmod5にあたるので、i3では$mod Mod5
することで良い感じに運用できそう。(他のPCと共有している設定なので他のPCでも設定しておかないと。。)
xorg.conf
から設定できるので、永続化も簡単。
過去の対処法
mod3が空いているのでxmodmapでとりあえず割り当ててしまう。
remove mod4 = Super_R add mod3 = Super_R
ちょうど自分の環境ではmod3を使っていないし、右のWindowsキーを殆んど使っていないので良い機会だしこういった設定にした。
GitHubの通知をまとめて開くブラウザ拡張機能を作った
https://github.com/notifications に出てくる通知をまとめてタブで開いて消化していくのが日課なんだけど、それを効率化できるブラウザ拡張機能を作った。
通知ページの右上にOpen All
と書いたボタンを追加します。一応新旧UIで動くようにしてある。もうすぐ新UIがデフォルトになって旧UIは無くなる雰囲気が出ている。
また、新UIではチェックボックスがあるので、チェックをした通知のみまとめて開くボタンも追加した。
インストールは下記からです。
CloudFlare Workersを使って無料プランでも無理矢理キャッシュの出し分けを実現する
originのVaryヘッダーを元にキャッシュを制御するべきだけど、あらかじめキャッシュを分けたい条件が判別できるなら、以下の方法でURLを書き換えてしまえばOK.
import { createHash } from 'crypto' export async function handleRequest(request: Request): Promise<Response> { const vary = request.headers.get('accept-language') || '' const varyHash = createHash('md5') .update(vary) .digest('hex') let url = new URL(request.url) url.searchParams.append('_vary', varyHash) request = new Request(url.toString(), request as RequestInit) return fetch(request, { cf: { cacheEverything: true, } }) }
まさに貧者のCDN