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 splitt...
Search
fsubal
September 05, 2018
Programming
3
3.7k
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
Webデザインと フロントエンド技術🔰勉強会
fsubal
2
180
Tailwind CSSを本気でカスタマイズする方法
fsubal
17
6.9k
デザインシステムで Tailwind CSSとCSS in JSに分散投資をしたら良かった話
fsubal
19
6.1k
『Tailwind CSS実践入門』 出版記念基調講演
fsubal
8
4.9k
Sprockets CSSもやめる なぜ / Why stop using Sprockets for CSS too
fsubal
3
1.5k
The Majestic MPA
fsubal
8
3k
Backbone.Model に 型をつけて剥がす - Typing to destroy Backbone.Model
fsubal
1
970
SVG + React でつくる レイヤーの自由変形 / Layer Transformation with React + SVG
fsubal
1
8.8k
カリー化はナンの役に立つのか
fsubal
27
8.2k
Other Decks in Programming
See All in Programming
レガシーシステムにどう立ち向かうか 複雑さと理想と現実/vs-legacy
suzukihoge
15
2.3k
最新TCAキャッチアップ
0si43
0
210
Jakarta EE meets AI
ivargrimstad
0
360
EMになってからチームの成果を最大化するために取り組んだこと/ Maximize team performance as EM
nashiusagi
0
100
React CompilerとFine Grained Reactivityと宣言的UIのこれから / The next chapter of declarative UI
ssssota
5
650
romajip: 日本の住所CSVデータを活用した英語住所変換ライブラリを作った話
sangunkang
0
180
Ethereum_.pdf
nekomatu
0
470
アジャイルを支えるテストアーキテクチャ設計/Test Architecting for Agile
goyoki
9
3.3k
cmp.Or に感動した
otakakot
3
260
Functional Event Sourcing using Sekiban
tomohisa
0
110
OSSで起業してもうすぐ10年 / Open Source Conference 2024 Shimane
furukawayasuto
0
110
TypeScriptでライブラリとの依存を限定的にする方法
tutinoko
3
730
Featured
See All Featured
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
506
140k
Rails Girls Zürich Keynote
gr2m
94
13k
Making Projects Easy
brettharned
115
5.9k
VelocityConf: Rendering Performance Case Studies
addyosmani
325
24k
Building Flexible Design Systems
yeseniaperezcruz
327
38k
Imperfection Machines: The Place of Print at Facebook
scottboms
265
13k
Principles of Awesome APIs and How to Build Them.
keavy
126
17k
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
28
9.1k
Docker and Python
trallard
40
3.1k
Why Our Code Smells
bkeepers
PRO
334
57k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
44
2.2k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
280
13k
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