logmi・ログミーTech

エンジニア向け勉強会の書き起こしメディア

ServiceWorker内でBabelを駆使して、JavaScriptをビルドする

ServiceWorker内でBabelを駆使して、JavaScriptをビルドする

2018年9月5日、第70回となる「HTML5とか勉強会」が開催されました。今回のテーマは「開発環境」。 Webフロントエンドの開発環境をテーマに、エディタプラクティスやServiceWorkerを開発ツールとして使うアプローチ、長期運用されたサービスのリニューアル方針など、登壇者たちがその知見を語ります。プレゼンテーション「ServiceWorker Side XXX」に登場したのは、mizchi氏。ServiceWorkerを駆使したある取り組みについて紹介します。講演資料はこちら

シリーズ
HTML5とか勉強会 > 第70回 HTML5とか勉強会「開発環境」 > ServiceWorker Side XXX
2018年9月5日のログ
スピーカー
mizchi 氏

開発環境のためにServiceWorkerを使う

mizchi氏(以下、mizchi) では「ServiceWorker Side XXX」ということで発表させていただきます。mizchiです。よろしくお願いします。 (会場拍手) ちょっと自己紹介とかはする気ないんですけど、最近本を書いたので、その紹介だけさせてください。 001 WEB+DB PRESS Vol.106』。最近、仮想DOM芸人みたいになってるんですけど、書いたのでよろしくお願いします。 最近趣味でnedi.appというエディタを作っていて。これはブラウザで独立して動くエディタで、Gitが動くんですけど、全部IndexedDBで動いています。 003 いろいろブラウザで完結してGitクローンからプッシュまでいけるというもので、Patreonでちょっとお金を集めたら月2万円ぐらいになってます。 これを作っててちょっといろいろ発展があって。開発環境のためにServiceWorkerをいろいろ酷使して、いろいろできそうだけど、いろいろできなかったという話をします。 ServiceWorkerって、今はこういうステータスなんです。 005 Opera Miniのことはみなさんたぶんどうでもいいと思っているので、一番左のIE11ですよね。これさえ無視すればもう動きます。ほぼ動きます。 実際使ったことある人? (会場挙手) 仕事で使ったことある人? (会場挙手) まぁ、意外といる感じですね。 ServiceWorkerとはなにかというと、ただのすごいローカルプロキシなんですよね。 007 クライアントがなぜかクライアント内でクライアントサーバモデルを取れるという。これは一番上が呼び出す例で、下がfetchを掴んでなにかログを吐くってだけの例です。 ServiceWorkerは開発者向けにはもう使ってOKだと思っています。 008 ただ、一般向けには2020年ぐらいにWindows 7が終わると、Windows 8……まぁWindows 10はEdgeを積んでいるので一応使えると言えるようになるので、ビジネス向けにはWindows 8も見ないといけないから2023年ぐらいなんですけど、今から作り始めるアプリはどうせリリースが2020に近い時期なので、もうやっていいと思います。

ServiceWorkerの2つの方針

ServiceWorkerにはいろいろな使い方はあるんですけど、2つ、僕が勝手に名前をつけたんですが、透過的なServiceWorkerと積極的なServiceWorkerの2つがあると思っています。 009 一番上のやつがよくPWAとかで使われるやつですよね。既存の振る舞いを拡張する。だから、キャッシュ構築したり、投機的先読みしたり、消極的にオフライン化するという感じです。 010 例えば、これは『dev.to』の記事引っ張ってきたんですが、マウスを乗せるだけでfrom ServiceWorkerとなって、裏側で引っ張ってきている。 011 投機的にキャッシュをためています。いろいろ先読みしたりして、裏側にキャッシュためておくみたいな。 あと、Google製のWorkboxというプラグインがあって、これがすごく便利です。 012 一応webpackのworkbox-webpack-plugin突っ込むだけでそれっぽくキャッシュレイヤーが挟まってくるので、すごく便利ですね。 これにWorkbox Strategyという概念があります。 013 Workboxではどういうキャッシュパターンがあるか、いろいろキャッシュパターンを選べるんですね。 1つがCacheFirstといって、とりあえずキャッシュ見にいってダメだったらネットワークから返すよとか。 014 これはおもしろいですね。StaleWhileRevalidate。 015 とりあえずいったんキャッシュを返しておくんだけど、裏側でネットワークのデータを返して裏でアップデートしておく。次見たら更新するというやつです。 表向き透過なだけでもいろんなキャッシュパターンがあって。全部トレードオフがあって、わからないと嵌まるので気をつけてください。 さっきも言ったように、今すぐ使えます。レガシー環境では振る舞いが透過なので単に無視される。使えるブラウザではすごい賢くなる。プログレッシブなんですね。Progressive Web AppsのProgressive。

透過的PWAの弱点

ただ、これにはちょっと弱点があると僕は思っています。ServiceWorkerって、その他リソースは並列で初期化されます。sw.jsというよくServiceWorkerで名前つけられるやつと、なにかほかのアセットは実は並列で読み込まれるので、どこからキャッシュされるか実は自明じゃないんです。 ちょっと簡単な図を作りました。 019 sw.jsがreadyになるタイミングとfoo.jsとbar.jsを読んだ段階で、foo.jsがServiceWorker Readyになる前にキャッシュが返ってしまうと、これServiceWorker通らずに返ってきてしまいます。 ServiceWorker、たぶん普通に使うと全部こうなります。だから、ServiceWorker使う人はだいたい3回リロードするんですけど。1回目でServiceWorker変えて、2回目でキャッシュ捨てて有効化して、3回目で保証された状態で見るみたいな、すごいだるい感じになります。

透過的PWAの弱点への対応

これは対策のしようはあります。例えば、コメントアウトされているのが本来のscript src=”./main.js”だったら、開発環境だけではこのように書き換えてしまう。 021 navigator.serviceWorkerをregisterして、readyして、終わったらimportする。dynamic importですね。開発環境だけこうすればいいので、本番ではdeleteして上のやつを読むイメージです。 ほかにも、ServiceWorkerが更新するのも問題になるので、裏側でsetIntervalで、これはregistrationというオブジェクトが入るんですけど、registrationを毎秒更新する。 022 更新があったら、このcontrollerchangeが来るので、windowをreloadして無理やり入れ替える、みたいなことができなくはないです。 ただ、現実としてベストプラクティスとかはとくになくて。これらは僕が勝手にやっているだけで、ちょっと時代が早いという感じですね。よく壊れます。ポートが壊れたり、serviceWorker.readyが来なくなったりします。だから、chrome://serviceworker-internalsという開発者ツールで chromeの内部状態、内部のオブジェクトを無理やり見て破壊したりします。

積極的PWA

ここまでが透過的なんですけど、積極的PWAという概念があると思っていて。「それってServiceWorkerじゃないとそもそも動かない機能も、たくさん使ったらなんでもできるじゃん。だってただのクライアントサーバモデルのローカルプロキシでしょ?」ということですね。 この弱点はすごく明確で、モダンブラウザでしか動かないということと、ServiceWorkerが初期化されていないとなにもかも見れないので、SEOは完全に壊滅します。 ただ、使い道はあると思っていて。例えばサーバサイドで、今回webpack疲れというのが話あったじゃないですか。webpackをインストールせずにBabelとかTypeScriptをサーバサイドでコンパイルしたりとか、Express serverをそのままServiceWorkerに放り込んじゃってそこで動かすとか、できると思います。 028 例えばServiceWorkerインストール後に、これは全部壊れるのでやっちゃいけないコードなんですけど、fetchですべてのオブジェクトが「event.respondWith(new Response(“console.log(‘hello world’)”))」ってやると、index.htmlですらこれになるので、2回目のリロードから壊れるのですが、まぁHello Worldにできます。 029 あと、これはBabelでawait fetchでevent.requestを、とりあえずサーバサイドに取りにいってオリジナルソースを取ってきて、それをbabel.transformで変換して返すということができますね。 030

ServiceWorkerでWebpackをエミュレートする

「だったらServiceWorkerでWebpackのエミュレーションすればいいんじゃないかな?」ってちょっと思って。なんでかというと、WebpackってES Modulesのエミュレータ+αなんだけど、でも、プラスαのところがいま重点にされてしまっています。 032 例えば、JSXとか絶対に将来にわたってサポートされない……まぁほぼサポートされないはずなので、なんだかんだでそれに近い機能が必要と考えると、将来的にも捨てられない可能性が高いんだったら、ServiceWorkerでWebpackをエミュレートすればいいんじゃないか? 
やってみました。こういうモジュールを作ったので……ええと、見ておいてください。
034 (会場笑) 先にデモしますが、これが動いているやつです。 035 ネットワークがどうなっているかというとjspm.ioというCDNがあって、そこからすべてReactのソースをその場で全部構築して。これReact Reduxが動いています。Babelの変換もこの場でやっています。 例えばこういうコードが動いています。 036 これはなにかというと、import React 、ReactDOM、App “./components/App”、ReactDOM.render。 これはWeb標準では動かないんですけど、これを無理やり動くようにするためには、まずはBabelでコンパイルしてnpmのモジュールをCDNの、このモジュールパスを書き換えたCDNに指し直す。拡張子がさらにApp.jsみたいに省略されてるのを復元します。これ「.js」「 .ts」「/index」とか全部探索して取れたやつを返してるんですけど。あとは、外部モジュールの相対パスの解決をしたり。 ES Modulesのimportを通る際はServiceWorker.onfetchという機構を通って変換されるので、それを変換できるというやつです。外部ライブラリのコードはバージョンごとに変わらないはずなので、それをServiceWorkerでキャッシュしてしまえばいいと。 037 やってみました。そして、動いた。React Reduxまで動いたんですけど、実際にはいろいろ問題はあって。 038 これはnpmモジュールのCommonJSからES Modulesに無理やり変換してる。通常の逆をしないといけないんですね。 webpackで、npmのモジュールはCommonJSなんですけど、CommonJSをES Modulesに再変換しないといけないという処理をやっているんですが、やっぱり手がかりが少なくて、しかも可逆じゃないので、やってみたけどできなかった。 じゃあES Modulesのソースを直接参照できればいいじゃんと思ったんだけど、そもそもES Modulesのソースをそのまま公開してくれているライブラリなんて少数で、あんまりダメ。できそうにない。あと、そもそも今回使ったCDN、jspm.ioというCDNが頻繁に落ちているという。Jspm.ioはnpmのモジュールをESMに変換してくれているというやつですね。 上のコードは動くんですけど、下のコードが動かなくて。 039 module.exportsをESMのexport defaultとして返してくれるんですけど、それ以外のエクスポートされたオブジェクトは取れないということになっちゃいました。 なので、可逆なコンパイラを自分で書くしかないなと思ってるんですけど。本当に使いたければ。 040 みんながrollupを使えばES Modulesも提供することになるので動くんじゃないですかね。

ServiceWorkerを使ったアイデア

あと、ほかにもいろいろできると思っていて。例えばExpressのServiceWorker。 042 これはモックサーバで使えると思っていて、一部のルーティング、まぁ、モックを作るのにほしいのはルーティング処理で。APIエンドポイントだけモックして、なにかJSON、適当なオブジェクトをモックサーバで返すとか。 あと、そもそもサーバでServiceWorker動かすみたいな。 043 これはなんとなぜかCloudflareが実装しているんですね。Cloudflare Workersといって、CDNでServiceWorkerを実装して、それを返す。 これは公式のコードで、ServiceWorkerのあのfetchでrespondWith(fetchAndApply)みたいな感じ。 044 これはなんでServiceWorkerなんだろうって、いまいちよくわからないです。 あとはServiceWorkerでServer Side Renderingする。 045 もうサーバなのかなんだかよくわからないですけど。 (会場笑) まぁ、できます。ServiceWorkerでindex.htmlを構築して返す……これSEOにはなんの役にも立たないんですけど、First Meaningful Paintを最適化したければ、サーバサイドの中でReactDOM.renderToStringして、initialStateもembedして実行しちゃえば動きます。 ある程度いろいろ考えてみたんですけど、いろいろ変えたら開発ツールには使える。現状はまだ早いので、みなさんがんばってこれ開拓してください。僕はちょっと疲れました。 (会場笑) あとはある種の開発者ツール。クライアントでBabelを動かす必要があるような開発者ツールとか、そういうときにServiceWorker Side Babelみたいものが便利かなという気がします。 まとめは、IEが死んだあとに動的ローダーとして使えるので。本当はIEのためにこれを利用したいのですが、IEじゃ動かないんですね。なので、デッドロックしてるんですけど、まぁIEは死ぬんで忘れましょう。 今のうちに経験値貯めておけば使えるように、2020年ぐらいにちょっと使えると思います。以上、お疲れ様でした。 (会場拍手)

  
この話をシェアしよう!
シェア ツイート はてブ
ServiceWorker内でBabelを駆使して、JavaScriptをビルドする

話題のログ

編集部のオススメ

人気ログランキング

TOPへ戻る