@abide/abide 0.43.1 → 0.44.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.
- package/AGENTS.md +293 -219
- package/CHANGELOG.md +39 -0
- package/README.md +191 -67
- package/package.json +2 -2
- package/src/abideLsp.ts +8 -4
- package/src/abideResolverPlugin.ts +11 -1
- package/src/build.ts +9 -0
- package/src/buildDisconnected.ts +2 -0
- package/src/devEntry.ts +1 -0
- package/src/lib/bundle/probeAbideServer.ts +1 -1
- package/src/lib/cli/parseArgvForRpc.ts +4 -2
- package/src/lib/cli/types/CliManifest.ts +6 -4
- package/src/lib/mcp/annotationsForMethod.ts +1 -1
- package/src/lib/mcp/toolResultFromResponse.ts +1 -1
- package/src/lib/mcp/types/JsonRpcRequest.ts +3 -3
- package/src/lib/server/AppModule.ts +1 -1
- package/src/lib/server/cli/handleCliDownload.ts +7 -5
- package/src/lib/server/cli/installScript.ts +3 -3
- package/src/lib/server/prompts/promptRegistry.ts +1 -1
- package/src/lib/server/prompts/types/Prompt.ts +2 -1
- package/src/lib/server/rpc/dispatchRpcInProcess.ts +2 -2
- package/src/lib/server/rpc/types/RpcHelper.ts +1 -1
- package/src/lib/server/runtime/createPublicAssetServer.ts +2 -2
- package/src/lib/server/runtime/createRouteDispatcher.ts +1 -1
- package/src/lib/server/runtime/createServer.ts +21 -0
- package/src/lib/server/runtime/createUiPageRenderer.ts +7 -11
- package/src/lib/server/runtime/crossOriginForbidden.ts +2 -2
- package/src/lib/server/runtime/crossOriginGate.ts +1 -1
- package/src/lib/server/runtime/devHotModuleResponse.ts +2 -2
- package/src/lib/server/runtime/disableIdleTimeoutForStream.ts +1 -1
- package/src/lib/server/runtime/logExposedSurfaces.ts +3 -4
- package/src/lib/server/runtime/serializeCacheSnapshot.ts +3 -8
- package/src/lib/server/runtime/snapshotEntryFromCache.ts +10 -7
- package/src/lib/server/runtime/streamFromIterator.ts +2 -2
- package/src/lib/server/runtime/types/InspectorRpc.ts +2 -2
- package/src/lib/server/socket.ts +1 -1
- package/src/lib/server/sockets/createSocketDispatcher.ts +2 -2
- package/src/lib/server/sockets/socketRegistry.ts +2 -2
- package/src/lib/server/sockets/types/SocketClientFrame.ts +3 -3
- package/src/lib/shared/CACHE_WRAPPED.ts +3 -2
- package/src/lib/shared/REMOTE_FUNCTION.ts +1 -1
- package/src/lib/shared/STREAMING_CONTENT_TYPES.ts +3 -2
- package/src/lib/shared/bodyValueForKind.ts +35 -0
- package/src/lib/shared/cache.ts +1 -1
- package/src/lib/shared/cacheEntryFromSnapshot.ts +23 -18
- package/src/lib/shared/createPushIterator.ts +1 -1
- package/src/lib/shared/decodeResponse.ts +11 -8
- package/src/lib/shared/hasReplayableRequest.ts +17 -0
- package/src/lib/shared/html.ts +7 -26
- package/src/lib/shared/manifestModule.ts +2 -2
- package/src/lib/shared/online.ts +1 -1
- package/src/lib/shared/outboxProbeSlot.ts +1 -1
- package/src/lib/shared/pageUrlForFile.ts +1 -1
- package/src/lib/shared/routeParamsShape.ts +20 -0
- package/src/lib/shared/snapshotShippable.ts +22 -0
- package/src/lib/shared/types/CacheEntry.ts +1 -1
- package/src/lib/shared/types/CacheInvalidation.ts +3 -3
- package/src/lib/shared/types/ClientFlags.ts +5 -3
- package/src/lib/shared/types/LastConnection.ts +1 -1
- package/src/lib/shared/url.ts +4 -1
- package/src/lib/shared/withBase.ts +1 -2
- package/src/lib/shared/writeDts.ts +2 -2
- package/src/lib/shared/writeRoutesDts.ts +3 -31
- package/src/lib/test/createTestSocketChannel.ts +3 -3
- package/src/lib/ui/COMPONENT_WRAPPER_PREFIX.ts +1 -1
- package/src/lib/ui/compile/ABIDE_SEMANTIC_TOKENS_LEGEND.ts +40 -6
- package/src/lib/ui/compile/REACTIVE_CALLEES.ts +1 -1
- package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +49 -37
- package/src/lib/ui/compile/abideUiPlugin.ts +1 -1
- package/src/lib/ui/compile/assertRuntimeHelpersBound.ts +3 -1
- package/src/lib/ui/compile/awaitPlan.ts +14 -2
- package/src/lib/ui/compile/catchBinding.ts +8 -0
- package/src/lib/ui/compile/classStyleMergePlan.ts +108 -0
- package/src/lib/ui/compile/collectAbideDiagnostics.ts +11 -0
- package/src/lib/ui/compile/compileComponent.ts +4 -4
- package/src/lib/ui/compile/compileModule.ts +20 -6
- package/src/lib/ui/compile/compileSSR.ts +12 -10
- package/src/lib/ui/compile/compileShadow.ts +88 -11
- package/src/lib/ui/compile/composeProps.ts +3 -3
- package/src/lib/ui/compile/createShadowLanguageService.ts +30 -2
- package/src/lib/ui/compile/createShadowProgram.ts +2 -1
- package/src/lib/ui/compile/createShadowScope.ts +42 -0
- package/src/lib/ui/compile/decodeHtmlEntities.ts +4 -0
- package/src/lib/ui/compile/desugarSignals.ts +12 -7
- package/src/lib/ui/compile/eachPlan.ts +61 -0
- package/src/lib/ui/compile/elementPlan.ts +55 -0
- package/src/lib/ui/compile/encodeSemanticTokens.ts +24 -1
- package/src/lib/ui/compile/generateBuild.ts +150 -127
- package/src/lib/ui/compile/generateSSR.ts +112 -73
- package/src/lib/ui/compile/ifPlan.ts +7 -1
- package/src/lib/ui/compile/interpolatedTemplateLiteral.ts +25 -0
- package/src/lib/ui/compile/isPlainIdentifier.ts +7 -0
- package/src/lib/ui/compile/isTextLeaf.ts +8 -7
- package/src/lib/ui/compile/lowerContext.ts +36 -46
- package/src/lib/ui/compile/lowerDocAccess.ts +2 -2
- package/src/lib/ui/compile/lowerScript.ts +1 -1
- package/src/lib/ui/compile/markupTokens.ts +313 -0
- package/src/lib/ui/compile/pagePropsType.ts +23 -0
- package/src/lib/ui/compile/parseTemplate.ts +131 -61
- package/src/lib/ui/compile/reactiveBinding.ts +47 -0
- package/src/lib/ui/compile/renameSignalRefs.ts +44 -10
- package/src/lib/ui/compile/skeletonable.ts +5 -5
- package/src/lib/ui/compile/snippetPlan.ts +28 -0
- package/src/lib/ui/compile/spreadExcludedNames.ts +4 -4
- package/src/lib/ui/compile/structuralBlockTokens.ts +3 -4
- package/src/lib/ui/compile/switchPlan.ts +7 -1
- package/src/lib/ui/compile/tryPlan.ts +8 -1
- package/src/lib/ui/compile/types/AnalyzedComponent.ts +2 -2
- package/src/lib/ui/compile/types/Binding.ts +24 -0
- package/src/lib/ui/compile/types/CompiledShadow.ts +13 -1
- package/src/lib/ui/compile/types/ElementPlan.ts +43 -0
- package/src/lib/ui/compile/types/ShadowKind.ts +9 -0
- package/src/lib/ui/compile/types/ShadowScope.ts +19 -0
- package/src/lib/ui/compile/types/TemplateAttr.ts +7 -0
- package/src/lib/ui/compile/types/TemplateNode.ts +8 -8
- package/src/lib/ui/compile/withBindings.ts +45 -0
- package/src/lib/ui/createScope.ts +2 -2
- package/src/lib/ui/dom/MATHML_NAMESPACE.ts +2 -2
- package/src/lib/ui/dom/SVG_NAMESPACE.ts +3 -2
- package/src/lib/ui/dom/appendText.ts +1 -1
- package/src/lib/ui/dom/applyResolved.ts +1 -1
- package/src/lib/ui/dom/buildDetachedRange.ts +4 -3
- package/src/lib/ui/dom/cloneStatic.ts +3 -2
- package/src/lib/ui/dom/each.ts +26 -7
- package/src/lib/ui/dom/foreignWrapperTag.ts +2 -2
- package/src/lib/ui/dom/mountChild.ts +8 -1
- package/src/lib/ui/dom/readCall.ts +6 -4
- package/src/lib/ui/dom/scopeLabel.ts +3 -3
- package/src/lib/ui/dom/switchBlock.ts +3 -1
- package/src/lib/ui/dom/types/EachRow.ts +4 -0
- package/src/lib/ui/dom/types/SkeletonHoles.ts +4 -2
- package/src/lib/ui/history.ts +1 -2
- package/src/lib/ui/html.ts +28 -0
- package/src/lib/ui/installHotBridge.ts +0 -2
- package/src/lib/ui/renderChain.ts +16 -3
- package/src/lib/ui/router.ts +24 -3
- package/src/lib/ui/rpcOutbox/createOutboxQueue.ts +1 -1
- package/src/lib/ui/runtime/CHILD_PRESENT.ts +8 -0
- package/src/lib/ui/runtime/OUTLET_MARKER.ts +2 -2
- package/src/lib/ui/runtime/RANGE_MARKER.ts +1 -1
- package/src/lib/ui/runtime/REACTIVE_CONTEXT.ts +9 -3
- package/src/lib/ui/runtime/captureModelDoc.ts +1 -1
- package/src/lib/ui/runtime/createDoc.ts +5 -1
- package/src/lib/ui/runtime/createEffectNode.ts +5 -5
- package/src/lib/ui/runtime/flushEffects.ts +22 -9
- package/src/lib/ui/runtime/historyEntries.ts +1 -1
- package/src/lib/ui/runtime/track.ts +4 -2
- package/src/lib/ui/runtime/trigger.ts +34 -20
- package/src/lib/ui/runtime/types/Route.ts +2 -2
- package/src/lib/ui/runtime/types/UiComponent.ts +3 -2
- package/src/lib/ui/runtime/types/UiProps.ts +9 -12
- package/src/lib/ui/runtime/walkPath.ts +15 -3
- package/src/lib/ui/startClient.ts +41 -14
- package/src/lib/ui/state.ts +3 -3
- package/src/lib/ui/types/Scope.ts +3 -3
- package/src/serverBuildPlugins.ts +11 -5
- package/src/zodCjsPlugin.ts +36 -0
- package/template/.zed/settings.json +7 -1
- package/template/package.json +3 -0
- package/template/src/ui/pages/layout.abide +3 -5
|
@@ -6,23 +6,30 @@ import { asOutlet } from './asOutlet.ts'
|
|
|
6
6
|
import { awaitPlan } from './awaitPlan.ts'
|
|
7
7
|
import { bindListenEvent } from './bindListenEvent.ts'
|
|
8
8
|
import { composeProps } from './composeProps.ts'
|
|
9
|
-
import {
|
|
9
|
+
import { eachPlan } from './eachPlan.ts'
|
|
10
|
+
import { elementPlan } from './elementPlan.ts'
|
|
10
11
|
import { groupBindParts } from './groupBindParts.ts'
|
|
11
12
|
import { ifPlan } from './ifPlan.ts'
|
|
13
|
+
import { interpolatedTemplateLiteral } from './interpolatedTemplateLiteral.ts'
|
|
12
14
|
import { isControlFlow } from './isControlFlow.ts'
|
|
15
|
+
import { isPlainIdentifier } from './isPlainIdentifier.ts'
|
|
13
16
|
import { isWhitespaceText } from './isWhitespaceText.ts'
|
|
14
17
|
import { lowerContext } from './lowerContext.ts'
|
|
15
18
|
import { makeVarNamer } from './makeVarNamer.ts'
|
|
16
|
-
import {
|
|
19
|
+
import { reactiveBinding } from './reactiveBinding.ts'
|
|
17
20
|
import { scopeAttr } from './scopeAttr.ts'
|
|
18
21
|
import { skeletonContext } from './skeletonContext.ts'
|
|
22
|
+
import { snippetPlan } from './snippetPlan.ts'
|
|
19
23
|
import { spreadExcludedNames } from './spreadExcludedNames.ts'
|
|
20
24
|
import { staticAttr } from './staticAttr.ts'
|
|
21
25
|
import { staticTextPart } from './staticTextPart.ts'
|
|
22
26
|
import { switchPlan } from './switchPlan.ts'
|
|
23
27
|
import { tryPlan } from './tryPlan.ts'
|
|
28
|
+
import type { Binding } from './types/Binding.ts'
|
|
29
|
+
import type { ShadowKind } from './types/ShadowKind.ts'
|
|
24
30
|
import type { TemplateNode } from './types/TemplateNode.ts'
|
|
25
31
|
import { VOID_TAGS } from './VOID_TAGS.ts'
|
|
32
|
+
import { withBindings } from './withBindings.ts'
|
|
26
33
|
|
|
27
34
|
/* The skeleton positioning anchor a control-flow block / component / slot / outlet
|
|
28
35
|
stamps into its skeleton markup, sourced from the same `ANCHOR` wire-alphabet constant
|
|
@@ -36,8 +43,9 @@ attributes, reactive `attr`/`text` bindings, `on` listeners, keyed `each`, and
|
|
|
36
43
|
conditional `when`. Every embedded expression is first rewritten from the signal
|
|
37
44
|
surface (`count` → `model.count`) and then lowered to the doc patch/read API
|
|
38
45
|
(cell-hoisting runs over the whole result afterwards). The output operates on
|
|
39
|
-
`hostVar` and expects the dom bindings
|
|
40
|
-
|
|
46
|
+
`hostVar` and expects the `$$`-aliased dom bindings (`$$effect`, `$$each`, …)
|
|
47
|
+
and the component's `$$model` (emitted by `desugarSignals`) in scope — the body
|
|
48
|
+
the component compiler wraps and hoists cells into.
|
|
41
49
|
*/
|
|
42
50
|
|
|
43
51
|
/* A JS-identifier-safe frame name from an authored construct label (an attribute name
|
|
@@ -97,49 +105,17 @@ export function generateBuild(
|
|
|
97
105
|
expression: lowerExpression,
|
|
98
106
|
statement: lowerStatement,
|
|
99
107
|
withNestedScripts,
|
|
100
|
-
|
|
108
|
+
withShadow,
|
|
101
109
|
bindRead,
|
|
102
110
|
bindWrite,
|
|
103
111
|
} = lowerContext(stateNames, derivedNames, computedNames)
|
|
104
112
|
|
|
105
|
-
/*
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
/* How a reactive value param binds: `param` is the thunk's value parameter (the cell),
|
|
112
|
-
`prefix` declares any per-leaf readers, `localNames` enter the deref scope for the body.
|
|
113
|
-
A plain identifier reads the cell directly (`item` → `item.value`); a destructure
|
|
114
|
-
re-applies over the cell per read so each leaf stays reactive (JS handles
|
|
115
|
-
defaults/rest/rename/nesting). The caller lowers the body with `localNames` derived. */
|
|
116
|
-
function reactiveBinding(authorParam: string): {
|
|
117
|
-
param: string
|
|
118
|
-
prefix: string
|
|
119
|
-
localNames: string[]
|
|
120
|
-
} {
|
|
121
|
-
if (isPlainIdentifier(authorParam)) {
|
|
122
|
-
return { param: authorParam, prefix: '', localNames: [authorParam] }
|
|
123
|
-
}
|
|
124
|
-
const cellParam = nextVar('aw')
|
|
125
|
-
const deriveVar = nextVar('ad')
|
|
126
|
-
const leaves = destructureBindingNames(authorParam)
|
|
127
|
-
/* Lower the destructure declaration so a default/computed-key initializer that
|
|
128
|
-
references a component signal (`{ label = fallback }`, `{ [key]: v }`) is rewritten
|
|
129
|
-
to its `model`/cell form — the bound leaf names are name-slots and stay untouched.
|
|
130
|
-
A pattern with no such initializer lowers to itself, so the common case is unchanged
|
|
131
|
-
and SSR (which lowers the same declaration) stays congruent. */
|
|
132
|
-
const declaration = lowerStatement(`const ${authorParam} = ${cellParam}.value`)
|
|
133
|
-
const prefix =
|
|
134
|
-
`const ${deriveVar} = { get value() { ${declaration} return { ${leaves.join(', ')} }; } };\n` +
|
|
135
|
-
leaves
|
|
136
|
-
.map(
|
|
137
|
-
(leaf) =>
|
|
138
|
-
`const ${leaf} = { get value() { return ${deriveVar}.value.${leaf}; } };\n`,
|
|
139
|
-
)
|
|
140
|
-
.join('')
|
|
141
|
-
return { param: cellParam, prefix, localNames: leaves }
|
|
142
|
-
}
|
|
113
|
+
/* Maps a plan `Binding`'s classification to the client `ShadowKind`: a `reactive` value
|
|
114
|
+
derefs as a `.value` cell (`derived`), a `plain` value as the bare local (`plain`). The
|
|
115
|
+
injected mapping `withBindings` registers every binding through — the one place a name's
|
|
116
|
+
kind is chosen on this back-end. */
|
|
117
|
+
const buildBindingKind = (binding: Binding): ShadowKind =>
|
|
118
|
+
binding.classification === 'reactive' ? 'derived' : 'plain'
|
|
143
119
|
|
|
144
120
|
/* Emits the wiring for one non-static attribute against an already-obtained skeleton
|
|
145
121
|
element var — reactive `attr`, `on` listener, `attach`, or a two-way `bind`. */
|
|
@@ -147,27 +123,36 @@ export function generateBuild(
|
|
|
147
123
|
node: Extract<TemplateNode, { kind: 'element' }>,
|
|
148
124
|
attr: Extract<
|
|
149
125
|
(typeof node.attrs)[number],
|
|
150
|
-
{
|
|
126
|
+
{
|
|
127
|
+
kind:
|
|
128
|
+
'expression' | 'interpolated' | 'event' | 'attach' | 'bind' | 'class' | 'style'
|
|
129
|
+
}
|
|
151
130
|
>,
|
|
152
131
|
varName: string,
|
|
153
132
|
): string {
|
|
154
133
|
if (attr.kind === 'expression') {
|
|
155
|
-
return
|
|
134
|
+
return `$$attr(${varName}, ${JSON.stringify(attr.name)}, ${namedThunk(`attr_${attr.name}`, `return (${lowerExpression(attr.code)})`)});\n`
|
|
135
|
+
}
|
|
136
|
+
/* `name="literal {expr}"` — the template-literal concatenation bound as a reactive
|
|
137
|
+
string attribute (always present). A class/style with directives is merged
|
|
138
|
+
upstream of this dispatch, so it never reaches here. */
|
|
139
|
+
if (attr.kind === 'interpolated') {
|
|
140
|
+
return `$$attr(${varName}, ${JSON.stringify(attr.name)}, ${namedThunk(`attr_${attr.name}`, `return (${lowerExpression(interpolatedTemplateLiteral(attr.parts))})`)});\n`
|
|
156
141
|
}
|
|
157
142
|
if (attr.kind === 'event') {
|
|
158
|
-
return
|
|
143
|
+
return `$$on(${varName}, ${JSON.stringify(attr.event)}, (${lowerExpression(attr.code)}));\n`
|
|
159
144
|
}
|
|
160
145
|
if (attr.kind === 'attach') {
|
|
161
|
-
return
|
|
146
|
+
return `$$attach(${varName}, (${lowerExpression(attr.code)}));\n`
|
|
162
147
|
}
|
|
163
148
|
/* `class:<name>` — toggle the class by truthiness; surgical, no element re-render.
|
|
164
149
|
Layers on top of any static `class="…"` in the skeleton (classList is additive). */
|
|
165
150
|
if (attr.kind === 'class') {
|
|
166
|
-
return
|
|
151
|
+
return `$$effect(${namedThunk(`class_${attr.name}`, `${varName}.classList.toggle(${JSON.stringify(attr.name)}, !!(${lowerExpression(attr.code)}));`)});\n`
|
|
167
152
|
}
|
|
168
153
|
/* `style:<property>` — write one inline style / custom property reactively. */
|
|
169
154
|
if (attr.kind === 'style') {
|
|
170
|
-
return
|
|
155
|
+
return `$$effect(${namedThunk(`style_${attr.property}`, `${varName}.style.setProperty(${JSON.stringify(attr.property)}, String(${lowerExpression(attr.code)}));`)});\n`
|
|
171
156
|
}
|
|
172
157
|
if (attr.property === 'group') {
|
|
173
158
|
/* Grouped two-way: radio binds the path to the single checked `value`;
|
|
@@ -180,13 +165,13 @@ export function generateBuild(
|
|
|
180
165
|
const value = lowerExpression(valueCode)
|
|
181
166
|
if (isRadio) {
|
|
182
167
|
return (
|
|
183
|
-
|
|
184
|
-
|
|
168
|
+
`$$effect(${namedThunk('bind_group', `${varName}.checked = (${lowerExpression(attr.code)}) === (${value});`)});\n` +
|
|
169
|
+
`$$on(${varName}, "change", () => { if (${varName}.checked) { ${lowerStatement(`${attr.code} = ${valueCode}`)} } });\n`
|
|
185
170
|
)
|
|
186
171
|
}
|
|
187
172
|
return (
|
|
188
|
-
|
|
189
|
-
|
|
173
|
+
`$$effect(${namedThunk('bind_group', `${varName}.checked = (${lowerExpression(attr.code)}).includes(${value});`)});\n` +
|
|
174
|
+
`$$on(${varName}, "change", () => { const $groupValue = ${value}; if (${varName}.checked) { if (!(${lowerExpression(attr.code)}).includes($groupValue)) { ${lowerStatement(`${attr.code}.push($groupValue)`)} } } else { const $groupIndex = (${lowerExpression(attr.code)}).indexOf($groupValue); if ($groupIndex !== -1) { ${lowerStatement(`delete ${attr.code}[$groupIndex]`)} } } });\n`
|
|
190
175
|
)
|
|
191
176
|
}
|
|
192
177
|
/* Two-way: drive the property from the bind target, and write it back on the
|
|
@@ -196,8 +181,8 @@ export function generateBuild(
|
|
|
196
181
|
`.get()` and writes via `.set(v)` — see `bindRead`/`bindWrite`. */
|
|
197
182
|
const event = bindListenEvent(attr.property, node.tag)
|
|
198
183
|
return (
|
|
199
|
-
|
|
200
|
-
|
|
184
|
+
`$$effect(${namedThunk(`bind_${attr.property}`, `${varName}.${attr.property} = ${bindRead(attr.code)};`)});\n` +
|
|
185
|
+
`$$on(${varName}, ${JSON.stringify(event)}, () => { ${bindWrite(attr.code, `${varName}.${attr.property}`)} });\n`
|
|
201
186
|
)
|
|
202
187
|
}
|
|
203
188
|
|
|
@@ -231,7 +216,7 @@ export function generateBuild(
|
|
|
231
216
|
return staticTextPart(part.value)
|
|
232
217
|
}
|
|
233
218
|
binds.push(
|
|
234
|
-
|
|
219
|
+
`$$appendTextAt(${skVar}.an[${holeIndex(anIndex, part)}], ${namedThunk('text', `return (${lowerExpression(part.code)})`)});\n`,
|
|
235
220
|
)
|
|
236
221
|
return ANCHOR_COMMENT
|
|
237
222
|
})
|
|
@@ -245,7 +230,9 @@ export function generateBuild(
|
|
|
245
230
|
(`anchor.parentNode`). A component takes an anchor like a block — no wrapper
|
|
246
231
|
element — so its root lays out as a true direct child of `anchor.parentNode`. */
|
|
247
232
|
const anchorVar = anchorVarAt(node, skVar, binds)
|
|
248
|
-
binds.push(
|
|
233
|
+
binds.push(
|
|
234
|
+
generateChild(node, `${anchorVar}.parentNode`, `$$anchorCursor(${anchorVar})`),
|
|
235
|
+
)
|
|
249
236
|
return ANCHOR_COMMENT
|
|
250
237
|
}
|
|
251
238
|
if (node.kind === 'script') {
|
|
@@ -270,7 +257,7 @@ export function generateBuild(
|
|
|
270
257
|
`outlet` boundary the router fills with the next chain layer (`fillBoundary`).
|
|
271
258
|
No wrapper element — the filled child lays out as a direct child of the parent. */
|
|
272
259
|
const anchorVar = anchorVarAt(node, skVar, binds)
|
|
273
|
-
binds.push(
|
|
260
|
+
binds.push(`$$outlet(${anchorVar}.parentNode, $$anchorCursor(${anchorVar}));\n`)
|
|
274
261
|
return ANCHOR_COMMENT
|
|
275
262
|
}
|
|
276
263
|
if (node.tag === 'slot') {
|
|
@@ -279,7 +266,7 @@ export function generateBuild(
|
|
|
279
266
|
const anchorVar = anchorVarAt(node, skVar, binds)
|
|
280
267
|
const hostVar = nextVar('host')
|
|
281
268
|
binds.push(
|
|
282
|
-
|
|
269
|
+
`$$mountSlot(${anchorVar}.parentNode, (${hostVar}) => {\n${generateSlot(node, hostVar)}}, $$anchorCursor(${anchorVar}));\n`,
|
|
283
270
|
)
|
|
284
271
|
return ANCHOR_COMMENT
|
|
285
272
|
}
|
|
@@ -294,6 +281,12 @@ export function generateBuild(
|
|
|
294
281
|
SSR back-end also reads, rather than re-deriving leaf-ness via `isTextLeaf` here. */
|
|
295
282
|
const textLeafBind =
|
|
296
283
|
reactiveTextChild !== undefined && markText.get(reactiveTextChild) === false
|
|
284
|
+
/* The shared per-element emission DECISION (one site both back-ends consult): each
|
|
285
|
+
attribute classified + tagged with its merge status, the class/style/directive merge
|
|
286
|
+
folded in, and void-tag status. The composed `classParts`/`styleParts` and the
|
|
287
|
+
merge triggers come from the same plan SSR renders, so neither the attribute set nor
|
|
288
|
+
the merged value can drift. Build still RENDERS each kind below as live wiring. */
|
|
289
|
+
const plan = elementPlan(node, lowerExpression)
|
|
297
290
|
let openTag = `<${node.tag}`
|
|
298
291
|
let elVar = ''
|
|
299
292
|
if (hasReactiveAttr || textLeafBind) {
|
|
@@ -303,13 +296,33 @@ export function generateBuild(
|
|
|
303
296
|
elVar = nextVar('el')
|
|
304
297
|
binds.push(`const ${elVar} = ${skVar}.el[${holeIndex(elIndex, node)}];\n`)
|
|
305
298
|
openTag += ` ${HOLE_ATTRIBUTE}`
|
|
306
|
-
|
|
299
|
+
/* An interpolated (reactive) class/style base can't layer additive directive toggles
|
|
300
|
+
on top — re-setting the base would wipe them — so base + its directives collapse
|
|
301
|
+
into ONE effect computing the whole value (`mergeClassBuild`/`mergeStyleBuild`,
|
|
302
|
+
the build trigger); their attrs are then skipped (`mergedBuild`) in the dispatch
|
|
303
|
+
below. A STATIC base keeps the surgical-toggle model (the base sits in the cloned
|
|
304
|
+
skeleton). */
|
|
305
|
+
if (plan.merge.mergeClassBuild) {
|
|
306
|
+
binds.push(
|
|
307
|
+
`$$effect(${namedThunk('class_merge', `${elVar}.setAttribute("class", [${plan.merge.classParts.join(', ')}].filter(Boolean).join(' '));`)});\n`,
|
|
308
|
+
)
|
|
309
|
+
}
|
|
310
|
+
if (plan.merge.mergeStyleBuild) {
|
|
311
|
+
binds.push(
|
|
312
|
+
`$$effect(${namedThunk('style_merge', `${elVar}.setAttribute("style", [${plan.merge.styleParts.join(', ')}].filter(Boolean).join(';'));`)});\n`,
|
|
313
|
+
)
|
|
314
|
+
}
|
|
315
|
+
for (const { attr, mergedBuild } of plan.attrs) {
|
|
316
|
+
/* Skip the attrs already folded into a merged class/style effect. */
|
|
317
|
+
if (mergedBuild) {
|
|
318
|
+
continue
|
|
319
|
+
}
|
|
307
320
|
if (attr.kind === 'spread') {
|
|
308
321
|
/* `{...expr}` onto the element: each key binds as a reactive attribute
|
|
309
322
|
(or an `on<event>` function as a listener) via `spreadAttrs`, skipping
|
|
310
323
|
any key explicitly named on the element (the explicit attr wins). */
|
|
311
324
|
binds.push(
|
|
312
|
-
|
|
325
|
+
`$$spreadAttrs(${elVar}, ${namedThunk('spread', `return (${lowerExpression(attr.code)})`)}, ${JSON.stringify(spreadExcludedNames(node.attrs))});\n`,
|
|
313
326
|
)
|
|
314
327
|
} else if (attr.kind !== 'static') {
|
|
315
328
|
binds.push(dynamicAttr(node, attr, elVar))
|
|
@@ -319,13 +332,16 @@ export function generateBuild(
|
|
|
319
332
|
for (const scope of node.scopes ?? []) {
|
|
320
333
|
openTag += scopeAttr(scope)
|
|
321
334
|
}
|
|
322
|
-
|
|
323
|
-
|
|
335
|
+
/* Static attrs sit in the cloned skeleton markup — emitted here regardless of whether
|
|
336
|
+
the element is a hole. A static class/style folded into a merged attribute (build
|
|
337
|
+
trigger) is skipped, since the merge effect re-sets the whole value. */
|
|
338
|
+
for (const { attr, mergedBuild } of plan.attrs) {
|
|
339
|
+
if (attr.kind === 'static' && !mergedBuild) {
|
|
324
340
|
openTag += staticAttr(attr.name, attr.value)
|
|
325
341
|
}
|
|
326
342
|
}
|
|
327
343
|
openTag += '>'
|
|
328
|
-
if (
|
|
344
|
+
if (plan.isVoid) {
|
|
329
345
|
return openTag
|
|
330
346
|
}
|
|
331
347
|
if (textLeafBind) {
|
|
@@ -352,7 +368,7 @@ export function generateBuild(
|
|
|
352
368
|
const skVar = nextVar('sk')
|
|
353
369
|
const binds: string[] = []
|
|
354
370
|
const html = skeletonMarkup(node, skVar, binds)
|
|
355
|
-
return `const ${skVar} = skeleton(${parentVar}, ${JSON.stringify(html)});\n${binds.join('')}`
|
|
371
|
+
return `const ${skVar} = $$skeleton(${parentVar}, ${JSON.stringify(html)});\n${binds.join('')}`
|
|
356
372
|
}
|
|
357
373
|
|
|
358
374
|
/* Emits code appending `node` to `parentVar`. */
|
|
@@ -378,8 +394,8 @@ export function generateBuild(
|
|
|
378
394
|
.map((part, index) => {
|
|
379
395
|
const splitAlways = index < consumers.length - 1 ? ', true' : ''
|
|
380
396
|
return part.kind === 'static'
|
|
381
|
-
?
|
|
382
|
-
:
|
|
397
|
+
? `$$appendStatic(${parentVar}, ${JSON.stringify(part.value)}${splitAlways});\n`
|
|
398
|
+
: `$$appendText(${parentVar}, ${namedThunk('text', `return (${lowerExpression(part.code)})`)}${splitAlways});\n`
|
|
383
399
|
})
|
|
384
400
|
.join('')
|
|
385
401
|
}
|
|
@@ -387,7 +403,7 @@ export function generateBuild(
|
|
|
387
403
|
/* A standalone layout outlet (a top-level/element-nested `<slot/>` rewritten by
|
|
388
404
|
`asOutlet`, reached outside any skeleton): an empty `outlet` boundary at
|
|
389
405
|
`before`, no anchor — the router fills it with the next chain layer. */
|
|
390
|
-
return
|
|
406
|
+
return `$$outlet(${parentVar}, ${before});\n`
|
|
391
407
|
}
|
|
392
408
|
if (node.kind === 'element' && node.tag === 'slot') {
|
|
393
409
|
/* In a layout, `<slot/>` is the router's page outlet (`outlet` boundary the
|
|
@@ -395,7 +411,7 @@ export function generateBuild(
|
|
|
395
411
|
are rewritten to `OUTLET_TAG` up front by `asOutlet` and handled above; this
|
|
396
412
|
covers a layout slot reached inside a control-flow branch. */
|
|
397
413
|
if (isLayout) {
|
|
398
|
-
return
|
|
414
|
+
return `$$outlet(${parentVar}, ${before});\n`
|
|
399
415
|
}
|
|
400
416
|
return generateSlot(node, parentVar)
|
|
401
417
|
}
|
|
@@ -452,7 +468,7 @@ export function generateBuild(
|
|
|
452
468
|
let runHtml = ''
|
|
453
469
|
const flush = (): void => {
|
|
454
470
|
if (runHtml !== '') {
|
|
455
|
-
code +=
|
|
471
|
+
code += `$$cloneStatic(${parentVar}, ${JSON.stringify(runHtml)});\n`
|
|
456
472
|
runHtml = ''
|
|
457
473
|
}
|
|
458
474
|
}
|
|
@@ -473,8 +489,14 @@ export function generateBuild(
|
|
|
473
489
|
the component scope (its `model`/cells); `args` are plain parameters bound by
|
|
474
490
|
the call. Appends nothing at the declaration site — `{name(args)}` mounts it. */
|
|
475
491
|
function generateSnippet(node: Extract<TemplateNode, { kind: 'snippet' }>): string {
|
|
476
|
-
const
|
|
477
|
-
|
|
492
|
+
const plan = snippetPlan(node)
|
|
493
|
+
/* `args` are plain call parameters, not component cells — `withBindings` registers the
|
|
494
|
+
plan's `plain` bindings so the body reads the bare local, shadowing a same-named
|
|
495
|
+
component signal rather than reading it. */
|
|
496
|
+
const body = withBindings(withShadow, plan.bindings, buildBindingKind, () =>
|
|
497
|
+
plan.children.map((child) => generateChild(child, '$host')).join(''),
|
|
498
|
+
)
|
|
499
|
+
return `function ${plan.name}(${plan.params ?? ''}) {\nreturn $$snippet(($host) => {\n${body}});\n}\n`
|
|
478
500
|
}
|
|
479
501
|
|
|
480
502
|
/* A switch: each `case` is `{ match: () => value, render }`, the default is
|
|
@@ -493,21 +515,17 @@ export function generateBuild(
|
|
|
493
515
|
return `{ match: ${match}, render: ${branchThunk(branch.children)} }`
|
|
494
516
|
})
|
|
495
517
|
.join(', ')
|
|
496
|
-
return
|
|
518
|
+
return `$$switchBlock(${parentVar}, () => (${lowerExpression(node.subject)}), [${cases}], ${before});\n`
|
|
497
519
|
}
|
|
498
520
|
|
|
499
|
-
/* A
|
|
500
|
-
|
|
521
|
+
/* A `{children()}` slot fill point: render the parent-provided content (`$children`).
|
|
522
|
+
A fallback is now an authored `{#if children}…{:else}…{/if}`, so the slot node never
|
|
523
|
+
carries children — the invoke is unconditional but guarded against a propless mount. */
|
|
501
524
|
function generateSlot(
|
|
502
|
-
|
|
525
|
+
_node: Extract<TemplateNode, { kind: 'element' }>,
|
|
503
526
|
parentVar: string,
|
|
504
527
|
): string {
|
|
505
|
-
|
|
506
|
-
const invoke = `$props.$children(${parentVar})`
|
|
507
|
-
if (fallback.trim() === '') {
|
|
508
|
-
return `if ($props && $props.$children) { ${invoke}; }\n`
|
|
509
|
-
}
|
|
510
|
-
return `if ($props && $props.$children) { ${invoke}; } else {\n${fallback}}\n`
|
|
528
|
+
return `if ($props && $props.$children) { $props.$children(${parentVar}); }\n`
|
|
511
529
|
}
|
|
512
530
|
|
|
513
531
|
/* The child's slot content as a host-taking builder (`$children`), or undefined when
|
|
@@ -534,11 +552,11 @@ export function generateBuild(
|
|
|
534
552
|
parentVar: string,
|
|
535
553
|
before: string,
|
|
536
554
|
): string {
|
|
537
|
-
return
|
|
555
|
+
return `$$mountChild(${parentVar}, ${node.name}, ${propsArg(node)}, ${before}, ${JSON.stringify(node.name)});\n`
|
|
538
556
|
}
|
|
539
557
|
|
|
540
558
|
/* An await block: pending → resolved(value) / error branches. Each branch is a
|
|
541
|
-
|
|
559
|
+
void render thunk `(parent[, value]) => void` that builds its content into `parent`. */
|
|
542
560
|
function generateAwait(
|
|
543
561
|
node: Extract<TemplateNode, { kind: 'await' }>,
|
|
544
562
|
parentVar: string,
|
|
@@ -551,21 +569,20 @@ export function generateBuild(
|
|
|
551
569
|
resolved content/binding feeds one thunk regardless of mode. */
|
|
552
570
|
const thenThunk = branchThunk(
|
|
553
571
|
plan.resolvedChildren,
|
|
554
|
-
plan.
|
|
572
|
+
plan.resolvedBindings,
|
|
555
573
|
plan.finallyChildren,
|
|
556
|
-
true,
|
|
557
574
|
)
|
|
558
575
|
/* Neither catch nor finally → pass `undefined` so awaitBlock re-throws the
|
|
559
576
|
rejection (surfacing it) instead of rendering an empty branch. A finally-only
|
|
560
577
|
block keeps a catch thunk that renders just finally. */
|
|
561
578
|
const catchThunk = plan.surfaceRejection
|
|
562
579
|
? 'undefined'
|
|
563
|
-
: branchThunk(plan.catchChildren, plan.
|
|
580
|
+
: branchThunk(plan.catchChildren, plan.catchBindings, plan.finallyChildren)
|
|
564
581
|
const pendingThunk = hasRenderableContent(plan.pending)
|
|
565
582
|
? branchThunk(plan.pending)
|
|
566
583
|
: 'undefined'
|
|
567
584
|
return (
|
|
568
|
-
|
|
585
|
+
`$$awaitBlock(${parentVar}, $$nextBlockId(), () => (${lowerExpression(node.promise)}), ` +
|
|
569
586
|
`${pendingThunk}, ` +
|
|
570
587
|
`${thenThunk}, ` +
|
|
571
588
|
`${catchThunk}, ${before});\n`
|
|
@@ -576,28 +593,33 @@ export function generateBuild(
|
|
|
576
593
|
builds its children — and an optional trailing `finally` branch — into
|
|
577
594
|
`parent`. The full-range model tracks the built content between markers, so a
|
|
578
595
|
branch holds ANY content (components, text, nested control-flow, snippets) and
|
|
579
|
-
is generated exactly like a normal child list. `
|
|
580
|
-
|
|
581
|
-
|
|
596
|
+
is generated exactly like a normal child list. `bindings` are the value param(s)
|
|
597
|
+
the plan declared (an `await` `then` value → `reactive`; a `catch` error → `plain`)
|
|
598
|
+
— at most one. Names flow to the deref scope ONLY through `withBindings` over the
|
|
599
|
+
plan's bindings; the cell wiring of a `reactive` binding is arranged by
|
|
600
|
+
`reactiveBinding`. Nested `<script>`s are emitted in document order by
|
|
601
|
+
`generateChildren`; `withNestedScripts` puts their bindings in deref scope. */
|
|
582
602
|
function branchThunk(
|
|
583
603
|
children: TemplateNode[],
|
|
584
|
-
|
|
604
|
+
bindings: Binding[] = [],
|
|
585
605
|
finallyChildren: TemplateNode[] = [],
|
|
586
|
-
reactiveValue = false,
|
|
587
606
|
): string {
|
|
588
607
|
const parentParam = nextVar('p')
|
|
589
|
-
/* A reactive
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
const
|
|
594
|
-
const
|
|
595
|
-
|
|
608
|
+
/* A `reactive` binding arrives as a `.value` cell the runtime can set in place (the
|
|
609
|
+
branch re-runs in place on a re-settle); `reactiveBinding` renders its cell param +
|
|
610
|
+
per-leaf reader prefix. A `plain` binding is the author param verbatim — a real arrow
|
|
611
|
+
parameter `withBindings` registers under `plain`, read as the bare local. */
|
|
612
|
+
const reactive = bindings.find((binding) => binding.classification === 'reactive')
|
|
613
|
+
const wiring =
|
|
614
|
+
reactive === undefined ? undefined : reactiveBinding(reactive, nextVar, lowerStatement)
|
|
615
|
+
const plainBinding = bindings.find((binding) => binding.classification === 'plain')
|
|
616
|
+
const param = wiring?.param ?? plainBinding?.name
|
|
617
|
+
const prefix = wiring?.prefix ?? ''
|
|
596
618
|
const head = param === undefined ? `(${parentParam})` : `(${parentParam}, ${param})`
|
|
597
619
|
const body = withNestedScripts(children, () =>
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
620
|
+
withBindings(withShadow, bindings, buildBindingKind, () =>
|
|
621
|
+
generateChildren(children, parentParam),
|
|
622
|
+
),
|
|
601
623
|
)
|
|
602
624
|
const finallyBody =
|
|
603
625
|
finallyChildren.length > 0
|
|
@@ -630,11 +652,11 @@ export function generateBuild(
|
|
|
630
652
|
before: string,
|
|
631
653
|
): string {
|
|
632
654
|
const plan = tryPlan(node)
|
|
633
|
-
const tryThunk = branchThunk(plan.guarded,
|
|
655
|
+
const tryThunk = branchThunk(plan.guarded, [], plan.finallyChildren)
|
|
634
656
|
const catchThunk = plan.hasCatch
|
|
635
|
-
? branchThunk(plan.catchChildren, plan.
|
|
657
|
+
? branchThunk(plan.catchChildren, plan.catchBindings, plan.finallyChildren)
|
|
636
658
|
: 'undefined'
|
|
637
|
-
return
|
|
659
|
+
return `$$tryBlock(${parentVar}, $$nextBlockId(), ${tryThunk}, ${catchThunk}, ${before});\n`
|
|
638
660
|
}
|
|
639
661
|
|
|
640
662
|
/* A conditional with an optional nested `<template else>` (a `case` child). Each
|
|
@@ -652,7 +674,7 @@ export function generateBuild(
|
|
|
652
674
|
const thenThunk = branchThunk(plan.thenChildren)
|
|
653
675
|
const elseThunk =
|
|
654
676
|
plan.elseBranch === undefined ? 'undefined' : branchThunk(plan.elseBranch.children)
|
|
655
|
-
return
|
|
677
|
+
return `$$when(${parentVar}, () => (${lowerExpression(node.condition)}), ${thenThunk}, ${elseThunk}, ${before});\n`
|
|
656
678
|
}
|
|
657
679
|
/* if/elseif/else is a cond-chain — reuse `switchBlock` over a constant `true`
|
|
658
680
|
subject with `Boolean`-coerced match thunks, so the first truthy branch wins
|
|
@@ -665,7 +687,7 @@ export function generateBuild(
|
|
|
665
687
|
: `{ match: undefined, render: ${branchThunk(branch.children)} }`,
|
|
666
688
|
),
|
|
667
689
|
]
|
|
668
|
-
return
|
|
690
|
+
return `$$switchBlock(${parentVar}, () => true, [${entries.join(', ')}], ${before});\n`
|
|
669
691
|
}
|
|
670
692
|
|
|
671
693
|
/* A keyed each. Each row is a content RANGE (any content, tracked between the
|
|
@@ -675,6 +697,7 @@ export function generateBuild(
|
|
|
675
697
|
parentVar: string,
|
|
676
698
|
before: string,
|
|
677
699
|
): string {
|
|
700
|
+
const plan = eachPlan(node)
|
|
678
701
|
const rowParam = nextVar('p')
|
|
679
702
|
/* The item is a reactive `.value` cell so a re-key with a changed value updates the row
|
|
680
703
|
in place (no rebuild). `keyOf` receives the RAW item; the key expression is lowered
|
|
@@ -683,34 +706,34 @@ export function generateBuild(
|
|
|
683
706
|
plain `as` returns its name; a destructuring `as` binds a fresh param and returns
|
|
684
707
|
THAT, not the pattern re-wrapped (`[i,crumb]` → `[i,crumb]` would allocate a fresh
|
|
685
708
|
array per reconcile, so keys never match and every row rebuilds). An explicit `key`
|
|
686
|
-
destructures the item via `
|
|
687
|
-
const rawItemParam = isPlainIdentifier(
|
|
688
|
-
const keyParam =
|
|
689
|
-
const keyExpression =
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
const
|
|
695
|
-
const
|
|
696
|
-
node.index === undefined ? binding.localNames : [...binding.localNames, node.index]
|
|
709
|
+
destructures the item via `plan.as` to read its leaves. */
|
|
710
|
+
const rawItemParam = isPlainIdentifier(plan.as) ? plan.as : nextVar('k')
|
|
711
|
+
const keyParam = plan.key === undefined ? rawItemParam : plan.as
|
|
712
|
+
const keyExpression = plan.key === undefined ? rawItemParam : lowerExpression(plan.key)
|
|
713
|
+
/* The item is the row's `reactive` binding (its `.value` cell + per-leaf reader prefix
|
|
714
|
+
rendered by `reactiveBinding`); `index="i"` is a second reactive cell param, a plain
|
|
715
|
+
identifier read as `i.value` (no prefix). The names of BOTH enter the deref scope via
|
|
716
|
+
`withBindings` over `plan.bindings`, not a hand-built list. */
|
|
717
|
+
const itemWiring = reactiveBinding(plan.bindings[0] as Binding, nextVar, lowerStatement)
|
|
718
|
+
const indexParam = plan.index === undefined ? '' : `, ${plan.index}`
|
|
697
719
|
/* The row body builds its children (a `<script>` declares per-row local signals,
|
|
698
720
|
emitted in document order) into the row parent. A `<template catch>` child is
|
|
699
721
|
consumed by the async-each, not the row — `generateChildren` skips it. */
|
|
700
|
-
const rowBody = withNestedScripts(
|
|
701
|
-
|
|
722
|
+
const rowBody = withNestedScripts(plan.children, () =>
|
|
723
|
+
withBindings(withShadow, plan.bindings, buildBindingKind, () =>
|
|
724
|
+
generateChildren(plan.children, rowParam),
|
|
725
|
+
),
|
|
702
726
|
)
|
|
703
727
|
/* `await` → the AsyncIterable runtime, drained row-by-row on the client, with an
|
|
704
728
|
optional `<template catch>` branch rendered (after the streamed rows) when the
|
|
705
729
|
iterator rejects. Absent → `undefined`, so the rejection surfaces instead. */
|
|
706
|
-
const fn =
|
|
707
|
-
const
|
|
708
|
-
|
|
709
|
-
? `, ${catchBranch === undefined ? 'undefined' : branchThunk(catchBranch.children, catchBranch.as ?? '_error')}`
|
|
730
|
+
const fn = plan.async ? '$$eachAsync' : '$$each'
|
|
731
|
+
const catchArg = plan.async
|
|
732
|
+
? `, ${plan.hasCatch ? branchThunk(plan.catchChildren, plan.catchBindings) : 'undefined'}`
|
|
710
733
|
: ''
|
|
711
734
|
return (
|
|
712
|
-
`${fn}(${parentVar}, () => (${lowerExpression(
|
|
713
|
-
`(${keyParam}) => (${keyExpression}), (${rowParam}, ${
|
|
735
|
+
`${fn}(${parentVar}, () => (${lowerExpression(plan.items)}), ` +
|
|
736
|
+
`(${keyParam}) => (${keyExpression}), (${rowParam}, ${itemWiring.param}${indexParam}) => {\n${itemWiring.prefix}${rowBody}}${catchArg}, ${before});\n`
|
|
714
737
|
)
|
|
715
738
|
}
|
|
716
739
|
|