Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Turbolinks が dynamic import になるまで / Code splitting in Rails Frontend
Search
fsubal
September 05, 2018
Programming
3
3.6k
Turbolinks が dynamic import になるまで / Code splitting in Rails Frontend
第70回 HTML5とか勉強会「開発環境」にて発表した資料です
https://html5j.connpass.com/event/96895/
fsubal
September 05, 2018
Tweet
Share
More Decks by fsubal
See All by fsubal
Tailwind CSSを本気でカスタマイズする方法
fsubal
15
5.9k
デザインシステムで Tailwind CSSとCSS in JSに分散投資をしたら良かった話
fsubal
19
5.1k
『Tailwind CSS実践入門』 出版記念基調講演
fsubal
7
4.4k
Sprockets CSSもやめる なぜ / Why stop using Sprockets for CSS too
fsubal
3
1.4k
The Majestic MPA
fsubal
8
2.8k
Backbone.Model に 型をつけて剥がす - Typing to destroy Backbone.Model
fsubal
1
840
SVG + React でつくる レイヤーの自由変形 / Layer Transformation with React + SVG
fsubal
1
8.2k
カリー化はナンの役に立つのか
fsubal
26
7.8k
Domain Driven reDux - or Redux as CQRS
fsubal
1
1.2k
Other Decks in Programming
See All in Programming
Deep Dive into React Stream/Serialize
mugi_uno
4
860
Sheets API使ってみた
toshi0383
2
180
The Cutting Edge Of Versioning (LambdaConf 2024)
chriskrycho
0
250
[RubyKaigi 2024] Ruby Mixology 101: adding shots of PHP, Elixir, and more
palkan
0
120
WinActorの勉強を継続する方法
tamai_63
0
130
Amazon Aurora Serverless v2が意外と高かった話と、AWS Database Migration Serviceの話
satoshi256kbyte
1
110
哲学史とモデリング
tanakahisateru
2
420
2024 コーディング研修
ckazu
2
650
Native Federation: The Future of Micro Frontends in Angular
manfredsteyer
PRO
0
170
Embedding it into Ruby code
soutaro
1
310
Timeline エディター拡張入門
yucchiy
0
450
JS RPCを理解する
yusukebe
5
270
Featured
See All Featured
BBQ
matthewcrist
80
8.8k
Keith and Marios Guide to Fast Websites
keithpitt
408
22k
Typedesign – Prime Four
hannesfritz
36
2.1k
How STYLIGHT went responsive
nonsquared
92
4.8k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
117
18k
The Illustrated Children's Guide to Kubernetes
chrisshort
32
47k
Principles of Awesome APIs and How to Build Them.
keavy
121
16k
Bash Introduction
62gerente
605
210k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
14
1.5k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
123
39k
Producing Creativity
orderedlist
PRO
338
39k
[RailsConf 2023] Rails as a piece of cake
palkan
28
4.1k
Transcript
Code splitting in Rails Frontend Turbolinks が dynamic import になるまで
pixiv Inc. f_subal 2018/09/04
2 誰 • 2016年新卒入社 • pixiv投稿画面リニューアル (1月 ~ 4月) •
pixivFACTORY フロントエンド (5月~) • TypeScript, React, Fluxible, Vue 他 • 巨大フォームの設計ばかりやってる @f_subal
3
4
最近やったこと 5
6
今日はその話ではなく 7
Code Splitting + Dynamic Import 8
Code splitting • JS などの バンドルファイルを分割すること • webpack などモジュールバンドラの機能として提供される •
dynamic import による遅延読み込みを伴う ◦ 今回は native ES module の話はしません 9
Dynamic Import • import() 関数を用いたモジュールの遅延読み込み • 「必要なタイミングで」モジュールをロードすることが可能になる • 実体は <script>
要素を挿入して、利用可能になったら resolve される Promise 10
// static import import myUtil from './myUtil' // dynamic import
(with webpack) import('./myUtil').then(util => ... ) 11
• SPA のページ遷移を表現する(遷移時、あるいはその手前で import ) • 重いモジュールだけを遅延で読み込む • 1枚の *.bundle.js
に全部入り をとにかくやめたい場合に使う ◦ SPA じゃなくても有用 12 主なユースケース
1枚の *.bundle.js をやめる 13
14
15
• トップレベルの JS ファイルで全部 import はありがち • バンドルサイズが不要に大きくなる • モジュール境界が不明になり、影響範囲も読みづらくなる
16 1枚の *.bundle.js をやめる
• Rails + Turbolinks + Webpack • Turbolinks は全部 1
枚にバンドルされてる状態のほうが都合が良いらしい ◦ pjax による擬似 SPA をすべく、JS はそのままで HTML だけ差し替える設計 • そんなわけでバンドルファイルは泥団子になる • 変更の影響範囲も読むのが大変 17 pixivFACTORY の場合
// こういう感じ document.addEventListener('turbolinks:load', () => { if (!/^\/addresses\/(\d+?\/edit|new)/.test(location.pathname)) { return
} // ページによって jQuery だったり React だったりする 11
// こういう感じ document.addEventListener('turbolinks:load', () => { if (!/^\/addresses\/(\d+?\/edit|new)/.test(location.pathname)) { return
} // ページによって jQuery だったり React だったりする 11 turbolinks 独自の遷 移イベント 今いるページの location.pathname を正規表現で判定 これのせいで何度か事 故っている
20
21
• 各ページの js ファイルが閉じたモジュールになってほしい • ページの判定を正規表現でやらないで欲しい • 必要ないページでは余分な js ファイルを読まないで欲しい
22 どうすると良いか ?
• 各ページの js ファイルが閉じたモジュールになってほしい ◦ 関数モジュールにリファクタリングする • ページの判定を正規表現でやらないで欲しい ◦ ルーターを入れる
• 必要ないページでは余分な js ファイルを読まないで欲しい ◦ ルーティング解決時に dynamic import する 23 こうすると良い
• 関数モジュールにリファクタリングする ◦ turbolinks を捨てる + 関数 export に変更 •
ルーターを入れる ◦ universal-router を導入 • ルーティング解決時に dynamic import する ◦ Webpack の設定をいろいろ変更 + webpacker を捨てる 24 必要なこと
✂ というわけで ✂ 25
• Turbolinks が邪魔なので消えてもらう • 一旦 turbolinks:load をただの DOMContentLoaded に変えればいい ◦
旧ブラウザ対応考えるなら ded/domready とか使う • やると遷移は遅くなるが、一旦許容して進む 26 関数モジュールにリファクタリングする
27 関数モジュールにリファクタリングする // こういう感じ document.addEventListener('turbolinks:load', () => { if (!/^\/addresses\/(\d+?\/edit|new)/.test(location.pathname))
{ return } // ページによって jQuery だったり React だったりする これが邪魔
28 Turbolinks をやめる document.addEventListener('DOMContentLoaded', () => { if (!/^\/addresses\/(\d+?\/edit|new)/.test(location.pathname))
{ return } // ページによって jQuery だったり React だったりする まずは単に DOMContentLoaded にする
が 29
30 本当はこうしたいはず export const setup = () => {
if (!/^\/addresses\/(\d+?\/edit|new)/.test(location.pathname)) { return } // ページによって jQuery だったり React だったりする 関数を export する
そのためには 31
• 各ページを関数モジュールをした場合、それを受ける層が必要 • ここで router が必要になる • 各ページを関数モジュールに変更し、ルーターがそれを受け取る設計にする • つまり、関数モジュール化を完遂するにはルーターに載せる必要がある
32 ルーターに載せる
• 何か react-router とか vue-router とか、特定のビュー実装にくっつきすぎじゃね? • 「全ページ React に載せないと改善できないように見える」問題
• 「LPとかFAQとか react 化してもなぁ…(いいんだけど優先度が」 • jQuery のページ、React のページ、いろいろあるけど全部同じ土俵に乗らないんですか? ◦ pixivFACTORY には jQuery のページ、Backboneのページ、React のページ、 Redux のページ、fluxible のページがある。死ぬ。 33 クライアントサイドルーターの悩み
kriasoft/universal-router 34
• 特定のビュー実装に依存しないルーター • Universal JavaScript ガチ勢といった作り • 中は path-to-regexp なので、だいたい
express • ルーティング解決、ミドルウェア、 URL生成 ぐらいしか機能がない ◦ ブラウザバックでスクロール位置の復元とか、そういうものは一切ない 35 kriasoft/universal-router
import UniversalRouter from 'universal-router' const router = new UniversalRouter([
{ path: 'books/orders/:id', async action ({ params }) => await import('./pages/books/orders).then(page => page.setup(params.id)) } ]) document.addEventListener('DOMContentLoaded', async () => const handler = await router.resolve(location.pathname) handler() 11
import UniversalRouter from 'universal-router' const router = new UniversalRouter([
{ path: 'books/orders/:id', async action ({ params }) => await import('./pages/books/orders).then(page => page.setup(params.id)) } ]) document.addEventListener('DOMContentLoaded', async () => const handler = await router.resolve(location.pathname) handler() 11 path-to-regexp 記 法で解決 ここで dynamic-import location.pathname を渡して解決
• universal-router は action() の返り値が undefined | null だと、Not found
と見なす • しょうがないので、setup() の型は params => void ではなく、params => () => void にして いる ◦ params を受け取ってできた関数を router.resolve 後に実行 ◦ 関数じゃなくて JSX.Element を返すようにすれば、今後このルーターを使って SPAに することも可能ではある(大変だけど 38 注意する点
39 export const setup = (id: string) => () =>
{ // この中にページ固有の処理を記述 }
40 export const setup = (id: string) => () =>
{ // この中にページ固有の処理を記述 } 各ページはコレさえexport していれば中は何でもいい 中が ReactDOM.render だろうが、$(...).on だろうが、 new SomeWidget() だろうが関係ない 外からの interface を保ったまま React 化を進められる
41
• 導入はいたって簡単 ◦ import() が 構文エラーにならないような設定 をする ◦ ビルド済みの ファイルに名前がつくように
する ◦ import() するコードを書く ◦ おわり :) 42 dynamic import を入れるには
• Babel の場合 ◦ yarn add babel-plugin-syntax-dynamic-import ◦ babel-loader あたりに
↑ を読ませる • TypeScript の場合 ◦ tsconfig.json の module: を esnext にする ◦ es2015 とかになってるとできない 43 Syntax support for import()
• モジュール名に名前をつけるマジックコメント • import() 関数の場合、文字列が好きに渡せるので、読まれるファイル名が自明ではなくな る • 何も指定しないと、雑に 0.bundle.js のようなファイルが解決順に作られる
• マジックコメントを指定することで、たとえば my-util.bundle.js にできる 44 /* webpackChunkName: */
import UniversalRouter from 'universal-router' const router = new UniversalRouter([
{ path: 'books/orders/:id', async action ({ params }) => await import(/* webpackChunkName: “order” */ './pages/books/orders).then(..) } ]) document.addEventListener('DOMContentLoaded', async () => const handler = await router.resolve(location.pathname) handler() 11 これで名前を指定する
• webpack のバージョンが古すぎて(2.2)、webpackChunkName が使えなかった • バージョンを上げるには webpacker が邪魔だった • webpacker
を剥がした 46 pixivFACTORY の場合
47
• Q. 細かくバンドル切りまくるとモジュール読み込み回数増えない? • A. もちろん増える。HTTP/2 でない環境だと辛いかもしれない ◦ pixivFACTORY はすでに
HTTP/2 だった 48 一応留意する点
• 全ページルーターに載せきったわけじゃないので今後もやっていく • 各バンドルを軽くする(webpack 設定に改善の余地が...) • ページごとの不統一とかもなんとか( LP だけ jQuery
で他 Redux を目指す… ) • とはいえ戦える基盤が整った ◦ 俺たちの戦いはこれからだ 49 今後
• モジュールがでかいと大変なので切ると良い • dynamic import は簡単 • ルーターを入れるほうがプロジェクトによっては困難 ◦ とにかく
universal に寄せることで上手く着地できる 50 まとめ
Code splitting in Rails Frontend Turbolinks が dynamic import になるまで
pixiv Inc. f_subal 2018/09/04