Files
insta-recipe/docs/plans/MigrateToNativeSvelteKitPWA.md
Giancarmine Salucci 8f13cba320 feat: add plan for migrating to native SvelteKit PWA
- Comprehensive plan to migrate from @vite-pwa/sveltekit to native implementation
- 5 stories with clear dependencies and acceptance criteria
- Preserves all existing PWA, push notification, and share target functionality
- Uses SvelteKit's native service worker APIs and manual manifest.json
2025-12-22 05:26:09 +01:00

22 KiB

Execution Plan: Migrate to Native SvelteKit PWA

Objective: Migrate away from @vite-pwa/sveltekit plugin to native SvelteKit PWA implementation with dedicated manifest.json, while preserving all existing functionality including push notifications, share target, and offline capabilities.

Outcome: MigrateToNativeSvelteKitPWA


Current State Analysis

Current SvelteKitPWA Plugin Implementation

Plugin Configuration (vite.config.ts):

SvelteKitPWA({
    srcDir: './src',
    strategies: 'injectManifest',
    filename: 'service-worker.ts',
    scope: '/',
    base: '/',
    selfDestroying: process.env.SELF_DESTROYING_SW === 'true',
    injectRegister: process.env.NODE_ENV === 'test' ? false : 'auto',
    injectManifest: {
        swSrc: 'src/service-worker.ts',
        swDest: 'service-worker.js',
        injectionPoint: 'self.__WB_MANIFEST',
        globPatterns: ['**/*.{js,css,html,ico,png,svg,webp,woff,woff2}'],
        maximumFileSizeToCacheInBytes: 4 * 1024 * 1024,
    },
    manifest: {
        short_name: 'InstaChef',
        name: 'InstaChef Recipe Saver',
        start_url: '/',
        scope: '/',
        display: 'standalone',
        theme_color: "#ffffff",
        background_color: "#ffffff",
        icons: [
            { src: '/favicon.png', sizes: '192x192', type: 'image/png' },
            { src: '/favicon.png', sizes: '512x512', type: 'image/png' }
        ],
        share_target: {
            action: '/share',
            method: 'GET',
            enctype: 'application/x-www-form-urlencoded',
            params: { title: 'title', text: 'text', url: 'url' }
        }
    },
    workbox: {
        globPatterns: ['client/**/*.{js,css,ico,png,svg,webp,woff,woff2}'],
        cleanupOutdatedCaches: true,
        skipWaiting: false,
        clientsClaim: false,
        maximumFileSizeToCacheInBytes: 4 * 1024 * 1024,
        runtimeCaching: [{
            urlPattern: /^https:\/\/api\./,
            handler: 'NetworkFirst',
            options: {
                cacheName: 'api-cache',
                networkTimeoutSeconds: 10
            }
        }]
    },
    devOptions: {
        enabled: process.env.NODE_ENV !== 'test',
        suppressWarnings: true,
        navigateFallback: '/',
    }
})

Current Service Worker Implementation:

  • Uses workbox imports: cleanupOutdatedCaches, createHandlerBoundToURL, precacheAndRoute
  • Uses workbox routing: NavigationRoute, registerRoute
  • Relies on self.__WB_MANIFEST injection for precaching
  • Comprehensive push notification handling with custom actions
  • Background sync for retry operations
  • Message passing for client communication

SvelteKit Configuration Status:

// svelte.config.js - Currently DISABLED to avoid conflicts
serviceWorker: {
    register: false
}

Critical Functionality That Must Be Preserved

PWA Features:

  • PWA installation capability
  • Offline functionality with precaching
  • Share target for Instagram URLs to /share route
  • PWA manifest with proper icons and theme colors

Push Notification System:

  • Push notification subscription/unsubscription
  • Custom notification actions (view, retry, dismiss)
  • Notification click handlers with client navigation
  • Background sync for retry operations
  • Service worker to client message passing

Caching Strategy:

  • Static asset precaching
  • Navigation route caching with fallbacks
  • API cache with network-first strategy
  • Automatic cache cleanup for updates

Development Experience:

  • Service worker disabled in test environment
  • Proper development vs production behavior
  • Hot reloading compatibility

Target Architecture Analysis

Native SvelteKit PWA Approach

Key Differences from Plugin:

  1. Manifest Management: Manual static/manifest.json instead of vite.config.ts generation
  2. Service Worker APIs: SvelteKit's $service-worker module instead of workbox
  3. Caching Strategy: Manual cache management using build, files, version arrays
  4. Registration: SvelteKit's built-in registration instead of plugin registration

SvelteKit Service Worker Module:

import { build, files, version } from '$service-worker';

// build: array of built app files
// files: array of static files  
// version: deployment version string for cache naming

Benefits of Migration:

  • Remove external plugin dependency
  • Align with SvelteKit best practices and roadmap
  • More control over service worker behavior
  • Simplified build process
  • Better TypeScript integration
  • Reduced bundle size without workbox overhead

Cross-Reference Dependency Analysis

Direct Dependencies

  1. Service Worker Registration: src/lib/client/PushNotificationManager.ts expects service worker to be available
  2. Share Target: src/routes/share/+page.svelte relies on manifest share_target configuration
  3. Queue System: Background sync functionality for retry operations
  4. Notification Handlers: Client code sends messages to service worker via postMessage()

Hidden Dependencies

  1. Build Process: Vite plugin currently handles TypeScript compilation and manifest generation
  2. Development Server: Plugin provides development mode service worker behavior
  3. Cache Invalidation: Workbox handles cache versioning automatically
  4. Routing: NavigationRoute handling for SPA behavior offline

Integration Points

  1. Push Notification Flow: Client → Service Worker → Push Server → Notification Display
  2. Share Target Flow: External App → Manifest → /share Route → Queue API
  3. Cache Flow: Service Worker → Cache API → Static Assets/API Responses
  4. Offline Flow: Navigation Request → Service Worker → Cache → Response

Story Breakdown

Story 1: Create Native PWA Manifest

Priority: High
Dependencies: None
Estimated Effort: 2-3 hours

Objective: Extract PWA manifest configuration from vite.config.ts to a dedicated static/manifest.json file following W3C Web App Manifest specification.

Tasks:

  1. Create static/manifest.json with exact configuration from vite.config.ts
  2. Ensure share_target configuration is preserved exactly
  3. Add manifest link to src/app.html if not present
  4. Validate manifest.json against W3C specification
  5. Test manifest loading in browser
  6. Verify PWA installation prompt still appears

Technical Details:

File: static/manifest.json

{
  "short_name": "InstaChef",
  "name": "InstaChef Recipe Saver",
  "start_url": "/",
  "scope": "/",
  "display": "standalone",
  "theme_color": "#ffffff",
  "background_color": "#ffffff",
  "icons": [
    {
      "src": "/favicon.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/favicon.png", 
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "share_target": {
    "action": "/share",
    "method": "GET", 
    "enctype": "application/x-www-form-urlencoded",
    "params": {
      "title": "title",
      "text": "text", 
      "url": "url"
    }
  }
}

File: src/app.html - Add manifest link if missing:

<link rel="manifest" href="/manifest.json">

Acceptance Criteria:

  • static/manifest.json created with identical configuration
  • Share target functionality preserved (test with external app)
  • PWA installation prompt appears in supported browsers
  • Manifest validation passes in browser DevTools
  • All manifest properties display correctly in browser
  • PWA installation works with same behavior as before

Testing Strategy:

  1. Validate manifest.json syntax and W3C compliance
  2. Test PWA installation in Chrome, Firefox, Safari, Edge
  3. Test share target from Instagram mobile app
  4. Verify manifest properties in DevTools Application tab
  5. Test icon display in app installer and home screen

Files:

  • static/manifest.json (create)
  • src/app.html (modify if needed)

Story 2: Remove SvelteKitPWA Plugin Dependencies

Priority: High
Dependencies: Story 1
Estimated Effort: 1-2 hours

Objective: Remove @vite-pwa/sveltekit plugin and all related configuration from the project.

Tasks:

  1. Remove @vite-pwa/sveltekit from package.json dependencies
  2. Remove SvelteKitPWA plugin import and configuration from vite.config.ts
  3. Clean up any vite-pwa related development dependencies
  4. Remove dev-dist/ generated files if present
  5. Update build process documentation if needed

Technical Details:

File: package.json

// Remove:
"@vite-pwa/sveltekit": "..."

File: vite.config.ts

// Remove import:
import { SvelteKitPWA } from '@vite-pwa/sveltekit';

// Remove from plugins array:
SvelteKitPWA({ /* entire configuration */ })

Acceptance Criteria:

  • @vite-pwa/sveltekit dependency removed from package.json
  • SvelteKitPWA plugin removed from vite.config.ts
  • No vite-pwa imports or references remaining
  • Project builds successfully without plugin
  • No build warnings related to missing plugin
  • Development server starts without plugin errors

Testing Strategy:

  1. Run npm install to verify dependency removal
  2. Run npm run build to ensure build process works
  3. Run npm run dev to ensure development server works
  4. Check for any console warnings or errors
  5. Verify no plugin-generated files remain

Files:

  • package.json (modify)
  • vite.config.ts (modify)

Story 3: Migrate Service Worker to SvelteKit Native

Priority: Critical
Dependencies: Story 1, Story 2
Estimated Effort: 4-6 hours

Objective: Rewrite service worker to use SvelteKit's native $service-worker module instead of workbox, while preserving all existing functionality including push notifications, caching, and background sync.

Tasks:

  1. Replace workbox imports with SvelteKit $service-worker imports
  2. Implement manual caching using build, files, version arrays
  3. Replace workbox precaching with manual cache management
  4. Preserve all push notification event handlers exactly
  5. Preserve background sync functionality
  6. Implement navigation routing without workbox NavigationRoute
  7. Add environment detection for development vs production
  8. Test all service worker functionality thoroughly

Technical Details:

File: src/service-worker.ts - New Implementation:

/// <reference types="@sveltejs/kit" />
/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />
/// <reference lib="webworker" />

import { build, files, version } from '$service-worker';

declare let self: ServiceWorkerGlobalScope;

// Create a unique cache name for this deployment  
const CACHE = `cache-${version}`;
const ASSETS = [
  ...build, // the app itself
  ...files  // everything in `static`
];

// Global error handlers (preserve existing)
self.addEventListener('error', (event) => {
  console.error('[SW] Global error:', event.error);
});

self.addEventListener('unhandledrejection', (event) => {
  console.error('[SW] Unhandled promise rejection:', event.reason);
  event.preventDefault();
});

console.log('[SW] Service worker script loading...');

// Install event - cache all assets
self.addEventListener('install', (event) => {
  console.log('[SW] Installing service worker...');
  
  async function addFilesToCache() {
    const cache = await caches.open(CACHE);
    await cache.addAll(ASSETS);
    console.log(`[SW] Cached ${ASSETS.length} assets`);
  }

  event.waitUntil(addFilesToCache());
});

// Activate event - clean up old caches
self.addEventListener('activate', (event) => {
  console.log('[SW] Activating service worker...');
  
  async function deleteOldCaches() {
    for (const key of await caches.keys()) {
      if (key !== CACHE) {
        console.log('[SW] Deleting old cache:', key);
        await caches.delete(key);
      }
    }
  }

  event.waitUntil(deleteOldCaches());
});

// Fetch event - serve from cache with network fallback
self.addEventListener('fetch', (event) => {
  // ignore POST requests etc
  if (event.request.method !== 'GET') return;

  async function respond() {
    const url = new URL(event.request.url);
    const cache = await caches.open(CACHE);

    // `build`/`files` can always be served from the cache
    if (ASSETS.includes(url.pathname)) {
      const response = await cache.match(url.pathname);
      if (response) {
        return response;
      }
    }

    // for everything else, try the network first, but
    // fall back to the cache if we're offline
    try {
      const response = await fetch(event.request);
      
      // if we're offline, fetch can return a value that is not a Response
      // instead of throwing - and we can't pass this non-Response to respondWith
      if (!(response instanceof Response)) {
        throw new Error('invalid response from fetch');
      }

      if (response.status === 200) {
        cache.put(event.request, response.clone());
      }
      return response;
    } catch (err) {
      const response = await cache.match(event.request);
      if (response) {
        return response;
      }
      
      // if there's no cache, then just error out
      // as there is nothing we can do to respond to this request
      throw err;
    }
  }

  event.respondWith(respond());
});

// PRESERVE ALL EXISTING PUSH NOTIFICATION FUNCTIONALITY
// (Copy exactly from existing service worker - push, notificationclick, notificationclose handlers)
// ... [preserve existing push notification code] ...

// PRESERVE BACKGROUND SYNC FUNCTIONALITY  
// (Copy exactly from existing service worker)
// ... [preserve existing sync handlers] ...

// PRESERVE MESSAGE HANDLING
// (Copy exactly from existing service worker)
// ... [preserve existing message handlers] ...

Key Migrations:

  1. Workbox Precaching → Manual Caching: Replace precacheAndRoute(self.__WB_MANIFEST) with manual cache management using build and files arrays
  2. NavigationRoute → Manual Routing: Replace workbox NavigationRoute with manual fetch handler logic
  3. Cleanup → Manual Cleanup: Replace cleanupOutdatedCaches() with manual cache key comparison
  4. Manifest Injection → Module Import: Replace self.__WB_MANIFEST with build, files, version imports

Acceptance Criteria:

  • Service worker uses SvelteKit's $service-worker module
  • Manual caching works for all static assets
  • Navigation routing works offline
  • All push notification functionality preserved exactly
  • Background sync continues to work
  • Service worker to client message passing works
  • Cache cleanup works on updates
  • Development and production modes both work
  • No service worker errors in console

Testing Strategy:

  1. Test service worker registration and activation
  2. Test offline functionality by going offline in DevTools
  3. Test all push notification scenarios (subscribe, receive, click actions)
  4. Test background sync with queue retry operations
  5. Test cache behavior with network disabled
  6. Test service worker updates with version changes
  7. Verify message passing between client and service worker

Files:

  • src/service-worker.ts (major rewrite)

Story 4: Enable SvelteKit Service Worker Registration

Priority: High
Dependencies: Story 3
Estimated Effort: 1-2 hours

Objective: Enable SvelteKit's built-in service worker registration and ensure proper coordination without conflicts.

Tasks:

  1. Enable serviceWorker.register: true in svelte.config.js
  2. Verify service worker registration timing and behavior
  3. Ensure no conflicts with development mode
  4. Test service worker registration across browsers
  5. Verify proper service worker lifecycle events

Technical Details:

File: svelte.config.js

import adapter from '@sveltejs/adapter-node';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';

/** @type {import('@sveltejs/kit').Config} */
const config = {
  preprocess: vitePreprocess(),
  kit: {
    adapter: adapter(),
    serviceWorker: {
      register: true, // Enable SvelteKit's service worker registration
      // Can add additional options if needed
    }
  }
};

export default config;

Acceptance Criteria:

  • Service worker registers automatically on page load
  • Only one service worker registration visible in DevTools
  • Service worker lifecycle events work correctly (install, activate)
  • No registration conflicts or duplicate registrations
  • Works in development and production environments
  • Service worker updates properly when code changes

Testing Strategy:

  1. Clear service workers in DevTools and reload page
  2. Verify single service worker registration in Application tab
  3. Test service worker lifecycle events in console
  4. Test across different browsers
  5. Test development vs production registration behavior

Files:

  • svelte.config.js (modify)

Story 5: Comprehensive Testing and Validation

Priority: Critical
Dependencies: All previous stories
Estimated Effort: 3-4 hours

Objective: Thoroughly test all PWA functionality to ensure no regressions and all features work as expected in the native SvelteKit implementation.

Tasks:

  1. Test PWA installation flow across browsers
  2. Test share target functionality from external apps
  3. Test all push notification scenarios
  4. Test offline functionality and caching
  5. Test service worker lifecycle and updates
  6. Cross-browser compatibility testing
  7. Performance validation vs previous implementation

Testing Scenarios:

PWA Installation:

  • Installation prompt appears in Chrome
  • Installation prompt appears in Edge
  • Installation works on Android Chrome
  • Installed app opens in standalone mode
  • App icons display correctly after installation

Share Target:

  • Share from Instagram mobile app works
  • Share target parameters (title, text, url) received correctly
  • Share page processes URLs automatically
  • Manual URL input still works
  • Share target works across different share sources

Push Notifications:

  • Push notification subscription works
  • Push notifications display with correct content
  • Custom notification actions work (view, retry, dismiss)
  • Notification click navigation works correctly
  • Background sync triggers on retry action
  • Service worker message passing works
  • Push notifications work after service worker updates

Offline Functionality:

  • Static assets load when offline
  • Navigation works when offline
  • Cached API responses serve when offline
  • Cache updates when back online
  • Cache cleanup works on app updates

Cross-Browser Testing:

  • Chrome (desktop and mobile)
  • Firefox (desktop and mobile)
  • Safari (desktop and mobile)
  • Edge (desktop)
  • Samsung Internet (mobile)

Performance Validation:

  • Service worker registration time comparable
  • Cache loading performance maintained
  • Bundle size reduced (no workbox)
  • PWA audit scores maintained or improved

Acceptance Criteria:

  • All existing functionality works exactly as before
  • No regressions in any PWA features
  • Cross-browser compatibility maintained
  • Performance meets or exceeds previous implementation
  • All tests pass in continuous integration
  • PWA audit scores are maintained or improved

Files:

  • Test documentation updates if needed

Risk Assessment

High Risk Areas

  1. Push Notification Functionality: Complex service worker message handling could break
  2. Share Target Integration: External app sharing could fail with manifest changes
  3. Cache Strategy: Manual cache management could introduce bugs vs workbox
  4. Service Worker Registration: Timing issues could cause registration failures

Medium Risk Areas

  1. Offline Functionality: Different caching approach might affect offline behavior
  2. Build Process: Removing plugin changes build pipeline significantly
  3. Development Experience: Service worker behavior could change in development

Low Risk Areas

  1. Manifest Properties: Direct translation should work reliably
  2. Static Asset Serving: SvelteKit handles this well natively
  3. PWA Installation: Standard manifest.json is well supported

Mitigation Strategies

  1. Comprehensive Testing: Extensive testing across all scenarios and browsers
  2. Incremental Migration: Complete each story fully before moving to next
  3. Rollback Plan: Keep plugin configuration in version control for quick rollback
  4. Staging Environment: Test fully in staging before production deployment

Implementation Roadmap

Phase 1: Foundation (Stories 1-2) - 3-5 hours

  • Create native manifest.json
  • Remove plugin dependencies
  • Establish baseline for migration

Phase 2: Service Worker Migration (Story 3) - 4-6 hours

  • Rewrite service worker with SvelteKit APIs
  • Preserve all existing functionality
  • Most complex and critical phase

Phase 3: Registration & Testing (Stories 4-5) - 4-6 hours

  • Enable SvelteKit registration
  • Comprehensive testing across all scenarios
  • Performance and compatibility validation

Total Estimated Effort: 11-17 hours


Success Criteria

Functional Requirements

  • All existing PWA functionality works identically
  • Push notifications work across all scenarios
  • Share target works from external apps
  • Offline functionality maintained
  • PWA installation works across browsers

Technical Requirements

  • No external PWA plugin dependencies
  • Uses SvelteKit native service worker APIs
  • Manual manifest.json in static/ directory
  • Service worker registration through SvelteKit
  • Maintains or improves performance

Quality Requirements

  • No regressions in existing functionality
  • Cross-browser compatibility maintained
  • PWA audit scores maintained or improved
  • Development experience not degraded
  • Build process simplified

Definition of Done

  • All user stories completed with acceptance criteria met
  • Comprehensive testing completed across all browsers
  • No regressions in existing functionality
  • Performance validated against baseline
  • Documentation updated as needed
  • Code review completed
  • Staging deployment tested successfully
  • Ready for production deployment