todo-add-alt

🌏 Hello World! Progressive Web-Blog!!

Web ComponentsでPWAなブログを作ってみた。[Loading編]

Feb 13, 2018

🔥 2022年にサイトの作りを変えまして、もう現状はこの記事の限りではありません。もしこちらの記事の実装を参照されるのであれば こちらのブランチ を参照ください。

これで何度目の挑戦でしょうか。記憶する限り、2年ぶり10度目くらいのブログでございます。会社のパイセンの@ymotongpooさんは12年もご自身のブログを継続されており、日々コンテンツとその作業に耐えうるだけの筋肉を増やし続けられているのを横目に、私はといえば過去に1年以上続いた書きものは日記も含めて一切なく、ブログはというと思い返してみれば2004年ごろのライブドアブログから始まり、辞めては乗り換え辞めては乗り換えを繰り返してきた挙句、最終的にはJekyllいじったあたりでさっぱり音信不通になったのが2年前。並びに、筋肉事情に関して申し上げると、最後にジムに行ったのも気づけば1年半が経っており、会員権が腐り始めている小太りクソ坊主になってしまいました。

実は書きたいことはたくさんあるのですが、ブログを再開するとなるとプラットフォーム選びに小一年ほどかかるわけです。「Mediumかっこいいなー」と思ってもすでに"uskay"というIDがとられていたりしてなかなか前に進みません。 なんて感じでまごついていたのですが、気づけば2018年になっており、これには流石に自分の実行力なさに絶望を感じたこともあって、今までの怠惰を払拭すべく心機一転。今回は(一番めんどくさくもある)「ブログを自作」というのをしてみました。ざっくり申し上げると、Progressive Web Appsなブログに仕立てております。いま時点はひとまずベータ版的な扱いでローンチ優先のツギハギものですが、今回当該プラットフォームを開発するに過程で意識した点を以下に記載しますので、何かのご縁でこのページまで辿り着かれた皆様におかれましては、当記事を今後のウェブ開発のヒントとなって消化して頂けますと幸甚に存じます(見せられる出来でもないのですが、現状のソースはこちらに晒しております)。

といいつつ書きたいことを全部書くと相当ボリューミーになるかと思われますので、複数記事に分けさせて頂きます。初回はLoading編ということでページ表示スピードの最適化について主に取り上げたいと思います。

そもそもProgressive Web Appsって?

こちらにも(ありがたいことに)記事にして頂いておりますが、Progressive Web AppsはPWAという略名で2015年後期ごろから流行っているWebフロントエンドのベスプラ集、かつそのベスプラをいい感じに適用したWebアプリの通称です。「最近インスタがPWA化したよね」的な語法で使われることが多いですが、念のためPWAのコンセプトを要約しますと、
  • とにかくパフォーマンスのいいサイトにしましょう。SPAでもSPAじゃなくても、どんなフレームワーク使っても使わなくても、もうなんでもいいです。なんでもいいから、表示が速くて画面遷移がスムーズなものにしてユーザーに好かれましょう。
  • 上記をチューニングしたら、今度は最近使えるようになってきたイケてるWeb APIを積極的に使ってよりよい体験をユーザーに提供しましょう。
  • イケてるWeb APIを使うと何ができるかというと、Webアプリをホーム画面に追加できるようになったり、オフラインでも動作するようになったり、プッシュ通知ができるようになったり、そんな今までWebでは実現できなかったアプリっぽいことができるので、やらない手はないでしょう?
といったものです。少し上記でも触れていますが、PWAはあくまでベスプラ集なので特定の3rd Partyフレームワークやライブラリには依存しません。世の中にはReactを使ったPWAもあるし、Polymerを駆使したPWAもあるし、PHPサーバーサイドページなPWAもあります。従って技術スタックを変えずとも適用可能なベスプラでありますし、一度に全部を適用するのではなく段階的に(=Progressively)最適化することもできます。

上記で言うところのイケてるWeb APIをより具体的に紹介すると、ServiceWorker、WebAppManifest、Push API/Notification API,Payment Request API、Credential Management APIがいわゆるビッグ5でスタメンとなります。とりわけ注目度が高い桜木花道は間違いなくServiceWorkerとなりますので、こちらについては多くの方がどこかで聞き覚えがあるものと思います。

ウェブボウズはどんな構成で作ったの?

ところで申し遅れましたが、当ブログ名は「ウェブボウズ」にしました。私はウェブが好きで、かつボウズ頭であることに由来しておりますが、気分がいいときはスキンヘッドにしておりますので「テクニカルハゲ」とどっちがいいかなと悩んでいたところ、妻からさすがに後者の案はひどいと諭され今に至ります。さて、「ウェブボウズ」はどのような技術スタックで成り立っているかというと、
  • フロントのメインはVanilla JavaScript (ES6) + Web Components
  • バックエンドはFirebase Hosting + Fireabase Cloud Functionsでサーバレス的なアレ
で構成されています。ちょうど絵にすると以下のような感じです。

todo-add-alt

恥ずかしながらUskayUIなるこのブログ専用のWeb Component群を作ってしまいました.. 後述しますが、記事自体はマークダウンで書けるようにつくっていて、<uskay-article>というコンポーネントがFirebase Hostingに置いてある.mdファイルをfetchしてきて、フロントエンドでマークダウンをパース・レンダリングしていきます。つまり、フロントエンドでJavaScriptを駆使して動的にコンテンツを構築していくタイプのページとなりますし、それでも如何に初回表示でもパフォーマンスよく、各種Botにもフレンドリーにするかがポイントとなってきます。

Web Components イケてるよ!

今回はなるべく3rd Partyフレームワークやライブラリは使わずに、Webプラットフォームの素材をそのまま活かすことを意識しています。ただ、そうはいっても最近のトレンドであるWebページ内の各要素郡(たとえばヘッダーとか)はコンポーネント化して一元管理したり再利用したりしたいものです。今でこそReactやVueJsなどコンポーネント指向な使いやすいライブラリが数多くありますが、古くはDojo toolkitでdeclareしてみたり、jQueryのプラグインなんか使ってみたり、その歴史は決して短くありません。そういったコンポーネントをWeb標準で作るための仕様がWeb Componentsとなります。Web ComponentsはCustom Elements、Shadow DOM、HTML Templatesという各Web APIから成り立っており、例えば(ものすごいシンプルな例ですが)以下のように簡単にコンポーネントを作れます(R.I.P HTML imports...)。

CODEPENで試す: Simple Custom Element

上記の通り、Custom ElementsはES6 Classesとして作るので他のコンポーネントの継承もできますし、DOM、内部のロジックおよびCSSはカプセル化されますので名前空間の衝突から開放されます。もう鼻血が出るくらいJavaScriptを構造化しやすくなりますので是非ともお試しください。

AMPを実装したことある方はご存知かと思いますが、<amp-img>や<amp-carousel>などといったAMPのコンポーネント達はまさにWeb Componentsです。AMPは、AMPHTMLライブラリが用意してあるCustom Elementsを再利用することで簡単にページがつくれちゃう、というフレームワークなのです。また、最近だとVueJsやAngularがWeb Componentsを吐き出す動きがたまらなくステキで、Web標準をちゃんとレバレッジしようぜ的機運を感じます。

Performance をあげるためには。

ということでウェブボウズはWeb Componentsだけでページを作っているのですが、Web ComponentsってJavaScriptを実行して初めてDOMが構築される代物ですので、ページ表示スピードの敵であるJavaScriptの読み込みをうまいこと工夫しないとパフォーマンス良くページが描画されません。そんな最適化が必要となる場合は、何かしらのデザインパターンを参考にするのが定石かと思いますが、最近はPRPL Pattern(パープルパターンと読みます)というものが流行っていますので、それに従ってチューニングを行っていきます。PRPLパターンはPush、Render、Pre-cache、Lazy-loadの頭文字を取ったものとなりまして、それぞれひとつひとつ見ていきたいとは思うのですが、文量の関係上、当記事ではページ表示スピード最適化に絞って主にPush/Renderについて見ていきたいと思います。

Push/Render

こちらは、Critical Path(=初期描画に必要なリソースたち)を最適化しようというもので「描画に必要なものはド頭で全部一気にとっていきたいのでH/2 Pushするか<link rel=preload>を使いましょう」といったものです。そもそもHTTP/1.1のままだとウェブボウズのCritical Path関連リソースは以下のように五月雨にダウンロードされてしまうわけです。少なくともJsは全部必要なんですけど、悲しいかな、五月雨に。こちらはChrome Developer ToolsのPerformanceタブで簡単に確認できます。

todo-add-alt

これをどのように最適化するかというと、
  • まずはH/2を有効にして一度に多くのリソースを取得できるようにしましょう。
  • 次にリソース取得の優先度を定義しましょう。
今回はお手軽にH/2 + <link rel=preload>を使用していますが、これだけで以下のようにCritical Path取得が最適化されます。Preloadされたリソースは取得優先順位が高まり、非同期でダウンロードされてメモリキャッシュに保存されます。

todo-add-alt

<link rel=preload>の宣言はこんな感じ。ちょっとcrossorigin属性についてはややこしい点もあるので、またの機会に記事化しようと思います。

また、ウェブボウズではタイトルにWeb Fontを使っているのですが、FOITを避けるためにCSS @font-face属性のひとつのfont-displayを使っています。現状font-display: swapとしていますので、Web Fontが使用可能になるまではシステムフォントを使って文字が表示されます。ユーザーに「読める」コンテンツをなるべく速く届けるために是非とも利用してみて下さい。


さらには、Web ComponentsをES6 Modulesを使って読み込んでいるので、<script type=module>なJavaScriptはすべてDeferされています。DeferしているのでJavaScriptがRender BlockすることなくHTMLのパースが行えてお作法としてはハッピーなのですが、そもそもHTMLにコンテンツを何も用意しないと最初は背景だけが表示されて、その後徐々にCustom Elementsが描画されるといった、レイアウトが動き回る残念な事象が発生します。これではちょっと恥ずかしい。いくらなんでも無防備するぎるので、スケルトンHTMLを用意しました。なんのことはない、HTMLにただインラインでダミーのヘッダーや記事を実装しているだけです。

todo-add-alt

ってな感じでPush/Renderの最適化する前と後を比較するとこんな感じです。分かりやすいようにNetworkとCPU Throttleをかけていますが、Afterのほうがだんぜん表示が気持ちいい...

todo-add-alt

Pre-cache

次にPRPLパターン2番目のP。Pre-cacheですが、これは次に画面遷移するであろうページ(Route)のリソースを先読みしておこうね、というものです。待ってました、主役!ServiceWorkerの登場です! ...ただ、こちらについては別記事で紹介させて頂きます。

Lazy-Load

Lazy-Loadは古くから伝わる手法ですが、ウェブボウズでは画像を全て<uskay-img>というWeb ComponentにWrapしており、内部的にIntersection Observerを利用してスクロールに応じた画像のLazy-Loadを実現しています。また、ページ上部の描画を優先するために下部のテキストも<uskay-article>内でLazy-Loadしていますし、ところどころ出てくるGistも非同期にShadow DOMに差し込めるように<uskay-gist>なるものも作りました。こちらもまた別記事で。

Lighthouseで測ってみた。

で結局どんなパフォーマンスなのよ、ということでLighthouseで測ってみるとこんな感じです。ちなみにLighthouseはChromeのチームが開発していますPerformance Auditツールで、今やWebサイトの最適化度合いを測るツールの中ではデファクト化しているものです。ChromeのDeveloper Toolから簡単に使えますので、まだ試されたことが無い方は是非ともお試しください。

todo-add-alt

悔しくも、Peformance 99点の阿部寛のサイトには負けますが、出だしとしては悪くない、といったところでしょうか。ちなみにLayoutするコンテンツ量も依存するJavaScriptもそれなりにある当ブログにおいて、Perfomanceを90点台後半まで伸ばすためには、これまたそれなりに頑張る必要がりました。ただ試行錯誤を繰り返しながら点数が徐々に伸びていくのは見ていて気持ちがいいですし、その過程で使った当記事のTipsが皆様にほんの少しでもお役に立てることがあれば幸いでございます。

まとめ

以上がLoadingに特化した開発メモとなります。お見苦しい点も多々あったかと存じますが、今回紹介できなかったServiceWorkerやWebAppManifestを始め、その他Bot用にはRendertronを使っていたり、Web Components Polyfillを使ってIE11対応したり、Web Share APIを利用したりしているので、またの機会に記事としてログらせて頂ければと思います。

Happy Tuesday!!😆


$whoami

Yusuke Utsunomiya (宇都宮 佑亮). Working on the Web Platform but mostly just a big fan of the web and its ecosystem. APAC Manager & Staff Partner Solutions Engineer @Google. Ex Systems Engineer @IBM. Opinions are my own.

Presentation

Article