見出し画像

【IT】SvelteKitのアプリをPWA化の対応ついて(第2回目:SvelteKitの設定編)

皆さま
こんにちは

本日は、作成したWebアプリをPWA対応します。

今回の環境は、
PC:MacBook Air(M1)
フレームワーク:SvelteKit V5

本記事は、SvelteKitの設定編となります。

・第1回目:事前準備編

・第3回目:動作確認(ローカル、ディプロイ)編


3.SvelteKitの設定

a.  パッケージインストール(vite-plugin-pwa)

以下のコマンドでパッケージをインストールします。
(脆弱性もありますが、今回は、リスクLowですので対応は見送ります。)

$ npm install vite-plugin-pwa --save-dev

npm warn deprecated inflight@1.0.6: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
npm warn deprecated glob@7.2.3: Glob versions prior to v9 are no longer supported
npm warn deprecated sourcemap-codec@1.4.8: Please use @jridgewell/sourcemap-codec instead

added 383 packages, and audited 461 packages in 9s

111 packages are looking for funding
  run `npm fund` for details

3 low severity vulnerabilities

To address all issues (including breaking changes), run:
  npm audit fix --force

Run `npm audit` for details.

b. 必要なアダプターのインストール(@sveltejs/adapter-vercel)

以下のコマンドで必要アダプターインストールします。
buildフォルダー指定で使用します。
※今回は、Vercelを利用しますので@sveltejs/adapter-vercelを利用します。
 他のところを利用の場合(static)は、@sveltejs/adapter-staticとなります。
 各ベンダーで用意されているものがあればそちらを使用ください)

$ npm install -D @sveltejs/adapter-vercel

up to date, audited 518 packages in 1s

123 packages are looking for funding
  run `npm fund` for details

5 low severity vulnerabilities

To address all issues (including breaking changes), run:
  npm audit fix --force

Run `npm audit` for details.

c. `vite.config.ts` に PWA プラグインを追加

プロジェクト直下の`vite.config.ts`に設定を追加します。
※workbox: {~}は、電波状況が悪い時にしようするキャッシュの設定となります。今回は、電波状況が悪いことを想定して入れてあります。

// Project-Dir/vite.config.ts

import tailwindcss from '@tailwindcss/vite';
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
import { VitePWA } from 'vite-plugin-pwa';

export default defineConfig({
  plugins: [
    tailwindcss(),
    sveltekit(),
    VitePWA({
      strategies: 'generateSW',
      registerType: 'autoUpdate',
      injectRegister: 'script',
      devOptions: {
        enabled: true
      },
      includeAssets: [
        'icons/icon-192.png',
        'icons/icon-512.png',
        'screenshots/screen1.png',
        'screenshots/screen2.png'
      ],
      manifest: {
        name: 'アプリ 使用記録',
        short_name: 'appname',
        start_url: '/',
        display: 'standalone',
        background_color: '#ffffff',
        theme_color: '#0f766e',
        lang: 'ja',
        icons: [
          {
            src: '/icons/icon-192.png',
            sizes: '192x192',
            type: 'image/png'
          },
          {
            src: '/icons/icon-512.png',
            sizes: '512x512',
            type: 'image/png'
          }
        ],
        screenshots: [
          {
            src: '/screenshots/screen1.png',
            sizes: '540x720',
            type: 'image/png',
            form_factor: 'wide'
          },
          {
            src: '/screenshots/screen2.png',
            sizes: '1280x720',
            type: 'image/png',
            form_factor: 'narrow'
          }
        ]
      },
      workbox: {
        globPatterns: ['**/*.{js,css,ico,png,svg,json,webmanifest}'],
        navigateFallback: undefined,
        runtimeCaching: [
          {
            urlPattern: /^\/$/,
            handler: 'NetworkFirst'
          },
          {
            urlPattern: /\.(?:png|jpg|jpeg|svg|webp|gif|ico)$/,
            handler: 'CacheFirst'
          }
        ]
      }
    })
  ],
  build: {
    cssCodeSplit: true,
    rollupOptions: {
      output: {
        manualChunks(id) {
          // node_modules以下はまとめて 'vendor' chunk に
          if (id.includes('node_modules')) {
            return 'vendor';
          }
          // Tailwind や Chart.js のスタイルは 'libs' chunk に
          if (id.includes('tailwind') || id.includes('chart.js')) {
            return 'libs';
          }
          // その他はデフォルトの分割に任せる
        }
      }
    }
  },
  esbuild: {
    logOverride: { 'this-is-undefined-in-esm': 'silent' }
  }
});


d. `src/app.html` にメタタグを追加 

src/app.htmlにメタタグを追加します。
※favicon.icoは、favicon.svgを元に作成してstaticフォルダーへ配置ください。

<!-- src/app.html --> 

<link rel="manifest" href="/manifest.webmanifest" />
<meta name="theme-color" content="#ffffff" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" /> 
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<!-- icoで旧ブラウザ・fallback対応 -->
<link rel="icon" href="/favicon.ico" type="image/x-icon" />

<ご参考>favicon.icoの作成方法

$ magick favicon.svg -define icon:auto-resize=64,48,32,16 favicon.ico

$ identify favicon.ico
favicon.ico[0] ICO 64x64 64x64+0+0 8-bit sRGB 0.000u 0:00.002
favicon.ico[1] ICO 48x48 48x48+0+0 8-bit sRGB 0.000u 0:00.001
favicon.ico[2] ICO 32x32 32x32+0+0 8-bit sRGB 0.000u 0:00.000
favicon.ico[3] ICO 16x16 16x16+0+0 8-bit sRGB 32038B 0.000u 0:00.000

e. インストールボタン表示(任意)

src/+layout.svelteにインストール用のボタンを追加します。
Android用となります。
iphoneは、共用ボタンから「ホーム画面に追加」を選択してアイコンを作成ください。

// src/+layout.svelte

<script lang="ts">
	import '../app.css';
	import { onMount } from 'svelte';

	let { children } = $props();

	const state = $state({
		deferredPrompt: null as Event | null,
		showInstallButton: false,
		isMobile: false
	});

	// onMount でSW登録とPWAインストール処理
	onMount(async () => {
		if (typeof window !== 'undefined') {
			// ✅ Service Worker 登録
			const { registerSW } = await import('virtual:pwa-register');
			registerSW({
				immediate: true,
				onNeedRefresh() {
					console.log('🔄 新しいバージョンがあります');
				},
				onOfflineReady() {
					console.log('✅ オフライン使用の準備ができました');
				}
			});

			// ✅ モバイル判定(タブレット含む)
			const ua = navigator.userAgent.toLowerCase();
			state.isMobile = /android|iphone|ipad|ipod/.test(ua);

			// ✅ Android/PC の PWA インストールイベント
			window.addEventListener('beforeinstallprompt', (e) => {
				if (!state.isMobile) return; // モバイル以外は無視
				e.preventDefault();
				state.deferredPrompt = e;
				state.showInstallButton = true;
			});
		}
	});

	function installApp() {
		if (state.deferredPrompt) {
			(state.deferredPrompt as any).prompt();
			(state.deferredPrompt as any).userChoice.then((choiceResult: any) => {
				if (choiceResult.outcome === 'accepted') {
					console.log('[PWA] インストール完了');
				} else {
					console.log('[PWA] ユーザーがキャンセル');
				}
				state.deferredPrompt = null;
				state.showInstallButton = false;
			});
		}
	}
</script>

<!-- ✅ Android モバイルのみ -->
{#if state.showInstallButton}
	<div class="fixed bottom-4 left-0 right-0 flex justify-center z-50">
		<button
			onclick={installApp}
			class="bg-teal-600 text-white px-6 py-2 rounded shadow-md hover:bg-teal-700 transition"
		>
			このアプリをインストール
		</button>
	</div>
{/if}

{@render children()}

virtual:pwa-registerで警告が出ますので抑止します。

src配下にvirtual-pwa-register.d.tsを配置します。

// src/virtual-pwa-register.d.ts

declare module 'virtual:pwa-register' {
  export function registerSW(options?: {
    immediate?: boolean;
    onNeedRefresh?: () => void;
    onOfflineReady?: () => void;
  }): void;
}

f. ビルド設定

ブロジェクトフォルダー配下の`svelte.config.js`にアダブターの設定をします。(Vercel)

// Project-Dir/svelte.config.js

//import adapter from '@sveltejs/adapter-auto';    //⬅️コメントアウト
import adapter from '@sveltejs/adapter-vercel';    //⬅️追加
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';

/** @type {import('@sveltejs/kit').Config} */
const config = {
	// Consult https://svelte.dev/docs/kit/integrations
	// for more information about preprocessors
	preprocess: vitePreprocess(),

	kit: {
		// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
		// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
		// See https://svelte.dev/docs/kit/adapters for more information about adapters.
    adapter: adapter(),
	},
};

export default config;



続きは次回へ
では

いいなと思ったら応援しよう!