2024.12.19
システムの穴を運用でカバーしようとしてミス多発… バグが大量発生、決算が合わない状態から業務効率化を実現するまで
Turbolinksがdynamic importになるまで(全1記事)
リンクをコピー
記事をブックマーク
fsubal氏(以下、fsubal):では始めさせていただきます。ピクシブ株式会社から来ました。『Code splitting in Rails Frontend. Turbolinksがdynamic importになるまで』という題で発表します。
まず自己紹介なんですが、subal(スバル)と申します。
2016年にピクシブに新卒入社しました。そこからいろいろやってきたんですけれども、直近やってきたこととしては1月ごろにpixivの投稿画面のスマホ版を丸ごとリニューアルしたりとか。
5月から異動してpixivFACTORYというプロジェクトに行って、そこでフロントエンジニアをやっております。
TypeScriptとかReactとかVue.jsとかいろいろ書いていて、主に巨大なフォームの設計とかをやったりしています。
pixivFACTORYなんですけど、こんな感じで画像をアップロードするとブラウザ上でグッズが作れるみたいなサービスです。
こういう感じに使われるサービスのフロントをやっています。
最近やったこととして、ちょっと前にWebpackerを脱出するような記事を書いたんですけれども。今日はその話ではなくて、Code splittingとDynamic importの話を紹介させていただきたいと思います。
Code splittingって何でしたっけ? という話ですけど、JavaScriptなどのバンドルファイルを分割してビルドすることですね。
webpackなどのモジュールバンドラの機能として提供されるやつで、dynamic importによる遅延読み込みとかを伴うものです。native ES moduleとかありますけど、今日はその話はしません。
Dynamic importはご存知import()関数を用いてモジュールを遅延読み込みするやつで、必要なタイミングでモジュールをロードすることが可能になります。
実態としては<script>要素を入れて利用可能になったら.then()できるPromiseみたいなやつですね。
通常のJSやDSのimport文は上のような感じで静的に書くんですが、dynamic importを書いているとこういう感じで、import(〇〇).then(〇〇)で使うと、読み込んだモジュールが降ってきます。
使い方としてはSPAのページ遷移を表現したり、重いモジュールを後から遅延で読み込んだり、あるいは1枚の*.bundle.jsに全部入ってるみたいな状況をとにかくやめたい場合に使う。dynamic import自体はSPAじゃなくても有用です。今回はどちらかと言うとこちら( 1枚の*.bundle.jsに全部入ってる状況をやめる )話をします。
たとえばみなさんこういうお悩みはないでしょうか? 1個のファイルにこういう感じでimportがバーっとあって、webpackのバンドルをすると[big]って出てくる、みたいな。
トップレベルのJSファイルで全部importするみたいなことって結構ありがちな状況で、バンドルサイズが不要に大きくなるし、モジュールの境界が不明になって影響範囲も読みづらくなったり、けっこう苦しいことになります。
うちのプロジェクトのケースをご紹介したいと思います。Ruby on RailsのプロジェクトでTurbolinksに載っていてwebpackでビルドしています。TurbolinksというのはRailsについてくる、pjaxでページ遷移を表現するためのライブラリです。
その実装の都合上、JSファイルが全部1枚にバンドルされているほうが都合が良いらしくて。擬似SPAをするために、JSはそのままでHTMLだけがサーバーから降ってきて差し替える設計になっている。これのせいでバンドルファイルが全部1個に集まって大変なことになりがちで、変更の影響範囲を読むのも困難になります。
当時のpixivFACTORYのコードの紹介をしたいと思います。
document.addEventListerner('turbolinks:load' というのがTurbolinksの上でのベージ遷移を表すイベントですね。これでやってきたら、今いるページのlocation.pathnameを正規表現で判定して、実行するかを判定する。このせいで何度か事故っているというのもあったんですけど。そのうえでそういうやつらを全部importしてドカっとまとめるみたいな感じです。
この状況をどうすると良いか、いくつかあげてみましょう。まず各ページのJSファイルは、影響範囲の閉じたモジュールになってほしいですし、ページの判定は正規表現ではやらないほうがいいです。また、必要ないページでは余分なJSファイルを読まないようにもっていけるとだいぶ改善しますね。
それで、それぞれどうすればいいか具体的に挙げると、各JSのファイルを閉じるためには関数モジュールにリファクタリングするのがよい。ページの判定、今どこにいるかを正規表現でやらないためにはルーターを入れるとよい。
必要ないページでは余分なJSファイルを読み込まないようにしたい場合はルーティング解決時にdynamic importをすると良いっていう話になってきます。
というわけで、1個1個具体的にやったことを説明しましょう。
関数モジュールにリファクタリングするんですけど、それをやるためにはそもそもTurbolinksが邪魔なので一旦なくそうという話になります。
turbolinks:loadっていうイベントDOMは一旦ただのDOMContentLoadedに変えます。やると遷移は遅くなるんですが、一旦許容して進むって感じになりますね。
ですが、本当はどうしたいかと言うと、こういう形にしたいはずです。
先ほど関数モジュールと言ったのは、要するに各ページがこの形式になっている状態です。これを本当は目指したいと。
そのためにはルーターに載せるとよくて、各ページを関数モジュールにした場合それを受け取る層が必要だよね、という話になります。各ページを関数モジュールに変更し、ルーターがそれを受け取る設計にする。関数モジュール化を完遂するためにはルーターに載せる必要がある、という話になります。
ここでクライアントサイドルーターにあるあるの悩みなんですが、react-routerとかvue-routerなどが有名なんですけど、特定のビュー実装にくっついてなんか苦しい感じがあります。全ページReactに載せないと改善を進めることができないのでは? みたいな悩みを持ったりすることがあるかと思います。
仮に全ページ変更するとして、どうするかを見回したとき、アプリっぽい動きが激しいページはともかく、優先度としてLPとかFAQをReact化してもなぁみたいな問題があります。
見回してみると、jQueryのページとかReactのページとかいろいろ混ざっている。いろいろ混ざってるんだけど、とりあえず同じ土俵に乗らないんですか? という風に考えたくなります。
実際pixivFACTORYの事例をお話すると、jQueryのページとBackboneのページとReactのページとReduxのページとfluxbleのページがありました。これはちょっと死んでしまいますね(笑)。
それで、どうしようかな……となって見つけたのが、kriasoft/universal-routerというもので、これは特定のビュー実装に依存しないルーターです。
中を見るとUniversal JavaScriptガチ勢といった感じの作りをしていて、中はpath-to-regexpなのでだいたいexpressと同じですね。
ルーティング解決とミドルウェアをやる機能とURLをルーターから生成するだけみたいなすごくシンプルな機能を提供していて、たとえば他のルーターだと、SPAで使えるようにブラウザバックでスクロール位置を復元するといった機能があるんですけど、そういったものが一切ありません。
どういうことになるかと言うと、例えばこういう感じですね。
new UniversalRouterして、path設定と中にactionを書いて、これは1個しか書いてないですけど実際にはダーっといっぱい書いていきます。下のほうでDOMContentLoadedを書き、中でルーターのインスタンスにlocation.pathnameを渡して解決します。
これで正規表現をやめてpath-to-regexp記法にできました。
加えて、actionの中でさっき言ったdynamic importをやるようにすると、このページで必要になるpages/books/ordersだけが降ってきます。
1個注意する点として、universal-routerはaction()の返り値がnullとかundefinedだとNot foundと見なしてしまうらしく。しょうがないので、setup() はparamsをもらってvoidを返すのではなく、paramsを受け取って「voidを返す関数」を返す形にしました。それを実行します。
ところで、例えばこれをあとあと(関数の代わりに、)JSX.Elementを返すようにすれば、これを使ってSPAにすることもできるかもしれません。ちょっと大変ですけど。
setupがこういう形になると、各ページは中がjQueryだろうとReactだろうとなんだろうと、ただこれさえexportしていればよくなります。
ReactDOM.renderを実行したり、$(...).onを実行したり、new SomeWidget()とかを実行したりするでしょうが、外からのinterfaceを保ったままとりあえずReact化を推し進めていけてるので、とっても便利なんですね。
肝心のdynamic importってどうやって入れるんですか? という話ですが、いたって簡単です。
import関数が構文エラーにならないように設定し、ビルド済みのファイルに名前がつくようにし、import()をする関数を書くだけです。
構文エラーにしない方法はいくつかあるんですが、Babelの場合はプラグインを入れます。yarn add babel-plugin-syntax-dynamic-importとかをして、babel-loaderあたりにこいつを読ませましょう。
TypeScriptの場合はtsconfig.jsonに”module”っていう設定があって、これが”es2015”だと読めないけど、”esnext”とかにすると使えるようになります。
また、dynamic importのときによく使うもので /* webpackChunkName: */というマジックコメントがあります。
import を関数で行う場合、動的な文字列でも好きに渡せるようになります。結果、読まれるファイル名が自明ではなくなり、また何も指定しないと0.bundle.jsみたいな適当な番号のついたファイルが勝手に作られるような形になります。
そこで、決まったファイル名を与えるためにマジックコメントで指定するということをやります。こうするとorder.bundle.jsみたいな名前でwebpackが吐いてくれて、ファイル名がわかりやすくなります。
このwebpackChunkNameなんですが、webpack 2.4あたりから入ったんですけど。そもそもpixivFACTORYはwebpackのバージョンが古かったのでwebpackChunkNameが使えなくて。しかしバージョンを上げるためにはwebpacker(Rails の提供するwebpackのラッパー)が邪魔だったので剥がしました。
一応留意する点として、「細かくバンドル切るとモジュール読み込み回数が増えたりするんじゃないですか?」と言われるんですが、もちろん増えます。HTTP/2じゃない環境だとちょっと辛い可能性があって。pixivFACTORYはすでにHTTP/2になっていたのでとりあえずいけるだろうという感じでやってます。
今後なんですけど、全ページルーターに載せ切ったわけじゃないので今後も引き続きやっていくという感じになるのと、できあがったバンドルを軽くする、という目標はまだここからやっていきます。
また、ページごとにいろいろな実装が混ざっているのはそもそもよくないので、これもなんとかします。しかしルーター化をやったことで、とりあえず今後戦える基盤は整いました。「僕たちの戦いはこれからだ」という感じです。
まとめます。JSのモジュールはでかいと大変なので、適切に切ると良いです。dynamic importですが、導入は簡単です。ルーターを入れるほうがプロジェクトによっては困難ですが、とにかくuniversalに寄せることでうまく着地することができます。
以上です。ありがとうございます。
(会場拍手)
関連タグ:
2024.12.20
日本の約10倍がん患者が殺到し、病院はキャパオーバー ジャパンハートが描く医療の未来と、カンボジアに新病院を作る理由
2024.12.19
12万通りの「資格の組み合わせ」の中で厳選された60の項目 532の資格を持つ林雄次氏の新刊『資格のかけ算』の見所
2024.12.16
32歳で成績最下位から1年でトップ営業になれた理由 売るテクニックよりも大事な「あり方」
2023.03.21
民間宇宙開発で高まる「飛行機とロケットの衝突」の危機...どうやって回避する?
PR | 2024.12.20
モンスター化したExcelが、ある日突然崩壊 昭和のガス工事会社を生まれ変わらせた、起死回生のノーコード活用術
2024.12.12
会議で発言しやすくなる「心理的安全性」を高めるには ファシリテーションがうまい人の3つの条件
2024.12.18
「社長以外みんな儲かる給与設計」にした理由 経営者たちが語る、優秀な人材集め・会社を発展させるためのヒント
2024.12.17
面接で「後輩を指導できなさそう」と思われる人の伝え方 歳を重ねるほど重視される経験の「ノウハウ化」
2024.12.13
ファシリテーターは「しゃべらないほうがいい」理由 入山章栄氏が語る、心理的安全性の高い場を作るポイント
2024.12.10
メールのラリー回数でわかる「評価されない人」の特徴 職場での評価を下げる行動5選
Climbers Startup JAPAN EXPO 2024 - 秋 -
2024.11.20 - 2024.11.21
『主体的なキャリア形成』を考える~資格のかけ算について〜
2024.12.07 - 2024.12.07
Startup CTO of the year 2024
2024.11.19 - 2024.11.19
社員の力を引き出す経営戦略〜ひとり一人が自ら成長する組織づくり〜
2024.11.20 - 2024.11.20
「確率思考」で未来を見通す 事業を成功に導く意思決定 ~エビデンス・ベースド・マーケティング思考の調査分析で事業に有効な予測手法とは~
2024.11.05 - 2024.11.05