thumbnail
thumbnail

SvelteKitをPWA化した

updated 2021-9-15

serviceWorker

src/service-worker.tsを作成して

import { build, files, timestamp } from "$service-worker";

const worker = (self as unknown) as ServiceWorkerGlobalScope;
const FILES = `cache${timestamp}`;

const to_cache = build.concat(files);
const staticAssets = new Set(to_cache);

worker.addEventListener("install", (event) => {
    event.waitUntil(
        caches
            .open(FILES)
            .then((cache) => cache.addAll(to_cache).then(() => {
                worker.skipWaiting();
            })
        )
    );
});

worker.addEventListener("activate", (event) => {
    event.waitUntil(
        caches.keys().then(async (keys) => {
            for (const key of keys) {
                if (key !== FILES) await caches.delete(key);
            }

            worker.clients.claim();
        })
    );
});

async function fetchAndCache(req: Request) {
    const cache = await caches.open(`offline${timestamp}`);

    try {
        const res = await fetch(req);
        cache.put(req, res.clone());
        return res;
    } catch (err) {
        const res = await cache.match(req);
        if (res) return res;

        throw err;
    }
}

worker.addEventListener("fetch", (event) => {
    if (event.request.method !== "GET" || event.request.headers.has("range")) return;

    const url = new URL(event.request.url);

    const isHTTP = url.protocol.startsWith("http");
    const isDevServerRequest = 
        url.hostname === worker.location.hostname && url.port !== worker.location.port;
    const isStaticAsset = url.host === worker.location.host && staticAssets.has(url.pathname);
    const skipBecauseUncached = event.request.cache === "only-if-cached" && !isStaticAsset;

    if (isHTTP && !isDevServerRequest && !skipBecauseUncached) {
        event.respondWith(
            (async () => {
                const cachedAsset = isStaticAsset && (await caches.match(event.request));

                return cachedAsset || fetchAndCache(event.request);
            })()
        );
    }
});

worker.addEventListener("message", (event) => {
    const replyPort = event.ports[0];
    const message = event.data;
    if (replyPort && message && message.type === "SKIP_WAITING") {
        event.waitUntil(
            worker
                .skipWaiting()
                .then(
                    () => event.ports[0].postMessage({ error: null }),
                    (error) => event.ports[0].postMessage({ error })
                )
        );
    }
});

manifest.json

static/manifest.jsonを作成して

{
    "manifest_version": 2,
    "name": "Signpost",
    "short_name": "Signpost",
    "start_url": ".",
    "icons": [
        {
            "src": "/path/to/icon-512.png",
            "size": "512x512",
            "type": "image/png"
        },
        {
            "src": "/path/to/icon-maskable.png",
            "size": "512x512",
            "type": "image/png",
            "purpose": "any maskable"
        }

    ],
    "developer": {
        "name": "meko",
        "url": "https://mekos.site"
    },
    "homepage_url": "https://mekos.site",
    "description": "Articles about Tech & Design & Game",
    "default_locale": "ja",
    "theme": {
        "headerUrl": {
            "images": "https://mekos.site/header.png"
        },
        "colors": {
            "accentxolor": "#935ac1",
            "textcolor": "#333"
        }
    },
    "thene_color": "#354962",
    "background_color": "#cdc0a2",
    "display": "fullscreen"
}

urlがhostから書いてあるものは自分のサイトのアドレスに置き換えてください
テストサイトの時にurlに本サイトのアドレスを書いていたのでエラーが出ました...

display

fullscreenstandaloneがあります
アドレスバーなどブラウザーの機能が必要ない場合はfullscreen
戻るボタンやリロードなどブラウザーの機能が必要な場合にはstandaloneを選びます

エラー

エラー1

error-reporting.js:215 Failed to register a ServiceWorker for scope ('http://localhost:3000/') with script ('http://localhost:3000/'): The script has an unsupported MIME type ('text/html').

原因と解決

<script type="text/javascript">if('serviceWorker' in navigator) {
    navigator.serviceWorker.register('');
  };
</script>

AMPの設定をした時に消すのを忘れていた。

<script type="text/javascript">if('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/service-worker.js');
  };
</script>

としてもよい
記述しなければSveltekitの方で同様の処理を書いてくれているので、必要ない

Is not configured for a custom splash screen

原因と解決

manifest.jsoniconsで画像が正しく設定されてない時に出るエラー
512px以上のアイコンを設定して、画像を用意してあげれば解決する

{
    ...
    "icons": [
        {
            "src": "/path/to/icon-maskable.png",
            "size": "512x512",
            "type": "image/png"
        }
    ]
}

Manifest doesn't have a maskable icon size

原因と解決

{
    ...
    "icons": [
        {
            "src": "/path/to/icon-512.png",
            "size": "512x512",
            "type": "image/png",
            "purpose": "any maskable"
        }
    ]
}

Does not register a service worker that controls page and start_url

原因と解決

{
    ...
    "start_url": "/"
}

Web app manifest or service worker do not meet the installability requirements

原因と解決

htmlにサービスワーカーのファイルを登録できていない場合には<head>の間に下記を追加する

<script>
	if ('serviceWorker' in navigator) {
		navigator.serviceWorker.register('/service-worker.js');
	}
</script>

キャッシュするファイルが多すぎて100を超える場合にはChromeの拡張であるLighthouseでは認識されない
その場合は一旦ファイルを絞って動かしてみると良い