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.6k
デザインシステムで Tailwind CSSとCSS in JSに分散投資をしたら良かった話
fsubal
18
5.1k
『Tailwind CSS実践入門』 出版記念基調講演
fsubal
7
4.3k
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
Domain-Driven Transformation
hschwentner
2
1.5k
JavaScript Closure
asoluka
0
700
Exploring the Implementation of “t.Run”, “t.Parallel”, and “t.Cleanup”
akarin
1
140
TCAとKMPを用いた新規動画配信アプリ 「ABEMA Live」の設計
tomu28
2
130
Polars入門
daikikatsuragawa
1
190
Micro Frontends for Java Microservices - Utah JUG 2024
mraible
PRO
1
110
Apache Hive 4 on Treasure Data
ryukobayashi
1
450
PostmanでAPIの動作確認が楽になった話
h455h1
0
190
Amazon SQSコンシューマー疎結合への旅 - 出張! #DevelopersIO IT技術ブログの中の人が語る勉強会 #3
quiver
0
340
敵対的ポイフル
futabato
0
150
Implementing Design Systems in Swift
seyfoyun
2
490
Try creating your own orderedmap
kazamori
1
260
Featured
See All Featured
Why You Should Never Use an ORM
jnunemaker
PRO
51
8.7k
Clear Off the Table
cherdarchuk
85
310k
Design by the Numbers
sachag
274
18k
We Have a Design System, Now What?
morganepeng
43
6.8k
Principles of Awesome APIs and How to Build Them.
keavy
121
16k
How STYLIGHT went responsive
nonsquared
92
4.8k
Building Adaptive Systems
keathley
32
1.9k
Web Components: a chance to create the future
zenorocha
306
41k
Building a Scalable Design System with Sketch
lauravandoore
457
32k
Designing for Performance
lara
601
67k
Art, The Web, and Tiny UX
lynnandtonic
290
19k
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
221
21k
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