@abide/abide 0.32.2 → 0.33.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.
Files changed (47) hide show
  1. package/AGENTS.md +3 -3
  2. package/CHANGELOG.md +79 -63
  3. package/package.json +6 -2
  4. package/src/lib/server/runtime/buildCacheSnapshot.ts +5 -4
  5. package/src/lib/server/runtime/types/InspectorCacheEntry.ts +1 -1
  6. package/src/lib/shared/cache.ts +43 -29
  7. package/src/lib/shared/types/CacheEntry.ts +12 -12
  8. package/src/lib/shared/types/CacheOptions.ts +17 -13
  9. package/src/lib/ui/README.md +1 -1
  10. package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +4 -1
  11. package/src/lib/ui/compile/componentWrapperTag.ts +1 -1
  12. package/src/lib/ui/compile/generateBuild.ts +265 -121
  13. package/src/lib/ui/compile/generateSSR.ts +78 -37
  14. package/src/lib/ui/compile/parseTemplate.ts +34 -0
  15. package/src/lib/ui/compile/skeletonable.ts +80 -0
  16. package/src/lib/ui/dom/MATHML_NAMESPACE.ts +6 -0
  17. package/src/lib/ui/dom/SVG_NAMESPACE.ts +7 -0
  18. package/src/lib/ui/dom/anchorCursor.ts +24 -0
  19. package/src/lib/ui/dom/appendSnippet.ts +1 -1
  20. package/src/lib/ui/dom/appendTextAt.ts +70 -0
  21. package/src/lib/ui/dom/awaitBlock.ts +27 -7
  22. package/src/lib/ui/dom/cloneStatic.ts +14 -7
  23. package/src/lib/ui/dom/each.ts +44 -25
  24. package/src/lib/ui/dom/eachAsync.ts +6 -2
  25. package/src/lib/ui/dom/effectiveChildNamespace.ts +13 -0
  26. package/src/lib/ui/dom/enterNamespace.ts +20 -0
  27. package/src/lib/ui/dom/fillBefore.ts +20 -3
  28. package/src/lib/ui/dom/foreignWrapperTag.ts +22 -0
  29. package/src/lib/ui/dom/hydrate.ts +1 -1
  30. package/src/lib/ui/dom/inheritedNamespace.ts +19 -0
  31. package/src/lib/ui/dom/mountSlot.ts +32 -0
  32. package/src/lib/ui/dom/openMarker.ts +4 -2
  33. package/src/lib/ui/dom/skeleton.ts +202 -0
  34. package/src/lib/ui/dom/switchBlock.ts +10 -3
  35. package/src/lib/ui/dom/tryBlock.ts +7 -5
  36. package/src/lib/ui/dom/types/SkeletonHoles.ts +8 -0
  37. package/src/lib/ui/dom/when.ts +6 -2
  38. package/src/lib/ui/installHotBridge.ts +8 -2
  39. package/src/lib/ui/runtime/HOLE_ATTRIBUTE.ts +9 -0
  40. package/src/lib/ui/runtime/RENDER.ts +7 -0
  41. package/src/lib/ui/runtime/types/UiProps.ts +3 -4
  42. package/template/src/ui/pages/about/page.abide +4 -6
  43. package/template/src/ui/pages/layout.abide +21 -0
  44. package/template/src/ui/pages/page.abide +5 -8
  45. package/src/lib/ui/compile/partitionSlots.ts +0 -36
  46. package/src/lib/ui/dom/openChild.ts +0 -22
  47. package/template/src/ui/Layout.abide +0 -19
@@ -2,9 +2,11 @@ import { html } from '../shared/html.ts'
2
2
  import { snippet } from '../shared/snippet.ts'
3
3
  import { derived } from './derived.ts'
4
4
  import { doc } from './doc.ts'
5
+ import { anchorCursor } from './dom/anchorCursor.ts'
5
6
  import { appendSnippet } from './dom/appendSnippet.ts'
6
7
  import { appendStatic } from './dom/appendStatic.ts'
7
8
  import { appendText } from './dom/appendText.ts'
9
+ import { appendTextAt } from './dom/appendTextAt.ts'
8
10
  import { attach } from './dom/attach.ts'
9
11
  import { attr } from './dom/attr.ts'
10
12
  import { awaitBlock } from './dom/awaitBlock.ts'
@@ -14,8 +16,9 @@ import { eachAsync } from './dom/eachAsync.ts'
14
16
  import { hydrate } from './dom/hydrate.ts'
15
17
  import { mount } from './dom/mount.ts'
16
18
  import { mountChild } from './dom/mountChild.ts'
19
+ import { mountSlot } from './dom/mountSlot.ts'
17
20
  import { on } from './dom/on.ts'
18
- import { openChild } from './dom/openChild.ts'
21
+ import { skeleton } from './dom/skeleton.ts'
19
22
  import { switchBlock } from './dom/switchBlock.ts'
20
23
  import { tryBlock } from './dom/tryBlock.ts'
21
24
  import { when } from './dom/when.ts'
@@ -48,11 +51,13 @@ export function installHotBridge(): void {
48
51
  derived,
49
52
  effect,
50
53
  mount,
51
- openChild,
52
54
  appendText,
55
+ appendTextAt,
53
56
  appendSnippet,
54
57
  appendStatic,
55
58
  cloneStatic,
59
+ skeleton,
60
+ anchorCursor,
56
61
  attr,
57
62
  on,
58
63
  attach,
@@ -62,6 +67,7 @@ export function installHotBridge(): void {
62
67
  awaitBlock,
63
68
  tryBlock,
64
69
  switchBlock,
70
+ mountSlot,
65
71
  mountChild,
66
72
  hydrate,
67
73
  nextBlockId,
@@ -0,0 +1,9 @@
1
+ /*
2
+ The marker attribute the client back-end stamps on each hole element in a skeleton
3
+ string — an element carrying reactive attributes/listeners/binds whose live node the
4
+ build must wire up. Its value is the hole's index, so attach code addresses holes by
5
+ number regardless of document order. `skeleton` records each marked node's path,
6
+ strips the attribute, and returns the holes indexed by it. One source of truth so the
7
+ compiler's emit and the runtime's read can't drift.
8
+ */
9
+ export const HOLE_ATTRIBUTE = 'data-abide-hole'
@@ -11,13 +11,20 @@ child components it inlines, so ids are globally unique within one render pass (
11
11
  SSR stream and client hydration agree on them — `RESUME` is keyed by id). `depth`
12
12
  tracks nesting so the OUTERMOST render/mount resets the counter and a child render/
13
13
  mount continues it. See `enterRenderPass`/`nextBlockId`.
14
+
15
+ `namespace` is the ambient foreign-content namespace (SVG/MathML) a control-flow block
16
+ sets from its insertion parent while building into a detached fragment, so foreign
17
+ elements built there get the right namespace — the fragment itself carries none. It is
18
+ undefined outside foreign content. See `enterNamespace`/`effectiveChildNamespace`.
14
19
  */
15
20
  export const RENDER: {
16
21
  hydration: { next: Map<Node, Node | null> } | undefined
17
22
  blockId: number
18
23
  depth: number
24
+ namespace: string | undefined
19
25
  } = {
20
26
  hydration: undefined,
21
27
  blockId: 0,
22
28
  depth: 0,
29
+ namespace: undefined,
23
30
  }
@@ -3,13 +3,12 @@ What a component is invoked with. Two real shapes flow through the one parameter
3
3
  A top-level page/layout is called by the router (client) and `renderChain` (SSR)
4
4
  with its decoded route params — a plain string map. A nested child is called by
5
5
  the compiler-emitted `mountChild` with a map of reactive thunks (each authored
6
- prop, read in the body as `$props[name]?.()`) plus optional slot builders:
7
- `$children` for default-slot markup and `$slots[name]` for named slots, each
8
- mounting into the host element it is handed.
6
+ prop, read in the body as `$props[name]?.()`) plus an optional `$children` slot
7
+ builder carrying the component's `<slot>` markup, mounting into the host element
8
+ it is handed.
9
9
  */
10
10
  export type UiProps =
11
11
  | Record<string, string>
12
12
  | (Record<string, () => unknown> & {
13
13
  $children?: (host: Element) => void
14
- $slots?: Record<string, (host: Element) => void>
15
14
  })
@@ -1,9 +1,7 @@
1
1
  <script>
2
- /* A second page — served at GET /about. Folder name becomes the URL segment. */
3
- import Layout from '../../Layout.abide'
2
+ /* A second page — served at GET /about. Folder name becomes the URL segment; the
3
+ root layout.abide wraps it, same as the home page. */
4
4
  </script>
5
5
 
6
- <Layout>
7
- <h1>About</h1>
8
- <p>This is a barebones abide app.</p>
9
- </Layout>
6
+ <h1>About</h1>
7
+ <p>This is a barebones abide app.</p>
@@ -0,0 +1,21 @@
1
+ <script>
2
+ /*
3
+ Root layout. A layout.abide wraps every page at or below its folder — here
4
+ src/ui/pages/, so it wraps the whole app. The page lands in the <slot/> outlet;
5
+ the resolver finds it by filename (no per-page import), and the client router
6
+ keeps it mounted across navigation, so this chrome never re-mounts. A nested
7
+ layout.abide (in a subfolder) wraps inside this one. Runs on the server during
8
+ SSR and on the client after hydration.
9
+ */
10
+ import '../app.css'
11
+ </script>
12
+
13
+ <header>
14
+ <nav>
15
+ <a href="/">Home</a>
16
+ <a href="/about">About</a>
17
+ </nav>
18
+ </header>
19
+ <main>
20
+ <slot></slot>
21
+ </main>
@@ -1,7 +1,7 @@
1
1
  <script>
2
2
  /*
3
3
  Root page — served at GET /. Every folder under src/ui/pages/ that contains a
4
- page.abide mounts at that folder's URL.
4
+ page.abide mounts at that folder's URL; the root layout.abide wraps it.
5
5
 
6
6
  The blocking await-block below (the `then` attribute sits ON the <template await>)
7
7
  resolves on the server during SSR and renders inline — no pending placeholder. The
@@ -11,12 +11,9 @@ instead would stream the resolution in out of order.
11
11
  */
12
12
  import { cache } from '@abide/abide/shared/cache'
13
13
  import { getHello } from '$server/rpc/getHello.ts'
14
- import Layout from '../Layout.abide'
15
14
  </script>
16
15
 
17
- <Layout>
18
- <template await={cache(getHello)()} then="hello">
19
- <h1>{hello.message}</h1>
20
- </template>
21
- <p>Edit <code>src/ui/pages/page.abide</code> and the page hot-reloads.</p>
22
- </Layout>
16
+ <template await={cache(getHello)()} then="hello">
17
+ <h1>{hello.message}</h1>
18
+ </template>
19
+ <p>Edit <code>src/ui/pages/page.abide</code> and the page hot-reloads.</p>
@@ -1,36 +0,0 @@
1
- import { staticAttrValue } from './staticAttrValue.ts'
2
- import type { TemplateNode } from './types/TemplateNode.ts'
3
-
4
- /* A component's slotted children split by destination slot. */
5
- export type SlotGroups = {
6
- default: TemplateNode[]
7
- named: { name: string; nodes: TemplateNode[] }[]
8
- }
9
-
10
- /*
11
- Partitions a component's children by their `slot="name"` attribute: an element
12
- carrying one goes to that named group (with the directive attr stripped so it
13
- never renders as a real attribute), everything else forms the default slot. Both
14
- back-ends partition identically, so SSR and client agree on which markup lands in
15
- which `<slot>`.
16
- */
17
- export function partitionSlots(children: TemplateNode[]): SlotGroups {
18
- const defaults: TemplateNode[] = []
19
- const named = new Map<string, TemplateNode[]>()
20
- for (const child of children) {
21
- const name = child.kind === 'element' ? staticAttrValue(child, 'slot') : undefined
22
- if (child.kind !== 'element' || name === undefined) {
23
- defaults.push(child)
24
- continue
25
- }
26
- const stripped = {
27
- ...child,
28
- attrs: child.attrs.filter((attr) => !(attr.kind === 'static' && attr.name === 'slot')),
29
- }
30
- named.set(name, [...(named.get(name) ?? []), stripped])
31
- }
32
- return {
33
- default: defaults,
34
- named: [...named].map(([name, nodes]) => ({ name, nodes })),
35
- }
36
- }
@@ -1,22 +0,0 @@
1
- import { claimChild } from '../runtime/claimChild.ts'
2
- import { RENDER } from '../runtime/RENDER.ts'
3
-
4
- /*
5
- Opens a child element of `parent`: creates and appends it (create mode), or claims
6
- the existing server-rendered node at the parent's current build position (hydrate
7
- mode), advancing the claim pointer. Returns the element so bindings and children
8
- attach to it. The compiler emits this for every element so the same build code
9
- serves both modes.
10
- */
11
- // @readme plumbing
12
- export function openChild(parent: Node, tag: string): Element {
13
- const hydration = RENDER.hydration
14
- if (hydration !== undefined) {
15
- const current = claimChild(hydration, parent)
16
- hydration.next.set(parent, current === null ? null : current.nextSibling)
17
- return current as unknown as Element
18
- }
19
- const element = document.createElement(tag)
20
- parent.appendChild(element)
21
- return element
22
- }
@@ -1,19 +0,0 @@
1
- <script>
2
- /*
3
- Userland root layout. abide has no framework layout resolution — a layout is just
4
- a component a page wraps its body in (<Layout>…</Layout>), and the body lands in
5
- the <slot>. Import and wrap this from each page.abide to share chrome. Runs on the
6
- server during SSR and on the client after hydration.
7
- */
8
- import './app.css'
9
- </script>
10
-
11
- <header>
12
- <nav>
13
- <a href="/">Home</a>
14
- <a href="/about">About</a>
15
- </nav>
16
- </header>
17
- <main>
18
- <slot></slot>
19
- </main>