Edited at

結局ライブラリはCDNから読み込むのとnpm installして自前配信のどっちが良いのか?(意見も求む)


執筆の動機

JavaScriptやらTypeScriptやらでコードを書いている際に、利用する各種ライブラリをCDNで読み込むこともnpm installしてからwebpackで固めて自前配信することも両方できることが多いと思うのだが、どちらを選択すべきなのだろう?

昔はCDN最強説があったし、私もそれで納得していたが、ここ数年で状況が変わったように思う。とはいえ、人によって意見が異なる部分だと思うので、重要技術について触れながら私なりに考察する。


考慮する観点


  • ユーザーの実行速度(実行速度)

    ユーザーがWebページを訪問した際に、応答速度が遅いと逃げられてしまうだろう。お客さんがいないとサービスは成り立たない。重要な観点だ。


  • サーバーのデータ転送量(サーバー転送量)

    何でもかんでも自前配信だと通信量が増えてしまう。自前サーバーだと負荷が上がるし、クラウドだと料金が上がる。お金は大切だ。重要な観点だ。


  • 開発容易性(開発効率)

    npm installしておくと、手元にコードがあるので、source mapが作成できることからTypeScriptのデバッグがしやすい。また、型ファイルが利用できるので、TypeScriptだと非常に開発しやすい(型については型ファイルだけ読み込めば良いのだが)。そもそも開発できなかったらサービス公開どころではないし、修正やら機能追加できないサービスはイケてないので、お客さんが増えない。重要な観点だ。



CDN最強説と疑問


CDNを使えば、世界各国のエッジから、ユーザーにとって最もレイテンシの小さい箇所を選択し配信を行うことができるため、非常に高速。また、jQuery等他のサイトでも使用しているライブラリの読み込みを行う際には、ブラウザのキャッシュから読み込みが行われるため、さらに高速に実行できる。


疑問

レイテンシの小さい箇所に接続する部分については、数十ミリsecで最小レイテンシなエッジを選択できるのかという疑問がある。jQuery等の話については、バージョンも完全一致している必要があるので、そんなに当てはまるケースが多いのか少し疑問。


注目すべき技術


  • Tree Shaking

    ライブラリを利用しているとしても、そのすべての関数を参照せず、一部しか利用していない場合が多いと思う。WebpackにはTree Shakingという機能があり、使用する部分だけbundleに含めることができる。つまり、CDNで読み込むと200kB全部ロードすることになる場合も、webpackで固めて自前配信した場合には100kBしかロードしなくて済むということになるかもしれない。


  • Code Splitting

    そもそも最初のタイミングではすべてのモジュールをロードしておかなくても良いかもしれない。WebpackのCode Splittingを使えば、必要になったときにモジュールのロードが走るようにできるので、ユーザーの訪問タイミングで一括でロードすることによる処理のブロックを防ぐことができる。ちゃんと理解するCode SplittingCode Splittingが参考になった。


  • HTTP/2

    HTTP/2では、同一ドメインからの複数ファイルの取得の際にはHTTPリクエストおよびレスポンスが省略できる。CDNによる配信は、当然ウェブサイトと異なるドメインからの配信となるため、HTTP/2による高速化が効かず、HTTPリクエストおよびHTTPレスポンスのやり取りが発生する。また、CDNは、ドメイン名からIPアドレスを解決するDNS lookupが必要になる。自前配信の場合は、CDNと異なり、HTTPリクエストおよびレスポンスが省略され、DNS lookupも待たずに済むので、データの取得が高速に行われる。


  • Transpile + Polyfill

    CDNは、様々なブラウザで動作させるため、ES2015+ではなく古いES5で記述されているのではないかと思う。これらのコードは一般的にES2015+で記述したときよりもコード量が多く、低速になる。今の時代にES2015+対応してないのって、IE11くらいの認識なので、そんな化石みたいな少数ユーザーのために95%の先進的ユーザーに低速コードを使ってもらうなんて割に合わない。

    この現状に対し、Vue CLIはModern Modeというビルドオプションを用意していて、ES2015+をサポートしているブラウザに対してはES2015+で記述されたコードだけをインポートさせることができる。つまり、自前配信の場合は、CDNよりも高速に動作する、サイズの小さいJSを配信できるということになる。


  • Service Worker

    PWAなウェブサイトを作成してService Workerを登録した場合には、2度目以降の実行では自ドメインのファイルはローカルキャッシュから読み込ませることができる。これにより、HTTPリクエストを飛ばすことなくファイルの読み込みができるため、最低でも更新の有無をサーバーに確認に行く他方式と比較すると圧倒的に速い。


  • Firebase Hosting等による配信

    CDNは、世界各国から最適なエッジが選択されるので高速とのことだが、Firebase Hostingからウェブページを配信する場合は、CDNと同様に最適なエッジからコンテンツが配信される。なので、この点におけるCDNの優位はなくなるのではないだろうか。また、転送データ量増加によるサーバー負荷という観点についても、おそらくGoogle様がうまくやってくれているので、心配しなくて良いだろう(費用はちょっと増えるけど)。



CDN使わなかったときの良し悪し表

CDNを使わずに自前配信した場合の、CDN使った場合と比較した良し悪しは以下のようになるかと思う。

自前サーバー
(HTTP/1.1)
自前サーバー
(HTTP/2)
Firebase Hosting
自前サーバー
(HTTP/2)
+ Modern Mode
+ Tree Shaking
+ Code Splitting
+ Service Worker
Firebase Hosting
+ Modern Mode
+ Tree Shaking
+ Code Splitting
+ Service Worker

実行速度




サーバー転送量




開発効率





実行速度確認


CDN使ってFirebase Hostingで配信


  • Lighthouse(1/3)

    スクリーンショット 2019-05-27 11.42.22.png


  • Lighthouse(2/3)

    スクリーンショット 2019-05-27 11.42.29.png


  • Lighthouse(3/3)

    スクリーンショット 2019-05-27 11.42.35.png



Code Splitting、Tree Shaking、Modern ModeでFirebase Hostingで自前配信


  • Lighthouse(1/3)

    スクリーンショット 2019-05-27 17.15.29.png


  • Lighthouse(2/3)

    スクリーンショット 2019-05-27 17.16.11.png


  • Lighthouse(3/3)

    スクリーンショット 2019-05-27 17.16.49.png



考察

First Contentful Paintが平均3.3秒から平均2.1秒に有意に短くなっている。これは主に、CDNからCode Splittingによる動的読み込みに変えたことで、headerでのスクリプト読み込みがなくなったことが影響していると思われる。しかし、その他の要素については影響が見られない。処理の複雑性の増加が他の利点を打ち消したのだろうか。


終わりに

私の知っている範囲で、npm installして自前配信したときにCDNに対して優位となる技術をまとめた。少なくともCDN一強であった数年前とは状況が変わっているので、状況に応じて適切な方法を選択すべきだろう。

私の理解が間違っている部分や、知識の及んでいない部分もあると思うので、意見がある方はコメント欄にコメントを残していただけるとありがたい。


実験対象

実行速度確認は以下のウェブサイトで実施した。気になる方は負荷をかけすぎない程度に遊んでみてほしい。

https://itsrun.info/