Upgrade to Pro — share decks privately, control downloads, hide ads and more …

jQuery + Sass な SPA Rails アプリを React + CSS in JS にリプレースした話 @kei-p @gotchane #railsdm2019

jQuery + Sass な SPA Rails アプリを React + CSS in JS にリプレースした話 @kei-p @gotchane #railsdm2019

株式会社ソニックガーデン 石澤 佳祐( @kei-p ), 後藤 雅之( @gotchane )

Railsアプリケーションの開発において、React/Vue.js 等のモダンなフレームワークが台頭し、これまで主流だった jQuery、Sass で運用されているサービスをリプレースする事例が増えてきています。その一方で、リプレースを検討しつつも、開発規模が大きすぎたり、リソースの確保ができなかったりという理由で、リプレースが進まないケースも多く存在するかと思います。

本セッションでは、そんな既存サービスのリプレースを検討しているエンジニアにとって、SonicGarden の価値観の一つである小口化という考え方を元に、限られたリソースの中でも着々とリプレースを進めていった知見についてお話ししたいと思います。

Recently, modern frontend frameworks such as React and Vue are getting more and more popular and Rails apps built with jQuery and Sass are getting replaced by those new frameworks. On the other hand, some replaces cannot be done due to the hugeness of the project or lack of resources.

In this talk, I'd like to talk about the case where we replaced with limited resources based on "making it small", that's one of the core values of SonicGarden.

Rails Developers Meetup 2019
https://railsdm.github.io/

Masayuki Goto

March 22, 2019
Tweet

Other Decks in Programming

Transcript

  1. jQuery + Sass な SPA Rails アプリを React + CSS

    in JS にリプレースした話 Rails Developer meetup 2019 - Keisuke Ishizawa, Masayuki Goto
  2. 2 今日話すこと • リプレースをうまく進めていくコツ、小口化 ◦ 機能:画面をパーツごとに ◦ 網羅性:保守性を上げる目的で及第点を目指す ◦ 影響度:限定公開することで

    • Rails における React を活かした UI/UX への刷新事例 ◦ Sass → CSS in JS への置き換え ◦ コンポーネントの振る舞いを jQuery → React に ◦ jQuery・React それぞれの spec を並行稼動 ◦ Rails における React i18n 化
  3. jQuery ⇒ React の リプレースをうまく進めていくコツ

  4. 4 自己紹介 • 氏名:石澤 佳祐 • GitHub, Qiita: @kei-p •

    株式会社 SonicGarden 3 年目 • Rails 歴 4 年 • 今日は @jnchito に送り込まれてきました ◦ 発表初めて!
  5. 5 • 納品のない受託開発 • 自社サービス → 今回は自社サービス「Remotty」についての話をします

  6. 6 Remotty について • SonicGarden のバーチャルオフィス / お客さんにも提供 ◦ https://www.remotty.net/

    • 技術的な要素 ◦ SPA ◦ チャット、掲示板 ◦ カメラ撮影 ◦ リアルタイム
  7. Q:jQuery 辛いひとー  

  8. 8 • 状態管理が煩雑 ◦ data-xxx 、selector がいっぱい ◦ change イベント拾って、ごにょごにょ

    • イベントが分散しがち ◦ 実際に、セレクターが複数の箇所で bind されてたことも。。。 • 技術的なトレンドから遅れてる感(2017年時点) jQuery 辛い
  9. そうだ、React にしよう

  10. 10 でも、なかなか進まない。。。 • まとまった時間がとれない • 既存のサービスの改善が優先されてしまう • 両方を進める同時に進めづらい ◦ コンフリクト怖い

    • そもそも、引き継いだプロジェクトだし ◦ どんぐらい大変?なんか不安。。。
  11. そうだ、小口化だ

  12. 12 小口化とは? ” 工程ごとに一斉に作るのではなく、動くものまで一気通貫で少しずつ作ってい く方法 “ 「生産性の高い組織を実現するための小口化の原則」 https://kuranuki.sonicgarden.jp/2018/08/babysteps.html SonicGarden の価値観の一つ

  13. 13 リニューアルを小口化する • 機能:パーツごとに開発 • 網羅性:保守性を上げる目的で及第点を目指す • 影響度:限定公開することで安全にリリース

  14. 機能の小口化

  15. 15 • 機能的に疎結合なパーツに分けて、段階的に置き換え • 順番としては、要素(機能)が少なそうなパーツから着手 ◦ ボタン、イベントの数 ◦ 閲覧 /

    編集 • 順番 ◦ ① アクティビティログ ◦ ② ビデオキャプチャ ◦ ③ チャット ◦ ④ 掲示板一覧 ◦ ⑤ 掲示板 機能の小口化 ① ② ③ / ⑤ ④
  16. 16 jQuery から React へ部分的に置き換え rooms/show.html.haml .room .board-menu - #

    掲示板一覧 .board - # 掲示板 components/room.jsx export default class Room extends React.Component { render() { return ( <div> <ParticipationContainer /> <Board /> <Logs /> </div> ) } }
  17. 網羅性の小口化

  18. 18 網羅性の小口化 • すべてのコンポーネントを完全に React には置き換えない ◦ ⇒ イベントハンドリングが複雑、state の管理が大変なものを優先

    • tooltip や popover など複雑な処理がないものは、jQuery を流用 ◦ React への置き換えは後半で説明します
  19. 19 componentDidUpdate() { this.updateParticipationTooltip(); } updateParticipationTooltip() { const $target =

    $(this.tooltipRef); const participation = this.props.participation; const content = `<img src=${participation.iconUrl} class="popover-icon">`; $target.popover('destroy').data('content', content); } render() { return ( <div ref={ ref => this.tooltipRef = ref }> <div className='participation-icon'> /* 省略 */ </div> </div> ) } React と jQuery をミックス props の内容が変わったときに、 popover の中身を更新
  20. 影響度の小口化

  21. 21 影響度の小口化 • いきなりお客さんに公開するのではなく、限定公開 ◦ ⇒ 社内(SonicGarden) での試験運用 ◦ フィーチャートグル

    • jQuery 版, React 版の両バージョンの動作はテストで担保
  22. 22 class RoomsController < ApplicationController before_action :set_react, only: %i[show] def

    show if react? render 'react' end end def set_react return unless @room.sonicgarden? && params[:react].present? session[:react] = params[:react].to_i end def react? session[:react] == 1 end end フィーチャートグル 限定公開 params で切り替え
  23. 23 def react? session[:react] == 1 || ENV['FORCE_REACT'] end •

    テスト実行時に強制的に react 状態にさせる React のテスト • feature spec は jQuery 版、React 版で共通化 - run: bundle exec rspec - run: FORCE_REACT=1 bundle exec rspec
  24. 24 まとめ • リプレースの小口化 ◦ 機能 / 網羅性 / 影響度

    • React のリプレースは、だいたい 8ヶ月 くらい ▪ 2017/07 ~ 2018/02 • 小口化の効用 ◦ サービスの改善とReact 化を柔軟に対応 ◦ 考える範囲や不安が減って、堅実に安心して進められた
  25. React へのリプレースはできた! ⇒ 次はより React を活かした UI/UX へ

  26. React を活かした UI/UX へ Rails での刷新事例

  27. 27 自己紹介 • 氏名:後藤雅之 • GitHub / Qiita: @gotchane •

    株式会社 SonicGarden もうすぐ2年目 • Rails 歴 約1年弱 • 入社してからインフラ→アプリに転向 • 僕も初発表!
  28. 28 • ユーザー数拡大のため、利用しやすい UI/UX を目指す • 現在、7割程度リニューアルが進んでいる状況 Remotty サイトリニューアルについて

  29. 29 サイトリニューアルの方針 • 公開されているサービスに影響を与えず刷新を進める • サイトリニューアルと併せて、React 正式公開も目指す

  30. 30 道は険しかった。。

  31. 31 いざ取り組むも、未知の領域 • React でのリニューアルの知見がほとんどない状況。。 • React どころかフロントエンドの経験もほぼゼロ。。 • 何度も課題にぶち当たりながら道を模索

    • 自分との闘い
  32. 32 課題に対する取り組み 1. Sass → CSS in JS への置き換え 2.

    コンポーネントの振る舞いを jQuery → React に 3. jQuery 版 / React 版での RSpec 並行稼動 4. Rails における React i18n 化
  33. 1. Sass → CSS in JS への置き換え

  34. 34 課題 • 既存のデザインには影響を出せない不安。。 • CSS in JS を使い、Sass と疎の状態で刷新できないか?

    • 簡単に試行錯誤を進められそうな CSS in JS は?
  35. 35 const Button = styled.a` padding: 0.5rem 0; margin: 0.5rem

    1rem; color: white; ` ReactDOM.render( <div> <Button>styled-components</Button> </div>, document.getElementById('root') ) そこで styled-components • CSS in JS の一種で、シンプルに CSS を記述可能 • Sass でできることはほぼ実現可能 タグ付きテンプレートリテラルで囲んだ形
  36. 36 styled-components 適用の進め方 • 機能の小口化と同じ要領で、小さいパーツから適用 ③ ② ① ④ ⑤

  37. 37 コード置き換え例 entry-badge.sass =badge display: inline-block .entry-badge +badge background: green

    &.is-mention background: red entry-badge.jsx const badge = () => css` display: inline-block; ` const EntryBadge = styled.div` ${badge()} background: ${ props => props.isMention ? 'red' : 'green' }; ` ReactDOM.render( <div><EntryBadge isMention={false} /></div>, document.getElementById('root') ) entry-badge.html.haml .entry-badge = render 'xxx' mixin の利用 props による切り替え
  38. 2. コンポーネントの振る舞いを jQuery → React に

  39. 39 課題 • 詳細な振る舞い、ペンディングしてたよな。。 • デザイン刷新にあたり、新たな振る舞いも出てくる • なるべく React に閉じた形を目指す

  40. 40 対応方法 • 振る舞いを個別に切り出し、対応ライブラリで置き換え • 新しい振る舞いも追加しやすくなった 振る舞い例 Before After モーダル

    Bootstrap reactjs/react-modal ポップオーバー Bootstrap littlebits/react-popover キーボードショートカット jQuery による実装 jaywcjlove/hotkeys
  41. 41 コード置き換え例 jump_view.html.haml %span.jump-btn = link_to 'Ctrl+k', '#' .jump-modal.modal =

    render 'modal' jump_view.js.coffee $(document).on 'click','.jump-btn',-> $('.jump-modal').modal('toggle') isShortCut = (event) -> event.key == 'k' && event.ctrlKey $ -> $modal = $('.jump-modal') $(document).on 'keydown',(event)-> if isShortCut(event) $modal.modal('toggle') jump_view_button.jsx class JumpView extends React.Component { constructor () { super() this.state = { showModal: false } } onKeyDown = () => {/* ... */} toggle = () => {/* ... */} render () { return ( <JumpViewWrapper> <Hotkeys keyName='Ctrl+k' onKeyDown={this.onKeyDown} /> <JumpModal isOpen={this.state.showModal} onRequestClose={this.toggle} /> </JumpViewWrapper> )} } 振る舞いが閉じた形に
  42. 3. jQuery 版 / React 版での RSpec 並行稼動

  43. 43 課題 • feature spec のセレクタは、旧デザインが適用された状態 • 新旧共通の動きのテストで spec を再利用できない。。

    • 新旧両方で spec をうまく活用できる方法はないか?
  44. 44 • デザインに依存しない、spec 用のクラスを適用 • spec の再利用・新規追加がスムーズになった menu_container.html.haml .menu__container.spec-menu__container =

    render 'xxx' 対応方法 menu_container.jsx <MenuContainer className="spec-menu__container" > xxx </MenuContainer> spec 用クラスを付与
  45. 45 menu_container_spec.rb feature '掲示板', js: true do scenario '掲示板の表示' do

    expect(page).to have_selector ".spec-menu__container" end end feature spec コード例 spec 用のクラスを指定し、新旧両方で再利用可能に
  46. 4. React での i18n 化対応

  47. 47 課題 • React 上では Ruby の I18n.t メソッドが使えない。。 •

    翻訳データは静的ファイルでなく自社 Web サービスで管理 • Web サービスの翻訳データから i18n 化するには?
  48. 48 • 静的な翻訳ファイルをエクスポートして表示する • Web サービス上の翻訳データを直接活用できないか? React i18n 化:静的な翻訳ファイルを挟む react-i18next

    翻訳ファイル (.yml など) react 翻訳データ管理 Webサービス
  49. 49 • 翻訳データを返す GET メソッドを準備し表示 React i18n 化:Webサービスから直接 react-i18next 翻訳データ管理

    Webサービス react i18next- xhr-backend Rails Controller 翻訳データを返すメソッドを準備し、XHR でリクエスト
  50. 50 コード例:翻訳データ取得 translations_controller.rb class TranslationsController < ApplicationController def index @translations

    = SampleTranslationClient .data render json: @translations end end i18n.js import i18n from "i18next" import { reactI18nextModule } from "react-i18next" import Backend from "i18next-xhr-backend" i18n.use(Backend).use(reactI18nextModule) .init({ backend: { loadPath: "./translations.json" , }, whitelist: ["ja", "en"] }) export default i18n 翻訳データを JSON で返すメソッドを準備 XHR でメソッドを実行する定義を追加
  51. 51 コード例:翻訳データ表示 sample_component.jsx import { t } from "i18next" import

    { withNamespaces } from "react-i18next" class Sample extends React.Component { render () { return ( <SampleComponent> {t("sample.component.text" )} </SampleComponent> ) } } export default withNamespaces( "translation")(Sample) キー指定で翻訳データを呼び出し
  52. 52 まとめ • 新と旧を徐々に疎の状態にしていくことを目指す • 課題を一つ一つ丁寧に潰すことで道が見えてくる ◦ Sass には手を入れず styled-components

    で刷新 ◦ jQuery に依存させず、React に振る舞いを閉じ込める ◦ 新旧デザインに依存しないセレクタで spec を活用 ◦ React i18n 化の仕組みを新たに準備
  53. 53 今日のまとめ • リプレースをうまく進めていくコツ、小口化 ◦ 機能:画面をパーツごとに ◦ 網羅性:保守性を上げる目的で及第点を目指す ◦ 影響度:限定公開することで

    • Rails で React を活かした UI/UX への刷新事例 ◦ Sass → CSS in JS への置き換え ◦ コンポーネントの振る舞いを jQuery → React に ◦ jQuery 版 / React 版での RSpec 並行稼動 ◦ Rails における React i18n 化
  54. ご清聴ありがとうございました!