mnfst-run 1.0.2 → 1.0.6
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/package.json +1 -2
- package/serve.mjs +120 -44
- package/idiomorph.min.js +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mnfst-run",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Zero-dependency dev server for Manifest projects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"bin",
|
|
11
11
|
"serve.mjs",
|
|
12
|
-
"idiomorph.min.js",
|
|
13
12
|
"README.md"
|
|
14
13
|
],
|
|
15
14
|
"publishConfig": {
|
package/serve.mjs
CHANGED
|
@@ -3,27 +3,32 @@
|
|
|
3
3
|
* mnfst-run — zero-dependency dev server for Manifest projects.
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
|
-
* npx mnfst-run [dir] [--port 5001]
|
|
6
|
+
* npx mnfst-run [dir] [--port 5001] [--idle-shutdown 30] [--no-idle-shutdown]
|
|
7
7
|
*
|
|
8
|
-
* dir
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* dir Directory to serve (default: current directory). Any
|
|
9
|
+
* depth of nesting is valid, e.g.
|
|
10
|
+
* npx mnfst-run docs/articles/publishing
|
|
11
|
+
* --port Preferred port (default: PORT env var, then 5001).
|
|
12
|
+
* Auto-increments if the port is already in use.
|
|
13
|
+
* --idle-shutdown N Exit after N seconds with no open browser tabs
|
|
14
|
+
* (default 30). Only arms once a tab has connected, so
|
|
15
|
+
* the auto-launched browser has time to load.
|
|
16
|
+
* --no-idle-shutdown Disable auto-shutdown (useful in CI / headless cases
|
|
17
|
+
* where no browser will connect).
|
|
12
18
|
*
|
|
13
19
|
* SPA vs MPA is auto-detected: if the root index.html contains
|
|
14
20
|
* <meta name="manifest:prerendered"> the server disables SPA fallback.
|
|
15
21
|
*
|
|
16
22
|
* Live reload:
|
|
17
|
-
* .css
|
|
18
|
-
* .
|
|
19
|
-
*
|
|
20
|
-
* other
|
|
23
|
+
* .css → hot-swaps the matching stylesheet href (no reload, no flash)
|
|
24
|
+
* .csv/.json/etc. → dispatches manifest:dev-reload; data plugin re-fetches local
|
|
25
|
+
* sources and updates Alpine store reactively (no reload)
|
|
26
|
+
* other → full page reload
|
|
21
27
|
*/
|
|
22
28
|
import { createServer } from 'http';
|
|
23
29
|
import { readFileSync, statSync, watch } from 'fs';
|
|
24
30
|
import { join, extname, resolve, basename } from 'path';
|
|
25
31
|
import { exec } from 'child_process';
|
|
26
|
-
import { fileURLToPath } from 'url';
|
|
27
32
|
|
|
28
33
|
const MIME = {
|
|
29
34
|
'.html': 'text/html; charset=utf-8',
|
|
@@ -48,16 +53,11 @@ const MIME = {
|
|
|
48
53
|
'.map': 'application/json',
|
|
49
54
|
};
|
|
50
55
|
|
|
51
|
-
// Read idiomorph from its own file to avoid backtick/template-literal escaping issues.
|
|
52
|
-
// idiomorph.min.js ships alongside serve.mjs in the mnfst-run package.
|
|
53
|
-
const __dir = fileURLToPath(new URL('.', import.meta.url));
|
|
54
|
-
const IDIOMORPH_SRC = readFileSync(join(__dir, 'idiomorph.min.js'), 'utf8');
|
|
55
|
-
|
|
56
56
|
// Built once at startup. Injected only into full HTML documents (not fragments).
|
|
57
|
-
// - CSS changes
|
|
58
|
-
// -
|
|
59
|
-
// - other changes
|
|
60
|
-
const LIVE_RELOAD_SCRIPT =
|
|
57
|
+
// - CSS changes → hot-swaps the matching <link> href (no reload, no flash)
|
|
58
|
+
// - data file changes → dispatches manifest:dev-reload (data plugin re-fetches)
|
|
59
|
+
// - other changes → full page reload
|
|
60
|
+
const LIVE_RELOAD_SCRIPT = `<script>
|
|
61
61
|
(function () {
|
|
62
62
|
var es = new EventSource('/__mnfst_sse__');
|
|
63
63
|
es.onmessage = function (e) {
|
|
@@ -67,19 +67,6 @@ const LIVE_RELOAD_SCRIPT = '<script>\n' + IDIOMORPH_SRC + `
|
|
|
67
67
|
var base = l.href.split('?')[0];
|
|
68
68
|
if (base.endsWith(d.file)) l.href = base + '?t=' + Date.now();
|
|
69
69
|
});
|
|
70
|
-
} else if (d.type === 'html') {
|
|
71
|
-
fetch(location.href, { cache: 'no-store' })
|
|
72
|
-
.then(function (r) { return r.text(); })
|
|
73
|
-
.then(function (html) {
|
|
74
|
-
var newDoc = new DOMParser().parseFromString(html, 'text/html');
|
|
75
|
-
Idiomorph.morph(document.documentElement, newDoc.documentElement, {
|
|
76
|
-
callbacks: {
|
|
77
|
-
afterNodeAdded: function (node) {
|
|
78
|
-
if (node.nodeType === 1 && window.Alpine) Alpine.initTree(node);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
70
|
} else if (d.type === 'data') {
|
|
84
71
|
window.dispatchEvent(new CustomEvent('manifest:dev-reload'));
|
|
85
72
|
} else {
|
|
@@ -94,9 +81,17 @@ const LIVE_RELOAD_SCRIPT = '<script>\n' + IDIOMORPH_SRC + `
|
|
|
94
81
|
const args = process.argv.slice(2);
|
|
95
82
|
let dir = '.';
|
|
96
83
|
let port = process.env.PORT ? parseInt(process.env.PORT, 10) : 5001;
|
|
84
|
+
// Auto-shutdown: when the last open browser tab disconnects (SSE drops to 0
|
|
85
|
+
// clients) and stays gone for `idleShutdownSec`, the server exits. Cancelled
|
|
86
|
+
// by `--no-idle-shutdown` (e.g. CI, headless smoke tests, or any case where
|
|
87
|
+
// no browser will ever connect).
|
|
88
|
+
let idleShutdownSec = 30;
|
|
89
|
+
let idleShutdownEnabled = true;
|
|
97
90
|
|
|
98
91
|
for (let i = 0; i < args.length; i++) {
|
|
99
92
|
if ((args[i] === '--port' || args[i] === '-p') && args[i + 1]) { port = parseInt(args[++i], 10); continue; }
|
|
93
|
+
if (args[i] === '--no-idle-shutdown') { idleShutdownEnabled = false; continue; }
|
|
94
|
+
if (args[i] === '--idle-shutdown' && args[i + 1]) { idleShutdownSec = parseInt(args[++i], 10); continue; }
|
|
100
95
|
if (!args[i].startsWith('-')) dir = args[i];
|
|
101
96
|
}
|
|
102
97
|
|
|
@@ -108,8 +103,7 @@ function detectMPA(rootDir) {
|
|
|
108
103
|
return /name=["']manifest:prerendered["']/i.test(readFileSync(join(rootDir, 'index.html'), 'utf8'));
|
|
109
104
|
} catch { return false; }
|
|
110
105
|
}
|
|
111
|
-
const spa
|
|
112
|
-
const mode = spa ? 'SPA' : 'MPA';
|
|
106
|
+
const spa = !detectMPA(root);
|
|
113
107
|
|
|
114
108
|
// --- SSE clients ---
|
|
115
109
|
let clients = [];
|
|
@@ -120,6 +114,51 @@ function broadcast(data) {
|
|
|
120
114
|
clients.forEach(res => { try { res.write(msg); } catch { /* client gone */ } });
|
|
121
115
|
}
|
|
122
116
|
|
|
117
|
+
// --- Idle auto-shutdown ---
|
|
118
|
+
// `everConnected` keeps the timer dormant until at least one tab has opened —
|
|
119
|
+
// otherwise the server would exit before the auto-launched browser tab finishes
|
|
120
|
+
// loading. `idleTimer` runs only while clients.length === 0; any new SSE
|
|
121
|
+
// connection cancels it. The grace window also covers hard-reload churn (Cmd+R
|
|
122
|
+
// drops the SSE briefly, then reconnects in well under a second).
|
|
123
|
+
let everConnected = false;
|
|
124
|
+
let idleTimer = null;
|
|
125
|
+
|
|
126
|
+
function armIdleShutdown() {
|
|
127
|
+
if (!idleShutdownEnabled || !everConnected || idleTimer) return;
|
|
128
|
+
if (clients.length > 0) return;
|
|
129
|
+
idleTimer = setTimeout(() => {
|
|
130
|
+
console.log(`\nmnfst-run: no open tabs for ${idleShutdownSec}s — shutting down.\n`);
|
|
131
|
+
process.exit(0);
|
|
132
|
+
}, idleShutdownSec * 1000);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function cancelIdleShutdown() {
|
|
136
|
+
if (idleTimer) { clearTimeout(idleTimer); idleTimer = null; }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// --- .env support ---
|
|
140
|
+
// Minimal dotenv parser. Skips comments/blank lines, splits on first `=`,
|
|
141
|
+
// trims whitespace, strips wrapping single/double quotes. No multiline values,
|
|
142
|
+
// no `${VAR}` substitution within .env itself — we just want plain KEY=VALUE.
|
|
143
|
+
function parseDotenv(text) {
|
|
144
|
+
const out = {};
|
|
145
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
146
|
+
const line = rawLine.trim();
|
|
147
|
+
if (!line || line.startsWith('#')) continue;
|
|
148
|
+
const eq = line.indexOf('=');
|
|
149
|
+
if (eq === -1) continue;
|
|
150
|
+
const key = line.slice(0, eq).trim();
|
|
151
|
+
if (!key) continue;
|
|
152
|
+
let value = line.slice(eq + 1).trim();
|
|
153
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
154
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
155
|
+
value = value.slice(1, -1);
|
|
156
|
+
}
|
|
157
|
+
out[key] = value;
|
|
158
|
+
}
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
161
|
+
|
|
123
162
|
// --- File watcher ---
|
|
124
163
|
const IGNORE = /node_modules|\.git/;
|
|
125
164
|
try {
|
|
@@ -128,10 +167,11 @@ try {
|
|
|
128
167
|
clearTimeout(debounce);
|
|
129
168
|
debounce = setTimeout(() => {
|
|
130
169
|
const ext = extname(filename).toLowerCase();
|
|
131
|
-
|
|
170
|
+
const base = basename(filename);
|
|
171
|
+
if (base === '.env') {
|
|
172
|
+
broadcast({ type: 'reload' });
|
|
173
|
+
} else if (ext === '.css') {
|
|
132
174
|
broadcast({ type: 'css', file: '/' + filename.replace(/\\/g, '/') });
|
|
133
|
-
} else if (ext === '.html') {
|
|
134
|
-
broadcast({ type: 'html' });
|
|
135
175
|
} else if (['.csv', '.json', '.yaml', '.yml', '.md'].includes(ext)) {
|
|
136
176
|
broadcast({ type: 'data' });
|
|
137
177
|
} else {
|
|
@@ -171,6 +211,27 @@ function serveFile(res, filePath) {
|
|
|
171
211
|
const server = createServer((req, res) => {
|
|
172
212
|
const urlPath = decodeURIComponent(req.url.split('?')[0]);
|
|
173
213
|
|
|
214
|
+
// Virtual /env.js — generated from .env at the project root.
|
|
215
|
+
// Loaded by HTML before manifest.data.js so window.env is populated for
|
|
216
|
+
// ${VAR} interpolation in manifest.json. Returns an empty no-op if no
|
|
217
|
+
// .env exists, so the <script src="/env.js"> tag is always safe to include.
|
|
218
|
+
if (urlPath === '/env.js') {
|
|
219
|
+
const envPath = join(root, '.env');
|
|
220
|
+
let body = 'window.env = window.env || {};';
|
|
221
|
+
try {
|
|
222
|
+
if (isFile(envPath)) {
|
|
223
|
+
const env = parseDotenv(readFileSync(envPath, 'utf8'));
|
|
224
|
+
body = `window.env = Object.assign(window.env || {}, ${JSON.stringify(env)});`;
|
|
225
|
+
}
|
|
226
|
+
} catch { /* fall through to no-op */ }
|
|
227
|
+
res.writeHead(200, {
|
|
228
|
+
'Content-Type': 'application/javascript; charset=utf-8',
|
|
229
|
+
'Cache-Control': 'no-store',
|
|
230
|
+
});
|
|
231
|
+
res.end(body);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
174
235
|
// SSE endpoint for live reload
|
|
175
236
|
if (urlPath === '/__mnfst_sse__') {
|
|
176
237
|
res.writeHead(200, {
|
|
@@ -180,7 +241,12 @@ const server = createServer((req, res) => {
|
|
|
180
241
|
});
|
|
181
242
|
res.write(':\n\n'); // initial keep-alive comment
|
|
182
243
|
clients.push(res);
|
|
183
|
-
|
|
244
|
+
everConnected = true;
|
|
245
|
+
cancelIdleShutdown();
|
|
246
|
+
req.on('close', () => {
|
|
247
|
+
clients = clients.filter(c => c !== res);
|
|
248
|
+
if (clients.length === 0) armIdleShutdown();
|
|
249
|
+
});
|
|
184
250
|
return;
|
|
185
251
|
}
|
|
186
252
|
|
|
@@ -215,15 +281,25 @@ function tryListen(p, attempt = 0) {
|
|
|
215
281
|
console.error('mnfst-run: could not find a free port after 20 attempts.');
|
|
216
282
|
process.exit(1);
|
|
217
283
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
284
|
+
// Use explicit listeners so we can remove the pending 'listening' handler
|
|
285
|
+
// when retrying — otherwise each failed attempt leaves a once('listening')
|
|
286
|
+
// handler registered, and the eventual successful listen fires ALL of them,
|
|
287
|
+
// opening a browser tab for every port that was tried (including ports
|
|
288
|
+
// already taken by other projects).
|
|
289
|
+
const onListening = () => {
|
|
290
|
+
server.removeListener('error', onError);
|
|
223
291
|
const url = `http://localhost:${p}`;
|
|
224
292
|
console.log(`\n${label} running at ${url}\n`);
|
|
225
293
|
openBrowser(url);
|
|
226
|
-
}
|
|
294
|
+
};
|
|
295
|
+
const onError = err => {
|
|
296
|
+
server.removeListener('listening', onListening);
|
|
297
|
+
if (err.code === 'EADDRINUSE') tryListen(p + 1, attempt + 1);
|
|
298
|
+
else throw err;
|
|
299
|
+
};
|
|
300
|
+
server.once('listening', onListening);
|
|
301
|
+
server.once('error', onError);
|
|
302
|
+
server.listen(p);
|
|
227
303
|
}
|
|
228
304
|
|
|
229
305
|
tryListen(port);
|
package/idiomorph.min.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
var Idiomorph=function(){"use strict";const e=()=>{};const n={morphStyle:"outerHTML",callbacks:{beforeNodeAdded:e,afterNodeAdded:e,beforeNodeMorphed:e,afterNodeMorphed:e,beforeNodeRemoved:e,afterNodeRemoved:e,beforeAttributeUpdated:e},head:{style:"merge",shouldPreserve:e=>e.getAttribute("im-preserve")==="true",shouldReAppend:e=>e.getAttribute("im-re-append")==="true",shouldRemove:e,afterHeadMorphed:e},restoreFocus:true};function t(t,e,n={}){t=d(t);const r=f(e);const i=u(t,r,n);const o=a(i,()=>{return c(i,t,r,e=>{if(e.morphStyle==="innerHTML"){s(e,t,r);return Array.from(t.childNodes)}else{return l(e,t,r)}})});i.pantry.remove();return o}function l(e,t,n){const r=f(t);s(e,r,n,t,t.nextSibling);return Array.from(r.childNodes)}function a(e,t){if(!e.config.restoreFocus)return t();let n=document.activeElement;if(!(n instanceof HTMLInputElement||n instanceof HTMLTextAreaElement)){return t()}const{id:r,selectionStart:i,selectionEnd:o}=n;const l=t();if(r&&r!==document.activeElement?.getAttribute("id")){n=e.target.querySelector(`[id="${r}"]`);n?.focus()}if(n&&!n.selectionEnd&&o){n.setSelectionRange(i,o)}return l}const s=function(){function e(e,t,n,r=null,i=null){if(t instanceof HTMLTemplateElement&&n instanceof HTMLTemplateElement){t=t.content;n=n.content}r||=t.firstChild;for(const o of n.childNodes){if(r&&r!=i){const a=f(e,o,r,i);if(a){if(a!==r){h(e,r,a)}b(a,o,e);r=a.nextSibling;continue}}if(o instanceof Element){const s=o.getAttribute("id");if(e.persistentIds.has(s)){const c=p(t,s,r,e);b(c,o,e);r=c.nextSibling;continue}}const l=d(t,o,r,e);if(l){r=l.nextSibling}}while(r&&r!=i){const u=r;r=r.nextSibling;m(e,u)}}function d(e,t,n,r){if(r.callbacks.beforeNodeAdded(t)===false)return null;if(r.idMap.has(t)){const i=document.createElement(t.tagName);e.insertBefore(i,n);b(i,t,r);r.callbacks.afterNodeAdded(i);return i}else{const o=document.importNode(t,true);e.insertBefore(o,n);r.callbacks.afterNodeAdded(o);return o}}const f=function(){function e(e,t,n,r){let i=null;let o=t.nextSibling;let l=0;let a=n;while(a&&a!=r){if(c(a,t)){if(s(e,a,t)){return a}if(i===null){if(!e.idMap.has(a)){i=a}}}if(i===null&&o&&c(a,o)){l++;o=o.nextSibling;if(l>=2){i=undefined}}if(e.activeElementAndParents.includes(a))break;a=a.nextSibling}return i||null}function s(e,t,n){let r=e.idMap.get(t);let i=e.idMap.get(n);if(!i||!r)return false;for(const o of r){if(i.has(o)){return true}}return false}function c(e,t){const n=e;const r=t;return n.nodeType===r.nodeType&&n.tagName===r.tagName&&(!n.getAttribute?.("id")||n.getAttribute?.("id")===r.getAttribute?.("id"))}return e}();function m(e,t){if(e.idMap.has(t)){l(e.pantry,t,null)}else{if(e.callbacks.beforeNodeRemoved(t)===false)return;t.parentNode?.removeChild(t);e.callbacks.afterNodeRemoved(t)}}function h(t,e,n){let r=e;while(r&&r!==n){let e=r;r=r.nextSibling;m(t,e)}return r}function p(e,t,n,r){const i=r.target.getAttribute?.("id")===t&&r.target||r.target.querySelector(`[id="${t}"]`)||r.pantry.querySelector(`[id="${t}"]`);o(i,r);l(e,i,n);return i}function o(t,n){const r=t.getAttribute("id");while(t=t.parentNode){let e=n.idMap.get(t);if(e){e.delete(r);if(!e.size){n.idMap.delete(t)}}}}function l(t,n,r){if(t.moveBefore){try{t.moveBefore(n,r)}catch(e){t.insertBefore(n,r)}}else{t.insertBefore(n,r)}}return e}();const b=function(){function e(e,t,n){if(n.ignoreActive&&e===document.activeElement){return null}if(n.callbacks.beforeNodeMorphed(e,t)===false){return e}if(e instanceof HTMLHeadElement&&n.head.ignore){}else if(e instanceof HTMLHeadElement&&n.head.style!=="morph"){m(e,t,n)}else{r(e,t,n);if(!f(e,n)){s(n,e,t)}}n.callbacks.afterNodeMorphed(e,t);return e}function r(e,t,n){let r=t.nodeType;if(r===1){const i=e;const o=t;const l=i.attributes;const a=o.attributes;for(const s of a){if(d(s.name,i,"update",n)){continue}if(i.getAttribute(s.name)!==s.value){i.setAttribute(s.name,s.value)}}for(let e=l.length-1;0<=e;e--){const c=l[e];if(!c)continue;if(!o.hasAttribute(c.name)){if(d(c.name,i,"remove",n)){continue}i.removeAttribute(c.name)}}if(!f(i,n)){u(i,o,n)}}if(r===8||r===3){if(e.nodeValue!==t.nodeValue){e.nodeValue=t.nodeValue}}}function u(n,r,i){if(n instanceof HTMLInputElement&&r instanceof HTMLInputElement&&r.type!=="file"){let e=r.value;let t=n.value;o(n,r,"checked",i);o(n,r,"disabled",i);if(!r.hasAttribute("value")){if(!d("value",n,"remove",i)){n.value="";n.removeAttribute("value")}}else if(t!==e){if(!d("value",n,"update",i)){n.setAttribute("value",e);n.value=e}}}else if(n instanceof HTMLOptionElement&&r instanceof HTMLOptionElement){o(n,r,"selected",i)}else if(n instanceof HTMLTextAreaElement&&r instanceof HTMLTextAreaElement){let e=r.value;let t=n.value;if(d("value",n,"update",i)){return}if(e!==t){n.value=e}if(n.firstChild&&n.firstChild.nodeValue!==e){n.firstChild.nodeValue=e}}}function o(e,t,n,r){const i=t[n],o=e[n];if(i!==o){const l=d(n,e,"update",r);if(!l){e[n]=t[n]}if(i){if(!l){e.setAttribute(n,"")}}else{if(!d(n,e,"remove",r)){e.removeAttribute(n)}}}}function d(e,t,n,r){if(e==="value"&&r.ignoreActiveValue&&t===document.activeElement){return true}return r.callbacks.beforeAttributeUpdated(e,t,n)===false}function f(e,t){return!!t.ignoreActiveValue&&e===document.activeElement&&e!==document.body}return e}();function c(t,e,n,r){if(t.head.block){const i=e.querySelector("head");const o=n.querySelector("head");if(i&&o){const l=m(i,o,t);return Promise.all(l).then(()=>{const e=Object.assign(t,{head:{block:false,ignore:true}});return r(e)})}}return r(t)}function m(e,t,r){let i=[];let o=[];let l=[];let a=[];let s=new Map;for(const n of t.children){s.set(n.outerHTML,n)}for(const u of e.children){let e=s.has(u.outerHTML);let t=r.head.shouldReAppend(u);let n=r.head.shouldPreserve(u);if(e||n){if(t){o.push(u)}else{s.delete(u.outerHTML);l.push(u)}}else{if(r.head.style==="append"){if(t){o.push(u);a.push(u)}}else{if(r.head.shouldRemove(u)!==false){o.push(u)}}}}a.push(...s.values());let c=[];for(const d of a){let n=document.createRange().createContextualFragment(d.outerHTML).firstChild;if(r.callbacks.beforeNodeAdded(n)!==false){if("href"in n&&n.href||"src"in n&&n.src){let t;let e=new Promise(function(e){t=e});n.addEventListener("load",function(){t()});c.push(e)}e.appendChild(n);r.callbacks.afterNodeAdded(n);i.push(n)}}for(const f of o){if(r.callbacks.beforeNodeRemoved(f)!==false){e.removeChild(f);r.callbacks.afterNodeRemoved(f)}}r.head.afterHeadMorphed(e,{added:i,kept:l,removed:o});return c}const u=function(){function e(e,t,n){const{persistentIds:r,idMap:i}=f(e,t);const o=a(n);const l=o.morphStyle||"outerHTML";if(!["innerHTML","outerHTML"].includes(l)){throw`Do not understand how to morph style ${l}`}return{target:e,newContent:t,config:o,morphStyle:l,ignoreActive:o.ignoreActive,ignoreActiveValue:o.ignoreActiveValue,restoreFocus:o.restoreFocus,idMap:i,persistentIds:r,pantry:s(),activeElementAndParents:c(e),callbacks:o.callbacks,head:o.head}}function a(e){let t=Object.assign({},n);Object.assign(t,e);t.callbacks=Object.assign({},n.callbacks,e.callbacks);t.head=Object.assign({},n.head,e.head);return t}function s(){const e=document.createElement("div");e.hidden=true;document.body.insertAdjacentElement("afterend",e);return e}function c(e){let t=[];let n=document.activeElement;if(n?.tagName!=="BODY"&&e.contains(n)){while(n){t.push(n);if(n===e)break;n=n.parentElement}}return t}function u(e){let t=Array.from(e.querySelectorAll("[id]"));if(e.getAttribute?.("id")){t.push(e)}return t}function d(n,e,r,t){for(const i of t){const o=i.getAttribute("id");if(e.has(o)){let t=i;while(t){let e=n.get(t);if(e==null){e=new Set;n.set(t,e)}e.add(o);if(t===r)break;t=t.parentElement}}}}function f(e,t){const n=u(e);const r=u(t);const i=m(n,r);let o=new Map;d(o,i,e,n);const l=t.__idiomorphRoot||t;d(o,i,l,r);return{persistentIds:i,idMap:o}}function m(e,t){let n=new Set;let r=new Map;for(const{id:o,tagName:l}of e){if(r.has(o)){n.add(o)}else{r.set(o,l)}}let i=new Set;for(const{id:o,tagName:l}of t){if(i.has(o)){n.add(o)}else if(r.get(o)===l){i.add(o)}}for(const o of n){i.delete(o)}return i}return e}();const{normalizeElement:d,normalizeParent:f}=function(){const i=new WeakSet;function e(e){if(e instanceof Document){return e.documentElement}else{return e}}function r(e){if(e==null){return document.createElement("div")}else if(typeof e==="string"){return r(l(e))}else if(i.has(e)){return e}else if(e instanceof Node){if(e.parentNode){return new o(e)}else{const t=document.createElement("div");t.append(e);return t}}else{const t=document.createElement("div");for(const n of[...e]){t.append(n)}return t}}class o{constructor(e){this.originalNode=e;this.realParentNode=e.parentNode;this.previousSibling=e.previousSibling;this.nextSibling=e.nextSibling}get childNodes(){const e=[];let t=this.previousSibling?this.previousSibling.nextSibling:this.realParentNode.firstChild;while(t&&t!=this.nextSibling){e.push(t);t=t.nextSibling}return e}querySelectorAll(r){return this.childNodes.reduce((t,e)=>{if(e instanceof Element){if(e.matches(r))t.push(e);const n=e.querySelectorAll(r);for(let e=0;e<n.length;e++){t.push(n[e])}}return t},[])}insertBefore(e,t){return this.realParentNode.insertBefore(e,t)}moveBefore(e,t){return this.realParentNode.moveBefore(e,t)}get __idiomorphRoot(){return this.originalNode}}function l(n){let r=new DOMParser;let e=n.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim,"");if(e.match(/<\/html>/)||e.match(/<\/head>/)||e.match(/<\/body>/)){let t=r.parseFromString(n,"text/html");if(e.match(/<\/html>/)){i.add(t);return t}else{let e=t.firstChild;if(e){i.add(e)}return e}}else{let e=r.parseFromString("<body><template>"+n+"</template></body>","text/html");let t=e.body.querySelector("template").content;i.add(t);return t}}return{normalizeElement:e,normalizeParent:r}}();return{morph:t,defaults:n}}();
|