@vite-pwa/nuxt 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,89 @@
1
+ <p align='center'>
2
+ <img src='./hero.png' alt="@vite-pwa/nuxt - Zero-config PWA for Nuxt 3"><br>
3
+ Zero-config PWA Plugin for Nuxt 3
4
+ </p>
5
+
6
+ <p align='center'>
7
+ <a href='https://www.npmjs.com/package/@vite-pwa/nuxt' target="__blank">
8
+ <img src='https://img.shields.io/npm/v/@vite-pwa/nuxt?color=33A6B8&label=' alt="NPM version">
9
+ </a>
10
+ <a href="https://www.npmjs.com/package/@vite-pwa/nuxt" target="__blank">
11
+ <img alt="NPM Downloads" src="https://img.shields.io/npm/dm/@vite-pwa/nuxt?color=476582&label=">
12
+ </a>
13
+ <a href="https://vite-pwa-org.netlify.app/frameworks/nuxt" target="__blank">
14
+ <img src="https://img.shields.io/static/v1?label=&message=docs%20%26%20guides&color=2e859c" alt="Docs & Guides">
15
+ </a>
16
+ <br>
17
+ <a href="https://github.com/vite-pwa/nuxt" target="__blank">
18
+ <img alt="GitHub stars" src="https://img.shields.io/github/stars/vite-pwa/nuxt?style=social">
19
+ </a>
20
+ </p>
21
+
22
+ <br>
23
+
24
+ <p align="center">
25
+ <a href="https://cdn.jsdelivr.net/gh/antfu/static/sponsors.svg">
26
+ <img src='https://cdn.jsdelivr.net/gh/antfu/static/sponsors.svg'/>
27
+ </a>
28
+ </p>
29
+
30
+
31
+ ## 🚀 Features
32
+
33
+ - 📖 [**Documentation & guides**](https://vite-pwa-org.netlify.app/)
34
+ - 👌 **Zero-Config**: sensible built-in default configs for common use cases
35
+ - 🔩 **Extensible**: expose the full ability to customize the behavior of the plugin
36
+ - 🦾 **Type Strong**: written in [TypeScript](https://www.typescriptlang.org/)
37
+ - 🔌 **Offline Support**: generate service worker with offline support (via Workbox)
38
+ - ⚡ **Fully tree shakable**: auto inject Web App Manifest
39
+ - 💬 **Prompt for new content**: built-in support for Vanilla JavaScript, Vue 3, React, Svelte, SolidJS and Preact
40
+ - ⚙️ **Stale-while-revalidate**: automatic reload when new content is available
41
+ - ✨ **Static assets handling**: configure static assets for offline support
42
+ - 🐞 **Development Support**: debug your custom service worker logic as you develop your application
43
+
44
+ ## 📦 Install
45
+
46
+ > Requires Vite 3.2.0+ and Nuxt 3.0.0+
47
+
48
+ ```bash
49
+ npm i @vite-pwa/nuxt -D
50
+
51
+ # yarn
52
+ yarn add @vite-pwa/nuxt -D
53
+
54
+ # pnpm
55
+ pnpm add @vite-pwa/nuxt -D
56
+ ```
57
+
58
+ ## 🦄 Usage
59
+
60
+ Add `@vite-pwa/nuxt` module to `nuxt.config.ts` and configure it:
61
+
62
+ ```ts
63
+ // nuxt.config.ts
64
+ import { defineNuxtConfig } from 'nuxt/config'
65
+
66
+ export default defineNuxtConfig({
67
+ modules: [
68
+ '@vite-pwa/nuxt'
69
+ ],
70
+ pwa: {
71
+ /* PWA options */
72
+ }
73
+ })
74
+ ```
75
+
76
+ Read the [📖 documentation](https://vite-pwa-org.netlify.app/frameworks/nuxt) for a complete guide on how to configure and use
77
+ this plugin.
78
+
79
+ ## 👀 Full config
80
+
81
+ Check out the type declaration [src/types.ts](./src/types.ts) and the following links for more details.
82
+
83
+ - [Web app manifests](https://developer.mozilla.org/en-US/docs/Web/Manifest)
84
+ - [Workbox](https://developers.google.com/web/tools/workbox)
85
+
86
+
87
+ ## 📄 License
88
+
89
+ MIT License © 2023-PRESENT [Anthony Fu](https://github.com/antfu)
@@ -0,0 +1,5 @@
1
+ module.exports = function(...args) {
2
+ return import('./module.mjs').then(m => m.default.call(this, ...args))
3
+ }
4
+ const _meta = module.exports.meta = require('./module.json')
5
+ module.exports.getMeta = () => Promise.resolve(_meta)
@@ -0,0 +1,48 @@
1
+ import * as _nuxt_schema from '@nuxt/schema';
2
+ import { VitePWAOptions } from 'vite-plugin-pwa';
3
+
4
+ interface ClientOptions {
5
+ /**
6
+ * Exposes the plugin: defaults to true.
7
+ */
8
+ registerPlugin?: boolean;
9
+ /**
10
+ * Registers a periodic sync for updates interval: value in seconds.
11
+ */
12
+ periodicSyncForUpdates?: number;
13
+ /**
14
+ * Will prevent showing native PWA install prompt: defaults to false.
15
+ *
16
+ * When set to true or no empty string, the native PWA install prompt will be prevented.
17
+ *
18
+ * When set to a string, it will be used as the key in `localStorage` to prevent show the PWA install prompt widget.
19
+ *
20
+ * When set to true, the key used will be `vite-pwa:hide-install`.
21
+ */
22
+ installPrompt?: boolean | string;
23
+ }
24
+ interface VitePWANuxtOptions extends Partial<VitePWAOptions> {
25
+ registerWebManifestInRouteRules?: boolean;
26
+ /**
27
+ * Writes the plugin to disk: defaults to false (debug).
28
+ */
29
+ writePlugin?: boolean;
30
+ /**
31
+ * Options for plugin.
32
+ */
33
+ client?: ClientOptions;
34
+ }
35
+ declare module '@nuxt/schema' {
36
+ interface NuxtConfig {
37
+ pwa?: {
38
+ [K in keyof VitePWANuxtOptions]?: Partial<VitePWANuxtOptions[K]>;
39
+ };
40
+ }
41
+ interface NuxtOptions {
42
+ pwa: VitePWANuxtOptions;
43
+ }
44
+ }
45
+
46
+ declare const _default: _nuxt_schema.NuxtModule<VitePWANuxtOptions>;
47
+
48
+ export { ClientOptions, VitePWANuxtOptions, _default as default };
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "pwa",
3
+ "configKey": "pwa",
4
+ "version": "0.0.1"
5
+ }
@@ -0,0 +1,147 @@
1
+ import { defineNuxtModule, createResolver, addPluginTemplate, addComponent } from '@nuxt/kit';
2
+ import { VitePWA } from 'vite-plugin-pwa';
3
+ import { resolve } from 'pathe';
4
+
5
+ function configurePWAOptions(options, nuxt) {
6
+ if (!options.outDir) {
7
+ const publicDir = nuxt.options.nitro?.output?.publicDir;
8
+ options.outDir = publicDir ? resolve(publicDir) : resolve(nuxt.options.buildDir, "../.output/public");
9
+ }
10
+ let config;
11
+ if (options.strategies === "injectManifest") {
12
+ options.injectManifest = options.injectManifest ?? {};
13
+ config = options.injectManifest;
14
+ } else {
15
+ options.workbox = options.workbox ?? {};
16
+ if (options.registerType === "autoUpdate" && (options.injectRegister === "script" || options.injectRegister === "inline")) {
17
+ options.workbox.clientsClaim = true;
18
+ options.workbox.skipWaiting = true;
19
+ }
20
+ if (nuxt.options.dev) {
21
+ options.workbox.navigateFallback = nuxt.options.app.baseURL ?? "/";
22
+ if (options.devOptions?.enabled && !options.devOptions.navigateFallbackAllowlist)
23
+ options.devOptions.navigateFallbackAllowlist = [new RegExp(nuxt.options.app.baseURL) ?? /\//];
24
+ }
25
+ config = options.workbox;
26
+ }
27
+ if (!nuxt.options.dev)
28
+ config.manifestTransforms = [createManifestTransform(nuxt.options.app.baseURL ?? "/")];
29
+ }
30
+ function createManifestTransform(base) {
31
+ return async (entries) => {
32
+ entries.filter((e) => e && e.url.endsWith(".html")).forEach((e) => {
33
+ const url = e.url.startsWith("/") ? e.url.slice(1) : e.url;
34
+ if (url === "index.html") {
35
+ e.url = base;
36
+ } else {
37
+ const parts = url.split("/");
38
+ parts[parts.length - 1] = parts[parts.length - 1].replace(/\.html$/, "");
39
+ e.url = parts.length > 1 ? parts.slice(0, parts.length - 1).join("/") : parts[0];
40
+ }
41
+ });
42
+ return { manifest: entries, warnings: [] };
43
+ };
44
+ }
45
+
46
+ const module = defineNuxtModule({
47
+ meta: {
48
+ name: "pwa",
49
+ configKey: "pwa"
50
+ },
51
+ defaults: (nuxt) => ({
52
+ base: nuxt.options.app.baseURL,
53
+ scope: nuxt.options.app.baseURL,
54
+ injectRegister: false,
55
+ includeManifestIcons: false,
56
+ registerPlugin: true,
57
+ client: {
58
+ registerPlugin: true,
59
+ installPrompt: false,
60
+ periodicSyncForUpdates: 0
61
+ }
62
+ }),
63
+ async setup(options, nuxt) {
64
+ const resolver = createResolver(import.meta.url);
65
+ let vitePwaClientPlugin;
66
+ const resolveVitePluginPWAAPI = () => {
67
+ return vitePwaClientPlugin?.api;
68
+ };
69
+ const client = options.client ?? { registerPlugin: true, installPrompt: false, periodicSyncForUpdates: 0 };
70
+ if (client.registerPlugin) {
71
+ addPluginTemplate({
72
+ src: resolver.resolve("../templates/pwa.client.ts"),
73
+ write: true,
74
+ options: {
75
+ periodicSyncForUpdates: typeof client.periodicSyncForUpdates === "number" ? client.periodicSyncForUpdates : 0,
76
+ installPrompt: typeof client.installPrompt === "undefined" || client.installPrompt === false ? void 0 : client.installPrompt === true || client.installPrompt.trim() === "" ? "vite-pwa:hide-install" : client.installPrompt.trim()
77
+ }
78
+ });
79
+ }
80
+ await addComponent({
81
+ name: "VitePwaManifest",
82
+ filePath: resolver.resolve("./runtime/VitePwaManifest")
83
+ });
84
+ nuxt.hook("nitro:init", (nitro) => {
85
+ options.outDir = nitro.options.output.publicDir;
86
+ options.injectManifest = options.injectManifest || {};
87
+ options.injectManifest.globDirectory = nitro.options.output.publicDir;
88
+ });
89
+ nuxt.hook("vite:extend", ({ config }) => {
90
+ const plugin = config.plugins?.find((p) => p && typeof p === "object" && "name" in p && p.name === "vite-plugin-pwa");
91
+ if (plugin)
92
+ throw new Error("Remove vite-plugin-pwa plugin from Vite Plugins entry in Nuxt config file!");
93
+ });
94
+ nuxt.hook("vite:extendConfig", async (viteInlineConfig, { isClient }) => {
95
+ viteInlineConfig.plugins = viteInlineConfig.plugins || [];
96
+ const plugin = viteInlineConfig.plugins.find((p) => p && typeof p === "object" && "name" in p && p.name === "vite-plugin-pwa");
97
+ if (plugin)
98
+ throw new Error("Remove vite-plugin-pwa plugin from Vite Plugins entry in Nuxt config file!");
99
+ configurePWAOptions(options, nuxt);
100
+ const plugins = VitePWA(options);
101
+ viteInlineConfig.plugins.push(plugins);
102
+ if (isClient)
103
+ vitePwaClientPlugin = plugins.find((p) => p.name === "vite-plugin-pwa");
104
+ });
105
+ if (nuxt.options.dev) {
106
+ const webManifest = `${nuxt.options.app.baseURL}${options.devOptions?.webManifestUrl ?? options.manifestFilename ?? "manifest.webmanifest"}`;
107
+ const devSw = `${nuxt.options.app.baseURL}dev-sw.js?dev-sw`;
108
+ const workbox = `${nuxt.options.app.baseURL}workbox-`;
109
+ const emptyHandle = (_req, _res, next) => {
110
+ next();
111
+ };
112
+ nuxt.hook("vite:serverCreated", (viteServer, { isServer }) => {
113
+ if (isServer)
114
+ return;
115
+ viteServer.middlewares.stack.push({ route: webManifest, handle: emptyHandle });
116
+ viteServer.middlewares.stack.push({ route: devSw, handle: emptyHandle });
117
+ });
118
+ if (!options.strategies || options.strategies === "generateSW") {
119
+ nuxt.hook("vite:serverCreated", (viteServer, { isServer }) => {
120
+ if (isServer)
121
+ return;
122
+ viteServer.middlewares.stack.push({ route: workbox, handle: emptyHandle });
123
+ });
124
+ nuxt.hook("close", async () => {
125
+ });
126
+ }
127
+ } else {
128
+ if (options.registerWebManifestInRouteRules) {
129
+ nuxt.hook("nitro:config", async (nitroConfig) => {
130
+ nitroConfig.routeRules = nitroConfig.routeRules || {};
131
+ nitroConfig.routeRules[`${nuxt.options.app.baseURL}${options.manifestFilename ?? "manifest.webmanifest"}`] = {
132
+ headers: {
133
+ "Content-Type": "application/manifest+json"
134
+ }
135
+ };
136
+ });
137
+ }
138
+ nuxt.hook("nitro:init", (nitro) => {
139
+ nitro.hooks.hook("rollup:before", async () => {
140
+ await resolveVitePluginPWAAPI()?.generateSW();
141
+ });
142
+ });
143
+ }
144
+ }
145
+ });
146
+
147
+ export { module as default };
@@ -0,0 +1,2 @@
1
+ declare const _default: import("vue").DefineComponent<{}, () => null, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{}>>, {}>;
2
+ export default _default;
@@ -0,0 +1,28 @@
1
+ import { defineComponent, ref } from "vue";
2
+ import { pwaInfo } from "virtual:pwa-info";
3
+ import { useHead } from "#imports";
4
+ export default defineComponent({
5
+ async setup() {
6
+ if (pwaInfo) {
7
+ const meta = ref({ link: [] });
8
+ useHead(meta);
9
+ const { webManifest } = pwaInfo;
10
+ if (webManifest) {
11
+ const { href, useCredentials } = webManifest;
12
+ if (useCredentials) {
13
+ meta.value.link.push({
14
+ rel: "manifest",
15
+ href,
16
+ crossorigin: "use-credentials"
17
+ });
18
+ } else {
19
+ meta.value.link.push({
20
+ rel: "manifest",
21
+ href
22
+ });
23
+ }
24
+ }
25
+ }
26
+ return () => null;
27
+ }
28
+ });
@@ -0,0 +1,6 @@
1
+
2
+ import { } from './module'
3
+
4
+
5
+
6
+ export { ClientOptions, VitePWANuxtOptions, default } from './module'
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@vite-pwa/nuxt",
3
+ "type": "module",
4
+ "version": "0.0.1",
5
+ "packageManager": "pnpm@7.26.1",
6
+ "description": "Zero-config PWA for Nuxt 3",
7
+ "author": "antfu <anthonyfu117@hotmail.com>",
8
+ "license": "MIT",
9
+ "funding": "https://github.com/sponsors/antfu",
10
+ "homepage": "https://github.com/vite-pwa/nuxt#readme",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/vite-pwa/nuxt.git"
14
+ },
15
+ "bugs": "https://github.com/vite-pwa/nuxt/issues",
16
+ "keywords": [
17
+ "nuxt",
18
+ "pwa",
19
+ "workbox",
20
+ "vite-plugin-pwa",
21
+ "nuxt-module"
22
+ ],
23
+ "exports": {
24
+ ".": {
25
+ "require": "./dist/module.cjs",
26
+ "import": "./dist/module.mjs"
27
+ }
28
+ },
29
+ "main": "./dist/module.cjs",
30
+ "types": "./dist/types.d.ts",
31
+ "files": [
32
+ "dist",
33
+ "templates"
34
+ ],
35
+ "scripts": {
36
+ "prepack": "nuxt-module-build",
37
+ "dev": "nuxi dev playground",
38
+ "dev:build": "nuxi build playground",
39
+ "dev:prepare": "nuxt-module-build --stub && nuxi prepare playground",
40
+ "release": "npm run lint && npm run prepack && npx bumpp --push --tag --commit && npm publish --access=public && git push --follow-tags",
41
+ "lint": "eslint .",
42
+ "lint-fix": "nr lint --fix"
43
+ },
44
+ "peerDependencies": {
45
+ "@nuxt/kit": "^3.0.0 || ^3.1.0",
46
+ "vite-plugin-pwa": "^0.14.0"
47
+ },
48
+ "dependencies": {
49
+ "@nuxt/kit": "^3.1.1"
50
+ },
51
+ "devDependencies": {
52
+ "@antfu/eslint-config": "^0.34.1",
53
+ "@antfu/ni": "^0.19.0",
54
+ "@nuxt/module-builder": "^0.2.1",
55
+ "@nuxt/schema": "^3.1.1",
56
+ "@nuxt/test-utils": "^3.1.1",
57
+ "changelogen": "^0.4.1",
58
+ "eslint": "^8.32.0",
59
+ "nuxt": "^3.1.1",
60
+ "typescript": "^4.9.4",
61
+ "vite-plugin-pwa": "^0.14.1"
62
+ },
63
+ "build": {
64
+ "externals": [
65
+ "node:child_process",
66
+ "node:fs",
67
+ "consola",
68
+ "pathe",
69
+ "ufo",
70
+ "vite-plugin-pwa"
71
+ ]
72
+ }
73
+ }
@@ -0,0 +1,130 @@
1
+ import { ref, reactive, nextTick } from 'vue'
2
+ import { useRegisterSW } from 'virtual:pwa-register/vue'
3
+ import { defineNuxtPlugin } from '#app'
4
+
5
+ // @ts-ignore
6
+ const options: { periodicSyncForUpdates: number; installPrompt?: string } = <%= JSON.stringify(options) %>
7
+
8
+ export default defineNuxtPlugin(() => {
9
+ const registrationError = ref(false)
10
+ const swActivated = ref(false)
11
+ const showInstallPrompt = ref(false)
12
+ const hideInstall = ref(!options.installPrompt ? true : localStorage.getItem(options.installPrompt) === 'true')
13
+
14
+ // https://thomashunter.name/posts/2021-12-11-detecting-if-pwa-twa-is-installed
15
+ const ua = navigator.userAgent
16
+ const ios = ua.match(/iPhone|iPad|iPod/)
17
+ const standalone = window.matchMedia('(display-mode: standalone)').matches
18
+ const isInstalled = !!(standalone || (ios && !ua.match(/Safari/)))
19
+
20
+ const registerPeriodicSync = (swUrl: string, r: ServiceWorkerRegistration, timeout: number) => {
21
+ setInterval(async () => {
22
+ if (('connection' in navigator) && !navigator.onLine)
23
+ return
24
+
25
+ const resp = await fetch(swUrl, {
26
+ cache: 'no-store',
27
+ headers: {
28
+ 'cache': 'no-store',
29
+ 'cache-control': 'no-cache',
30
+ },
31
+ })
32
+
33
+ if (resp?.status === 200)
34
+ await r.update()
35
+ }, timeout)
36
+ }
37
+
38
+ const {
39
+ offlineReady, needRefresh, updateServiceWorker,
40
+ } = useRegisterSW({
41
+ immediate: true,
42
+ onRegisterError() {
43
+ registrationError.value = true
44
+ },
45
+ onRegisteredSW(swUrl, r) {
46
+ const timeout = options.periodicSyncForUpdates
47
+ if (timeout > 0) {
48
+ // should add support in pwa plugin
49
+ if (r?.active?.state === 'activated') {
50
+ swActivated.value = true
51
+ registerPeriodicSync(swUrl, r, timeout * 1000)
52
+ }
53
+ else if (r?.installing) {
54
+ r.installing.addEventListener('statechange', (e) => {
55
+ const sw = e.target as ServiceWorker
56
+ swActivated.value = sw.state === 'activated'
57
+ if (swActivated.value)
58
+ registerPeriodicSync(swUrl, r, timeout * 1000)
59
+ })
60
+ }
61
+ }
62
+ },
63
+ })
64
+
65
+ const cancelPrompt = async () => {
66
+ offlineReady.value = false
67
+ needRefresh.value = false
68
+ }
69
+
70
+ let install: () => Promise<void> = () => Promise.resolve()
71
+ let cancelInstall: () => void = () => {}
72
+
73
+ if (!hideInstall.value) {
74
+ type InstallPromptEvent = Event & {
75
+ prompt: () => void
76
+ userChoice: Promise<{ outcome: 'dismissed' | 'accepted' }>
77
+ }
78
+
79
+ let deferredPrompt: InstallPromptEvent | undefined
80
+
81
+ const beforeInstallPrompt = (e: Event) => {
82
+ e.preventDefault()
83
+ deferredPrompt = e as InstallPromptEvent
84
+ showInstallPrompt.value = true
85
+ }
86
+ window.addEventListener('beforeinstallprompt', beforeInstallPrompt)
87
+ window.addEventListener('appinstalled', () => {
88
+ deferredPrompt = undefined
89
+ showInstallPrompt.value = false
90
+ })
91
+
92
+ cancelInstall = () => {
93
+ deferredPrompt = undefined
94
+ showInstallPrompt.value = false
95
+ window.removeEventListener('beforeinstallprompt', beforeInstallPrompt)
96
+ hideInstall.value = true
97
+ localStorage.setItem(options.installPrompt!, 'true')
98
+ }
99
+
100
+ install = async () => {
101
+ if (!showInstallPrompt.value || !deferredPrompt) {
102
+ showInstallPrompt.value = false
103
+ return
104
+ }
105
+
106
+ showInstallPrompt.value = false
107
+ await nextTick()
108
+ deferredPrompt.prompt()
109
+ await deferredPrompt.userChoice
110
+ }
111
+ }
112
+
113
+
114
+ return {
115
+ provide: {
116
+ pwa: reactive({
117
+ isInstalled,
118
+ showInstallPrompt,
119
+ cancelInstall,
120
+ install,
121
+ swActivated,
122
+ registrationError,
123
+ offlineReady,
124
+ needRefresh,
125
+ updateServiceWorker,
126
+ cancelPrompt,
127
+ }),
128
+ },
129
+ }
130
+ })