Service Worker では、importScriptsを使うことでスクリプトをインポートできる。
ES Modules (以下、ESM)を使うこともできるが、ブラウザによってはまだ対応していない。Chrome ではバージョン 91 から利用できるようになった。
この記事では、これらの機能を使って Service Worker でスクリプトをインポートする方法を見ていく。
動作確認には Next.js と Google Chrome を使っている。それぞれのバージョンは以下の通り。
- Next.js 10.2.0
- Google Chrome 91.0.4472.77
ユーザーが初めてページにアクセスした状況を再現するために、シークレットウィンドウでページを開くようにしている。
Module Service Worker と Classic Service Worker
Service Worker は、Module Service Worker と Classic Service Worker に分類できる。
全ての Service Worker は必ずどちらかになる。
どちらであるかは、Service Worker の登録時に決まる。
registerで登録する際にtypeの値をmoduleにすると、Module Service Worker になる。
// Module Service Worker になる navigator.serviceWorker.register('/sw.js', {type: 'module'})
typeをclassicにした場合、あるいは設定を省略した場合は、Classic Service Worker になる。
// Classic Service Worker になる navigator.serviceWorker.register('/sw.js', {type: 'classic'}) // Classic Service Worker になる navigator.serviceWorker.register('/sw.js')
そしてどちらの Service Worker であるかで、利用できるインポート方法が変わる。
Module Service Worker では ESM を、Classic Service Worker ではimportScriptsを、利用できる。
利用できない方のインポートを使おうとすると、エラーになる。
ES Modules の使い方
まずは、Module Service Worker による ESM の使い方を見ていく。
以下の内容のpublic/sw.jsを用意する。
import {x} from './module.js'; console.log(x);
public/module.jsも用意する。
export const x = 1;
そして最後に、pages/index.jsを以下の内容にする。
import {useEffect} from 'react'; export default function Home() { useEffect(() => { navigator.serviceWorker.register('/sw.js', {type: 'module'}); }, []); return ( <> <p>Hello Service Worker!</p> </> ); }
この状態で$ yarn devを実行しhttp://localhost:3000/にアクセスする。
そうするとログに1が表示されるので、Service Worker の登録、そしてその Service Worker によるmodule.jsの読み込みが上手くいっていることが分かる。
Dynamic Import は使えず、使おうとするとエラーになる。
// TypeError: import() is disallowed on ServiceWorkerGlobalScope by the HTML specification. import('./module.js').then((res) => { console.log(res.x); });
importScripts の使い方
次に、importScriptsの使い方を見ていく。
ESM と異なり、「エクスポートした値をインポートする」というような挙動にはならない。
読み込んだスクリプトを実行し、名前空間が共有される。
HTML ファイルのなかでscriptタグを使ったときと同じような使用感になる。
そのため、public/module.jsの内容を以下のようにしてそれをimportScriptsで読み込んだ場合、Service Worker 側でfooやbarを使えるようになる。
const foo = 'foo'; function bar() { return 'bar'; }
public/sw.jsを書き換えて、確認してみる。
importScripts('./module.js');
console.log(foo);
console.log(bar);
console.log(bar());
最後にpages/index.jsを書き換えて、Classic Service Worker として登録されるようにする。
navigator.serviceWorker.register('/sw.js');
この状態でhttp://localhost:3000/にアクセスすると以下のログが流れ、module.jsと名前空間が共有されていること分かる。
foo
ƒ bar() {
return 'bar';
}
bar
名前空間が共有されているので、同じ名前の変数を定義してしまうと、エラーになる。
そのため以下のようにするとエラーになる。
// public/module.js const x = 1;
// public/sw.js importScripts('./module.js'); const x = 9;
ここまでの例ではトップレベルでimportScriptsを実行していたが、installイベントのなかで実行することもできる。
そのため以下の内容にすると、ページを表示してから約3秒後にThis is module.jsがログに流れ、そこからさらに約3秒後に、fooがログに流れる。
// public/module.js const foo = 'foo'; console.log('This is module.js');
// public/sw.js function sleep(ms) { const startTime = performance.now(); while (performance.now() - startTime < ms); } self.addEventListener('install', (e) => { e.waitUntil( new Promise((resolve) => { sleep(3000); importScripts('./module.js'); sleep(3000); console.log(foo); resolve(); }) ); });
installイベントよりも後にimportScriptsを実行するとエラーになる。
そのため、以下のコードはエラーになる。
self.addEventListener('activate', (e) => { e.waitUntil( new Promise((resolve) => { importScripts('./module.js'); resolve(); }) ); });
installイベントよりも後にimportScriptsでスクリプトを実行したい場合は、installイベント、あるいはトップレベルで、そのスクリプトを実行しておく必要がある。
以下のようにするとエラーにはならず、ページ表示時にThis is module.jsというログが 2 回流れる。
// public/module.js console.log('This is module.js');
// public/sw.js importScripts('./module.js'); self.addEventListener('activate', (e) => { e.waitUntil( new Promise((resolve) => { importScripts('./module.js'); resolve(); }) ); });
ただ、public/module.jsのなかで変数を定義していた場合、その変数の定義を 2 回実行することになりpublic/module.jsでエラーが発生するので、注意する。
Classic Service Worker のなかで Dynamic Import を使うことはできない。
// TypeError: import() is disallowed on ServiceWorkerGlobalScope by the HTML specification. import('./module.js').then((res) => { console.log(res.x); });
一方で、typeにmoduleを設定していないscript要素や Web Worker では、Dynamic Import を使える。
Service Worker だけ挙動が異なっているため注意する。
script要素での Dynamic Import については以下を参照。
Web Worker での Dynamic Import については以下を参照。
更新確認とインポート
更新確認の際の挙動は、ESM でもimportScriptsでも同じ。挙動が変わることはない。
updateViaCacheの設定によって挙動が変化し、更新があった場合は Service Worker スクリプト自体が改めて読み込まれる。
更新確認やupdateViaCacheの詳細は以下の記事に書いた。