@markless/router 0.6.0 → 0.7.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,4 @@
1
- import { a as ROUTE_SCRIPT_TYPE, c as PREFETCH_ATTRIBUTE, i as HTML_ATTRIBUTES_SLOT, l as REPLACE_ATTRIBUTE, n as requestLocals, r as takeMiddlewareHeaders, s as LINK_ATTRIBUTE, u as SCROLL_ATTRIBUTE } from "./request-locals-z3rJTizs.js";
1
+ import { a as ROUTE_SCRIPT_TYPE, c as servedHtmlAttributesScript, d as REPLACE_ATTRIBUTE, f as SCROLL_ATTRIBUTE, i as HTML_ATTRIBUTES_SLOT, l as LINK_ATTRIBUTE, n as requestLocals, o as SERVED_HTML_ATTRIBUTES_PROPERTY, r as takeMiddlewareHeaders, u as PREFETCH_ATTRIBUTE } from "./request-locals-BDAhNpEa.js";
2
2
  import { i as matchRouteManifest, n as buildRouteManifestFromFileIds, o as normalizeRouteFileId } from "./route-manifest-DMizh42B.js";
3
3
  import { n as __marklessDebugBootstrapSource } from "./debug-channel-Di6SppZv.js";
4
4
  import { CONTAINER_ATTRIBUTE, PROTOCOL_CONTAINER_END_COMMENT, PROTOCOL_CONTAINER_START_COMMENT, PROTOCOL_LOCATOR_PATHS_ATTRIBUTE } from "@markless/web/fns/container-region";
@@ -864,16 +864,17 @@ function createServerEntry(options) {
864
864
  resumeModuleUrl: options.resumeEntryPath ?? baseArtifact?.resumeModuleUrl,
865
865
  modulePreloads: (html, resumes) => landingModulePreloads(resumes ? options.routeSsrModulePreloads?.[file] : void 0, linkBridges?.[html.includes("href=\"#/") ? 1 : 0])
866
866
  };
867
+ const headStart = (documentLoads && fragmentNavigation && !fragment ? servedHtmlAttributesScript() : "") + (options.importMap ?? "");
867
868
  if (options.render === "blocking") {
868
869
  const pageHtml = splitPageHtml(await renderToString(routedArtifact, renderOptions));
869
870
  privateToVisitor();
870
- const shell = await renderDocumentShell(documentModule, pageProps, pageHtml.headHtml, options.importMap);
871
+ const shell = await renderDocumentShell(documentModule, pageProps, pageHtml.headHtml, headStart);
871
872
  return new Response(wholeDocument(shell, pageHtml, fragment, documentLoads), http.commit(status, headers));
872
873
  }
873
874
  const stream = await renderToStream(routedArtifact, renderOptions);
874
875
  privateToVisitor();
875
876
  const pageHtml = splitPageHtml(stream.shell);
876
- const shell = await renderDocumentShell(documentModule, pageProps, pageHtml.headHtml, options.importMap);
877
+ const shell = await renderDocumentShell(documentModule, pageProps, pageHtml.headHtml, headStart);
877
878
  if (stream.pendingArmCount === 0) return new Response(wholeDocument(shell, pageHtml, fragment, documentLoads), http.commit(status, headers));
878
879
  const placed = placePage(shell, DOCUMENT_CHILDREN_PLACEHOLDER, pageHtml);
879
880
  const prefix = fragment ? fragmentDocument(shell, pageHtml, documentLoads) + FRAGMENT_REGION_END : placed.prefix;
@@ -1269,6 +1270,7 @@ function fragmentConfig(fragment, documentLoads) {
1269
1270
  replaceAttribute: REPLACE_ATTRIBUTE,
1270
1271
  scrollAttribute: SCROLL_ATTRIBUTE,
1271
1272
  routeScriptType: ROUTE_SCRIPT_TYPE,
1273
+ servedHtmlAttributesProperty: SERVED_HTML_ATTRIBUTES_PROPERTY,
1272
1274
  retireProperty: CONTAINER_RETIRE_PROPERTY,
1273
1275
  streamExecutorGlobal: STREAM_ARM_EXECUTOR_GLOBAL,
1274
1276
  streamedArmSelector: STREAMED_ARM_SELECTOR,
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { c as PREFETCH_ATTRIBUTE, i as HTML_ATTRIBUTES_SLOT, l as REPLACE_ATTRIBUTE, o as routeMountTarget, s as LINK_ATTRIBUTE, t as rememberRequestLocals, u as SCROLL_ATTRIBUTE } from "./request-locals-z3rJTizs.js";
1
+ import { d as REPLACE_ATTRIBUTE, f as SCROLL_ATTRIBUTE, i as HTML_ATTRIBUTES_SLOT, l as LINK_ATTRIBUTE, s as routeMountTarget, t as rememberRequestLocals, u as PREFETCH_ATTRIBUTE } from "./request-locals-BDAhNpEa.js";
2
2
  import { a as normalizeRequestPathname, c as parseRequestFile, i as matchRouteManifest, l as transformRequestFileSource, n as buildRouteManifestFromFileIds, o as normalizeRouteFileId, s as normalizeRequestFileId } from "./route-manifest-DMizh42B.js";
3
3
  import { createExternalDelegateStructureTokens, createExternalDelegateView } from "@markless/web/fns/external-delegate";
4
4
  import { render } from "@markless/web/render";
@@ -8,6 +8,10 @@ const PREFETCH_ATTRIBUTE = "data-markless-router-prefetch";
8
8
  //#region src/route-dom.ts
9
9
  const ROUTE_SCRIPT_TYPE = "@markless/core/route";
10
10
  const HTML_ATTRIBUTES_SLOT = "__marklessRouterHtmlAttributes";
11
+ const SERVED_HTML_ATTRIBUTES_PROPERTY = "__marklessServedHtmlAttributes";
12
+ function servedHtmlAttributesScript() {
13
+ return `<script>document.documentElement.${SERVED_HTML_ATTRIBUTES_PROPERTY}=document.documentElement.getAttributeNames()<\/script>`;
14
+ }
11
15
  function routeMountTarget(document, currentRoot) {
12
16
  const root = currentRoot ?? document.querySelector(`script[type="@markless/core/route"]`)?.parentElement;
13
17
  if (!root) return document.body;
@@ -87,4 +91,4 @@ function takeMiddlewareHeaders(request) {
87
91
  return record.taken;
88
92
  }
89
93
  //#endregion
90
- export { ROUTE_SCRIPT_TYPE as a, PREFETCH_ATTRIBUTE as c, HTML_ATTRIBUTES_SLOT as i, REPLACE_ATTRIBUTE as l, requestLocals as n, routeMountTarget as o, takeMiddlewareHeaders as r, LINK_ATTRIBUTE as s, rememberRequestLocals as t, SCROLL_ATTRIBUTE as u };
94
+ export { ROUTE_SCRIPT_TYPE as a, servedHtmlAttributesScript as c, REPLACE_ATTRIBUTE as d, SCROLL_ATTRIBUTE as f, HTML_ATTRIBUTES_SLOT as i, LINK_ATTRIBUTE as l, requestLocals as n, SERVED_HTML_ATTRIBUTES_PROPERTY as o, takeMiddlewareHeaders as r, routeMountTarget as s, rememberRequestLocals as t, PREFETCH_ATTRIBUTE as u };
@@ -1,2 +1,2 @@
1
- import { n as routerLinkBridgeModules, t as createServerEntry } from "../../create-server-entry-DaYssO9x.js";
1
+ import { n as routerLinkBridgeModules, t as createServerEntry } from "../../create-server-entry-yboa-2m6.js";
2
2
  export { createServerEntry, routerLinkBridgeModules };
@@ -1,5 +1,64 @@
1
1
  import { i as protocolStateVersion, n as protocolInstanceQualifies, t as protocolInstancePath } from "../../protocol-constants-B4WxrZAS.js";
2
2
  //#endregion
3
+ //#region ../serializer/src/protocol-record-keys.ts
4
+ /** The field names each templated record list may spell, in the order its templates index them. */
5
+ const PROTOCOL_RECORD_KEYS = {
6
+ "markless/state": {
7
+ computed: "graphNodeId name async deriveSymbolId value version root $type $ref source flags records id type fields items entries values bytes arrayType buffer byteOffset length byteLength directValue dependencies path snapshot status key error",
8
+ sharedSeeds: "graphNodeId deriveSymbolId dependencies path reads",
9
+ sharedDefinitions: "id name exportedName scope version graphNodeIds projectionIds dependencies definitionId definitionName returnProperties kind graphNodeId path"
10
+ },
11
+ "markless/view": {
12
+ domUpdates: "hostNodeId source graphNodeId path target kind prefix suffix trueValue falseValue textNode name constantClass leadingClass symbolId",
13
+ behaviors: "hostNodeId source functionSource inputSources inputValues inputGraphReads inputIndex graphNodeId path symbolId usesGraph",
14
+ elementHandles: "hostNodeId handleId name plural"
15
+ }
16
+ };
17
+ //#endregion
18
+ //#region ../serializer/src/protocol-templated-lists.ts
19
+ /**
20
+ * The record lists a payload script may serve as record templates instead of a plain array. The
21
+ * inline resumer reads the others at page load, before any decoder has loaded, so they stay plain.
22
+ */
23
+ const PROTOCOL_TEMPLATED_LISTS = {
24
+ "markless/state": [
25
+ "computed",
26
+ "sharedSeeds",
27
+ "sharedDefinitions"
28
+ ],
29
+ "markless/view": [
30
+ "domUpdates",
31
+ "behaviors",
32
+ "elementHandles"
33
+ ]
34
+ };
35
+ //#endregion
36
+ //#region ../serializer/src/protocol-record-templates.ts
37
+ function expandRecordTemplates(list, keys = "") {
38
+ if (Array.isArray(list)) return list;
39
+ const { t, r } = list;
40
+ const names = keys.split(" ");
41
+ const parts = t.map((text) => text.replace(/~(\d+):/g, (_, at) => `"${names[+at]}":`).split(/\^(\d+)/));
42
+ return r.map((ref) => {
43
+ if (typeof ref === "number") return JSON.parse(parts[ref][0]);
44
+ if (!Array.isArray(ref)) return ref;
45
+ const part = parts[ref[0]];
46
+ let text = part[0];
47
+ for (let at = 1; at < part.length; at += 2) text += ref[+part[at] + 1] + part[at + 1];
48
+ return JSON.parse(text);
49
+ });
50
+ }
51
+ /** Expands, in place, every templated list of a freshly parsed payload script. */
52
+ function decodeProtocolPayloadLists(type, payload) {
53
+ const record = payload;
54
+ const keys = PROTOCOL_RECORD_KEYS[type];
55
+ if (record) for (const key of PROTOCOL_TEMPLATED_LISTS[type]) {
56
+ const list = record[key];
57
+ if (list && !Array.isArray(list)) record[key] = expandRecordTemplates(list, keys[key]);
58
+ }
59
+ return payload;
60
+ }
61
+ //#endregion
3
62
  //#region ../serializer/src/storage-cookie.ts
4
63
  const STORAGE_RENDER_CONTEXT_READER = "storageValue";
5
64
  //#endregion
@@ -747,7 +806,7 @@ function locate(root, view, host) {
747
806
  }
748
807
  function readScript(root, type) {
749
808
  const text = root.querySelector(`script[type="${type}"]`)?.textContent;
750
- return text ? JSON.parse(text) : void 0;
809
+ return text ? decodeProtocolPayloadLists(type, JSON.parse(text)) : void 0;
751
810
  }
752
811
  function references(value, id) {
753
812
  return value === id || !!value && typeof value === "object" && Object.values(value).some((child) => references(child, id));
package/dist/vite.js CHANGED
@@ -1,11 +1,11 @@
1
- import { c as PREFETCH_ATTRIBUTE, l as REPLACE_ATTRIBUTE, s as LINK_ATTRIBUTE, u as SCROLL_ATTRIBUTE } from "./request-locals-z3rJTizs.js";
2
- import { l as transformRequestFileSource, n as buildRouteManifestFromFileIds, o as normalizeRouteFileId, r as isPageModuleFile, t as assertNoEndpointConflicts } from "./route-manifest-DMizh42B.js";
1
+ import { d as REPLACE_ATTRIBUTE, f as SCROLL_ATTRIBUTE, l as LINK_ATTRIBUTE, u as PREFETCH_ATTRIBUTE } from "./request-locals-BDAhNpEa.js";
2
+ import { i as matchRouteManifest, l as transformRequestFileSource, n as buildRouteManifestFromFileIds, o as normalizeRouteFileId, r as isPageModuleFile, t as assertNoEndpointConflicts } from "./route-manifest-DMizh42B.js";
3
3
  import { t as __marklessRouteHref } from "./route-href-CGooO_g6.js";
4
4
  import { r as protocolIslandSegment } from "./protocol-constants-B4WxrZAS.js";
5
- import { a as runFragmentNavigation, i as FRAGMENT_PATH_PREFIX, n as routerLinkBridgeModules, o as startFragmentNavigation, s as listenForLinkIntent } from "./create-server-entry-DaYssO9x.js";
5
+ import { a as runFragmentNavigation, i as FRAGMENT_PATH_PREFIX, n as routerLinkBridgeModules, o as startFragmentNavigation, s as listenForLinkIntent } from "./create-server-entry-yboa-2m6.js";
6
6
  import { dirname, extname, isAbsolute, join, normalize, relative, resolve } from "pathe";
7
- import { decodePath, joinURL, parsePath, parseQuery, parseURL, stringifyQuery, withLeadingSlash, withQuery, withoutLeadingSlash } from "ufo";
8
- import { existsSync } from "node:fs";
7
+ import { decodePath, joinURL, parsePath, parseQuery, parseURL, stringifyQuery, withLeadingSlash, withQuery, withoutBase, withoutLeadingSlash } from "ufo";
8
+ import { existsSync, readdirSync } from "node:fs";
9
9
  import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
10
10
  import { nitro } from "nitro/vite";
11
11
  import { minifySync, sortUserPlugins } from "vite";
@@ -1574,6 +1574,7 @@ function router(options = {}) {
1574
1574
  return [
1575
1575
  routerConfigPlugin(nitroPlugins, resumeEntry, prerenderWakeEntry, navigationEntry, routePreloads, fragmentEntry, serverBuildState()),
1576
1576
  devSourceModuleRequestPlugin(),
1577
+ devPageForUrlPlugin(options),
1577
1578
  pageServerOnlyPropsPlugin(),
1578
1579
  mdxTransformPlugin(),
1579
1580
  requestFileTransformPlugin(),
@@ -1595,6 +1596,37 @@ function pageServerOnlyPropsPlugin() {
1595
1596
  }
1596
1597
  };
1597
1598
  }
1599
+ function devPageForUrlPlugin(options) {
1600
+ return {
1601
+ name: "markless-router:dev-page-for-url",
1602
+ apply: "serve",
1603
+ configResolved(config) {
1604
+ config.plugins.find((plugin) => typeof plugin.api?.declarePageForUrl === "function")?.api.declarePageForUrl((href) => devPageForUrl(config.root, config.base, options.mode, href));
1605
+ }
1606
+ };
1607
+ }
1608
+ function devPageForUrl(root, base, mode, href) {
1609
+ let url;
1610
+ try {
1611
+ url = new URL(href);
1612
+ } catch {
1613
+ return;
1614
+ }
1615
+ const pagesDir = join(root, "pages");
1616
+ if (!existsSync(pagesDir)) return void 0;
1617
+ const fileIds = readdirSync(pagesDir, {
1618
+ recursive: true,
1619
+ encoding: "utf8"
1620
+ }).map((file) => join("pages", file));
1621
+ let manifest;
1622
+ try {
1623
+ manifest = buildRouteManifestFromFileIds(fileIds);
1624
+ } catch {
1625
+ return;
1626
+ }
1627
+ const pathname = mode === "hash" && url.hash.startsWith("#/") ? url.hash.slice(1) : withoutBase(url.pathname, base);
1628
+ return matchRouteManifest(pathname, manifest)?.route.file ?? manifest.statusPages.notFound;
1629
+ }
1598
1630
  function devSourceModuleRequestPlugin() {
1599
1631
  let root = "";
1600
1632
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markless/router",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Markless router: client-side navigation with SSR streaming and build-time preload maps.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -64,11 +64,11 @@
64
64
  "pathe": "^2.0.3",
65
65
  "satteri": "0.6.3",
66
66
  "ufo": "^1.6.4",
67
- "@markless/bundler": "0.6.0",
68
- "@markless/web": "0.6.0"
67
+ "@markless/web": "0.7.0",
68
+ "@markless/bundler": "0.7.0"
69
69
  },
70
70
  "devDependencies": {
71
- "@markless/analyzer": "0.6.0"
71
+ "@markless/analyzer": "0.7.0"
72
72
  },
73
73
  "peerDependencies": {
74
74
  "typescript": "^5.9.3",
@@ -9,6 +9,7 @@ export interface FragmentNavigationConfig {
9
9
  readonly replaceAttribute: string;
10
10
  readonly scrollAttribute: string;
11
11
  readonly routeScriptType: string;
12
+ readonly servedHtmlAttributesProperty: string;
12
13
  readonly retireProperty: string;
13
14
  readonly streamExecutorGlobal: string;
14
15
  readonly streamedArmSelector: string;
@@ -409,10 +410,13 @@ async function mergeHead(d: Document, next: Document) {
409
410
  // Only attributes a server document set are the server's to remove: scripts own the rest (a theme class).
410
411
  const serverHtmlAttributes = new WeakMap<Document, Set<string>>();
411
412
 
412
- function syncHtmlAttributes(d: Document, next: Document): void {
413
+ function syncHtmlAttributes(d: Document, next: Document, servedProperty: string): void {
413
414
  const root = d.documentElement;
414
415
  const incoming = next.documentElement;
415
- for (const name of serverHtmlAttributes.get(d) ?? [])
416
+ const served =
417
+ serverHtmlAttributes.get(d) ??
418
+ ((root as unknown as Record<string, readonly string[] | undefined>)[servedProperty] || []);
419
+ for (const name of served)
416
420
  if (!incoming.hasAttribute(name)) root.removeAttribute(name);
417
421
  for (const attribute of incoming.attributes)
418
422
  if (root.getAttribute(attribute.name) !== attribute.value)
@@ -702,7 +706,7 @@ export async function navigate(
702
706
  (w as unknown as Record<string, unknown>)[state.config.streamExecutorGlobal] = undefined;
703
707
  for (const element of d.querySelectorAll(state.config.streamedArmSelector)) element.remove();
704
708
  state.shown = pageKey(new URL(w.location.href));
705
- if (documentLoads) syncHtmlAttributes(d, next);
709
+ if (documentLoads) syncHtmlAttributes(d, next, state.config.servedHtmlAttributesProperty);
706
710
  let region: Element;
707
711
  let end: Comment;
708
712
  let scripts: Element[];