$30 off During Our Annual Pro Sale. View Details »
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Turbolinks が dynamic import になるまで / Code splitt...
fsubal
September 05, 2018
Programming
3
3.9k
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
負債になりにくいCSSをデザイナとつくるには?
fsubal
11
3.4k
Webデザインと フロントエンド技術🔰勉強会
fsubal
2
310
Tailwind CSSを本気でカスタマイズする方法
fsubal
17
7.6k
デザインシステムで Tailwind CSSとCSS in JSに分散投資をしたら良かった話
fsubal
20
7.3k
『Tailwind CSS実践入門』 出版記念基調講演
fsubal
9
5.3k
Sprockets CSSもやめる なぜ / Why stop using Sprockets for CSS too
fsubal
3
1.6k
The Majestic MPA
fsubal
8
3.3k
Backbone.Model に 型をつけて剥がす - Typing to destroy Backbone.Model
fsubal
1
1.1k
SVG + React でつくる レイヤーの自由変形 / Layer Transformation with React + SVG
fsubal
1
9.5k
Other Decks in Programming
See All in Programming
社内オペレーション改善のためのTypeScript / TSKaigi Hokuriku 2025
dachi023
1
140
しっかり学ぶ java.lang.*
nagise
1
460
WebRTC と Rust と8K 60fps
tnoho
2
1.5k
配送計画の均等化機能を提供する取り組みについて(⽩⾦鉱業 Meetup Vol.21@六本⽊(数理最適化編))
izu_nori
0
110
AIコードレビューがチームの"文脈"を 読めるようになるまで
marutaku
0
240
React Native New Architecture 移行実践報告
taminif
1
120
関数の挙動書き換える
takatofukui
4
760
Media Capture and Streams: W3C仕様と現場での知見
nowaki28
0
110
GeistFabrik and AI-augmented software development
adewale
PRO
0
220
【レイトレ合宿11】kagayaki_v4
runningoutrate
0
170
ViewファーストなRailsアプリ開発のたのしさ
sugiwe
0
230
Microservices Platforms: When Team Topologies Meets Microservices Patterns
cer
PRO
1
730
Featured
See All Featured
GraphQLの誤解/rethinking-graphql
sonatard
73
11k
Visualization
eitanlees
150
16k
GitHub's CSS Performance
jonrohan
1032
470k
Java REST API Framework Comparison - PWX 2021
mraible
34
9k
The Language of Interfaces
destraynor
162
25k
GraphQLとの向き合い方2022年版
quramy
49
14k
Reflections from 52 weeks, 52 projects
jeffersonlam
355
21k
YesSQL, Process and Tooling at Scale
rocio
174
15k
Leading Effective Engineering Teams in the AI Era
addyosmani
8
1.2k
Rebuilding a faster, lazier Slack
samanthasiow
84
9.3k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
234
17k
Embracing the Ebb and Flow
colly
88
4.9k
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