vite-plugin-firebase-messaging-sw

npm version license

Generate a Firebase Cloud Messaging (FCM) service worker for any Vite app β€” env-aware config, a no-store dev preview that honors your Vite base, and zero runtime dependencies.


Features


Installation

npm i -D vite-plugin-firebase-messaging-sw
# or: pnpm add -D vite-plugin-firebase-messaging-sw
# or: yarn add -D vite-plugin-firebase-messaging-sw

Requires Node β‰₯ 18 and Vite β‰₯ 4.


Usage

// vite.config.ts
import { defineConfig } from 'vite';
import { generateFirebaseMessagingSw } from 'vite-plugin-firebase-messaging-sw';

export default defineConfig({
  plugins: [
    generateFirebaseMessagingSw({
      firebase: {
        apiKey: process.env.FIREBASE_API_KEY!,
        authDomain: process.env.FIREBASE_AUTH_DOMAIN!,
        projectId: process.env.FIREBASE_PROJECT_ID!,
        storageBucket: process.env.FIREBASE_STORAGE_BUCKET!,
        messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID!,
        appId: process.env.FIREBASE_APP_ID!,
        measurementId: process.env.FIREBASE_MEASUREMENT_ID, // optional
      },
      outputDir: 'static', // optional: SvelteKit serves /static at the site root
      analytics: {
        impressionUrl: 'https://your-tracker/impression',
        openUrl: 'https://your-tracker/open',
      },
    }),
  ],
});
Phase Behavior
dev (vite) Served at the SW route under your Vite base with Cache-Control: no-store + weak ETag. The SW is built once per dev session (stable across requests).
build (vite build) Emitted as a build asset to the root of your build outDir.
outputDir Also written to disk (dev & build), skipped when unchanged.

The Firebase service worker must be served from the site root for FCM to register it. If you deploy under a sub-path or your build root differs from your web root, use outputDir (e.g. "static"/"public") to place it where it’ll be served at /firebase-messaging-sw.js.

Config from environment variables

Any field omitted from firebase falls back to the matching env var, so you can pass firebase: {} and rely entirely on the environment:

Field Env var
apiKey FIREBASE_API_KEY
authDomain FIREBASE_AUTH_DOMAIN
projectId FIREBASE_PROJECT_ID
storageBucket FIREBASE_STORAGE_BUCKET
messagingSenderId FIREBASE_MESSAGING_SENDER_ID
appId FIREBASE_APP_ID
measurementId FIREBASE_MEASUREMENT_ID

Explicit firebase values win over env vars. If any required field is still missing, the plugin throws with the exact list of missing fields.

Per-environment config

generateFirebaseMessagingSw({
  firebase: ({ mode }) =>
    mode === 'production' ? prodFirebaseConfig : devFirebaseConfig,
});

Options

Option Type Default Description
firebase FirebaseConfig \| (ctx) => FirebaseConfig required Firebase web config or a resolver. ctx is { mode, command, config }.
filename string "firebase-messaging-sw.js" Output filename.
outputDir string β€” (no mirror) Also write the SW to this directory on disk.
firebaseCompatVersion string "9.22.2" Firebase compat SDK version loaded from the gstatic CDN. Bump to a newer release as needed.
meta.appEnv string current mode Arbitrary environment label embedded in the SW.
meta.target string "web" Arbitrary target label.
analytics FcmAnalytics { srcParam: 'src', cidParam: 'cid', srcValue: 'fcm' } Tracking endpoints + query-param names.
minify boolean true in production Apply the built-in light minifier.
transform (code: string) => string β€” Final transform over the generated SW (runs after minify β€” plug in your own minifier here if you want).

FirebaseConfig

type FirebaseConfig = {
  apiKey: string;
  authDomain: string;
  projectId: string;
  storageBucket: string;
  messagingSenderId: string;
  appId: string;
  measurementId?: string;
};

Generated output (excerpt)

/* Auto-generated at 2025-09-09T13:45:00.000Z */
importScripts("https://www.gstatic.com/firebasejs/9.22.2/firebase-app-compat.js");
importScripts("https://www.gstatic.com/firebasejs/9.22.2/firebase-messaging-compat.js");

(function(){
  const firebaseConfig = { /* your config */ };
  firebase.initializeApp(firebaseConfig);
  const messaging = firebase.messaging();

  messaging.onBackgroundMessage((payload) => {
    self.registration.showNotification(
      payload.notification?.title || "Notification",
      { body: payload.notification?.body || "", data: payload.data || {} }
    );
  });

  self.addEventListener("notificationclick", (event) => {
    event.notification.close();
    event.waitUntil(clients.openWindow(event.notification?.data?.url || "/"));
  });
})();

The firebase/analytics values are safely escaped before being embedded, so URLs containing quotes or backslashes won’t break the generated script. Customize the handlers via transform(code).


License

MIT Β© dev.zarghami