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

    View Slide

  2. 2
    今日話すこと
    ● リプレースをうまく進めていくコツ、小口化
    ○ 機能:画面をパーツごとに
    ○ 網羅性:保守性を上げる目的で及第点を目指す
    ○ 影響度:限定公開することで
    ● Rails における React を活かした UI/UX への刷新事例
    ○ Sass → CSS in JS への置き換え
    ○ コンポーネントの振る舞いを jQuery → React に
    ○ jQuery・React それぞれの spec を並行稼動
    ○ Rails における React i18n 化

    View Slide

  3. jQuery ⇒ React の
    リプレースをうまく進めていくコツ

    View Slide

  4. 4
    自己紹介
    ● 氏名:石澤 佳祐
    ● GitHub, Qiita: @kei-p
    ● 株式会社 SonicGarden 3 年目
    ● Rails 歴 4 年
    ● 今日は @jnchito に送り込まれてきました
    ○ 発表初めて!

    View Slide

  5. 5
    ● 納品のない受託開発
    ● 自社サービス
    → 今回は自社サービス「Remotty」についての話をします

    View Slide

  6. 6
    Remotty について
    ● SonicGarden のバーチャルオフィス / お客さんにも提供
    ○ https://www.remotty.net/
    ● 技術的な要素
    ○ SPA
    ○ チャット、掲示板
    ○ カメラ撮影
    ○ リアルタイム

    View Slide

  7. Q:jQuery 辛いひとー  

    View Slide

  8. 8
    ● 状態管理が煩雑
    ○ data-xxx 、selector がいっぱい
    ○ change イベント拾って、ごにょごにょ
    ● イベントが分散しがち
    ○ 実際に、セレクターが複数の箇所で bind されてたことも。。。
    ● 技術的なトレンドから遅れてる感(2017年時点)
    jQuery 辛い

    View Slide

  9. そうだ、React にしよう

    View Slide

  10. 10
    でも、なかなか進まない。。。
    ● まとまった時間がとれない
    ● 既存のサービスの改善が優先されてしまう
    ● 両方を進める同時に進めづらい
    ○ コンフリクト怖い
    ● そもそも、引き継いだプロジェクトだし
    ○ どんぐらい大変?なんか不安。。。

    View Slide

  11. そうだ、小口化だ

    View Slide

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

    View Slide

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

    View Slide

  14. 機能の小口化

    View Slide

  15. 15
    ● 機能的に疎結合なパーツに分けて、段階的に置き換え
    ● 順番としては、要素(機能)が少なそうなパーツから着手
    ○ ボタン、イベントの数
    ○ 閲覧 / 編集
    ● 順番
    ○ ① アクティビティログ
    ○ ② ビデオキャプチャ
    ○ ③ チャット
    ○ ④ 掲示板一覧
    ○ ⑤ 掲示板
    機能の小口化


    ③ / ⑤

    View Slide

  16. 16
    jQuery から React へ部分的に置き換え
    rooms/show.html.haml
    .room
    .board-menu
    - # 掲示板一覧
    .board
    - # 掲示板
    components/room.jsx
    export default class Room extends React.Component {
    render() {
    return (





    )
    }
    }

    View Slide

  17. 網羅性の小口化

    View Slide

  18. 18
    網羅性の小口化
    ● すべてのコンポーネントを完全に React には置き換えない
    ○ ⇒ イベントハンドリングが複雑、state の管理が大変なものを優先
    ● tooltip や popover など複雑な処理がないものは、jQuery を流用
    ○ React への置き換えは後半で説明します

    View Slide

  19. 19
    componentDidUpdate() {
    this.updateParticipationTooltip();
    }
    updateParticipationTooltip() {
    const $target = $(this.tooltipRef);
    const participation = this.props.participation;
    const content = ``;
    $target.popover('destroy').data('content', content);
    }
    render() {
    return (
    this.tooltipRef = ref }>

    /* 省略 */


    )
    }
    React と jQuery をミックス
    props の内容が変わったときに、
    popover の中身を更新

    View Slide

  20. 影響度の小口化

    View Slide

  21. 21
    影響度の小口化
    ● いきなりお客さんに公開するのではなく、限定公開
    ○ ⇒ 社内(SonicGarden) での試験運用
    ○ フィーチャートグル
    ● jQuery 版, React 版の両バージョンの動作はテストで担保

    View Slide

  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 で切り替え

    View Slide

  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

    View Slide

  24. 24
    まとめ
    ● リプレースの小口化
    ○ 機能 / 網羅性 / 影響度
    ● React のリプレースは、だいたい 8ヶ月 くらい
    ■ 2017/07 ~ 2018/02
    ● 小口化の効用
    ○ サービスの改善とReact 化を柔軟に対応
    ○ 考える範囲や不安が減って、堅実に安心して進められた

    View Slide

  25. React へのリプレースはできた!
    ⇒ 次はより React を活かした UI/UX へ

    View Slide

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

    View Slide

  27. 27
    自己紹介
    ● 氏名:後藤雅之
    ● GitHub / Qiita: @gotchane
    ● 株式会社 SonicGarden もうすぐ2年目
    ● Rails 歴 約1年弱
    ● 入社してからインフラ→アプリに転向
    ● 僕も初発表!

    View Slide

  28. 28
    ● ユーザー数拡大のため、利用しやすい UI/UX を目指す
    ● 現在、7割程度リニューアルが進んでいる状況
    Remotty サイトリニューアルについて

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  32. 32
    課題に対する取り組み
    1. Sass → CSS in JS への置き換え
    2. コンポーネントの振る舞いを jQuery → React に
    3. jQuery 版 / React 版での RSpec 並行稼動
    4. Rails における React i18n 化

    View Slide

  33. 1. Sass → CSS in JS への置き換え

    View Slide

  34. 34
    課題
    ● 既存のデザインには影響を出せない不安。。
    ● CSS in JS を使い、Sass と疎の状態で刷新できないか?
    ● 簡単に試行錯誤を進められそうな CSS in JS は?

    View Slide

  35. 35
    const Button = styled.a`
    padding: 0.5rem 0;
    margin: 0.5rem 1rem;
    color: white;
    `
    ReactDOM.render(

    styled-components
    ,
    document.getElementById('root')
    )
    そこで styled-components
    ● CSS in JS の一種で、シンプルに CSS を記述可能
    ● Sass でできることはほぼ実現可能
    タグ付きテンプレートリテラルで囲んだ形

    View Slide

  36. 36
    styled-components 適用の進め方
    ● 機能の小口化と同じ要領で、小さいパーツから適用






    View Slide

  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(
    ,
    document.getElementById('root')
    )
    entry-badge.html.haml
    .entry-badge
    = render 'xxx'
    mixin の利用
    props による切り替え

    View Slide

  38. 2. コンポーネントの振る舞いを jQuery →
    React に

    View Slide

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

    View Slide

  40. 40
    対応方法
    ● 振る舞いを個別に切り出し、対応ライブラリで置き換え
    ● 新しい振る舞いも追加しやすくなった
    振る舞い例 Before After
    モーダル Bootstrap reactjs/react-modal
    ポップオーバー Bootstrap littlebits/react-popover
    キーボードショートカット jQuery による実装 jaywcjlove/hotkeys

    View Slide

  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 (

    keyName='Ctrl+k'
    onKeyDown={this.onKeyDown} />
    isOpen={this.state.showModal}
    onRequestClose={this.toggle} />

    )}
    }
    振る舞いが閉じた形に

    View Slide

  42. 3. jQuery 版 / React 版での
    RSpec 並行稼動

    View Slide

  43. 43
    課題
    ● feature spec のセレクタは、旧デザインが適用された状態
    ● 新旧共通の動きのテストで spec を再利用できない。。
    ● 新旧両方で spec をうまく活用できる方法はないか?

    View Slide

  44. 44
    ● デザインに依存しない、spec 用のクラスを適用
    ● spec の再利用・新規追加がスムーズになった
    menu_container.html.haml
    .menu__container.spec-menu__container
    = render 'xxx'
    対応方法
    menu_container.jsx
    >
    xxx

    spec 用クラスを付与

    View Slide

  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 用のクラスを指定し、新旧両方で再利用可能に

    View Slide

  46. 4. React での i18n 化対応

    View Slide

  47. 47
    課題
    ● React 上では Ruby の I18n.t メソッドが使えない。。
    ● 翻訳データは静的ファイルでなく自社 Web サービスで管理
    ● Web サービスの翻訳データから i18n 化するには?

    View Slide

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

    View Slide

  49. 49
    ● 翻訳データを返す GET メソッドを準備し表示
    React i18n 化:Webサービスから直接
    react-i18next
    翻訳データ管理
    Webサービス
    react
    i18next-
    xhr-backend
    Rails
    Controller
    翻訳データを返すメソッドを準備し、XHR でリクエスト

    View Slide

  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 でメソッドを実行する定義を追加

    View Slide

  51. 51
    コード例:翻訳データ表示
    sample_component.jsx
    import { t } from "i18next"
    import { withNamespaces } from "react-i18next"
    class Sample extends React.Component {
    render () {
    return (

    {t("sample.component.text" )}

    )
    }
    }
    export default withNamespaces( "translation")(Sample)
    キー指定で翻訳データを呼び出し

    View Slide

  52. 52
    まとめ
    ● 新と旧を徐々に疎の状態にしていくことを目指す
    ● 課題を一つ一つ丁寧に潰すことで道が見えてくる
    ○ Sass には手を入れず styled-components で刷新
    ○ jQuery に依存させず、React に振る舞いを閉じ込める
    ○ 新旧デザインに依存しないセレクタで spec を活用
    ○ React i18n 化の仕組みを新たに準備

    View Slide

  53. 53
    今日のまとめ
    ● リプレースをうまく進めていくコツ、小口化
    ○ 機能:画面をパーツごとに
    ○ 網羅性:保守性を上げる目的で及第点を目指す
    ○ 影響度:限定公開することで
    ● Rails で React を活かした UI/UX への刷新事例
    ○ Sass → CSS in JS への置き換え
    ○ コンポーネントの振る舞いを jQuery → React に
    ○ jQuery 版 / React 版での RSpec 並行稼動
    ○ Rails における React i18n 化

    View Slide

  54. ご清聴ありがとうございました!

    View Slide