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
の詳細は以下の記事に書いた。