Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Turbolinks が dynamic import になるまで / Code splitt...
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
fsubal
September 05, 2018
Programming
4.1k
3
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Turbolinks が dynamic import になるまで / Code splitting in Rails Frontend
第70回 HTML5とか勉強会「開発環境」にて発表した資料です
https://html5j.connpass.com/event/96895/
fsubal
September 05, 2018
More Decks by fsubal
See All by fsubal
負債になりにくいCSSをデザイナとつくるには?
fsubal
11
3.7k
Webデザインと フロントエンド技術🔰勉強会
fsubal
2
370
Tailwind CSSを本気でカスタマイズする方法
fsubal
17
8k
デザインシステムで Tailwind CSSとCSS in JSに分散投資をしたら良かった話
fsubal
20
7.7k
『Tailwind CSS実践入門』 出版記念基調講演
fsubal
10
6k
Sprockets CSSもやめる なぜ / Why stop using Sprockets for CSS too
fsubal
3
1.7k
The Majestic MPA
fsubal
8
3.5k
Backbone.Model に 型をつけて剥がす - Typing to destroy Backbone.Model
fsubal
1
1.3k
SVG + React でつくる レイヤーの自由変形 / Layer Transformation with React + SVG
fsubal
1
10k
Other Decks in Programming
See All in Programming
AI Augmentation Techniques with Liberty
ivargrimstad
1
370
高橋メソッドでCVE解説シリーズ 〜CVE-2026-112816〜
hakkadaikon
0
220
DroidKnights 2026 Android A2UI Jetpack Compose 기반의 안전한 AI UI 렌더링 - 박제창
itsmedreamwalker
1
180
Crash Auto Pipelines
veronikapj
1
360
小さなアプリを作って公開するまで
onozaty
0
120
[JA] Domain Modeling with Vanilla Rails: 「操作」から再考するRailsの設計論
yasaichi
4
1.8k
AIがコードを書いてくれる時代、我々は何を確かめるのか
yosuke_furukawa
PRO
13
5.7k
React Native経験者が Flutterを30⽇間触って驚いたこと
3saito
0
150
Herb in Rails 8.2: Your ERB views, now HTML-aware @ Kaigi on Rails 2026, Tokyo, Japan
marcoroth
2
410
Enforcing Your Architecture with Unit Tests in the AI Era (Swift Connection X)
steliosf
PRO
1
140
全ての永続トークンを、生まれる前に消し去りたい!
sylph01
3
1.7k
学びを記事・スライド・Xへ高速展開する― AI-assisted Learning Loopで回すコンテンツ生成術
ochtum
0
190
Featured
See All Featured
Writing Fast Ruby
sferik
630
63k
Winning Ecommerce Organic Search in an AI Era - #searchnstuff2025
aleyda
2
2.2k
GitHub's CSS Performance
jonrohan
1033
470k
Sam Torres - BigQuery for SEOs
techseoconnect
PRO
0
610
Automating Front-end Workflow
addyosmani
1370
210k
Designing Experiences People Love
moore
143
24k
Neural Spatial Audio Processing for Sound Field Analysis and Control
skoyamalab
0
610
Imperfection Machines: The Place of Print at Facebook
scottboms
270
14k
Paper Plane (Part 1)
katiecoart
PRO
2
12k
The Limits of Empathy - UXLibs8
cassininazir
2
770
Reality Check: Gamification 10 Years Later
codingconduct
0
2.4k
技術選定の審美眼(2025年版) / Understanding the Spiral of Technologies 2025 edition
twada
PRO
121
120k
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