@markless/bundler 0.2.1 → 0.3.0

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.
@@ -1,4 +1,5 @@
1
1
  import { joinURL } from "ufo";
2
+ import { ASYNC_PROTOCOL_VERSION } from "@markless/serializer";
2
3
  //#region src/build/preload-plan.ts
3
4
  const DEFAULT_MIN_PROBABILITY = 0;
4
5
  const JAVASCRIPT_MODULE_RE = /\.(?:mjs|js)(?:[?#].*)?$/;
@@ -33,7 +34,7 @@ function planModulePreloads(input) {
33
34
  probability
34
35
  });
35
36
  };
36
- const visit = (name, probability, priority, fetchPriority, seen, suppressed = false) => {
37
+ const visit = (name, probability, priority, fetchPriority, seen, suppressed = false, suppressRootOnly = false) => {
37
38
  if (probability < minProbability || planned.size >= maxPreloads) return;
38
39
  if (!suppressed) {
39
40
  const priorityRank = PRIORITY_RANK[priority];
@@ -52,7 +53,7 @@ function planModulePreloads(input) {
52
53
  if (!suppressed) addModule(name, probability, priority, fetchPriority);
53
54
  return;
54
55
  }
55
- for (const dep of record.deps.filter((item) => item.kind === "static")) visit(dep.name, probability * dep.probability, priority, fetchPriority, nextSeen, suppressed);
56
+ for (const dep of record.deps.filter((item) => item.kind === "static")) visit(dep.name, probability * dep.probability, priority, fetchPriority, nextSeen, suppressed && !suppressRootOnly);
56
57
  if (!suppressed) addModule(name, probability, priority, fetchPriority);
57
58
  for (const dep of record.deps.filter((item) => item.kind === "dynamic")) {
58
59
  const edgeProbability = JAVASCRIPT_MODULE_RE.test(name) ? dep.probability : 1;
@@ -61,7 +62,7 @@ function planModulePreloads(input) {
61
62
  };
62
63
  for (const root of input.roots) {
63
64
  const normalized = normalizeRoot(root);
64
- visit(normalized.name, 1, normalized.priority, normalized.fetchPriority, /* @__PURE__ */ new Set(), normalized.edges === "dynamic-only");
65
+ visit(normalized.name, 1, normalized.priority, normalized.fetchPriority, /* @__PURE__ */ new Set(), normalized.edges === "dynamic-only" || normalized.edges === "dependencies-only", normalized.edges === "dependencies-only");
65
66
  }
66
67
  return [...planned.values()].sort((left, right) => {
67
68
  const priority = PRIORITY_RANK[right.priority] - PRIORITY_RANK[left.priority];
@@ -128,179 +129,18 @@ function priorityToFetchPriority(priority) {
128
129
  if (priority === "auto") return "auto";
129
130
  return priority;
130
131
  }
131
- const MARKLESS_BUILD_PREFIX = `build/`;
132
- const MARKLESS_BUNDLE_GRAPH = `${MARKLESS_BUILD_PREFIX}bundle-graph.json`;
133
- const WEB_RUNTIME_CAPABILITY_GROUPS = [
134
- {
135
- name: "markless-resume-branches",
136
- test: fileBasenamePattern("resume-branches")
137
- },
138
- {
139
- name: "markless-resume-behaviors",
140
- test: fileBasenamePattern("resume-behaviors")
141
- },
142
- {
143
- name: "markless-resume-repeats",
144
- test: fileBasenamePattern("resume-keyed-repeats", "repeat-runtime")
145
- },
146
- {
147
- name: "markless-resume-async",
148
- test: fileBasenamePattern("resume-async-boundaries", "resume-async-wiring")
149
- },
150
- {
151
- name: "markless-resume-shared-patch",
152
- test: fileBasenamePattern("resume-shared-patch")
153
- },
154
- {
155
- name: "markless-resume-runtime",
156
- test: fileBasenamePattern("resume-runtime")
157
- },
158
- {
159
- name: "markless-resume-runtime-start",
160
- test: fileBasenamePattern("resume-runtime-start")
161
- },
162
- {
163
- name: "markless-resume-runtime-shared",
164
- test: fileBasenamePattern("resume-runtime-shared")
165
- },
166
- {
167
- name: "markless-resume-events",
168
- test: fileBasenamePattern("resume-events")
169
- },
170
- {
171
- name: "markless-resume-handoff",
172
- test: fileBasenamePattern("resume-handoff")
173
- },
174
- {
175
- name: "markless-resume-locators",
176
- test: fileBasenamePattern("resume-locators")
177
- },
178
- {
179
- name: "markless-resume-errors",
180
- test: fileBasenamePattern("resume-errors")
181
- },
182
- {
183
- name: "markless-resume-sync-computed",
184
- test: fileBasenamePattern("resume-sync-computed")
185
- },
186
- {
187
- name: "markless-resume-sync-demand",
188
- test: fileBasenamePattern("resume-sync-demand")
189
- },
190
- {
191
- name: "markless-payload-full",
192
- test: fileBasenamePattern("payload-full")
193
- },
194
- {
195
- name: "markless-payload-resume",
196
- test: fileBasenamePattern("payload-resume")
197
- },
198
- {
199
- name: "markless-inline-payload-document",
200
- test: /[/\\]web[/\\]src[/\\]inline[/\\]payload-document\.ts(?:[?#].*)?$/
201
- },
202
- {
203
- name: "markless-payload-document",
204
- test: fileBasenamePattern("payload-document", "payload-resume-registry")
205
- },
206
- {
207
- name: "markless-payload-graph-construct",
208
- test: fileBasenamePattern("payload-graph-construct")
209
- },
210
- {
211
- name: "markless-dom-journal",
212
- test: fileBasenamePattern("dom-journal")
213
- },
214
- {
215
- name: "markless-protocol-decode",
216
- test: fileBasenamePattern("protocol-client", "protocol-state")
217
- },
218
- {
219
- name: "markless-value-decode",
220
- test: fileBasenamePattern("value-decode-client", "value-decode-extensions")
221
- },
222
- {
223
- name: "markless-payload-leaves",
224
- test: fileBasenamePattern("payload", "payload-document")
225
- },
226
- {
227
- name: "markless-dev-log",
228
- test: /virtual:markless:dev-log|[/\\]web[/\\]src[/\\]dev-log\.ts(?:[?#].*)?$/
229
- },
230
- {
231
- name: "markless-resume-core",
232
- test: fileBasenamePattern("resume")
233
- },
234
- {
235
- name: "markless-runtime-graph-core",
236
- test: fileBasenamePattern("graph-core")
237
- },
238
- {
239
- name: "markless-runtime-graph-collections",
240
- test: fileBasenamePattern("graph-collections")
241
- },
242
- {
243
- name: "markless-runtime-graph-computed",
244
- test: fileBasenamePattern("graph-computed")
245
- },
246
- {
247
- name: "markless-runtime-graph-async",
248
- test: fileBasenamePattern("graph-async")
249
- },
250
- {
251
- name: "markless-runtime-graph-scheduler",
252
- test: fileBasenamePattern("graph-scheduler")
253
- },
254
- {
255
- name: "markless-runtime-graph-shared",
256
- test: fileBasenamePattern("graph-shared")
257
- },
258
- {
259
- name: "markless-graph",
260
- test: /[/\\](?:runtime[/\\]src[/\\](?!graph-(?:core|collections|computed|async|scheduler|shared)\.ts)|core[/\\]src[/\\]runtime)[/\\]?/
261
- },
262
- {
263
- name: "markless-serializer",
264
- test: /[/\\]serializer[/\\]src[/\\](?!protocol-validation)/
265
- }
266
- ];
267
- function fileBasenamePattern(...names) {
268
- return new RegExp(`[/\\\\](?:${names.join("|")})\\.ts(?:[?#].*)?$`);
269
- }
270
- const MARKLESS_RUNTIME_GROUPS = [...WEB_RUNTIME_CAPABILITY_GROUPS, {
271
- name: "markless-runtime",
272
- test: /[/\\]@markless[/\\]runtime[/\\]/
273
- }];
274
- function outputDefaults(output, environment) {
275
- if (environment === "lib") return output;
276
- const next = {
277
- ...output,
278
- hoistTransitiveImports: false
279
- };
280
- if (environment === "server") {
281
- next.entryFileNames ??= "[name].js";
282
- next.chunkFileNames ??= "chunk-[hash].js";
283
- next.codeSplitting = marklessCodeSplitting(next.codeSplitting);
284
- return next;
285
- }
286
- next.entryFileNames ??= `${MARKLESS_BUILD_PREFIX}chunk-[hash].js`;
287
- next.chunkFileNames ??= `${MARKLESS_BUILD_PREFIX}chunk-[hash].js`;
288
- next.minifyInternalExports = true;
289
- next.strictExecutionOrder = true;
290
- next.codeSplitting = marklessCodeSplitting(next.codeSplitting);
291
- return next;
292
- }
293
- function marklessCodeSplitting(codeSplitting) {
294
- if (typeof codeSplitting === "boolean") throw new Error("@markless/bundler requires output.codeSplitting to be an object so runtime chunks can be grouped.");
295
- return {
296
- ...codeSplitting,
297
- groups: [...MARKLESS_RUNTIME_GROUPS, ...codeSplitting?.groups ?? []]
298
- };
299
- }
300
132
  //#endregion
301
133
  //#region src/execution-log.ts
302
134
  const MARKLESS_EXECUTION_LOG_MODULE_ID = "virtual:markless:dev-log";
303
- const MARKLESS_EXECUTION_SIZES = `${MARKLESS_BUILD_PREFIX}execution-sizes.json`;
135
+ const EXECUTION_LOG_DISPATCH_MODULE_IDS = ["web:render-csr", "web:resume-events"];
136
+ const MARKLESS_EXECUTION_LEDGER_GLOBAL = "__marklessExecutionLedger";
137
+ const MARKLESS_EXECUTION_LEDGER_INIT = "{ unit: 'chunk-raw-bytes', load: { app: 0, framework: 0, instrument: 0, inline: 0, modules: [] }, total: { app: 0, framework: 0, instrument: 0, inline: 0 }, turns: [], incomplete: null }";
138
+ const EXECUTION_LOG_GESTURE_EVENTS = [
139
+ "pointerdown",
140
+ "touchstart",
141
+ "keydown"
142
+ ];
143
+ const EXECUTION_LOG_FRAMEWORK_ID_PATTERN = "/^(?:web|runtime|serializer|router|core|analyzer):/";
304
144
  function normalizeExecutionLogMode(mode) {
305
145
  return mode ?? "auto";
306
146
  }
@@ -309,6 +149,9 @@ function injectExecutionLogModuleHook(source, moduleId, mode) {
309
149
  return `globalThis.__mxLog?.add(${JSON.stringify(moduleId)});\n${source}`;
310
150
  }
311
151
  const EXECUTION_LOG_HOOK_LINE = /^globalThis\.__mxLog\?\.add\("(?:[^"\\]|\\.)*"\);\n/;
152
+ function hasExecutionLogModuleHook(source) {
153
+ return EXECUTION_LOG_HOOK_LINE.test(source);
154
+ }
312
155
  function requalifyExecutionLogModuleHook(source, moduleId) {
313
156
  if (!EXECUTION_LOG_HOOK_LINE.test(source)) return source;
314
157
  return source.replace(EXECUTION_LOG_HOOK_LINE, `globalThis.__mxLog?.add(${JSON.stringify(moduleId)});\n`);
@@ -334,9 +177,10 @@ const marklessSizesUrl = ${JSON.stringify(options.sizesUrl ?? null)};
334
177
  let marklessSizesPromise;
335
178
  let marklessAttribution;
336
179
  let marklessInstallPromise;
180
+ let marklessRenderSummaryTurn;
337
181
  function modules(log) { return log ? [...log] : []; }
338
182
  function sizeRecord(input) { return !input ? undefined : typeof input === 'number' ? { raw: input, estimated: true } : input; }
339
- function normalizeSizes(input) { if (!input) return undefined; const map = new Map(); for (const id of Object.keys(input)) if (id !== 'attribution') map.set(id, sizeRecord(input[id])); return map; }
183
+ function normalizeSizes(input) { if (!input) return undefined; const map = new Map(); for (const id of Object.keys(input)) if (id !== 'attribution' && id !== 'unshipped') map.set(id, sizeRecord(input[id])); return map; }
340
184
  async function loadModuleSizes(input) {
341
185
  if (input.moduleSizes) return normalizeSizes(input.moduleSizes);
342
186
  if (marklessDefaultModuleSizes) { marklessAttribution = marklessDefaultAttribution; return normalizeSizes(marklessDefaultModuleSizes); }
@@ -347,7 +191,20 @@ async function loadModuleSizes(input) {
347
191
  function symbolParts(id) { const match = /^virtual:markless:symbol:([^:]+):([^:]+)$/.exec(id); if (!match) return null; try { return { source: decodeURIComponent(match[1]), symbolId: decodeURIComponent(match[2]) }; } catch { return null; } }
348
192
  function baseSymbolId(id) { if (!id.startsWith('bound:')) return null; const separator = id.indexOf(':', 'bound:'.length); if (separator < 0) return null; try { return decodeURIComponent(id.slice('bound:'.length, separator)); } catch { return null; } }
349
193
  function routeFile() { const text = document.querySelector?.('script[type="@markless/core/route"]')?.textContent; if (!text) return null; try { const value = JSON.parse(text).file; return typeof value === 'string' ? value : null; } catch { return null; } }
350
- function qualifySymbolIds(ids, hostNodeId, sizes, tables) { const route = routeFile(); if (!route || route.includes('..') || route.includes('\\\\')) return ids; const canonical = route.replace(/^\\/+/, ''); const scopes = tables && Object.prototype.hasOwnProperty.call(tables, canonical) ? tables[canonical] : undefined; if (!scopes) return ids; const host = /^((?:c\\d+:)*)[^:]+$/.exec(hostNodeId); const hostScope = host && host[1]; return ids.map((id) => { const own = /^((?:c\\d+:)+)(.*)$/.exec(id); const scope = own ? own[1] : hostScope; if (scope === null || !Object.prototype.hasOwnProperty.call(scopes, scope)) return id; const local = own ? own[2] : id; const base = baseSymbolId(local); const sizeSymbolId = base || local; if (!sizeSymbolId.startsWith('symbol:')) return id; const matches = [...(sizes?.keys() || [])].filter((key) => { const match = /^virtual:markless:symbol:([^:]+):([^:]+)$/.exec(key); if (!match || match[1] !== scopes[scope]) return false; try { return decodeURIComponent(match[2]) === sizeSymbolId; } catch { return false; } }); if (matches.length !== 1) return id; if (!base) return matches[0]; const match = /^virtual:markless:symbol:([^:]+):/.exec(matches[0]); return match ? 'virtual:markless:symbol:' + match[1] + ':' + encodeURIComponent(local) : id; }); }
194
+ // Attribution tables are keyed by the bare path this lookup can name: the
195
+ // document's route file, or — for a routeless page — the build's single root,
196
+ // which is the only page it can be.
197
+ function attributionScopes(tables) { if (!tables) return undefined; const route = routeFile(); if (route) { if (route.includes('..') || route.includes('\\\\')) return undefined; const canonical = route.replace(/^\\/+/, ''); return Object.prototype.hasOwnProperty.call(tables, canonical) ? tables[canonical] : undefined; } const roots = Object.keys(tables); return roots.length === 1 ? tables[roots[0]] : undefined; }
198
+ // A handler renders inside one component but is owned by whichever component
199
+ // created the closure — a prop-passed onClick renders at the child's scope and
200
+ // belongs to the parent. The owner is therefore somewhere on the host's render
201
+ // ancestry, so once the host's OWN scope is one the table names, the chain is
202
+ // walked outward and the first scope holding exactly one candidate wins. Never a
203
+ // guess: a host in a scope the table does not name is refused outright, a scope
204
+ // with zero or several candidates is skipped, and an id nobody owns uniquely
205
+ // stays unmapped.
206
+ function scopeChain(scope) { const chain = []; for (let current = scope; ; ) { chain.push(current); if (!current) return chain; const next = current.replace(/c\\d+:$/, ''); if (next === current) return chain; current = next; } }
207
+ function qualifySymbolIds(ids, hostNodeId, sizes, tables) { const scopes = attributionScopes(tables); if (!scopes) return ids; const host = /^((?:c\\d+:)*)[^:]+$/.exec(hostNodeId); const hostScope = host && host[1]; return ids.map((id) => { const own = /^((?:c\\d+:)+)(.*)$/.exec(id); const scope = own ? own[1] : hostScope; if (scope === null || scope === undefined || !Object.prototype.hasOwnProperty.call(scopes, scope)) return id; const local = own ? own[2] : id; const base = baseSymbolId(local); const sizeSymbolId = base || local; if (!sizeSymbolId.startsWith('symbol:')) return id; for (const candidate of scopeChain(scope)) { if (!Object.prototype.hasOwnProperty.call(scopes, candidate)) continue; const matches = [...(sizes?.keys() || [])].filter((key) => { const match = /^virtual:markless:symbol:([^:]+):([^:]+)$/.exec(key); if (!match || match[1] !== scopes[candidate]) return false; try { return decodeURIComponent(match[2]) === sizeSymbolId; } catch { return false; } }); if (matches.length !== 1) continue; if (!base) return matches[0]; const match = /^virtual:markless:symbol:([^:]+):/.exec(matches[0]); return match ? 'virtual:markless:symbol:' + match[1] + ':' + encodeURIComponent(local) : id; } return id; }); }
351
208
  function canonicalId(id, sizes) {
352
209
  if (!sizes || sizes.has(id)) return id;
353
210
  const qualified = symbolParts(id); const local = qualified ? qualified.symbolId : id.replace(/^(?:c\\d+:)+/, ''); const sizeSymbolId = baseSymbolId(local) || local;
@@ -361,11 +218,91 @@ function accounting(items, sizes) {
361
218
  const ids = [...new Set(items.map((id) => canonicalId(id, sizes)))];
362
219
  if (!sizes) return { appBytes: ids.length ? null : 0, instrumentBytes: 0, appModules: ids.length, instrumentModules: 0, estimated: { app: false, instrument: false }, unmappedIds: [] };
363
220
  let appBytes = 0, instrumentBytes = 0, appModules = 0, instrumentModules = 0, appEstimated = false, instrumentEstimated = false; const unmappedIds = [];
364
- for (const id of ids) { const record = sizes.get(id); if (!record) { appModules++; unmappedIds.push(id); continue; } const bytes = record.estimated ? record.raw : (record.gzip ?? record.raw); if (record.instrument) { instrumentModules++; instrumentBytes += bytes; instrumentEstimated ||= !!record.estimated; } else { appModules++; appBytes += bytes; appEstimated ||= !!record.estimated; } }
221
+ for (const id of ids) { const record = sizes.get(id); if (!record) { appModules++; unmappedIds.push(id); continue; } const bytes = record.raw; if (record.instrument) { instrumentModules++; instrumentBytes += bytes; instrumentEstimated ||= !!record.estimated; } else { appModules++; appBytes += bytes; appEstimated ||= !!record.estimated; } }
365
222
  return { appBytes: unmappedIds.length ? null : appBytes, instrumentBytes, appModules, instrumentModules, estimated: { app: appEstimated, instrument: instrumentEstimated }, unmappedIds };
366
223
  }
367
- function category(a, name) { const bytes = name === 'app' ? a.appBytes : a.instrumentBytes; const count = name === 'app' ? a.appModules : a.instrumentModules; if (bytes === null) return count + ' ' + name + ' module' + (count === 1 ? '' : 's') + (name === 'app' && a.unmappedIds.length ? ' (bytes unknown; ' + a.unmappedIds.length + ' unmapped)' : ''); return (bytes / 1024).toFixed(1) + ' KB' + (a.estimated[name] ? ' est. source' : '') + ' ' + name; }
368
224
  function rowKb(items, sizes) { const a = accounting(items, sizes); if (a.unmappedIds.length) return 'bytes unknown'; const total = (a.appBytes || 0) + (a.instrumentBytes || 0); return (total / 1024).toFixed(1) + ' KB' + (a.estimated.app || a.estimated.instrument ? ' est. source' : ''); }
225
+ // One cumulative ledger owns every number the console prints. A module's charge
226
+ // is the whole raw byte length of the chunk that absorbed it (several modules in
227
+ // one chunk are each charged the whole chunk), which is what unit says.
228
+ function marklessLedger() { return (globalThis.${MARKLESS_EXECUTION_LEDGER_GLOBAL} ||= ${MARKLESS_EXECUTION_LEDGER_INIT}); }
229
+ function ledgerCategory(id, record) { return record && record.instrument ? 'instrument' : ${EXECUTION_LOG_FRAMEWORK_ID_PATTERN}.test(id) ? 'framework' : 'app'; }
230
+ function ledgerCharge(l, ids, sizes) {
231
+ const seen = (l.chargedIds ||= new Set());
232
+ const seenChunks = (l.chargedChunks ||= new Set());
233
+ const delta = { app: 0, framework: 0, instrument: 0, gzip: 0, modules: [], unmapped: [] };
234
+ for (const raw of ids) {
235
+ const id = canonicalId(raw, sizes);
236
+ const record = sizes && sizes.get(id);
237
+ // The unit is the chunk, so the dedupe unit is the chunk: one chunk reached
238
+ // under two names (a symbol id and the emitted specifier Rolldown rewrote
239
+ // its hook to) is one charge, not two. Dev estimates carry no chunk, so
240
+ // there the id is still the unit.
241
+ const chunk = record && record.chunk;
242
+ if (chunk ? seenChunks.has(chunk) : seen.has(id)) continue;
243
+ if (chunk) seenChunks.add(chunk); else seen.add(id);
244
+ if (!record) { delta.unmapped.push(id); continue; }
245
+ if (record.estimated) l.unit = 'estimated-source-bytes';
246
+ delta[ledgerCategory(id, record)] += record.raw; delta.gzip += record.gzip || 0; delta.modules.push(id);
247
+ }
248
+ if (delta.unmapped.length) l.incomplete = { reason: 'unmapped-id', ids: [...new Set([...(l.incomplete ? l.incomplete.ids : []), ...delta.unmapped])] };
249
+ return delta;
250
+ }
251
+ // Load is everything charged before the FIRST USER GESTURE, which the activation
252
+ // script latches on the gesture itself (loadClosed) rather than on the kind of
253
+ // whichever turn reports first. On an SSR page nothing runs until the click, and
254
+ // the instrument and the wake entry both arrive with it; their render/resume
255
+ // turns are work that gesture caused, so their charge joins the gesture's turn —
256
+ // merged into it if it already exists, carried until it does if it does not.
257
+ function ledgerApply(l, kind, label, delta) {
258
+ const gesture = kind !== 'render' && kind !== 'resume';
259
+ if (gesture) l.loadClosed = true;
260
+ for (const name of ['app', 'framework', 'instrument']) { l.total[name] += delta[name]; if (!l.loadClosed) l.load[name] += delta[name]; }
261
+ l.totalGzip = (l.totalGzip || 0) + delta.gzip;
262
+ if (!l.loadClosed) { l.loadGzip = (l.loadGzip || 0) + delta.gzip; for (const id of delta.modules) l.load.modules.push(id); }
263
+ if (!gesture && l.loadClosed) {
264
+ const open = l.open;
265
+ if (open) { open.delta.app += delta.app; open.delta.framework += delta.framework; open.delta.instrument += delta.instrument; for (const id of delta.modules) open.modules.push(id); return open; }
266
+ const held = (l.carry ||= { app: 0, framework: 0, instrument: 0, modules: [] });
267
+ held.app += delta.app; held.framework += delta.framework; held.instrument += delta.instrument; for (const id of delta.modules) held.modules.push(id);
268
+ delta = { app: 0, framework: 0, instrument: 0, modules: [] };
269
+ }
270
+ const carry = gesture ? l.carry : undefined;
271
+ if (carry) l.carry = undefined;
272
+ const turn = { kind, label, delta: { app: delta.app + (carry ? carry.app : 0), framework: delta.framework + (carry ? carry.framework : 0), instrument: delta.instrument + (carry ? carry.instrument : 0) }, modules: carry ? [...carry.modules, ...delta.modules] : delta.modules };
273
+ l.turns.push(turn); if (gesture) l.open = turn;
274
+ return turn;
275
+ }
276
+ function ledgerKb(bytes) { return (bytes / 1024).toFixed(1) + ' KB'; }
277
+ // The instrument is dev-only weight, so it stays out of the headline totals and
278
+ // keeps its own category, exactly as the pre-ledger lines did.
279
+ function ledgerVisible(part) { return part.app + part.framework + part.inline; }
280
+ function ledgerLine(l, turn) {
281
+ let line = 'markless: ' + ledgerKb(ledgerVisible(l.load)) + ' executed at load';
282
+ if (turn && turn.kind !== 'render' && turn.kind !== 'resume') line += ' · this ' + turn.kind + ' +' + ledgerKb(turn.delta.app + turn.delta.framework);
283
+ line += ' · total ' + ledgerKb(ledgerVisible(l.total));
284
+ if (l.totalGzip) line += ' (gzip ' + ledgerKb(l.totalGzip) + ')';
285
+ if (l.incomplete) line += ' · ' + l.incomplete.ids.length + ' unmapped (excluded)';
286
+ return line;
287
+ }
288
+ function ledgerMirror(l, turn, text, attribute) {
289
+ const root = document.documentElement; if (!root) return;
290
+ root.setAttribute(attribute, text);
291
+ root.setAttribute('data-markless-log-unit', l.unit);
292
+ root.setAttribute('data-markless-log-load-bytes', String(ledgerVisible(l.load)));
293
+ root.setAttribute('data-markless-log-total-bytes', String(ledgerVisible(l.total)));
294
+ root.setAttribute('data-markless-log-inline-bytes', String(l.total.inline));
295
+ if (turn) {
296
+ root.setAttribute('data-markless-log-app-bytes', String(turn.delta.app));
297
+ root.setAttribute('data-markless-log-framework-bytes', String(turn.delta.framework));
298
+ root.setAttribute('data-markless-log-instrument-bytes', String(turn.delta.instrument));
299
+ root.setAttribute('data-markless-log-turn-bytes', String(turn.delta.app + turn.delta.framework));
300
+ root.setAttribute('data-markless-log-turn-modules', turn.modules.join(' '));
301
+ // The headline carries no selector, so the turn's identity lives here.
302
+ root.setAttribute('data-markless-log-turn-label', turn.kind + (turn.label ? ' ' + turn.label : ''));
303
+ }
304
+ if (l.incomplete) root.setAttribute('data-markless-log-incomplete', String(l.incomplete.ids.length)); else root.removeAttribute('data-markless-log-incomplete');
305
+ }
369
306
  function warmIds(event) { return [...new Set([event.dispatchModuleId, ...((event.eventRecord && event.eventRecord.symbolIds) || [])].filter(Boolean))]; }
370
307
  function causeRows(input) {
371
308
  const before = input.before || new Set(); const after = input.after || new Set(); const woken = [...after].filter((id) => !before.has(id)); const record = input.eventRecord;
@@ -376,34 +313,40 @@ function causeRows(input) {
376
313
  if (record && !(input.view?.behaviors || []).some((b) => b.hostNodeId === record.hostNodeId)) rows.push('skip behavior — no matching record touched');
377
314
  return rows;
378
315
  }
379
- function mirror(text, a) {
316
+ function mirrorInteraction(l, turn, text) {
380
317
  const root = document.documentElement; if (!root) return;
381
- const count = Number(root.getAttribute('data-markless-log-interactions') || '0') + 1;
382
- root.setAttribute('data-markless-log-interactions', String(count)); root.setAttribute('data-markless-log-last', text); mirrorBytes(root, a);
318
+ root.setAttribute('data-markless-log-interactions', String(Number(root.getAttribute('data-markless-log-interactions') || '0') + 1));
319
+ ledgerMirror(l, turn, text, 'data-markless-log-last');
383
320
  }
384
- function mirrorBytes(root, a) { if (a.appBytes === null || a.instrumentBytes === null) { root.removeAttribute('data-markless-log-app-bytes'); root.removeAttribute('data-markless-log-instrument-bytes'); return; } root.setAttribute('data-markless-log-app-bytes', String(a.appBytes)); root.setAttribute('data-markless-log-instrument-bytes', String(a.instrumentBytes)); }
385
321
  export async function installMarklessExecutionLog(input = {}) {
386
322
  const log = globalThis.__mxLog; if (!log || globalThis.__mxLogInteraction) return;
387
323
  if (marklessInstallPromise) return marklessInstallPromise;
388
324
  marklessInstallPromise = (async () => {
389
- const moduleSizes = await loadModuleSizes(input); const preloaded = input.preloadedModuleCount || document.querySelectorAll('link[rel="modulepreload"]').length; const current = modules(log);
325
+ const moduleSizes = await loadModuleSizes(input); const current = modules(log);
390
326
  if (input.printResumeSummary !== false) {
391
- const a = accounting(current, moduleSizes); const summary = 'markless: resumed — ' + category(a, 'app') + ' executed, ' + preloaded + ' modules preloaded (' + a.appModules + ' app executed) · ' + category(a, 'instrument');
392
- console.log(summary); const root = document.documentElement; if (root) { root.setAttribute('data-markless-log-summary', summary); mirrorBytes(root, a); }
327
+ const l = marklessLedger(); const turn = ledgerApply(l, 'resume', undefined, ledgerCharge(l, current, moduleSizes)); const summary = ledgerLine(l, turn);
328
+ // This summary publishes into the ledger and mirrors; it never prints. The
329
+ // ledger's turn line is the one printed line, and a resume that lands
330
+ // beside a render turn would otherwise print the same load figure twice.
331
+ // A turn merged into an open gesture belongs to that gesture, so it mirrors
332
+ // onto the interaction attribute instead of the load one.
333
+ ledgerMirror(l, turn, summary, turn.kind === 'resume' ? 'data-markless-log-summary' : 'data-markless-log-last');
393
334
  }
394
335
  globalThis.__mxLogInteraction = (event) => {
395
336
  const capturedAfter = modules(event.after || log);
396
337
  const symbolIds = qualifySymbolIds((event.eventRecord && event.eventRecord.symbolIds) || [], event.eventRecord && event.eventRecord.hostNodeId, moduleSizes, marklessAttribution);
397
338
  if (event.eventRecord) event = { ...event, eventRecord: { ...event.eventRecord, symbolIds } };
398
339
  const after = capturedAfter; const before = event.before || new Set(); if (log.has(${JSON.stringify(MARKLESS_EXECUTION_LOG_MODULE_ID)}) && !after.includes(${JSON.stringify(MARKLESS_EXECUTION_LOG_MODULE_ID)})) after.push(${JSON.stringify(MARKLESS_EXECUTION_LOG_MODULE_ID)}); const woken = after.filter((id) => !before.has(id)); const warm = warmIds(event);
340
+ const l = marklessLedger(); const label = '[' + (event.selector || 'event target') + ']';
399
341
  if (event.noMatch) {
400
- const a = accounting([], moduleSizes); const line = 'markless: ' + event.eventName + ' [' + (event.selector || 'event target') + '] — no event record matched · 0.0 KB app · 0.0 KB instrument';
401
- console.info(line); mirror(line, a); return;
342
+ const turn = ledgerApply(l, event.eventName, label, ledgerCharge(l, [], moduleSizes)); const line = ledgerLine(l, turn) + ' · no event record matched ' + label;
343
+ console.info(line); mirrorInteraction(l, turn, line); return;
402
344
  }
403
- const a = accounting([...woken, ...warm], moduleSizes); const header = 'markless: ' + event.eventName + ' [' + (event.selector || 'event target') + '] · woke ' + woken.length + ' modules · ran warm ' + warm.length + ' modules · ' + category(a, 'app') + ' · ' + category(a, 'instrument');
345
+ const turn = ledgerApply(l, event.eventName, label, ledgerCharge(l, [...woken, ...warm], moduleSizes)); const header = ledgerLine(l, turn);
404
346
  console.groupCollapsed(header);
347
+ console.log(event.eventName + ' ' + label + ' · woke ' + woken.length + ' modules · ran warm ' + warm.length + ' modules');
405
348
  for (const row of causeRows({ ...event, after: new Set(after), moduleSizes })) console.log(row);
406
- console.groupEnd(); mirror(header, a);
349
+ console.groupEnd(); mirrorInteraction(l, turn, header);
407
350
  };
408
351
  log.__marklessInstalled = true;
409
352
  })();
@@ -415,96 +358,47 @@ export async function logMarklessSpecializedInteraction(input, before) {
415
358
  const tag = typeof target?.tagName === 'string' ? target.tagName.toLowerCase() : 'element';
416
359
  await logMarklessInteraction({ eventName: input.event?.type ?? 'event', eventRecord: input.eventRecord, before, selector: tag + (target?.id ? '#' + target.id : ''), after: new Set(globalThis.__mxLog) });
417
360
  }
361
+ function marklessRenderSummaryTurnEnd() {
362
+ // Each injected entry module calls in from its own dynamic-import
363
+ // resolution, so a microtask window closes between callers of one render;
364
+ // a frame holds them all. The timeout is the backstop for a document that
365
+ // never paints, and whichever lands first ends the turn.
366
+ if (typeof requestAnimationFrame !== 'function') return Promise.resolve();
367
+ return new Promise((resolve) => { let ended; const end = () => { if (ended) return; ended = true; resolve(); }; requestAnimationFrame(end); setTimeout(end, 50); });
368
+ }
369
+ // Only the console line coalesces: the attribute mirror keeps its per-call
370
+ // last-wins timing, which the interaction mirror interleaves with.
371
+ function coalesceMarklessRenderSummaryLine(summary) {
372
+ if (marklessRenderSummaryTurn) { marklessRenderSummaryTurn.summary = summary; return marklessRenderSummaryTurn.printed; }
373
+ const turn = { summary };
374
+ turn.printed = marklessRenderSummaryTurnEnd().then(() => { marklessRenderSummaryTurn = undefined; console.log(turn.summary); });
375
+ marklessRenderSummaryTurn = turn;
376
+ return turn.printed;
377
+ }
418
378
  export async function logMarklessRenderSummary(input = {}) {
419
379
  const log = globalThis.__mxLog; if (!log) return;
420
- const moduleSizes = await loadModuleSizes(input); const current = modules(log); const a = accounting(current, moduleSizes);
421
- const summary = 'markless: rendered — ' + a.appModules + ' app module' + (a.appModules === 1 ? '' : 's') + ' executed (' + category(a, 'app') + ') · ' + a.instrumentModules + ' instrument module' + (a.instrumentModules === 1 ? '' : 's') + ' executed (' + category(a, 'instrument').replace(/ instrument$/, '') + ')';
422
- console.log(summary); const root = document.documentElement; if (root) { root.setAttribute('data-markless-log-summary', summary); mirrorBytes(root, a); }
380
+ const moduleSizes = await loadModuleSizes(input); const l = marklessLedger();
381
+ const turn = ledgerApply(l, 'render', undefined, ledgerCharge(l, modules(log), moduleSizes));
382
+ const summary = ledgerLine(l, turn);
383
+ if (turn.kind !== 'render') { ledgerMirror(l, turn, summary, 'data-markless-log-last'); return; }
384
+ ledgerMirror(l, turn, summary, 'data-markless-log-summary');
385
+ return coalesceMarklessRenderSummaryLine(summary);
423
386
  }
424
387
  `;
425
388
  }
426
- async function createExecutionSizesAsset(bundle, metadata, canonPath = (fileName) => fileName, attribution) {
427
- const entries = {};
428
- const symbolLogIdsByChunk = /* @__PURE__ */ new Map();
429
- for (const module of metadata.modules) for (const symbol of module.symbols) {
430
- if (!symbol.fileName) continue;
431
- const chunk = symbolLogIdsByChunk.get(symbol.fileName) ?? [];
432
- chunk.push(stripResolvedIdMarker(symbol.virtualModuleId));
433
- symbolLogIdsByChunk.set(symbol.fileName, chunk);
434
- }
435
- for (const item of Object.values(bundle)) {
436
- if (item.type !== "chunk") continue;
437
- const chunk = canonPath(item.fileName);
438
- const logIds = new Set([...item.moduleIds.flatMap((id) => chunkModuleLogId(id) ?? []), ...symbolLogIdsByChunk.get(chunk) ?? []]);
439
- if (logIds.size === 0) continue;
440
- if (logIds.has("virtual:markless:dev-log") && logIds.size > 1) {
441
- const cohabitingIds = [...logIds].filter((id) => id !== MARKLESS_EXECUTION_LOG_MODULE_ID).sort();
442
- throw new Error(`Markless execution sizes require an isolated dev-log chunk; chunk "${chunk}" also contains logged module ids: ${cohabitingIds.join(", ")}.`);
443
- }
444
- const size = {
445
- raw: item.code.length,
446
- gzip: await gzipByteLength(item.code),
447
- chunk
448
- };
449
- for (const id of logIds) entries[id] = id === "virtual:markless:dev-log" ? {
450
- ...size,
451
- instrument: true
452
- } : size;
453
- }
454
- return {
455
- type: "asset",
456
- fileName: MARKLESS_EXECUTION_SIZES,
457
- source: JSON.stringify({
458
- ...sortRecord(entries),
459
- ...attribution && Object.keys(attribution).length > 0 ? { attribution: sortNestedRecord(attribution) } : {}
460
- })
461
- };
462
- }
463
389
  function executionLogActivationInjection(mode) {
464
390
  if (normalizeExecutionLogMode(mode) === "never") return null;
465
391
  return {
466
392
  tag: "script",
467
393
  location: "head",
468
- children: `(() => { const l = location; if (${mode === "always" ? "true" : "/^https?:\\/\\/(?:localhost|127\\.0\\.0\\.1|\\[::1\\])(?::|$)/.test(l.origin) || new URLSearchParams(l.search).has('markless-log') || (() => { try { return localStorage.getItem('marklessLog') === '1'; } catch { return false; } })()"}) globalThis.__mxLog = globalThis.__mxLog || new Set(); })();`
394
+ children: `(() => { const l = location; if (${mode === "always" ? "true" : "/^https?:\\/\\/(?:localhost|127\\.0\\.0\\.1|\\[::1\\])(?::|$)/.test(l.origin) || new URLSearchParams(l.search).has('markless-log') || (() => { try { return localStorage.getItem('marklessLog') === '1'; } catch { return false; } })()"}) { globalThis.__mxLog = globalThis.__mxLog || new Set(); const g = globalThis.${MARKLESS_EXECUTION_LEDGER_GLOBAL} = globalThis.${MARKLESS_EXECUTION_LEDGER_GLOBAL} || ${MARKLESS_EXECUTION_LEDGER_INIT}; const s = document.currentScript && document.currentScript.textContent; if (s) { g.total.inline += s.length; if (!g.loadClosed) g.load.inline += s.length; } const c = (e) => { if (e.isTrusted) g.loadClosed = true; }; for (const t of ${JSON.stringify(EXECUTION_LOG_GESTURE_EVENTS)}) addEventListener(t, c, { capture: true, passive: true }); } })();`
469
395
  };
470
396
  }
471
- function chunkModuleLogId(id) {
472
- const path = stripResolvedIdMarker(id);
473
- if (path === "virtual:markless:dev-log") return path;
474
- const match = path.match(/[/\\](web|runtime|serializer)[/\\]src[/\\]([^?#]+)\.ts$/);
475
- return match ? `${match[1]}:${match[2].replace(/[/\\]/g, "/")}` : null;
476
- }
477
- function stripResolvedIdMarker(id) {
478
- return id.startsWith("\0") ? id.slice(1) : id;
479
- }
480
- async function gzipByteLength(code) {
481
- const encoded = new TextEncoder().encode(code);
482
- const reader = new ReadableStream({ start(controller) {
483
- controller.enqueue(encoded);
484
- controller.close();
485
- } }).pipeThrough(new CompressionStream("gzip")).getReader();
486
- let total = 0;
487
- while (true) {
488
- const { done, value } = await reader.read();
489
- if (done) break;
490
- total += value.byteLength;
491
- }
492
- return total;
493
- }
494
- function sortRecord(record) {
495
- const next = {};
496
- for (const key of Object.keys(record).sort()) next[key] = record[key];
497
- return next;
498
- }
499
- function sortNestedRecord(record) {
500
- const next = {};
501
- for (const key of Object.keys(record).sort()) next[key] = sortRecord({ ...record[key] });
502
- return next;
503
- }
504
397
  //#endregion
505
398
  //#region src/source-module.ts
506
399
  const MARKLESS_VIRTUAL_PREFIX = "virtual:markless:";
507
400
  const SMALL_SYMBOL_DIRECT_LOAD_LIMIT = 8;
401
+ const MARKLESS_BOUND_SYMBOLS_EXPORT = "marklessBoundSymbols";
508
402
  const SYMBOL_VIRTUAL_PREFIX = `${MARKLESS_VIRTUAL_PREFIX}symbol:`;
509
403
  function symbolVirtualModuleId(filename, symbolId) {
510
404
  return `${SYMBOL_VIRTUAL_PREFIX}${encodeURIComponent(filename)}:${encodeURIComponent(symbolId)}`;
@@ -528,6 +422,49 @@ function symbolVirtualModuleSourceFile(moduleId) {
528
422
  function resumeVirtualModuleId(filename) {
529
423
  return `${MARKLESS_VIRTUAL_PREFIX}resume:${encodeURIComponent(filename)}`;
530
424
  }
425
+ function prerenderWakeVirtualModuleId(filename) {
426
+ return `${MARKLESS_VIRTUAL_PREFIX}prerender-wake:${encodeURIComponent(filename)}`;
427
+ }
428
+ function settleVirtualModuleId(filename) {
429
+ return `${MARKLESS_VIRTUAL_PREFIX}settle:${encodeURIComponent(filename)}`;
430
+ }
431
+ /**
432
+ * The settle module: import targets and data, no logic. It carries the DOM
433
+ * filler, each boundary's async runner keyed by graph node, and each bound
434
+ * symbol's compiled derive function keyed by the id the fill plan references.
435
+ * Rolldown resolves the real chunk urls, so nothing here is a url the document
436
+ * has to carry, and a page that never settles an arm never loads it.
437
+ */
438
+ function emitSettleModule(input) {
439
+ const imports = [...input.runners, ...input.derives].map((entry) => entry.symbol);
440
+ const seen = /* @__PURE__ */ new Map();
441
+ const lines = ["export { marklessApplySettlePlan as f } from '@markless/web/fns/settle-plan';"];
442
+ for (const symbol of imports) {
443
+ if (seen.has(symbol.id)) continue;
444
+ seen.set(symbol.id, symbol.exportName);
445
+ lines.push(`import { ${symbol.exportName} } from ${JSON.stringify(symbol.chunk)};`);
446
+ }
447
+ const entries = (rows) => `{${rows.map((row) => `${JSON.stringify(row.key)}: ${seen.get(row.symbolId)}`).join(",")}}`;
448
+ lines.push(`export const r = ${entries(input.runners.map((entry) => ({
449
+ key: entry.node,
450
+ symbolId: entry.symbol.id
451
+ })))};`);
452
+ lines.push(`export const d = ${entries(input.derives.map((entry) => ({
453
+ key: entry.id,
454
+ symbolId: entry.symbol.id
455
+ })))};`);
456
+ return `${lines.join("\n")}\n`;
457
+ }
458
+ function prerenderWakeVirtualModuleSourceFile(moduleId) {
459
+ const bare = moduleId.startsWith("\0") ? moduleId.slice(1) : moduleId;
460
+ const prefix = `${MARKLESS_VIRTUAL_PREFIX}prerender-wake:`;
461
+ if (!bare.startsWith(prefix)) return null;
462
+ try {
463
+ return decodeURIComponent(bare.slice(prefix.length));
464
+ } catch {
465
+ return null;
466
+ }
467
+ }
531
468
  function scopedSymbolExportName(filename, exportName) {
532
469
  return `${exportName}_${stringHash(filename)}`;
533
470
  }
@@ -557,25 +494,31 @@ function emitPublicRenderModule(source, input) {
557
494
  ].join("\n");
558
495
  }
559
496
  function emitSourceModule(input) {
497
+ if (input.directCsr && input.prerenderRecords) input = {
498
+ ...input,
499
+ prerenderRecords: false
500
+ };
560
501
  const symbolsOnly = input.environment === "client" && input.clientOutput === "symbols-only";
561
502
  const routeSymbols = input.environment === "client" && input.symbolRoutes.length > 0;
503
+ const canonicalRenderData = input.environment === "client" && !symbolsOnly && !input.prerenderRecords && input.canonicalRenderData === true && input.publicRenderRootExportName === null && !!input.publicSsrModuleSource && !!input.renderDataId;
562
504
  return [
505
+ !input.publicSsrModuleSource && !input.publicRenderModuleSource || symbolsOnly || !input.renderDataId || input.environment === "client" && !input.publicRenderModuleSource && !input.prerenderRecords && !input.dev && !canonicalRenderData ? "" : canonicalRenderData ? `import { marklessPrerenderData } from '${input.renderDataId}';` : `import { marklessRenderData } from '${input.renderDataId}';`,
563
506
  symbolsOnly ? "" : `import { state as payloadState, view as payloadView, runtimeDemandMap as payloadRuntimeDemandMap } from '${input.payloadId}';`,
564
507
  "",
565
508
  emitLoadSymbol(input),
509
+ input.behaviorSymbols?.length ? emitDirectSourceSymbolLoader(input.behaviorSymbols, "readMarklessBehaviorSourceSymbol").replace("function loadSymbol(", "function loadBehaviorSymbol(") : "",
566
510
  input.environment === "client" && input.executionLog !== "never" ? emitExecutionLogLoader() : "",
567
511
  routeSymbols ? "const marklessLoadLocalSymbol = loadSymbol;" : "",
568
- symbolsOnly && !routeSymbols ? "export { loadSymbol };" : "",
512
+ (symbolsOnly || input.publicRenderRootExportName === null) && !routeSymbols ? "export { loadSymbol };" : "",
569
513
  symbolsOnly ? "" : "export { payloadView };",
570
514
  symbolsOnly ? "" : "export { payloadRuntimeDemandMap };",
571
515
  input.devResumeReexport && input.environment === "client" ? `export { resumeContainerEvent } from '${resumeVirtualModuleId(input.filename)}';` : "",
572
516
  "",
573
- input.environment === "server" || symbolsOnly ? "" : emitPublicRenderModule(input.publicRenderModuleSource, {
517
+ input.environment === "server" || symbolsOnly || input.prerenderRecords ? "" : emitPublicRenderModule(input.publicRenderModuleSource, {
574
518
  executionLogEnabled: input.executionLog !== "never",
575
519
  rootExportName: input.publicRenderRootExportName
576
520
  }),
577
- input.environment === "server" || symbolsOnly ? "" : input.publicCsrModuleSource,
578
- input.environment === "client" ? "" : input.publicSsrModuleSource,
521
+ input.environment === "client" && (symbolsOnly || !input.prerenderRecords && !input.dev) ? "" : input.publicSsrModuleSource,
579
522
  routeSymbols ? emitLazySymbolRouteFunction(input.symbolRoutes, "marklessSsrLoadSymbolRoute", "marklessLoadLocalSymbol") : "",
580
523
  symbolsOnly && routeSymbols ? "export { marklessSsrLoadSymbolRoute as loadSymbol };" : "",
581
524
  symbolsOnly ? "" : emitCompiledAppDefault({
@@ -584,10 +527,15 @@ function emitSourceModule(input) {
584
527
  headInjections: input.headInjections,
585
528
  storageSeeds: input.storageSeeds,
586
529
  inlineResumerSources: input.inlineResumerSources,
530
+ prerenderBoot: input.prerenderBoot,
587
531
  resumeModuleUrl: input.resumeModuleUrl,
532
+ prerenderWakeModuleUrl: input.prerenderWakeModuleUrl,
588
533
  rootExportName: input.publicRenderRootExportName,
589
- csrExportName: input.publicRenderCsrExportName,
590
- ssrExportName: input.publicRenderSsrExportName
534
+ symbolLoaderName: routeSymbols ? "marklessSsrLoadSymbolRoute" : "loadSymbol",
535
+ ssrExportName: input.publicRenderSsrExportName,
536
+ canonicalRenderData,
537
+ prerenderRecords: input.prerenderRecords,
538
+ dev: input.dev
591
539
  }),
592
540
  ""
593
541
  ].filter((line) => line !== null).join("\n");
@@ -596,24 +544,63 @@ function emitResumeModule(input) {
596
544
  const routeSymbols = input.symbolRoutes.length > 0;
597
545
  const resumeSymbolLoader = routeSymbols ? "marklessSsrLoadSymbolRoute" : "loadSymbol";
598
546
  const scalarSpecializations = scalarDispatcherSpecializations(input);
547
+ const stagedPrerender = (input.prerenderTriggerGroups?.length ?? 0) > 0;
548
+ const streamedResume = stagedPrerender || (input.payloadView?.asyncBoundaries?.length ?? 0) > 0;
549
+ const bareResumeContainerEvent = emitResumeContainerEvent(resumeSymbolLoader, input.needsFullResume ?? false, input.payloadState?.version === ASYNC_PROTOCOL_VERSION, input.symbolRoutes.length > 0 ? "none" : leanResumeMode(input.runtimeDemandMap), scalarSpecializations, input.runtimeDemandMap, input.executionLog !== "never", input.prerenderDataId, stagedPrerender);
550
+ const resumeContainerEvent = streamedResume ? emitQueuedResumeContainerEvent(bareResumeContainerEvent) : bareResumeContainerEvent;
599
551
  return [
552
+ input.boundSymbolDescriptors ? `export const ${MARKLESS_BOUND_SYMBOLS_EXPORT} = ${emitBoundSymbolDescriptors(input.boundSymbolDescriptors)};` : null,
553
+ input.prerenderDataId && !stagedPrerender && resumeContainerEvent.includes("marklessPrerenderData") ? `import { marklessPrerenderData } from '${input.prerenderDataId}';` : null,
554
+ stagedPrerender && input.prerenderDataId ? `async function marklessLoadPrerenderData() { const module = await import('${input.prerenderDataId}'); return module.marklessPrerenderData; }` : null,
600
555
  `import { runtimeDemandMap as payloadRuntimeDemandMap } from '${input.payloadId}';`,
601
556
  scalarSpecializations.length > 0 ? [
602
- "import { marklessDecodeScalarCell, marklessFindElementAtDomOrderIndex, marklessReadScalarCell, marklessScalarSpecializedError } from '@markless/web/fns/scalar-specialized';",
557
+ "import { marklessDecodeScalarCell, marklessReadScalarCell, marklessScalarSpecializedError } from '@markless/web/fns/scalar-specialized';",
603
558
  "import { marklessWriteScalar } from '@markless/web/fns/write-scalar';",
604
559
  "import { marklessUpdateText } from '@markless/web/fns/update-text';"
605
560
  ].join("\n") : "",
561
+ scalarSpecializations.length > 0 || stagedPrerender ? "import { marklessFindElementAtDomOrderIndex } from '@markless/web/fns/dom-order';" : "",
606
562
  "",
607
563
  input.executionLog === "never" ? "" : emitExecutionLogLoader(),
564
+ input.installResumeSummary && input.executionLog !== "never" ? "globalThis.__mxLoadLog().then(log => log.installMarklessExecutionLog());" : null,
608
565
  "",
609
- emitLoadSymbol(input),
566
+ emitLoadSymbol(input, stagedPrerender ? "readMarklessWakeSourceSymbol" : void 0),
610
567
  routeSymbols ? "const marklessLoadLocalSymbol = loadSymbol;" : "",
611
568
  routeSymbols ? emitLazySymbolRouteFunction(input.symbolRoutes, "marklessSsrLoadSymbolRoute", "marklessLoadLocalSymbol") : "",
612
569
  routeSymbols ? "export { marklessSsrLoadSymbolRoute as loadSymbol };" : "",
613
- emitResumeContainerEvent(resumeSymbolLoader, input.needsFullResume ?? false, input.symbolRoutes.length > 0 ? "none" : leanResumeMode(input.runtimeDemandMap), scalarSpecializations, input.runtimeDemandMap, input.executionLog !== "never"),
570
+ stagedPrerender ? emitPrerenderTriggerGroupLoader(input.prerenderTriggerGroups ?? []) : "",
571
+ resumeContainerEvent,
614
572
  ""
615
573
  ].filter((line) => line !== null).join("\n");
616
574
  }
575
+ function emitBoundSymbolDescriptors(descriptors) {
576
+ return JSON.stringify(Object.fromEntries(Object.keys(descriptors).sort().map((boundaryId) => [boundaryId, descriptors[boundaryId]])));
577
+ }
578
+ function emitQueuedResumeContainerEvent(source) {
579
+ const implementation = source.replace("export async function resumeContainerEvent", "async function marklessResumeContainerEvent");
580
+ if (implementation === source) throw new Error("MARKLESS_RESUME_DISPATCH_EXPORT_MISSING");
581
+ return [
582
+ implementation,
583
+ "export function resumeContainerEvent(input) {",
584
+ " const root = input.root;",
585
+ " if (!root.__marklessDispatch) {",
586
+ " let handler = marklessResumeContainerEvent, tail = Promise.resolve(), lastEvent = null, lastEventTime = undefined;",
587
+ " root.__marklessRegisterDispatch = next => {",
588
+ " const fallback = handler;",
589
+ " handler = input => next(input, fallback);",
590
+ " };",
591
+ " root.__marklessDispatch = next => {",
592
+ " const event = next.event, eventTime = event?.timeStamp;",
593
+ " if (event === lastEvent && eventTime === lastEventTime) return tail;",
594
+ " lastEvent = event;",
595
+ " lastEventTime = eventTime;",
596
+ " const run = () => handler(next);",
597
+ " return tail = tail.then(run, run);",
598
+ " };",
599
+ " }",
600
+ " return root.__marklessDispatch(input);",
601
+ "}"
602
+ ].join("\n");
603
+ }
617
604
  function emitLazySymbolRouteFunction(routes, functionName, fallbackName = "loadSymbol") {
618
605
  if (routes.length === 0) return "";
619
606
  return [
@@ -631,14 +618,18 @@ function symbolRouteImportSource(importSource) {
631
618
  return importSource.includes("?") ? `${importSource}&markless-symbols` : `${importSource}?markless-symbols`;
632
619
  }
633
620
  function emitCompiledAppDefault(input) {
634
- const renderCsrEntry = (input.rootExportName || input.csrExportName) && input.environment !== "server" ? [` renderCsr: ${input.rootExportName ?? input.csrExportName},`] : [];
635
- const renderSsrEntry = input.ssrExportName && input.environment !== "client" ? [
621
+ const renderCsrEntry = input.rootExportName && input.environment !== "server" && !input.prerenderRecords ? [` renderCsr: ${input.rootExportName},`] : [];
622
+ const symbolLoaderEntry = input.environment !== "server" && input.rootExportName === null ? [`\tloadSymbol: ${input.symbolLoaderName},`] : input.prerenderRecords === true ? [`\tloadSymbol: ${input.symbolLoaderName},`] : [];
623
+ const renderDataEntry = input.canonicalRenderData ? [" renderData: marklessPrerenderData,"] : [];
624
+ const renderSsrEntry = input.ssrExportName && (input.environment !== "client" || input.prerenderRecords || input.dev) ? [
636
625
  " renderSsr(props, marklessRenderContext) {",
637
626
  ` return ${input.ssrExportName}(props, marklessRenderContext);`,
638
627
  " },"
639
628
  ] : [];
640
629
  const resumeModuleEntry = input.resumeModuleUrl && input.environment !== "client" ? [` resumeModuleUrl: ${JSON.stringify(input.resumeModuleUrl)},`] : [];
630
+ const prerenderWakeModuleEntry = input.prerenderWakeModuleUrl && input.environment !== "client" ? [`\tprerenderWakeModuleUrl: ${JSON.stringify(input.prerenderWakeModuleUrl)},`] : [];
641
631
  const inlineResumerEntry = input.inlineResumerSources && input.environment !== "client" ? [`\tinlineResumerSources: ${JSON.stringify(input.inlineResumerSources)},`] : [];
632
+ const prerenderBootEntry = input.prerenderBoot && input.environment !== "client" ? [`\tprerenderBoot: ${JSON.stringify(input.prerenderBoot)},`] : [];
642
633
  const headInjectionEntry = input.headInjections?.length && input.environment !== "client" ? [` headInjections: ${JSON.stringify(input.headInjections)},`] : [];
643
634
  const storageSeedEntry = input.storageSeeds?.length && input.environment !== "client" ? [`\tstorageSeeds: ${JSON.stringify(input.storageSeeds)},`] : [];
644
635
  const modulePreloadEntry = input.resumeModuleUrl && input.environment === "server" ? [` modulePreloads: [{ href: ${JSON.stringify(input.resumeModuleUrl)}, fetchPriority: "high" }],`] : [];
@@ -646,7 +637,9 @@ function emitCompiledAppDefault(input) {
646
637
  ...headInjectionEntry,
647
638
  ...storageSeedEntry,
648
639
  ...resumeModuleEntry,
640
+ ...prerenderWakeModuleEntry,
649
641
  ...inlineResumerEntry,
642
+ ...prerenderBootEntry,
650
643
  ...modulePreloadEntry,
651
644
  input.executionLog && input.executionLog !== "never" ? ` executionLog: ${JSON.stringify(input.executionLog)},` : "",
652
645
  " payloadView,"
@@ -654,17 +647,62 @@ function emitCompiledAppDefault(input) {
654
647
  return [
655
648
  "const marklessCompiledApp = {",
656
649
  ...renderCsrEntry,
650
+ ...renderDataEntry,
651
+ ...symbolLoaderEntry,
657
652
  ...renderSsrEntry,
658
653
  ...metadataEntries,
659
654
  "};",
660
655
  "export default marklessCompiledApp;"
661
656
  ].join("\n");
662
657
  }
663
- function emitResumeContainerEvent(loadSymbolName, needsFullResume, leanMode, scalarSpecializations, runtimeDemandMap, executionLogEnabled) {
664
- const fullResumeHandoff = [
658
+ function emitResumeContainerEvent(loadSymbolName, needsFullResume, storageFreePayload, leanMode, scalarSpecializations, runtimeDemandMap, executionLogEnabled, prerenderDataId, stagedPrerender = false) {
659
+ const fullResumeHandoff = stagedPrerender && prerenderDataId ? [
660
+ "async function marklessFullResumeHandoff(handoff) {",
661
+ " handoff.root.__asyncResumeRuntimeStarted = true;",
662
+ " const group = await marklessLoadPrerenderTriggerGroup(handoff);",
663
+ " const { mergePrerenderPayloadRecords, resumePrerenderTriggerGroup } = await import('@markless/web/fns/prerender-trigger-resume');",
664
+ " if (!group) {",
665
+ " const { derivePrerenderResumeRecords, renderPrerenderBoundary, resumeFromPrerenderRecords } = await import('@markless/web/fns/prerender-resume');",
666
+ " handoff.root.__marklessFullResumeRuntime ??= (async () => {",
667
+ " const marklessPrerenderData = await marklessLoadPrerenderData();",
668
+ `\t\t\tconst fallbackRecords = mergePrerenderPayloadRecords(await derivePrerenderResumeRecords(marklessPrerenderData, ${loadSymbolName}), handoff.root);`,
669
+ " return (await resumeFromPrerenderRecords({",
670
+ " ...fallbackRecords,",
671
+ " root: handoff.root,",
672
+ `\t\t\t\tloadSymbol: ${loadSymbolName},`,
673
+ `\t\t\t\trenderAsyncBoundary: (boundaryId, status, graph) => renderPrerenderBoundary(marklessPrerenderData, boundaryId, status, graph, ${loadSymbolName}),`,
674
+ " })).runtime;",
675
+ " })();",
676
+ " const marklessFullRuntime = await handoff.root.__marklessFullResumeRuntime;",
677
+ " await marklessFullRuntime.dispatch(handoff.event, { syncPolicyAlreadyApplied: handoff.syncPolicyAlreadyApplied === true, ignoreUnmatched: true });",
678
+ " return;",
679
+ " }",
680
+ " const records = mergePrerenderPayloadRecords({ state: group.state, view: group.view }, handoff.root);",
681
+ " const { runtime } = await resumePrerenderTriggerGroup({",
682
+ " ...records, groupId: group.groupId, graphNodeIds: group.graphNodeIds,",
683
+ " root: handoff.root, loadSymbol: group.loadSymbol, prepareBoundaryArm: group.prepareBoundaryArm, renderBoundaryArm: group.renderBoundaryArm,",
684
+ " });",
685
+ " await runtime.dispatch(handoff.event, { syncPolicyAlreadyApplied: handoff.syncPolicyAlreadyApplied === true, ignoreUnmatched: true });",
686
+ "}"
687
+ ].join("\n") : prerenderDataId ? [
665
688
  "async function marklessFullResumeHandoff(handoff) {",
689
+ " handoff.root.__marklessLinkedRenderDataBoot = true;",
666
690
  " handoff.root.__asyncResumeRuntimeStarted = true;",
667
- " const { resumeFromPayloadDocument } = await import('@markless/core/web/resume');",
691
+ " const { derivePrerenderResumeRecords, mergePrerenderPayloadRecords, renderPrerenderBoundary, resumeFromPrerenderRecords } = await import('@markless/web/fns/prerender-resume');",
692
+ `\tconst records = await derivePrerenderResumeRecords(marklessPrerenderData, ${loadSymbolName});`,
693
+ " const mergedRecords = mergePrerenderPayloadRecords(records, handoff.root);",
694
+ " const { runtime } = await resumeFromPrerenderRecords({",
695
+ " ...mergedRecords,",
696
+ " root: handoff.root,",
697
+ `\t\tloadSymbol: ${loadSymbolName},`,
698
+ `\t\trenderAsyncBoundary: (boundaryId, status, graph) => renderPrerenderBoundary(marklessPrerenderData, boundaryId, status, graph, ${loadSymbolName}),`,
699
+ " });",
700
+ " await runtime.dispatch(handoff.event, { syncPolicyAlreadyApplied: handoff.syncPolicyAlreadyApplied === true, ignoreUnmatched: true });",
701
+ "}"
702
+ ].join("\n") : [
703
+ "async function marklessFullResumeHandoff(handoff) {",
704
+ " handoff.root.__asyncResumeRuntimeStarted = true;",
705
+ ` const { resumeFromPayloadDocument } = await import('${storageFreePayload ? "@markless/core/web/resume-storage-free" : "@markless/core/web/resume"}');`,
668
706
  " const { runtime } = await resumeFromPayloadDocument({",
669
707
  " document: handoff.document,",
670
708
  " root: handoff.root,",
@@ -722,6 +760,7 @@ function emitResumeContainerEvent(loadSymbolName, needsFullResume, leanMode, sca
722
760
  "}"
723
761
  ].join("\n");
724
762
  }
763
+ if (prerenderDataId) return "export async function resumeContainerEvent(_input) {}";
725
764
  return [
726
765
  fullResumeHandoff,
727
766
  "export async function resumeContainerEvent(input) {",
@@ -729,6 +768,37 @@ function emitResumeContainerEvent(loadSymbolName, needsFullResume, leanMode, sca
729
768
  "}"
730
769
  ].join("\n");
731
770
  }
771
+ function emitPrerenderTriggerGroupLoader(groups) {
772
+ return [
773
+ "function marklessPrerenderTriggerMatches(input, index, tagName, eventName) {",
774
+ " if (input.event?.type !== eventName) return false;",
775
+ " const host = marklessFindElementAtDomOrderIndex(input.root, index);",
776
+ " if (!host || (tagName !== \"*\" && host.tagName.toLowerCase() !== tagName)) return false;",
777
+ " const target = input.event?.target;",
778
+ " return host === target || (!!target?.nodeType && typeof host.contains === \"function\" && host.contains(target));",
779
+ "}",
780
+ "function marklessPrerenderBranchTriggerMatches(input, startIndex, endIndex, hostPath, eventName) {",
781
+ " if (input.event?.type !== eventName) return false;",
782
+ " const comments = [];",
783
+ " const collect = node => { for (const child of node.childNodes ?? []) { if (child.nodeType === 8) comments.push(child); collect(child); } };",
784
+ " collect(input.root);",
785
+ " const start = comments[startIndex], end = comments[endIndex], armNodes = [];",
786
+ " let within = false, done = false;",
787
+ " const between = node => { for (const child of node.childNodes ?? []) { if (child === start) { within = true; continue; } if (child === end) { done = true; return; } if (within) armNodes.push(child); else between(child); if (done) return; } };",
788
+ " between(input.root);",
789
+ " let host = armNodes[hostPath[0]];",
790
+ " for (let index = 1; host && index < hostPath.length; index++) host = host.childNodes?.[hostPath[index]];",
791
+ " const target = input.event?.target;",
792
+ " return host?.nodeType === 1 && (host === target || (!!target?.nodeType && typeof host.contains === \"function\" && host.contains(target)));",
793
+ "}",
794
+ "function marklessLoadPrerenderTriggerGroup(input) {",
795
+ ...groups.map((group) => {
796
+ return `\tif (${group.eventName === "self-wake" ? "input.event === 0" : group.hostPath ? `marklessPrerenderBranchTriggerMatches(input, ${group.branchStartIndex}, ${group.branchEndIndex}, ${JSON.stringify(group.hostPath)}, ${JSON.stringify(group.eventName)})` : `marklessPrerenderTriggerMatches(input, ${group.hostIndex}, ${JSON.stringify(group.hostTagName.toLowerCase())}, ${JSON.stringify(group.eventName)})`}) return import(${JSON.stringify(group.moduleId)});`;
797
+ }),
798
+ " return Promise.resolve(null);",
799
+ "}"
800
+ ].join("\n");
801
+ }
732
802
  function scalarDispatcherSpecializations(input) {
733
803
  const state = input.payloadState;
734
804
  const view = input.payloadView;
@@ -740,11 +810,12 @@ function scalarDispatcherSpecializations(input) {
740
810
  const plan = action?.plan;
741
811
  if (action?.recordKind !== "event" || plan?.version !== 1 || plan?.kind !== "scalar") return [];
742
812
  const cellIndex = cells.findIndex((cell) => cell?.graphNodeId === plan.cell);
813
+ const initialCell = cells[cellIndex];
743
814
  const host = locators.find((locator) => locator?.hostNodeId === action.hostNodeId);
744
815
  const event = view?.events?.find((candidate) => candidate?.hostNodeId === action.hostNodeId && candidate?.eventName === action.eventName);
745
816
  const symbolId = scalarActionSymbolId(plan.symbolId, event, input.symbolRoutes ?? []);
746
817
  if (!symbolId) return [];
747
- if (cellIndex < 0 || !host || typeof host.index !== "number") return [];
818
+ if (cellIndex < 0 || !initialCell || !host || typeof host.index !== "number") return [];
748
819
  if (event && syncPolicyGraphNodeIds(event.syncPolicy).some((graphNodeId) => graphNodeId !== plan.cell)) return [];
749
820
  const textUpdates = (plan.textUpdates ?? []).flatMap((update) => {
750
821
  const locator = locators.find((candidate) => candidate?.hostNodeId === update.hostNodeId);
@@ -763,6 +834,7 @@ function scalarDispatcherSpecializations(input) {
763
834
  symbolId,
764
835
  cell: plan.cell,
765
836
  cellIndex,
837
+ initialCell,
766
838
  hostIndex: host.index,
767
839
  hostTagName: String(host.tagName ?? "*"),
768
840
  syncPolicy: event?.syncPolicy ?? null,
@@ -826,7 +898,7 @@ function emitScalarAction(action, loadSymbolName) {
826
898
  " let syncPolicyAlreadyApplied = input.syncPolicyAlreadyApplied === true;",
827
899
  " const values = input.root.__marklessEventOnlyGraph || new Map();",
828
900
  " input.root.__marklessEventOnlyGraph = values;",
829
- ` if (!values.has(${JSON.stringify(action.cell)})) values.set(${JSON.stringify(action.cell)}, marklessDecodeScalarCell(marklessReadScalarCell(input.root, ${action.cellIndex}), ${JSON.stringify(action.cell)}, ${JSON.stringify(`markless/state cell[${action.cellIndex}]`)}));`,
901
+ ` if (!values.has(${JSON.stringify(action.cell)})) values.set(${JSON.stringify(action.cell)}, marklessDecodeScalarCell(marklessReadScalarCell(input.root, ${JSON.stringify(action.cell)}) ?? ${JSON.stringify(action.initialCell)}, ${JSON.stringify(action.cell)}, ${JSON.stringify(`markless/state cell ${action.cell}`)}));`,
830
902
  ` const state = { value: values.get(${JSON.stringify(action.cell)}), dirty: false };`,
831
903
  " try {",
832
904
  ` const host = marklessFindElementAtDomOrderIndex(input.root, ${action.hostIndex});`,
@@ -907,8 +979,12 @@ function leanResumeMode(runtimeDemandMap) {
907
979
  function emitExecutionLogLoader() {
908
980
  return `globalThis.__mxLoadLog ||= () => import(${JSON.stringify(MARKLESS_EXECUTION_LOG_MODULE_ID)});`;
909
981
  }
910
- function emitLoadSymbol(input) {
911
- if (!input.hasBoundSymbols && input.symbols.length > 0 && input.symbols.length <= SMALL_SYMBOL_DIRECT_LOAD_LIMIT) return emitDirectSourceSymbolLoader(input.symbols);
982
+ function emitLoadSymbol(input, sourceReaderName) {
983
+ if (input.symbols.length > 0 && input.symbols.length <= SMALL_SYMBOL_DIRECT_LOAD_LIMIT) {
984
+ const direct = emitDirectSourceSymbolLoader(input.symbols, sourceReaderName);
985
+ if (!input.hasBoundSymbols) return direct;
986
+ return [`const marklessSymbolResolverModule = () => import('${input.resolverId}');`, direct.replace(" return Promise.reject(new Error(`Unknown async symbol ${symbolId}`));", " return marklessSymbolResolverModule().then((mod) => mod.loadSymbol(symbolId));")].join("\n");
987
+ }
912
988
  return [
913
989
  `const marklessSymbolResolverModule = () => import('${input.resolverId}');`,
914
990
  "function loadSymbol(symbolId) {",
@@ -916,18 +992,18 @@ function emitLoadSymbol(input) {
916
992
  "}"
917
993
  ].join("\n");
918
994
  }
919
- function emitDirectSourceSymbolLoader(symbols) {
995
+ function emitDirectSourceSymbolLoader(symbols, readerName = "readMarklessSourceSymbol") {
920
996
  return [
921
997
  "function loadSymbol(symbolId) {",
922
- ...symbols.flatMap((symbol) => [` if (symbolId === ${JSON.stringify(symbol.id)}) return import('${symbol.chunk}')`, ` .then((mod) => readMarklessSourceSymbol(mod, ${JSON.stringify(symbol.exportName)}));`]),
998
+ ...symbols.flatMap((symbol) => [` if (symbolId === ${JSON.stringify(symbol.id)}) return import('${symbol.chunk}')`, ` .then((mod) => ${readerName}(mod, ${JSON.stringify(symbol.exportName)}));`]),
923
999
  " return Promise.reject(new Error(`Unknown async symbol ${symbolId}`));",
924
1000
  "}",
925
- emitSourceSymbolExportReader()
1001
+ emitSourceSymbolExportReader(readerName)
926
1002
  ].join("\n");
927
1003
  }
928
- function emitSourceSymbolExportReader() {
1004
+ function emitSourceSymbolExportReader(name = "readMarklessSourceSymbol") {
929
1005
  return [
930
- "function readMarklessSourceSymbol(mod, exportName) {",
1006
+ `function ${name}(mod, exportName) {`,
931
1007
  " mod.init__virtual_markless_symbol?.();",
932
1008
  " return mod[exportName];",
933
1009
  "}"
@@ -942,4 +1018,4 @@ function stringHash(value) {
942
1018
  return (hash >>> 0).toString(36);
943
1019
  }
944
1020
  //#endregion
945
- export { planModulePreloads as C, planModulePreloadUrls as S, normalizeExecutionLogMode as _, payloadModule as a, MARKLESS_BUNDLE_GRAPH as b, scopedSymbolExportName as c, MARKLESS_EXECUTION_LOG_MODULE_ID as d, MARKLESS_EXECUTION_SIZES as f, injectExecutionLogModuleHook as g, executionLogVirtualModuleSource as h, encodedSymbolSource as i, symbolVirtualModuleId as l, executionLogActivationInjection as m, emitResumeModule as n, resumeVirtualModuleId as o, createExecutionSizesAsset as p, emitSourceModule as r, rewriteSymbolModuleExport as s, MARKLESS_VIRTUAL_PREFIX as t, symbolVirtualModuleSourceFile as u, requalifyExecutionLogModuleHook as v, outputDefaults as x, MARKLESS_BUILD_PREFIX as y };
1021
+ export { planModulePreloadUrls as C, requalifyExecutionLogModuleHook as S, executionLogActivationInjection as _, encodedSymbolSource as a, injectExecutionLogModuleHook as b, prerenderWakeVirtualModuleSourceFile as c, scopedSymbolExportName as d, settleVirtualModuleId as f, MARKLESS_EXECUTION_LOG_MODULE_ID as g, EXECUTION_LOG_DISPATCH_MODULE_IDS as h, emitSourceModule as i, resumeVirtualModuleId as l, symbolVirtualModuleSourceFile as m, emitResumeModule as n, payloadModule as o, symbolVirtualModuleId as p, emitSettleModule as r, prerenderWakeVirtualModuleId as s, MARKLESS_VIRTUAL_PREFIX as t, rewriteSymbolModuleExport as u, executionLogVirtualModuleSource as v, planModulePreloads as w, normalizeExecutionLogMode as x, hasExecutionLogModuleHook as y };