spark-html-manifest 0.1.1 → 0.1.3
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 +10 -11
- package/package.json +5 -6
- package/src/bun.js +118 -0
- package/src/index.js +4 -4
- package/src/vite.d.ts +0 -23
- package/src/vite.js +0 -128
package/README.md
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
# ⚡ spark-html-manifest
|
|
2
2
|
|
|
3
3
|
PWA setup for [spark-html](https://www.npmjs.com/package/spark-html) sites
|
|
4
|
-
from a **single config** — a
|
|
4
|
+
from a **single config** — a `spark-html-bun` build step that generates
|
|
5
5
|
`manifest.webmanifest`, resizes your icons from one source image, injects
|
|
6
6
|
the `<head>` tags, and (optionally) emits a minimal offline app-shell
|
|
7
7
|
service worker. No manual icon exports, no copy-paste boilerplate.
|
|
8
8
|
|
|
9
9
|
```js
|
|
10
|
-
//
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import manifest from 'spark-html-manifest/vite';
|
|
10
|
+
// spark.config.js
|
|
11
|
+
import prerender from 'spark-prerender/bun';
|
|
12
|
+
import manifest from 'spark-html-manifest/bun';
|
|
14
13
|
|
|
15
14
|
export default {
|
|
16
|
-
|
|
17
|
-
spark(),
|
|
15
|
+
pipeline: [
|
|
18
16
|
prerender(),
|
|
19
17
|
manifest({
|
|
20
18
|
name: 'My Spark App',
|
|
@@ -27,7 +25,7 @@ export default {
|
|
|
27
25
|
};
|
|
28
26
|
```
|
|
29
27
|
|
|
30
|
-
That's the whole setup. `
|
|
28
|
+
That's the whole setup. `bun run build` now produces:
|
|
31
29
|
|
|
32
30
|
- `manifest.webmanifest` — name, colors, display mode, icons
|
|
33
31
|
- `icons/spark-192.png`, `icons/spark-512.png` — resized from your source
|
|
@@ -41,7 +39,7 @@ Lighthouse/devtools "installable" checks pass locally too.
|
|
|
41
39
|
## Install
|
|
42
40
|
|
|
43
41
|
```bash
|
|
44
|
-
|
|
42
|
+
bun add spark-html-manifest
|
|
45
43
|
```
|
|
46
44
|
|
|
47
45
|
## Config
|
|
@@ -69,7 +67,7 @@ manifest({
|
|
|
69
67
|
`offline: true` emits a deliberately small service worker:
|
|
70
68
|
|
|
71
69
|
- the **app shell** (`shell` URLs) is precached at install;
|
|
72
|
-
-
|
|
70
|
+
- the build's hash-named `/assets/…` files are **cache-first** (they're immutable);
|
|
73
71
|
- everything else same-origin is **network-first** with cache fallback — the
|
|
74
72
|
app opens offline but is never a deploy behind while online;
|
|
75
73
|
- offline navigation to any route falls back to the shell.
|
|
@@ -102,6 +100,7 @@ virtual DOM, no build step required. Add only what you use.
|
|
|
102
100
|
| Package | What it does |
|
|
103
101
|
|---|---|
|
|
104
102
|
| [`spark-html`](https://www.npmjs.com/package/spark-html) | The runtime — components, reactivity, stores, forms, scoped styles. 13 kB gzip, 0 deps. |
|
|
103
|
+
| [`spark-html-bun`](https://www.npmjs.com/package/spark-html-bun) | Dev server, bundler & preview on Bun — scoped HMR, no-build dev, post-build pipeline. |
|
|
105
104
|
| [`spark-html-router`](https://www.npmjs.com/package/spark-html-router) | `<template route>` routing — nested routes/layouts, `route.query`, active links. |
|
|
106
105
|
| [`spark-html-theme`](https://www.npmjs.com/package/spark-html-theme) | Dark/light/system theming in one line — persisted, no flash. |
|
|
107
106
|
| [`spark-html-head`](https://www.npmjs.com/package/spark-html-head) | Reactive `<title>`/`<meta>` per route + a `head` store. |
|
|
@@ -116,7 +115,7 @@ virtual DOM, no build step required. Add only what you use.
|
|
|
116
115
|
| [`spark-html-manifest`](https://www.npmjs.com/package/spark-html-manifest) | PWA manifest + icons + head tags (and optional service worker) from one config. |
|
|
117
116
|
| [`spark-html-offline`](https://www.npmjs.com/package/spark-html-offline) | Offline URL imports — a service worker that caches CDN components. |
|
|
118
117
|
| [`spark-html-sri`](https://www.npmjs.com/package/spark-html-sri) | Subresource Integrity — hash + verify assets and remote components. |
|
|
119
|
-
| [`create-spark-html-app`](https://www.npmjs.com/package/create-spark-html-app) | Scaffold a
|
|
118
|
+
| [`create-spark-html-app`](https://www.npmjs.com/package/create-spark-html-app) | Scaffold a spark-html app in one command. |
|
|
120
119
|
| [`prettier-plugin-spark`](https://www.npmjs.com/package/prettier-plugin-spark) | Prettier for components — formats `<script>`/`<style>`, markup stays byte-for-byte. |
|
|
121
120
|
| [`spark-html-language-server`](https://www.npmjs.com/package/spark-html-language-server) | LSP — diagnostics, go-to-definition, prop autocomplete, hover docs. |
|
|
122
121
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spark-html-manifest",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "PWA manifest + icons + offline app shell for spark-html sites from a single config — a
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "PWA manifest + icons + offline app shell for spark-html sites from a single config — a spark-html-bun build step that generates manifest.webmanifest, resizes icons, injects the head tags, and optionally emits a minimal service worker.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
7
|
"types": "./src/index.d.ts",
|
|
@@ -11,9 +11,8 @@
|
|
|
11
11
|
"types": "./src/index.d.ts",
|
|
12
12
|
"default": "./src/index.js"
|
|
13
13
|
},
|
|
14
|
-
"./
|
|
15
|
-
"
|
|
16
|
-
"default": "./src/vite.js"
|
|
14
|
+
"./bun": {
|
|
15
|
+
"default": "./src/bun.js"
|
|
17
16
|
}
|
|
18
17
|
},
|
|
19
18
|
"scripts": {
|
|
@@ -37,7 +36,7 @@
|
|
|
37
36
|
"webmanifest",
|
|
38
37
|
"icons",
|
|
39
38
|
"service-worker",
|
|
40
|
-
"
|
|
39
|
+
"bun"
|
|
41
40
|
],
|
|
42
41
|
"license": "MIT"
|
|
43
42
|
}
|
package/src/bun.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* spark-html-manifest/bun — one config → a full PWA, as a spark-html-bun
|
|
3
|
+
* pipeline step. Same output, split the two ways the Bun
|
|
4
|
+
* runner consumes:
|
|
5
|
+
*
|
|
6
|
+
* • run({ outDir }) — emit manifest.webmanifest, resize the source icon into
|
|
7
|
+
* every configured size (sharp, imported lazily), optionally emit the
|
|
8
|
+
* app-shell worker, then stamp <link rel="manifest"> + <meta theme-color>
|
|
9
|
+
* (+ worker registration) into every built page. Put it after prerender()
|
|
10
|
+
* so per-route pages are covered.
|
|
11
|
+
* • devRoutes() — serve the manifest (and worker) straight from config so
|
|
12
|
+
* devtools' "installable" checks work in dev too.
|
|
13
|
+
* • transformHtml() — inject the same head tags into served pages in dev.
|
|
14
|
+
*
|
|
15
|
+
* import manifest from 'spark-html-manifest/bun';
|
|
16
|
+
* export default { pipeline: [prerender(), manifest({
|
|
17
|
+
* name: 'My App', themeColor: '#ffd24a', icon: 'public/icon.png', offline: true,
|
|
18
|
+
* })] };
|
|
19
|
+
*/
|
|
20
|
+
import { join, resolve, dirname } from 'node:path';
|
|
21
|
+
import { readFile, writeFile, readdir, stat, mkdir } from 'node:fs/promises';
|
|
22
|
+
import { existsSync } from 'node:fs';
|
|
23
|
+
import { manifestJson, manifestHtml, swSource, iconPath, ICON_SIZES } from './index.js';
|
|
24
|
+
|
|
25
|
+
async function htmlFiles(dir) {
|
|
26
|
+
const out = [];
|
|
27
|
+
for (const name of await readdir(dir)) {
|
|
28
|
+
const full = join(dir, name);
|
|
29
|
+
const s = await stat(full);
|
|
30
|
+
if (s.isDirectory()) out.push(...await htmlFiles(full));
|
|
31
|
+
else if (name.endsWith('.html')) out.push(full);
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default function sparkManifest(config = {}) {
|
|
37
|
+
const filename = config.filename || 'manifest.webmanifest';
|
|
38
|
+
const offline = config.offline || false;
|
|
39
|
+
const swFile = (offline && offline.file) || 'spark-manifest-sw.js';
|
|
40
|
+
const headBlock = () =>
|
|
41
|
+
manifestHtml(config, { href: filename, sw: offline ? swFile : undefined });
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
name: 'spark-html-manifest',
|
|
45
|
+
|
|
46
|
+
async run({ outDir }) {
|
|
47
|
+
const root = resolve(outDir);
|
|
48
|
+
if (!existsSync(root)) return;
|
|
49
|
+
|
|
50
|
+
// Emit manifest + worker.
|
|
51
|
+
await writeFile(join(root, filename), JSON.stringify(manifestJson(config), null, 2), 'utf8');
|
|
52
|
+
if (offline) {
|
|
53
|
+
await writeFile(join(root, swFile), swSource(typeof offline === 'object' ? offline : {}), 'utf8');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Icons (unless the author supplied explicit ones).
|
|
57
|
+
if (config.icon && !config.icons) {
|
|
58
|
+
if (!existsSync(config.icon)) {
|
|
59
|
+
console.warn(`[spark-html-manifest] icon source not found: ${config.icon} — icons skipped`);
|
|
60
|
+
} else {
|
|
61
|
+
// sharp is imported lazily so the plugin never breaks a build where
|
|
62
|
+
// native deps can't load — you just generate icons elsewhere.
|
|
63
|
+
let sharp;
|
|
64
|
+
try {
|
|
65
|
+
sharp = (await import('sharp')).default;
|
|
66
|
+
} catch (e) {
|
|
67
|
+
console.warn(`[spark-html-manifest] sharp unavailable — icons skipped (${e.message})`);
|
|
68
|
+
sharp = null;
|
|
69
|
+
}
|
|
70
|
+
if (sharp) {
|
|
71
|
+
const sizes = config.sizes || ICON_SIZES;
|
|
72
|
+
for (const size of sizes) {
|
|
73
|
+
const dest = join(root, iconPath(config, size));
|
|
74
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
75
|
+
await sharp(config.icon).resize(size, size, { fit: 'cover' }).png().toFile(dest);
|
|
76
|
+
}
|
|
77
|
+
console.log(`[spark-html-manifest] ${sizes.length} icon(s) generated from ${config.icon}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Stamp head tags into every page (after prerender wrote its pages).
|
|
83
|
+
const block = headBlock();
|
|
84
|
+
let pages = 0;
|
|
85
|
+
for (const file of await htmlFiles(root)) {
|
|
86
|
+
const html = await readFile(file, 'utf8');
|
|
87
|
+
if (!/<\/head>/i.test(html)) continue; // fragment — skip
|
|
88
|
+
if (html.includes('data-spark-manifest')) continue; // already injected
|
|
89
|
+
await writeFile(file, html.replace(/<\/head>/i, `${block}\n</head>`), 'utf8');
|
|
90
|
+
pages++;
|
|
91
|
+
}
|
|
92
|
+
if (pages) console.log(`[spark-html-manifest] injected PWA tags into ${pages} page(s)`);
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
// Dev: serve the manifest (and worker) straight from config.
|
|
96
|
+
devRoutes() {
|
|
97
|
+
const routes = {
|
|
98
|
+
[`/${filename}`]: {
|
|
99
|
+
type: 'application/manifest+json',
|
|
100
|
+
body: () => JSON.stringify(manifestJson(config), null, 2),
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
if (offline) {
|
|
104
|
+
routes[`/${swFile}`] = {
|
|
105
|
+
type: 'text/javascript',
|
|
106
|
+
body: () => swSource(typeof offline === 'object' ? offline : {}),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return routes;
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
transformHtml(html, { dev }) {
|
|
113
|
+
if (!dev) return html;
|
|
114
|
+
if (html.includes('data-spark-manifest') || !/<\/head>/i.test(html)) return html;
|
|
115
|
+
return html.replace(/<\/head>/i, `${headBlock()}\n</head>`);
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* spark-html-manifest — PWA setup from a single config.
|
|
3
3
|
*
|
|
4
|
-
* name, icons, colors, display mode in one place; the
|
|
4
|
+
* name, icons, colors, display mode in one place; the bun build step turns it
|
|
5
5
|
* into manifest.webmanifest + resized icons + the <head> tags + (optionally)
|
|
6
6
|
* a minimal app-shell service worker. No manual icon exports, no copy-paste
|
|
7
7
|
* boilerplate.
|
|
8
8
|
*
|
|
9
|
-
* //
|
|
10
|
-
* import manifest from 'spark-html-manifest/
|
|
9
|
+
* // spark.config.js
|
|
10
|
+
* import manifest from 'spark-html-manifest/bun';
|
|
11
11
|
* plugins: [spark(), prerender(), manifest({
|
|
12
12
|
* name: 'My App',
|
|
13
13
|
* themeColor: '#ffd24a',
|
|
@@ -136,7 +136,7 @@ self.addEventListener('fetch', function (event) {
|
|
|
136
136
|
var url;
|
|
137
137
|
try { url = new URL(req.url); } catch (e) { return; }
|
|
138
138
|
if (url.origin !== self.location.origin) return;
|
|
139
|
-
//
|
|
139
|
+
// The build's hash-named assets are immutable — cache-first is always correct.
|
|
140
140
|
var immutable = /\\/assets\\/[^/]*[-.][A-Za-z0-9_-]{8,}\\.\\w+$/.test(url.pathname);
|
|
141
141
|
event.respondWith(caches.open(CACHE).then(function (cache) {
|
|
142
142
|
return cache.match(req, { ignoreSearch: req.mode === 'navigate' }).then(function (cached) {
|
package/src/vite.d.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type { ManifestConfig, AppShellSwOptions } from './index.js';
|
|
2
|
-
|
|
3
|
-
export interface ManifestVitePluginOptions extends ManifestConfig {
|
|
4
|
-
/** Source image — resized to every size in `sizes` (requires sharp). */
|
|
5
|
-
icon?: string;
|
|
6
|
-
/** Emitted manifest file name (default 'manifest.webmanifest'). */
|
|
7
|
-
filename?: string;
|
|
8
|
-
/** Emit + register the offline app-shell worker. */
|
|
9
|
-
offline?: boolean | AppShellSwOptions;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Vite plugin: manifest.webmanifest + resized icons + head tags +
|
|
14
|
-
* (optionally) the app-shell service worker. Put it after prerender().
|
|
15
|
-
*/
|
|
16
|
-
export default function sparkManifest(options: ManifestVitePluginOptions): {
|
|
17
|
-
name: string;
|
|
18
|
-
configResolved(config: unknown): void;
|
|
19
|
-
configureServer(server: unknown): void;
|
|
20
|
-
transformIndexHtml(html: string): string;
|
|
21
|
-
generateBundle(): Promise<void>;
|
|
22
|
-
closeBundle: { order: 'post'; handler(): Promise<void> };
|
|
23
|
-
};
|
package/src/vite.js
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* spark-html-manifest/vite — one config → a full PWA.
|
|
3
|
-
*
|
|
4
|
-
* Build: emits manifest.webmanifest, resizes the source icon into every
|
|
5
|
-
* configured size (sharp, imported lazily), optionally emits the app-shell
|
|
6
|
-
* worker, and injects <link rel="manifest"> + <meta name="theme-color">
|
|
7
|
-
* (+ worker registration) into every built page — runs in closeBundle
|
|
8
|
-
* (order post, after spark-prerender wrote its per-route HTML).
|
|
9
|
-
* Dev: serves the manifest + injects the tags so devtools' "installable"
|
|
10
|
-
* checks work locally too.
|
|
11
|
-
*
|
|
12
|
-
* import manifest from 'spark-html-manifest/vite';
|
|
13
|
-
* plugins: [spark(), prerender(), manifest({
|
|
14
|
-
* name: 'My App', themeColor: '#ffd24a', icon: 'public/icon.png', offline: true,
|
|
15
|
-
* })]
|
|
16
|
-
*/
|
|
17
|
-
import { join, resolve } from 'node:path';
|
|
18
|
-
import { readFile, writeFile, readdir, stat } from 'node:fs/promises';
|
|
19
|
-
import { existsSync } from 'node:fs';
|
|
20
|
-
import { manifestJson, manifestHtml, swSource, iconPath, ICON_SIZES } from './index.js';
|
|
21
|
-
|
|
22
|
-
async function htmlFiles(dir) {
|
|
23
|
-
const out = [];
|
|
24
|
-
for (const name of await readdir(dir)) {
|
|
25
|
-
const full = join(dir, name);
|
|
26
|
-
const s = await stat(full);
|
|
27
|
-
if (s.isDirectory()) out.push(...await htmlFiles(full));
|
|
28
|
-
else if (name.endsWith('.html')) out.push(full);
|
|
29
|
-
}
|
|
30
|
-
return out;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* @param {object} config Everything manifestJson takes, plus:
|
|
35
|
-
* @param {string} [config.icon] Source image — resized to every size in `sizes`.
|
|
36
|
-
* @param {string} [config.filename='manifest.webmanifest']
|
|
37
|
-
* @param {boolean|object} [config.offline=false] Emit + register the app-shell
|
|
38
|
-
* worker; pass { shell, version, file } to tune it.
|
|
39
|
-
*/
|
|
40
|
-
export default function sparkManifest(config = {}) {
|
|
41
|
-
const filename = config.filename || 'manifest.webmanifest';
|
|
42
|
-
const offline = config.offline || false;
|
|
43
|
-
const swFile = (offline && offline.file) || 'spark-manifest-sw.js';
|
|
44
|
-
const headBlock = () =>
|
|
45
|
-
manifestHtml(config, { href: filename, sw: offline ? swFile : undefined });
|
|
46
|
-
let outDir = 'dist';
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
name: 'spark-html-manifest',
|
|
50
|
-
configResolved(viteConfig) {
|
|
51
|
-
if (viteConfig && viteConfig.build && viteConfig.build.outDir) outDir = viteConfig.build.outDir;
|
|
52
|
-
},
|
|
53
|
-
|
|
54
|
-
// Dev: serve the manifest (and worker) straight from config.
|
|
55
|
-
configureServer(server) {
|
|
56
|
-
server.middlewares.use((req, res, next) => {
|
|
57
|
-
const path = (req.url || '').split('?')[0];
|
|
58
|
-
if (path === `/${filename}`) {
|
|
59
|
-
res.setHeader('Content-Type', 'application/manifest+json');
|
|
60
|
-
res.end(JSON.stringify(manifestJson(config), null, 2));
|
|
61
|
-
} else if (offline && path === `/${swFile}`) {
|
|
62
|
-
res.setHeader('Content-Type', 'text/javascript');
|
|
63
|
-
res.end(swSource(typeof offline === 'object' ? offline : {}));
|
|
64
|
-
} else {
|
|
65
|
-
next();
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
},
|
|
69
|
-
transformIndexHtml(html) {
|
|
70
|
-
if (html.includes('data-spark-manifest')) return html;
|
|
71
|
-
return html.replace(/<\/head>/i, `${headBlock()}\n</head>`);
|
|
72
|
-
},
|
|
73
|
-
|
|
74
|
-
// Build: emit manifest + icons + worker.
|
|
75
|
-
async generateBundle() {
|
|
76
|
-
this.emitFile({
|
|
77
|
-
type: 'asset',
|
|
78
|
-
fileName: filename,
|
|
79
|
-
source: JSON.stringify(manifestJson(config), null, 2),
|
|
80
|
-
});
|
|
81
|
-
if (offline) {
|
|
82
|
-
const shellOpts = typeof offline === 'object' ? offline : {};
|
|
83
|
-
this.emitFile({ type: 'asset', fileName: swFile, source: swSource(shellOpts) });
|
|
84
|
-
}
|
|
85
|
-
if (!config.icon || config.icons) return; // explicit icons — nothing to generate
|
|
86
|
-
if (!existsSync(config.icon)) {
|
|
87
|
-
console.warn(`[spark-html-manifest] icon source not found: ${config.icon} — icons skipped`);
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
// sharp is imported lazily so the plugin never breaks a build where
|
|
91
|
-
// native deps can't load — you just generate icons elsewhere.
|
|
92
|
-
let sharp;
|
|
93
|
-
try {
|
|
94
|
-
sharp = (await import('sharp')).default;
|
|
95
|
-
} catch (e) {
|
|
96
|
-
console.warn(`[spark-html-manifest] sharp unavailable — icons skipped (${e.message})`);
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
for (const size of config.sizes || ICON_SIZES) {
|
|
100
|
-
const png = await sharp(config.icon)
|
|
101
|
-
.resize(size, size, { fit: 'cover' })
|
|
102
|
-
.png()
|
|
103
|
-
.toBuffer();
|
|
104
|
-
this.emitFile({ type: 'asset', fileName: iconPath(config, size), source: png });
|
|
105
|
-
}
|
|
106
|
-
console.log(`[spark-html-manifest] ${(config.sizes || ICON_SIZES).length} icon(s) generated from ${config.icon}`);
|
|
107
|
-
},
|
|
108
|
-
|
|
109
|
-
// After prerender wrote its pages: stamp the head tags into every page.
|
|
110
|
-
closeBundle: {
|
|
111
|
-
order: 'post',
|
|
112
|
-
async handler() {
|
|
113
|
-
const root = resolve(outDir);
|
|
114
|
-
if (!existsSync(root)) return;
|
|
115
|
-
const block = headBlock();
|
|
116
|
-
let pages = 0;
|
|
117
|
-
for (const file of await htmlFiles(root)) {
|
|
118
|
-
const html = await readFile(file, 'utf8');
|
|
119
|
-
if (!/<\/head>/i.test(html)) continue; // fragment — skip
|
|
120
|
-
if (html.includes('data-spark-manifest')) continue; // already injected
|
|
121
|
-
await writeFile(file, html.replace(/<\/head>/i, `${block}\n</head>`), 'utf8');
|
|
122
|
-
pages++;
|
|
123
|
-
}
|
|
124
|
-
if (pages) console.log(`[spark-html-manifest] injected PWA tags into ${pages} page(s)`);
|
|
125
|
-
},
|
|
126
|
-
},
|
|
127
|
-
};
|
|
128
|
-
}
|