{"id":13677526,"url":"https://github.com/tobua/inject-manifest-plugin","last_synced_at":"2025-04-12T08:34:23.782Z","repository":{"id":180761731,"uuid":"665206486","full_name":"tobua/inject-manifest-plugin","owner":"tobua","description":"Injects a Workbox PWA manifest into a Service Worker.","archived":false,"fork":false,"pushed_at":"2024-06-18T17:25:25.000Z","size":61,"stargazers_count":3,"open_issues_count":0,"forks_count":3,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-02T20:05:44.745Z","etag":null,"topics":["manifest","plugin","pwa","rspack","webpack"],"latest_commit_sha":null,"homepage":"https://npmjs.com/inject-manifest-plugin","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tobua.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-07-11T17:15:07.000Z","updated_at":"2024-06-18T17:24:55.000Z","dependencies_parsed_at":null,"dependency_job_id":"0c8f1a07-be3d-46ce-b052-e2d1d6d2bfd5","html_url":"https://github.com/tobua/inject-manifest-plugin","commit_stats":null,"previous_names":["tobua/inject-manifest-plugin"],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tobua%2Finject-manifest-plugin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tobua%2Finject-manifest-plugin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tobua%2Finject-manifest-plugin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tobua%2Finject-manifest-plugin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tobua","download_url":"https://codeload.github.com/tobua/inject-manifest-plugin/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248540801,"owners_count":21121425,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["manifest","plugin","pwa","rspack","webpack"],"created_at":"2024-08-02T13:00:43.532Z","updated_at":"2025-04-12T08:34:23.764Z","avatar_url":"https://github.com/tobua.png","language":"TypeScript","funding_links":[],"categories":["Plugins"],"sub_categories":["Rspack Plugins"],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://github.com/tobua/inject-manifest-plugin/raw/main/logo.png\" alt=\"inject-manifest-plugin\" width=\"30%\"\u003e\n\u003c/p\u003e\n\n# inject-manifest-plugin\n\nInjects a Workbox PWA manifest into a Service Worker. Alternative for the InjectManifest plugin in the [workbox-webpack-plugin](https://www.npmjs.com/package/workbox-webpack-plugin).\n\n- Supports **webpack** and **Rspack**\n- Compatible with Google Workbox\n- Check out the [demo](https://papua-pwa.vercel.app) or the [papua PWA template](https://github.com/tobua/papua/tree/main/template/pwa)\n  - Get started by running `npm init now papua ./my-pwa pwa`\n- Replaces `self.INJECT_MANIFEST_PLUGIN` variable in a `service-worker.[jt]s` file with a `{ url: string; revision: string (hash) }` array.\n\n## service-worker File\n\nTo get started add a `service-worker.js` or `service-worker.ts` file to the root of the project and install the necessary dependencies with `npm install url-join workbox-core workbox-expiration workbox-precaching workbox-routing workbox-strategies`.\n\n```ts\nimport join from 'url-join'\nimport { clientsClaim } from 'workbox-core'\nimport { ExpirationPlugin } from 'workbox-expiration'\nimport { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching'\nimport { registerRoute } from 'workbox-routing'\nimport { StaleWhileRevalidate } from 'workbox-strategies'\n\nclientsClaim() // Allows updating open service workers.\n\n// Add types for the plugin and workbox.\ndeclare global {\n  interface Window {\n    INJECT_MANIFEST_PLUGIN: { url: string; revision: string }[]\n    skipWaiting: Function\n  }\n}\n\n// Add all assets generated during build to the browser cache.\nprecacheAndRoute(self.INJECT_MANIFEST_PLUGIN)\n\nconst fileExtensionRegexp = new RegExp('/[^/?]+\\\\.[^/]+$')\nregisterRoute(\n  // Return false to exempt requests from being fulfilled by index.html.\n  ({ request, url }) =\u003e {\n    // If this isn't a navigation, skip.\n    if (request.mode !== 'navigate') {\n      return false\n    } // If this is a URL that starts with /_, skip.\n\n    if (url.pathname.startsWith('/_')) {\n      return false\n    } // If this looks like a URL for a resource, because it contains // a file extension, skip.\n\n    if (url.pathname.match(fileExtensionRegexp)) {\n      return false\n    } // Return true to signal that we want to use the handler.\n\n    return true\n  },\n  createHandlerBoundToURL(join(process.env.PUBLIC_URL as string, '/index.html'))\n)\n\n// An example runtime caching route for requests that aren't handled by the\n// precache, in this case same-origin .png requests like those from in public/\nregisterRoute(\n  // Add in any other file extensions or routing criteria as needed.\n  ({ url }) =\u003e url.origin === self.location.origin \u0026\u0026 url.pathname.endsWith('.png'), // Customize this strategy as needed, e.g., by changing to CacheFirst.\n  new StaleWhileRevalidate({\n    cacheName: 'images',\n    plugins: [\n      // Ensure that once this runtime cache reaches a maximum size the\n      // least-recently used images are removed.\n      new ExpirationPlugin({ maxEntries: 50 }),\n    ],\n  })\n)\n\n// Update cached assets after reload without the need for the user to close all open tabs.\nself.addEventListener('message', (event) =\u003e {\n  if (event.data \u0026\u0026 event.data.type === 'SKIP_WAITING') {\n    self.skipWaiting()\n  }\n})\n```\n\n## Service Worker Registration\n\nThe `service-worker.js` asset created during the build needs to be dynamically loaded and registered from any regular JavaScript entry.\n\n```js\nimport join from 'url-join'\n\nconst store = {\n  ready: false,\n  update: false,\n  error: false,\n  offline: false\n}\n\nconst isLocalhost = Boolean(\n  window.location.hostname === 'localhost' ||\n    // [::1] is the IPv6 localhost address.\n    window.location.hostname === '[::1]' ||\n    // 127.0.0.0/8 are considered localhost for IPv4.\n    window.location.hostname.match(/^127(?:\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)\n)\n\nfunction registerValidSW(swUrl: string) {\n  navigator.serviceWorker\n    .register(swUrl)\n    .then((registration) =\u003e {\n      store.ready = true\n      registration.onupdatefound = () =\u003e {\n        const installingWorker = registration.installing\n        if (installingWorker == null) {\n          return\n        }\n        installingWorker.onstatechange = () =\u003e {\n          if (installingWorker.state === 'installed') {\n            if (navigator.serviceWorker.controller) {\n              // Force contents to update on reload.\n              if (registration \u0026\u0026 registration.waiting) {\n                registration.waiting.postMessage({ type: 'SKIP_WAITING' })\n              }\n              // Timeout to ensure message passed.\n              setTimeout(() =\u003e { store.update = true }, 100)\n            }\n          }\n        }\n      }\n    })\n    .catch(() =\u003e { error = true })\n}\n\nfunction checkValidServiceWorker(swUrl: string) {\n  fetch(swUrl, {\n    headers: { 'Service-Worker': 'script' },\n  })\n    .then((response) =\u003e {\n      const contentType = response.headers.get('content-type')\n      if (\n        response.status === 404 ||\n        (contentType != null \u0026\u0026 contentType.indexOf('javascript') === -1)\n      ) {\n        navigator.serviceWorker.ready.then((registration) =\u003e {\n          registration.unregister().then(() =\u003e {\n            window.location.reload()\n          })\n        })\n      } else {\n        registerValidSW(swUrl)\n      }\n    })\n    .catch(() =\u003e { store.offline = true })\n}\n\nexport function register() {\n  if (process.env.NODE_ENV === 'production' \u0026\u0026 'serviceWorker' in navigator) {\n    const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href)\n    if (publicUrl.origin !== window.location.origin) {\n      store.error = true\n      return\n    }\n\n    window.addEventListener('load', () =\u003e {\n      const swUrl = join(process.env.PUBLIC_URL, '/service-worker.js')\n\n      if (isLocalhost) {\n        checkValidServiceWorker(swUrl)\n        navigator.serviceWorker.ready.then(() =\u003e Todo.setReady())\n      } else {\n        registerValidSW(swUrl)\n      }\n    })\n  }\n}\n\n// Useful if you had a worker registered in the past on this url.\nexport function unregister() {\n  if ('serviceWorker' in navigator) {\n    navigator.serviceWorker.ready\n      .then((registration) =\u003e {\n        registration.unregister()\n      })\n      .catch((error) =\u003e { store.error = error.message })\n  }\n}\n```\n\nCall the `register()` method from anywhere:\n\n```jsx\nimport { register } from './registration'\n\ncreateRoot(document.body).render(\u003cp\u003emy App!\u003c/p\u003e)\n\nregister()\n```\n\n## Plugin Usage in `webpack.config.mjs` / `rspack.config.mjs`\n\nWhen used in a project with `webpack-cli` or `@rspack/cli` make sure that the `package.json` contains `\"type\": \"module\"` or the configuration file ends with `.mjs`. Both the plugin as well as the registration require the `process.env.PUBLIC_URL` variable to be set.\n\n```js\nimport { InjectManifestPlugin } from 'inject-manifest-plugin'\n\nexport default {\n  plugins: [new InjectManifestPlugin()],\n}\n```\n\n## Configuration\n\n```js\nnew InjectManifestPlugin({\n  file: 'my-worker.js', // Default: service-worker.js\n  injectionPoint: 'global.replace-this', // Default: self.INJECT_MANIFEST_PLUGIN\n  exclude: ['extension/*'], // Default: []\n  removeHash: true, // Default: false\n  chunkFilename: 'worker': // Default: service-worker\n})\n```\n\n`file` should point to a Service Worker file in the project and will automatically be added as an entry. The Service Worker entry chunk will be called `service-worker`. The `injectionPoint` can be any text that will be replaced in the Service Worker code with the generated manifest. Using the `exclude` array it's possible to keep some assets from appearing in the manifest to avoid caching in the Service Worker. The array can include globs and items are matched against the generated assets using [minimatch](https://www.npmjs.com/package/minimatch). With the `removeHash` option it's possible to ensure the name of the generated Service Worker asset matches the input file. A Service Worker cannot change it's name once registered, therefore it's important that no hash is added. Since, this option uses a workaround, it's generally recommended to avoid hashing any JavaScript assets as the Service Worker will usually ensure well enough that all assets are up-to-date. The `chunkFilename` names the injected Service Worker chunk.\n\n## Programmatic Usage\n\n```ts\nimport webpack from 'webpack'\nimport HtmlWebpackPlugin from 'html-webpack-plugin'\nimport { InjectManifestPlugin } from 'inject-manifest-plugin'\n\nconst configuration: webpack.Configuration = {\n  plugins: [\n    new InjectManifestPlugin(),\n    new HtmlWebpackPlugin()\n  ],\n}\n\n// When using programmatic API, otherwise place configuration in webpack.config.js.\nwebpack(configuration, (error, stats) =\u003e { ... })\n```\n\n```ts\nimport { Configuration, rspack } from '@rspack/core'\nimport { InjectManifestPlugin } from 'inject-manifest-plugin'\n\nconst configuration: Configuration = {\n  builtins: {\n    html: [{}], // Empty object creates a default html template (index.html).\n  },\n  plugins: [new InjectManifestPlugin()],\n}\n\n// When using programmatic API, otherwise place configuration in rspack.config.js.\nrspack(configuration, (error, stats) =\u003e { ... })\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftobua%2Finject-manifest-plugin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftobua%2Finject-manifest-plugin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftobua%2Finject-manifest-plugin/lists"}