@abide/abide 0.49.0 → 0.50.1
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 +564 -437
- package/CHANGELOG.md +183 -0
- package/README.md +165 -202
- package/package.json +14 -5
- package/src/abideLsp.ts +5 -6
- package/src/abideResolverPlugin.ts +197 -169
- package/src/build.ts +79 -56
- package/src/buildDisconnected.ts +0 -2
- package/src/checkAbide.ts +12 -2
- package/src/devEntry.ts +113 -28
- package/src/discoveryEntry.ts +3 -2
- package/src/lib/bundle/disconnected.abide +69 -73
- package/src/lib/bundle/infoPlist.ts +13 -7
- package/src/lib/bundle/spawnEmbeddedServer.ts +13 -4
- package/src/lib/bundle/waitForServer.ts +6 -1
- package/src/lib/mcp/mcpTools.ts +15 -6
- package/src/lib/server/error.ts +6 -6
- package/src/lib/server/json.ts +17 -3
- package/src/lib/server/rpc/defineRpc.ts +58 -24
- package/src/lib/server/rpc/parseArgs.ts +98 -13
- package/src/lib/server/rpc/resolveRpcJsonSchema.ts +28 -0
- package/src/lib/server/rpc/types/RpcHelper.ts +75 -105
- package/src/lib/server/rpc/types/RpcRegistryEntry.ts +24 -9
- package/src/lib/server/rpc/validationError.ts +1 -1
- package/src/lib/server/runtime/DEV_RELOAD_CLIENT_SCRIPT.ts +0 -15
- package/src/lib/server/runtime/buildCacheSnapshot.ts +10 -9
- package/src/lib/server/runtime/buildInspectorSurface.ts +6 -4
- package/src/lib/server/runtime/buildOpenApiSpec.ts +55 -11
- package/src/lib/server/runtime/buildPreloadManifest.ts +12 -10
- package/src/lib/server/runtime/createAppAssetServer.ts +18 -15
- package/src/lib/server/runtime/createAppRouteResolver.ts +145 -0
- package/src/lib/server/runtime/createPlumbingRouter.ts +212 -0
- package/src/lib/server/runtime/createRouteDispatcher.ts +4 -10
- package/src/lib/server/runtime/createServer.ts +120 -324
- package/src/lib/server/runtime/createUiPageRenderer.ts +137 -26
- package/src/lib/server/runtime/devClientFingerprint.ts +19 -34
- package/src/lib/server/runtime/installAmbientScopeStore.ts +30 -9
- package/src/lib/server/runtime/logExposedSurfaces.ts +8 -7
- package/src/lib/server/runtime/pathStore.ts +15 -0
- package/src/lib/server/runtime/registryManifests.ts +1 -1
- package/src/lib/server/runtime/renderCellBarrierStore.ts +15 -0
- package/src/lib/server/runtime/runWithRequestScope.ts +3 -0
- package/src/lib/server/runtime/serializeCacheSnapshot.ts +6 -4
- package/src/lib/server/runtime/snapshotEntryFromCache.ts +14 -14
- package/src/lib/server/runtime/streamCacheResolutions.ts +14 -7
- package/src/lib/server/runtime/streamFromIterator.ts +30 -2
- package/src/lib/server/runtime/textResponse.ts +23 -0
- package/src/lib/server/runtime/types/DevReloadStamp.ts +6 -10
- package/src/lib/server/runtime/types/InspectorCacheEntry.ts +1 -1
- package/src/lib/server/runtime/types/RequestStore.ts +27 -0
- package/src/lib/server/runtime/warnUnguardedMcp.ts +15 -5
- package/src/lib/server/sockets/createSocketDispatcher.ts +9 -11
- package/src/lib/server/sockets/defineSocket.ts +5 -4
- package/src/lib/server/sse.ts +5 -1
- package/src/lib/shared/ASYNC_CELL.ts +5 -0
- package/src/lib/shared/DEV_REBUILD_PATH.ts +6 -0
- package/src/lib/shared/HTTP_METHODS.ts +6 -0
- package/src/lib/shared/HttpError.ts +1 -5
- package/src/lib/shared/MCP_PATH.ts +6 -0
- package/src/lib/shared/PROXIED_SERVER_SUBDIRS.ts +15 -0
- package/src/lib/shared/REF_JSON_TAGS.ts +13 -1
- package/src/lib/shared/RPC_SHIM_GLOBALS.ts +9 -0
- package/src/lib/shared/activeCacheStore.ts +1 -0
- package/src/lib/shared/activePage.ts +1 -0
- package/src/lib/shared/buildArtifact.ts +4 -1
- package/src/lib/shared/buildSocketOverChannel.ts +4 -3
- package/src/lib/shared/bundleGraphFromMetafile.ts +68 -0
- package/src/lib/shared/cache.ts +156 -131
- package/src/lib/shared/changeAffectsClient.ts +12 -7
- package/src/lib/shared/createCacheStore.ts +31 -8
- package/src/lib/shared/createLifecycleChannel.ts +7 -1
- package/src/lib/shared/createReachable.ts +47 -78
- package/src/lib/shared/createRemoteFunction.ts +52 -30
- package/src/lib/shared/createRpcServerProgram.ts +820 -0
- package/src/lib/shared/decodeRefJson.ts +4 -4
- package/src/lib/shared/decodeResponse.ts +2 -2
- package/src/lib/shared/decodeWireBody.ts +35 -0
- package/src/lib/shared/detectRpcMethod.ts +8 -1
- package/src/lib/shared/done.ts +8 -2
- package/src/lib/shared/encodeRefJson.ts +4 -4
- package/src/lib/shared/encodeWireBody.ts +18 -0
- package/src/lib/{server/rpc → shared}/fieldErrorsFromIssues.ts +5 -1
- package/src/lib/shared/generateDeclarations.ts +72 -0
- package/src/lib/shared/hasSeedableRequest.ts +25 -0
- package/src/lib/shared/hydrationWindow.ts +53 -0
- package/src/lib/shared/isAsyncCell.ts +11 -0
- package/src/lib/shared/isAsyncIterable.ts +8 -0
- package/src/lib/shared/isSubscribable.ts +3 -3
- package/src/lib/shared/isThenable.ts +9 -0
- package/src/lib/shared/jsonSchemaForType.ts +258 -0
- package/src/lib/shared/lenientDecode.ts +15 -0
- package/src/lib/shared/loadProjectTsConfig.ts +28 -0
- package/src/lib/shared/markFrameworkSourcesIgnored.ts +1 -1
- package/src/lib/shared/matchRoute.ts +4 -14
- package/src/lib/shared/peek.ts +14 -0
- package/src/lib/shared/pending.ts +9 -6
- package/src/lib/shared/pendingAsyncCellsSlot.ts +13 -0
- package/src/lib/shared/prepareRpcModule.ts +91 -93
- package/src/lib/shared/prepareSocketModule.ts +3 -2
- package/src/lib/shared/probeRegistries.ts +5 -18
- package/src/lib/shared/reachable.ts +7 -10
- package/src/lib/shared/refresh.ts +12 -2
- package/src/lib/shared/refreshing.ts +8 -2
- package/src/lib/shared/resolvedCellsSlot.ts +13 -0
- package/src/lib/shared/reviveWireField.ts +49 -0
- package/src/lib/shared/reviveWireOutput.ts +36 -0
- package/src/lib/shared/rpcServerForRoot.ts +25 -0
- package/src/lib/shared/scanPages.ts +25 -0
- package/src/lib/shared/snapshotShippable.ts +8 -7
- package/src/lib/shared/streamedCellsSlot.ts +14 -0
- package/src/lib/shared/subscribableFromResponse.ts +4 -4
- package/src/lib/shared/subscribableProbes.ts +1 -1
- package/src/lib/shared/tailProbeSlot.ts +1 -1
- package/src/lib/shared/types/AsyncComputed.ts +20 -0
- package/src/lib/shared/types/AsyncState.ts +13 -0
- package/src/lib/shared/types/CacheEntry.ts +7 -7
- package/src/lib/shared/types/CacheOptions.ts +23 -37
- package/src/lib/shared/types/CachePolicy.ts +25 -0
- package/src/lib/shared/types/CacheSnapshotEntry.ts +6 -5
- package/src/lib/shared/types/ErrorJsonSchemas.ts +8 -0
- package/src/lib/shared/types/HttpMethod.ts +3 -1
- package/src/lib/shared/types/InputCoercion.ts +17 -0
- package/src/lib/shared/types/{Subscribable.ts → NamedAsyncIterable.ts} +1 -1
- package/src/lib/shared/types/OutputWirePlan.ts +17 -0
- package/src/lib/shared/types/PagesScan.ts +7 -0
- package/src/lib/shared/types/PendingAsyncCells.ts +10 -0
- package/src/lib/shared/types/RawRemoteFunction.ts +6 -0
- package/src/lib/shared/types/RemoteCallable.ts +8 -7
- package/src/lib/shared/types/RemoteFunction.ts +16 -15
- package/src/lib/shared/types/ResolvedCells.ts +11 -0
- package/src/lib/shared/types/ReturnBody.ts +15 -0
- package/src/lib/shared/types/RpcBuildStamps.ts +23 -0
- package/src/lib/shared/types/RpcErrorGuard.ts +3 -8
- package/src/lib/shared/types/Socket.ts +4 -4
- package/src/lib/shared/types/SsrPayload.ts +5 -1
- package/src/lib/shared/types/StreamPolicy.ts +11 -0
- package/src/lib/shared/types/StreamedCells.ts +19 -0
- package/src/lib/shared/types/StreamedResolution.ts +24 -6
- package/src/lib/shared/types/TailHooks.ts +1 -1
- package/src/lib/shared/types/WireKind.ts +11 -0
- package/src/lib/shared/validationHttpError.ts +33 -0
- package/src/lib/shared/warmSeedKey.ts +16 -0
- package/src/lib/shared/wireJsonReplacer.ts +30 -0
- package/src/lib/shared/writeRpcDts.ts +11 -1
- package/src/lib/shared/writeTestSocketsDts.ts +1 -1
- package/src/lib/test/createTestApp.ts +19 -1
- package/src/lib/ui/README.md +1 -1
- package/src/lib/ui/activePendingCells.ts +14 -0
- package/src/lib/ui/compile/BLOCK_KEYWORDS.ts +21 -0
- package/src/lib/ui/compile/SSR_ESCAPE.ts +4 -1
- package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +35 -8
- package/src/lib/ui/compile/abideUiPlugin.ts +19 -2
- package/src/lib/ui/compile/analyzeComponent.ts +89 -6
- package/src/lib/ui/compile/assignmentTargetNames.ts +54 -0
- package/src/lib/ui/compile/asyncInterpolationFields.ts +152 -0
- package/src/lib/ui/compile/asyncValuePositionError.ts +21 -0
- package/src/lib/ui/compile/asyncValuePositionInterpolations.ts +64 -0
- package/src/lib/ui/compile/attrLiftPosition.ts +18 -0
- package/src/lib/ui/compile/cachedSourceFile.ts +40 -0
- package/src/lib/ui/compile/catchBinding.ts +7 -5
- package/src/lib/ui/compile/classifyInterpolationType.ts +46 -0
- package/src/lib/ui/compile/collectAbideDiagnostics.ts +96 -0
- package/src/lib/ui/compile/compileComponent.ts +19 -3
- package/src/lib/ui/compile/compileModule.ts +27 -58
- package/src/lib/ui/compile/compileSSR.ts +51 -12
- package/src/lib/ui/compile/compileShadow.ts +145 -18
- package/src/lib/ui/compile/composeProps.ts +12 -2
- package/src/lib/ui/compile/createShadowLanguageService.ts +116 -68
- package/src/lib/ui/compile/createShadowProgram.ts +35 -6
- package/src/lib/ui/compile/declaredNames.ts +36 -0
- package/src/lib/ui/compile/desugarSignals.ts +418 -36
- package/src/lib/ui/compile/expressionIsPrefixEvaluable.ts +19 -0
- package/src/lib/ui/compile/generateBuild.ts +52 -9
- package/src/lib/ui/compile/generateSSR.ts +242 -43
- package/src/lib/ui/compile/hoistableAwaits.ts +193 -0
- package/src/lib/ui/compile/hoistableChildRenders.ts +112 -0
- package/src/lib/ui/compile/interpolatedTemplateLiteral.ts +3 -1
- package/src/lib/ui/compile/interpolationClassifierForRoot.ts +37 -0
- package/src/lib/ui/compile/isSpuriousAsyncReadDiagnostic.ts +174 -0
- package/src/lib/ui/compile/liftAsyncSubExpressions.ts +166 -0
- package/src/lib/ui/compile/lowerAsyncInterpolations.ts +92 -0
- package/src/lib/ui/compile/lowerContext.ts +10 -0
- package/src/lib/ui/compile/lowerDocAccess.ts +26 -23
- package/src/lib/ui/compile/lowerScript.ts +40 -4
- package/src/lib/ui/compile/nodeAtShadowOffset.ts +28 -0
- package/src/lib/ui/compile/parseTemplate.ts +18 -1092
- package/src/lib/ui/compile/parseTemplateRecovering.ts +1385 -0
- package/src/lib/ui/compile/referencedIdentifiers.ts +35 -0
- package/src/lib/ui/compile/renameSignalRefs.ts +21 -22
- package/src/lib/ui/compile/seedTypeClassifierForRoot.ts +65 -0
- package/src/lib/ui/compile/shadowInterpolationClassifier.ts +47 -0
- package/src/lib/ui/compile/sourceFileOptionsSignature.ts +20 -0
- package/src/lib/ui/compile/structuralHeadTokens.ts +107 -0
- package/src/lib/ui/compile/templateSemanticTokens.ts +21 -0
- package/src/lib/ui/compile/templateStartOffset.ts +15 -0
- package/src/lib/ui/compile/tryPlan.ts +4 -3
- package/src/lib/ui/compile/types/AnalyzedComponent.ts +4 -0
- package/src/lib/ui/compile/types/AsyncInterpolationField.ts +19 -0
- package/src/lib/ui/compile/types/InterpolationClassifier.ts +12 -0
- package/src/lib/ui/compile/types/InterpolationKind.ts +7 -0
- package/src/lib/ui/compile/types/ParseDiagnostic.ts +8 -0
- package/src/lib/ui/compile/types/SeedTypeClassifier.ts +15 -0
- package/src/lib/ui/compile/types/TemplateNode.ts +36 -4
- package/src/lib/ui/compile/types/ValuePositionInterpolation.ts +13 -0
- package/src/lib/ui/compile/writtenTemplateNames.ts +84 -0
- package/src/lib/ui/computed.ts +28 -1
- package/src/lib/ui/createScope.ts +35 -68
- package/src/lib/ui/dom/anchoredBranch.ts +142 -0
- package/src/lib/ui/dom/appendText.ts +8 -3
- package/src/lib/ui/dom/awaitBlock.ts +43 -94
- package/src/lib/ui/dom/bindProp.ts +22 -0
- package/src/lib/ui/dom/bindableProp.ts +47 -0
- package/src/lib/ui/dom/cellPending.ts +24 -0
- package/src/lib/ui/dom/disposeRange.ts +2 -1
- package/src/lib/ui/dom/each.ts +26 -4
- package/src/lib/ui/dom/eachAsync.ts +39 -3
- package/src/lib/ui/dom/fillBoundary.ts +2 -3
- package/src/lib/ui/dom/fillRange.ts +4 -4
- package/src/lib/ui/dom/hydrate.ts +6 -12
- package/src/lib/ui/dom/isComment.ts +1 -1
- package/src/lib/ui/dom/matchingRangeClose.ts +29 -0
- package/src/lib/ui/dom/mount.ts +1 -2
- package/src/lib/ui/dom/mountChild.ts +9 -40
- package/src/lib/ui/dom/mountRange.ts +2 -3
- package/src/lib/ui/dom/mountStreamedChild.ts +84 -0
- package/src/lib/ui/dom/mountSwappableRange.ts +46 -4
- package/src/lib/ui/dom/mutateDocContainer.ts +65 -0
- package/src/lib/ui/dom/on.ts +2 -2
- package/src/lib/ui/dom/readCell.ts +40 -0
- package/src/lib/ui/dom/switchBlock.ts +30 -7
- package/src/lib/ui/dom/tryBlock.ts +230 -66
- package/src/lib/ui/dom/types/SwitchCase.ts +5 -1
- package/src/lib/ui/dom/when.ts +12 -2
- package/src/lib/ui/dom/withScope.ts +2 -2
- package/src/lib/ui/finalizeStreamedChildren.ts +89 -0
- package/src/lib/ui/flight.ts +56 -0
- package/src/lib/ui/html.ts +12 -1
- package/src/lib/ui/isolateCellBarrier.ts +17 -0
- package/src/lib/ui/linked.ts +48 -2
- package/src/lib/ui/remoteProxy.ts +84 -159
- package/src/lib/ui/renderChain.ts +49 -13
- package/src/lib/ui/renderToStream.ts +22 -18
- package/src/lib/ui/resumeSeedScript.ts +1 -1
- package/src/lib/ui/router.ts +63 -36
- package/src/lib/ui/runtime/AsyncCellError.ts +20 -0
- package/src/lib/ui/runtime/CELL_SEED.ts +14 -0
- package/src/lib/ui/runtime/CURRENT_BOUNDARY.ts +11 -0
- package/src/lib/ui/runtime/CURRENT_PATH.ts +29 -0
- package/src/lib/ui/runtime/RENDER.ts +10 -7
- package/src/lib/ui/runtime/RESUME.ts +4 -4
- package/src/lib/ui/runtime/STREAMED_CELLS.ts +57 -0
- package/src/lib/ui/runtime/ambientPathBacking.ts +32 -0
- package/src/lib/ui/runtime/applyPatchToTree.ts +1 -1
- package/src/lib/ui/runtime/blockId.ts +31 -0
- package/src/lib/ui/runtime/boundaryFor.ts +11 -0
- package/src/lib/ui/runtime/cellBarrierBacking.ts +30 -0
- package/src/lib/ui/runtime/createAsyncCell.ts +300 -0
- package/src/lib/ui/runtime/createDoc.ts +3 -42
- package/src/lib/ui/runtime/createEffectNode.ts +9 -0
- package/src/lib/ui/runtime/enterRenderPass.ts +5 -4
- package/src/lib/ui/runtime/flushEffects.ts +16 -6
- package/src/lib/ui/runtime/isAsyncFunction.ts +14 -0
- package/src/lib/ui/runtime/nextBlockId.ts +8 -7
- package/src/lib/ui/runtime/renderPath.ts +16 -0
- package/src/lib/ui/runtime/types/Boundary.ts +9 -0
- package/src/lib/ui/runtime/types/RenderContext.ts +9 -7
- package/src/lib/ui/runtime/types/SsrRender.ts +9 -2
- package/src/lib/ui/runtime/types/UiComponent.ts +1 -5
- package/src/lib/ui/runtime/withOptionalPath.ts +8 -0
- package/src/lib/ui/runtime/withPath.ts +16 -0
- package/src/lib/ui/runtime/withPathFrom.ts +20 -0
- package/src/lib/ui/seedStreamedResolution.ts +31 -6
- package/src/lib/ui/settleAsyncCells.ts +24 -0
- package/src/lib/ui/socketProxy.ts +1 -1
- package/src/lib/ui/startClient.ts +16 -31
- package/src/lib/ui/trackedComputed.ts +68 -0
- package/src/lib/ui/types/Scope.ts +8 -24
- package/src/lib/ui/watch.ts +3 -3
- package/src/serverEntry.ts +14 -0
- package/template/src/server/rpc/getHello.ts +15 -13
- package/template/test/app.test.ts +1 -1
- package/src/lib/server/runtime/devHotModuleResponse.ts +0 -41
- package/src/lib/shared/DEV_HOT_PREFIX.ts +0 -7
- package/src/lib/shared/UNREACHABLE_STATUSES.ts +0 -13
- package/src/lib/shared/hasReplayableRequest.ts +0 -17
- package/src/lib/shared/hydratingSlot.ts +0 -12
- package/src/lib/shared/outboxProbeSlot.ts +0 -20
- package/src/lib/shared/types/Outbox.ts +0 -9
- package/src/lib/shared/types/OutboxEntry.ts +0 -27
- package/src/lib/shared/types/SmartReadOptions.ts +0 -36
- package/src/lib/shared/wakeHydrationPeeks.ts +0 -20
- package/src/lib/ui/COMPONENT_WRAPPER_PREFIX.ts +0 -5
- package/src/lib/ui/compile/REACTIVE_CALLEES.ts +0 -11
- package/src/lib/ui/compile/assertRuntimeHelpersBound.ts +0 -80
- package/src/lib/ui/compile/markupTokens.ts +0 -313
- package/src/lib/ui/compile/structuralBlockTokens.ts +0 -100
- package/src/lib/ui/dom/applyResolved.ts +0 -87
- package/src/lib/ui/dom/mutateDocArray.ts +0 -38
- package/src/lib/ui/dom/scopeLabel.ts +0 -21
- package/src/lib/ui/dom/text.ts +0 -20
- package/src/lib/ui/history.ts +0 -108
- package/src/lib/ui/installHotBridge.ts +0 -95
- package/src/lib/ui/installInspectorBridge.ts +0 -140
- package/src/lib/ui/outbox.ts +0 -35
- package/src/lib/ui/persist.ts +0 -115
- package/src/lib/ui/rpcOutbox/createOutboxQueue.ts +0 -281
- package/src/lib/ui/rpcOutbox/outboxRegistry.ts +0 -69
- package/src/lib/ui/runtime/PATCH_BUS.ts +0 -28
- package/src/lib/ui/runtime/captureModelDoc.ts +0 -43
- package/src/lib/ui/runtime/hotInstances.ts +0 -10
- package/src/lib/ui/runtime/hotReloadEnabled.ts +0 -8
- package/src/lib/ui/runtime/hotReplace.ts +0 -38
- package/src/lib/ui/runtime/liveScopes.ts +0 -15
- package/src/lib/ui/runtime/localStoragePersistence.ts +0 -47
- package/src/lib/ui/runtime/registerHotInstance.ts +0 -23
- package/src/lib/ui/runtime/seedModelDoc.ts +0 -26
- package/src/lib/ui/runtime/types/HotInstance.ts +0 -22
- package/src/lib/ui/runtime/types/PatchEvent.ts +0 -16
- package/src/lib/ui/seedResolved.ts +0 -28
- package/src/lib/ui/sync.ts +0 -48
- package/src/lib/ui/types/History.ts +0 -14
- package/src/lib/ui/types/PersistHandle.ts +0 -11
- package/src/lib/ui/types/PersistenceStore.ts +0 -12
- package/src/lib/ui/types/ResolvedFrame.ts +0 -15
- package/src/lib/ui/types/SyncTransport.ts +0 -13
|
@@ -9,6 +9,7 @@ import { composeProps } from './composeProps.ts'
|
|
|
9
9
|
import { eachPlan } from './eachPlan.ts'
|
|
10
10
|
import { elementPlan } from './elementPlan.ts'
|
|
11
11
|
import { groupBindParts } from './groupBindParts.ts'
|
|
12
|
+
import { hoistableChildRenders } from './hoistableChildRenders.ts'
|
|
12
13
|
import { ifPlan } from './ifPlan.ts'
|
|
13
14
|
import { interpolatedTemplateLiteral } from './interpolatedTemplateLiteral.ts'
|
|
14
15
|
import { isControlFlow } from './isControlFlow.ts'
|
|
@@ -74,8 +75,19 @@ export function generateBuild(
|
|
|
74
75
|
derivedNames: ReadonlySet<string>,
|
|
75
76
|
computedNames: ReadonlySet<string>,
|
|
76
77
|
isLayout = false,
|
|
78
|
+
/* `linked` / async `computed` names, lowered to `$$readCell(name)` in template exprs. */
|
|
79
|
+
cellReadNames: ReadonlySet<string> = new Set(),
|
|
77
80
|
): string {
|
|
78
81
|
const nextVar = makeVarNamer()
|
|
82
|
+
/* A per-body source-order ordinal for each `<Child/>` mount site, so two same-type siblings
|
|
83
|
+
get distinct render-path segments. Combined with the ambient branch/row path a control-flow
|
|
84
|
+
ancestor pushes, this makes every mounted instance's id unique and stable (ADR render-path
|
|
85
|
+
identity). Drawn in document-order walk, matching the SSR back-end's identical walk. */
|
|
86
|
+
let childOrdinal = 0
|
|
87
|
+
/* ADR-0039: the SAME hoistable-child set the SSR back-end computes (identical inputs → identical
|
|
88
|
+
node identities), so the client emits the dual-mode `$$mountStreamedChild` for exactly the
|
|
89
|
+
children the server may stream. A non-hoistable child keeps the plain `$$mountChild`. */
|
|
90
|
+
const streamCapable = hoistableChildRenders(nodes, cellReadNames)
|
|
79
91
|
|
|
80
92
|
/* In a layout, `<slot/>` outlets are rewritten to `OUTLET_TAG` elements up front
|
|
81
93
|
(`asOutlet`) so the static-clone path carries them as ordinary structure. `asOutlet`
|
|
@@ -109,7 +121,7 @@ export function generateBuild(
|
|
|
109
121
|
withShadow,
|
|
110
122
|
bindRead,
|
|
111
123
|
bindWrite,
|
|
112
|
-
} = lowerContext(stateNames, derivedNames, computedNames)
|
|
124
|
+
} = lowerContext(stateNames, derivedNames, computedNames, cellReadNames)
|
|
113
125
|
|
|
114
126
|
/* Maps a plan `Binding`'s classification to the client `ShadowKind`: a `reactive` value
|
|
115
127
|
derefs as a `.value` cell (`derived`), a `plain` value as the bare local (`plain`). The
|
|
@@ -544,6 +556,12 @@ export function generateBuild(
|
|
|
544
556
|
return `{ match: ${match}, render: ${branchThunk(branch.children)} }`
|
|
545
557
|
})
|
|
546
558
|
.join(', ')
|
|
559
|
+
/* A bare async subject holds the whole switch while the cell is pending — no case, not
|
|
560
|
+
even the default — then matches on the settled value's peek. */
|
|
561
|
+
if (node.asyncSubject === true) {
|
|
562
|
+
const cell = node.subject.trim()
|
|
563
|
+
return `$$switchBlock(${parentVar}, () => $$readCell(${cell}), [${cases}], ${before}, () => $$cellPending(${cell}));\n`
|
|
564
|
+
}
|
|
547
565
|
return `$$switchBlock(${parentVar}, () => (${lowerExpression(node.subject)}), [${cases}], ${before});\n`
|
|
548
566
|
}
|
|
549
567
|
|
|
@@ -575,15 +593,14 @@ export function generateBuild(
|
|
|
575
593
|
/* The props bag a child mount receives — composed by the shared `composeProps` so the
|
|
576
594
|
build and SSR back-ends emit the same last-wins layering. */
|
|
577
595
|
function propsArg(node: Extract<TemplateNode, { kind: 'component' }>): string {
|
|
578
|
-
return composeProps(node.props, lowerExpression, slotPart(node))
|
|
596
|
+
return composeProps(node.props, lowerExpression, slotPart(node), bindRead, bindWrite)
|
|
579
597
|
}
|
|
580
598
|
|
|
581
599
|
/* Mounts a child component as a marker-bounded range on `parentVar`, positioned at
|
|
582
600
|
`before` (a skeleton anchor's `anchorCursor`, or `null` for a standalone child).
|
|
583
601
|
`mountRange` opens the `[`/`]` markers and builds the child between them — no
|
|
584
602
|
wrapper element — so the child's root is a true direct child of `parentVar`.
|
|
585
|
-
Hydration stays ambient, so the child claims its server range in place.
|
|
586
|
-
component name passes as the scope label (the inspector's `<Counter>` name). */
|
|
603
|
+
Hydration stays ambient, so the child claims its server range in place. */
|
|
587
604
|
function generateChildComponent(
|
|
588
605
|
node: Extract<TemplateNode, { kind: 'component' }>,
|
|
589
606
|
parentVar: string,
|
|
@@ -595,7 +612,11 @@ export function generateBuild(
|
|
|
595
612
|
cell, so `<Icon>` from `{#for {icon: Icon} of …}` mounts the component the cell
|
|
596
613
|
holds, not the cell object (whose `.build` is undefined → `build is not a
|
|
597
614
|
function`). SSR emits the same lowering for congruence. */
|
|
598
|
-
|
|
615
|
+
/* A hoistable child may have STREAMED (server chose per-render): emit the dual-mode adopter,
|
|
616
|
+
which probes the cursor to adopt an inlined `[ … ]` range OR a streamed `abide:await`
|
|
617
|
+
boundary (ADR-0039). Same args as mountChild. */
|
|
618
|
+
const mountFn = streamCapable.has(node) ? '$$mountStreamedChild' : '$$mountChild'
|
|
619
|
+
return `${mountFn}(${parentVar}, ${lowerExpression(node.name)}, ${propsArg(node)}, ${before}, ${childOrdinal++});\n`
|
|
599
620
|
}
|
|
600
621
|
|
|
601
622
|
/* An await block: pending → resolved(value) / error branches. Each branch is a
|
|
@@ -717,16 +738,35 @@ export function generateBuild(
|
|
|
717
738
|
const thenThunk = branchThunk(plan.thenChildren)
|
|
718
739
|
const elseThunk =
|
|
719
740
|
plan.elseBranch === undefined ? 'undefined' : branchThunk(plan.elseBranch.children)
|
|
741
|
+
/* A bare async subject (`{#if getX()}`) passes `when` a pending probe so a
|
|
742
|
+
still-loading cell renders NO branch; the condition itself is the plain peek read
|
|
743
|
+
(`$$readCell`), evaluated only once no longer pending. */
|
|
744
|
+
if (node.asyncSubject === true) {
|
|
745
|
+
const cell = node.condition.trim()
|
|
746
|
+
return `$$when(${parentVar}, () => $$readCell(${cell}), ${thenThunk}, ${elseThunk}, ${before}, () => $$cellPending(${cell}));\n`
|
|
747
|
+
}
|
|
720
748
|
return `$$when(${parentVar}, () => (${lowerExpression(node.condition)}), ${thenThunk}, ${elseThunk}, ${before});\n`
|
|
721
749
|
}
|
|
722
750
|
/* if/elseif/else is a cond-chain — reuse `switchBlock` over a constant `true`
|
|
723
751
|
subject with `Boolean`-coerced match thunks, so the first truthy branch wins
|
|
724
|
-
(`else` is the match-less default).
|
|
752
|
+
(`else` is the match-less default). A branch whose own condition is a bare async
|
|
753
|
+
subject carries a `pending` probe: `switchBlock` holds the chain there while it loads,
|
|
754
|
+
so sync and async branches interleave and each resolves at render time — a pending
|
|
755
|
+
async branch never lets a later branch render on its not-yet-known condition. */
|
|
756
|
+
const condEntry = (
|
|
757
|
+
condition: string,
|
|
758
|
+
asyncSubject: boolean | undefined,
|
|
759
|
+
children: TemplateNode[],
|
|
760
|
+
): string => {
|
|
761
|
+
const pending =
|
|
762
|
+
asyncSubject === true ? `, pending: () => $$cellPending(${condition.trim()})` : ''
|
|
763
|
+
return `{ match: () => Boolean(${lowerExpression(condition)}), render: ${branchThunk(children)}${pending} }`
|
|
764
|
+
}
|
|
725
765
|
const entries = [
|
|
726
|
-
|
|
766
|
+
condEntry(node.condition, node.asyncSubject, plan.thenChildren),
|
|
727
767
|
...plan.branches.map((branch) =>
|
|
728
768
|
branch.condition !== undefined
|
|
729
|
-
?
|
|
769
|
+
? condEntry(branch.condition, branch.asyncSubject, branch.children)
|
|
730
770
|
: `{ match: undefined, render: ${branchThunk(branch.children)} }`,
|
|
731
771
|
),
|
|
732
772
|
]
|
|
@@ -787,9 +827,12 @@ export function generateBuild(
|
|
|
787
827
|
const catchArg = plan.async
|
|
788
828
|
? `, ${plan.hasCatch ? branchThunk(plan.catchChildren, plan.catchBindings) : 'undefined'}`
|
|
789
829
|
: ''
|
|
830
|
+
/* Whether the each has an explicit `by` key — the row then keys its render-path segment
|
|
831
|
+
on the stable key rather than its position (see `each`/`eachAsync`). */
|
|
832
|
+
const keyed = plan.key !== undefined
|
|
790
833
|
return (
|
|
791
834
|
`${fn}(${parentVar}, () => (${lowerExpression(plan.items)}), ` +
|
|
792
|
-
`(${keyParam}) => (${keyExpression}), (${rowParam}, ${itemWiring.param}${indexParam}) => {\n${itemWiring.prefix}${rowBody}}${catchArg}, ${before});\n`
|
|
835
|
+
`(${keyParam}) => (${keyExpression}), (${rowParam}, ${itemWiring.param}${indexParam}) => {\n${itemWiring.prefix}${rowBody}}${catchArg}, ${before}, ${keyed});\n`
|
|
793
836
|
)
|
|
794
837
|
}
|
|
795
838
|
|
|
@@ -13,6 +13,8 @@ import { composeProps } from './composeProps.ts'
|
|
|
13
13
|
import { eachPlan } from './eachPlan.ts'
|
|
14
14
|
import { elementPlan } from './elementPlan.ts'
|
|
15
15
|
import { groupBindParts } from './groupBindParts.ts'
|
|
16
|
+
import { hoistableAwaits } from './hoistableAwaits.ts'
|
|
17
|
+
import { hoistableChildRenders } from './hoistableChildRenders.ts'
|
|
16
18
|
import { ifPlan } from './ifPlan.ts'
|
|
17
19
|
import { interpolatedTemplateLiteral } from './interpolatedTemplateLiteral.ts'
|
|
18
20
|
import { isAnchorPositioned } from './isAnchorPositioned.ts'
|
|
@@ -66,10 +68,16 @@ export function generateSSR(
|
|
|
66
68
|
derivedNames: ReadonlySet<string>,
|
|
67
69
|
computedNames: ReadonlySet<string>,
|
|
68
70
|
isLayout = false,
|
|
69
|
-
)
|
|
71
|
+
/* `linked` / async `computed` names, lowered to `$$readCell(name)` in template exprs. */
|
|
72
|
+
cellReadNames: ReadonlySet<string> = new Set(),
|
|
73
|
+
): { body: string; flightDecls: string; hasStagedChildren: boolean } {
|
|
70
74
|
/* Unique temp var names (child render results); runtime block ids are
|
|
71
75
|
allocated separately at runtime via `$ctx.next++`. */
|
|
72
76
|
const nextVar = makeVarNamer()
|
|
77
|
+
/* A per-body source-order ordinal for each `<Child/>` render site — the render-path segment the
|
|
78
|
+
child roots under, matching the client `mountChild`'s `childOrdinal` (`generateBuild`) drawn
|
|
79
|
+
in the identical document-order walk, so both compose the same cell scope ids. */
|
|
80
|
+
let childOrdinal = 0
|
|
73
81
|
|
|
74
82
|
/* The enclosing `<select bind:value>`s, innermost last: each carries the JS var holding
|
|
75
83
|
its bound value and whether it's a `multiple` (array) select, so an `<option>` rendered
|
|
@@ -84,7 +92,8 @@ export function generateSSR(
|
|
|
84
92
|
withNestedScripts,
|
|
85
93
|
withShadow,
|
|
86
94
|
bindRead,
|
|
87
|
-
|
|
95
|
+
bindWrite,
|
|
96
|
+
} = lowerContext(stateNames, derivedNames, computedNames, cellReadNames)
|
|
88
97
|
|
|
89
98
|
/* SSR has no cells, so every plan `Binding` — `reactive` or `plain` — renders as a `plain`
|
|
90
99
|
shadow (a real JS local / loop var / arrow param, read as the bare identifier). The one
|
|
@@ -150,6 +159,28 @@ export function generateSSR(
|
|
|
150
159
|
through the generic element path exactly as the client clones it. */
|
|
151
160
|
const rootNodes = isLayout ? nodes.map(asOutlet) : nodes
|
|
152
161
|
|
|
162
|
+
/* ADR-0034: the await blocks whose promise-start hoists to the synchronous render prefix so
|
|
163
|
+
independent flights overlap instead of serializing. Server-only — this rewires only the SSR
|
|
164
|
+
promise SOURCE (a blocking `await $flightN`, a streaming `promise: () => $flightN`); the
|
|
165
|
+
block's markers, `$ctx.next++` id, RESUME wire, and the whole client build stay byte-identical.
|
|
166
|
+
The `flightDecls` are emitted by compileSSR after the lowered script and BEFORE the barrier, so
|
|
167
|
+
a hoisted flight is already in-flight while the barrier awaits any unrelated blocking cell. */
|
|
168
|
+
const flightNameByNode = new Map<Extract<TemplateNode, { kind: 'await' }>, string>()
|
|
169
|
+
for (const flight of hoistableAwaits(rootNodes, cellReadNames)) {
|
|
170
|
+
flightNameByNode.set(flight.node, flight.name)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/* ADR-0037 Phase 2: the top-level-spine `<Child/>` renders whose start hoists to the prefix so
|
|
174
|
+
sibling renders overlap instead of serializing behind each other's `await`. The component walk
|
|
175
|
+
below emits each hoisted child's flight decl into `childFlightDecls` (with its childOrdinal and
|
|
176
|
+
lowered props computed at the SAME site as the body's `await`, so the two can't drift) and
|
|
177
|
+
awaits the flight const at the structural position. Each hoisted render starts under
|
|
178
|
+
`$$isolateCellBarrier` so its async cells drain in their own list, not a concurrent sibling's
|
|
179
|
+
(the request-scoped barrier is `splice(0)`-drained). */
|
|
180
|
+
const hoistableChildSet = hoistableChildRenders(rootNodes, cellReadNames)
|
|
181
|
+
const childFlightDecls: string[] = []
|
|
182
|
+
let childFlightCounter = 0
|
|
183
|
+
|
|
153
184
|
/* A snippet name (any identifier, `$` included) interpolated into a RegExp must have its
|
|
154
185
|
regex metacharacters escaped, or e.g. a trailing `$` would read as an end-anchor and the
|
|
155
186
|
call site would never match — leaving an un-awaited Promise stringified as `[object
|
|
@@ -266,6 +297,26 @@ export function generateSSR(
|
|
|
266
297
|
const openRange = (target: string): string => push(target, RANGE_OPEN)
|
|
267
298
|
const closeRange = (target: string): string => push(target, RANGE_CLOSE)
|
|
268
299
|
|
|
300
|
+
/* Wrap a control-flow branch/row body in the render-path segment the CLIENT pushes for it (the
|
|
301
|
+
`withPathFrom`/`withPath` call the `each`/`when`/`switchBlock` dom runtimes make), so a scope
|
|
302
|
+
created inside — a nested `<Child/>`, always an inline `await` in SSR — composes the SAME
|
|
303
|
+
serialization-stable id on both sides (the warm-seed key). Emitted ONLY when the subtree can
|
|
304
|
+
create such a scope: a component render is the sole scope-creating construct in a branch/row
|
|
305
|
+
and always lowers to an inline `await`, so `subtreeAwaits` is the gate. A purely-synchronous
|
|
306
|
+
branch creates no scope, so its segment is inert — skipping the wrap keeps the static render
|
|
307
|
+
synchronous (no per-row closure/await, matching today's output byte-for-byte). `segment` is a
|
|
308
|
+
JS expression (a literal branch key, or the each row's key/index); `$$withPath` escapes it,
|
|
309
|
+
exactly like the client. The row/branch RANGE markers stay OUTSIDE this call (they create no
|
|
310
|
+
scope), so only the content composes the path — mirroring the client, where the markers are
|
|
311
|
+
built outside `withPathFrom`. */
|
|
312
|
+
const withPathBranch = (segment: string, children: TemplateNode[], target: string): string => {
|
|
313
|
+
const content = branchContent(children, target)
|
|
314
|
+
if (!subtreeAwaits(children)) {
|
|
315
|
+
return content
|
|
316
|
+
}
|
|
317
|
+
return `await $$withPath(${segment}, async () => {\n${content}});\n`
|
|
318
|
+
}
|
|
319
|
+
|
|
269
320
|
/* In a skeleton, a control-flow block or slot is positioned by an `<!--a-->` anchor
|
|
270
321
|
(cloned into the located parent), so it can sit anywhere among static siblings.
|
|
271
322
|
Emitted both sides in document order — the client's anchor scan lines up with it.
|
|
@@ -303,31 +354,98 @@ export function generateSSR(
|
|
|
303
354
|
.join('')
|
|
304
355
|
}
|
|
305
356
|
if (node.kind === 'if') {
|
|
306
|
-
/* `case` children are the `elseif`/`else` branches in source order; the rest are
|
|
307
|
-
|
|
308
|
-
|
|
357
|
+
/* `case` children are the `elseif`/`else` branches in source order; the rest are the
|
|
358
|
+
`then` content. The whole `if`/`elseif`/`else` chain desugars to a run of `if` /
|
|
359
|
+
`else if` clauses. A branch whose condition is a bare async subject contributes TWO
|
|
360
|
+
clauses in order — an empty `$$cellPending` guard (renders nothing, and stops the
|
|
361
|
+
chain: a still-loading branch holds, so nothing later renders on its unknown value)
|
|
362
|
+
then the `$$readCell` truthy test — so sync and async branches interleave and each
|
|
363
|
+
resolves at render time, mirroring the client `switchBlock` cond-chain. */
|
|
309
364
|
const plan = ifPlan(node)
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
365
|
+
/* The client build lowers a simple `if`/`else` through `when` (branch keys `'then'` /
|
|
366
|
+
`'else'`) and an `if` with any `elseif` through `switchBlock` over `[then, ...branches]`
|
|
367
|
+
(branch key = the case's array index). Mirror whichever the client picks so a nested
|
|
368
|
+
`<Child/>`'s scope id composes identically. */
|
|
369
|
+
const clauses: string[] = []
|
|
370
|
+
const conditional = (
|
|
371
|
+
condition: string,
|
|
372
|
+
asyncSubject: boolean | undefined,
|
|
373
|
+
children: TemplateNode[],
|
|
374
|
+
segment: string,
|
|
375
|
+
): void => {
|
|
376
|
+
if (asyncSubject === true) {
|
|
377
|
+
const cell = condition.trim()
|
|
378
|
+
clauses.push(`($$cellPending(${cell})) {\n}`)
|
|
379
|
+
clauses.push(
|
|
380
|
+
`($$readCell(${cell})) {\n${withPathBranch(segment, children, target)}}`,
|
|
381
|
+
)
|
|
382
|
+
} else {
|
|
383
|
+
clauses.push(
|
|
384
|
+
`(${lowerExpression(condition)}) {\n${withPathBranch(segment, children, target)}}`,
|
|
385
|
+
)
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
/* `then` is the `when` `'then'` branch, or the `switchBlock` case at index 0. */
|
|
389
|
+
conditional(
|
|
390
|
+
node.condition,
|
|
391
|
+
node.asyncSubject,
|
|
392
|
+
plan.thenChildren,
|
|
393
|
+
JSON.stringify(plan.hasElseif ? '0' : 'then'),
|
|
394
|
+
)
|
|
395
|
+
for (let index = 0; index < plan.branches.length; index += 1) {
|
|
396
|
+
const branch = plan.branches[index] as Extract<TemplateNode, { kind: 'case' }>
|
|
397
|
+
if (branch.condition !== undefined) {
|
|
398
|
+
/* An `elseif` is the `switchBlock` case at `[then, ...branches]` index `index + 1`
|
|
399
|
+
(a chain with any condition always lowers to `switchBlock`). */
|
|
400
|
+
conditional(
|
|
401
|
+
branch.condition,
|
|
402
|
+
branch.asyncSubject,
|
|
403
|
+
branch.children,
|
|
404
|
+
JSON.stringify(String(index + 1)),
|
|
405
|
+
)
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
let code = clauses
|
|
409
|
+
.map((clause, index) => `${index === 0 ? 'if' : ' else if'} ${clause}`)
|
|
410
|
+
.join('')
|
|
411
|
+
if (plan.elseBranch !== undefined) {
|
|
412
|
+
/* `else` is the `when` `'else'` branch, or (for a cond-chain) the `switchBlock`
|
|
413
|
+
default at its own `[then, ...branches]` index. */
|
|
414
|
+
const elseSegment = plan.hasElseif
|
|
415
|
+
? String(plan.branches.indexOf(plan.elseBranch) + 1)
|
|
416
|
+
: 'else'
|
|
417
|
+
code += ` else {\n${withPathBranch(JSON.stringify(elseSegment), plan.elseBranch.children, target)}}`
|
|
316
418
|
}
|
|
317
419
|
return `${anchor}${openRange(target)}${code}\n${closeRange(target)}`
|
|
318
420
|
}
|
|
319
421
|
if (node.kind === 'switch') {
|
|
320
422
|
const plan = switchPlan(node)
|
|
321
|
-
|
|
423
|
+
/* A bare async subject: read the peek, but only match once the cell has settled —
|
|
424
|
+
a pending subject renders no case (matching the client's `switchBlock`). */
|
|
425
|
+
const subjectExpr =
|
|
426
|
+
node.asyncSubject === true
|
|
427
|
+
? `$$readCell(${node.subject.trim()})`
|
|
428
|
+
: `(${lowerExpression(node.subject)})`
|
|
429
|
+
let code = `{ const $s = ${subjectExpr};\n`
|
|
430
|
+
if (node.asyncSubject === true) {
|
|
431
|
+
code += `if (!$$cellPending(${node.subject.trim()})) {\n`
|
|
432
|
+
}
|
|
322
433
|
let started = false
|
|
323
|
-
|
|
434
|
+
/* The client `switchBlock` keys each branch by its index in `plan.cases` (source order,
|
|
435
|
+
the default at its own position); mirror that index so a nested `<Child/>` matches. */
|
|
436
|
+
for (let index = 0; index < plan.cases.length; index += 1) {
|
|
437
|
+
const branch = plan.cases[index] as Extract<TemplateNode, { kind: 'case' }>
|
|
324
438
|
if (branch.match !== undefined) {
|
|
325
|
-
code += `${started ? 'else ' : ''}if ($s === (${lowerExpression(branch.match)})) {\n${
|
|
439
|
+
code += `${started ? 'else ' : ''}if ($s === (${lowerExpression(branch.match)})) {\n${withPathBranch(JSON.stringify(String(index)), branch.children, target)}}\n`
|
|
326
440
|
started = true
|
|
327
441
|
}
|
|
328
442
|
}
|
|
329
443
|
if (plan.fallback !== undefined) {
|
|
330
|
-
|
|
444
|
+
const fallbackSegment = String(plan.cases.indexOf(plan.fallback))
|
|
445
|
+
code += `${started ? 'else ' : ''}{\n${withPathBranch(JSON.stringify(fallbackSegment), plan.fallback.children, target)}}\n`
|
|
446
|
+
}
|
|
447
|
+
if (node.asyncSubject === true) {
|
|
448
|
+
code += `}\n`
|
|
331
449
|
}
|
|
332
450
|
return `${anchor}${openRange(target)}${code}}\n${closeRange(target)}`
|
|
333
451
|
}
|
|
@@ -369,26 +487,45 @@ export function generateSSR(
|
|
|
369
487
|
if (plan.async) {
|
|
370
488
|
return anchor
|
|
371
489
|
}
|
|
490
|
+
/* The client `each` pushes a render-path segment per row (keyed → the `by` key evaluated
|
|
491
|
+
on the raw item, exactly like its `keyOf`; keyless → the row position), so a nested
|
|
492
|
+
`<Child/>`'s scope id composes identically. Mirror it only when the row can create a
|
|
493
|
+
scope (`subtreeAwaits` — it holds a component); a static row needs no segment and stays
|
|
494
|
+
synchronous. A keyless row that needs its position reuses a bound `index`, else a
|
|
495
|
+
synthesized loop index — the same 0-based `entries()` position the client's row cell
|
|
496
|
+
carries, so the segments agree. */
|
|
497
|
+
const rowSegments = subtreeAwaits(plan.children)
|
|
498
|
+
const keylessIndex =
|
|
499
|
+
plan.key === undefined && plan.index === undefined && rowSegments
|
|
500
|
+
? nextVar('$i')
|
|
501
|
+
: undefined
|
|
502
|
+
const indexBinding = plan.index ?? keylessIndex
|
|
372
503
|
/* The row item (and index) are real `for`-loop locals, so the body must lower
|
|
373
504
|
references to them as the bare identifier — `withBindings` registers the plan's
|
|
374
505
|
row bindings (under `plain`, SSR's only kind) so a row binding that shadows a
|
|
375
506
|
same-named component signal reads the loop value, not the (whole-list) signal it
|
|
376
507
|
shadows. The names come straight from `plan.bindings` (the single source the
|
|
377
508
|
client also reads); the items expression stays outside the shadow. */
|
|
378
|
-
const rowBody = withBindings(
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
509
|
+
const rowBody = withBindings(withShadow, plan.bindings, ssrBindingKind, () => {
|
|
510
|
+
/* The row's render-path segment, lowered inside the row's plain shadow so a keyed
|
|
511
|
+
`by` reads the raw loop item exactly like the client `keyOf` (a keyless row uses
|
|
512
|
+
its position). Unused when `rowSegments` is false — `withPathBranch` then skips it. */
|
|
513
|
+
const segment =
|
|
514
|
+
plan.key === undefined
|
|
515
|
+
? (indexBinding ?? '0')
|
|
516
|
+
: `(${lowerExpression(plan.key)})`
|
|
517
|
+
return `${openRange(target)}${withPathBranch(segment, plan.children, target)}${closeRange(target)}`
|
|
518
|
+
})
|
|
385
519
|
/* `index="i"` binds the row position. SSR reads it as a plain number from
|
|
386
520
|
`entries()` over a materialized array; the client reads the same number from a
|
|
387
521
|
cell, so first paint is congruent. No index → a plain `for…of` over the items. */
|
|
522
|
+
/* An undefined source renders no rows, not a throw — a `{#for x in promise}`
|
|
523
|
+
whose lifted source peeks undefined while pending (ADR-0032 D3). Mirrors the
|
|
524
|
+
client `each`'s undefined-as-empty guard. */
|
|
388
525
|
const header =
|
|
389
|
-
|
|
390
|
-
? `for (const ${plan.as} of (${lowerExpression(plan.items)}))`
|
|
391
|
-
: `for (const [${
|
|
526
|
+
indexBinding === undefined
|
|
527
|
+
? `for (const ${plan.as} of ((${lowerExpression(plan.items)}) ?? []))`
|
|
528
|
+
: `for (const [${indexBinding}, ${plan.as}] of [...((${lowerExpression(plan.items)}) ?? [])].entries())`
|
|
392
529
|
return `${anchor}${header} {\n${rowBody}}\n`
|
|
393
530
|
}
|
|
394
531
|
if (node.kind === 'await') {
|
|
@@ -430,22 +567,54 @@ export function generateSSR(
|
|
|
430
567
|
: `"children": () => (async () => { const $slot = []; ${slotCode}return $snip($slot.join('')); })`
|
|
431
568
|
/* The same last-wins layering the client build emits (`composeProps`), so SSR
|
|
432
569
|
and hydration read the same prop bag. */
|
|
433
|
-
const propsExpr = composeProps(
|
|
570
|
+
const propsExpr = composeProps(
|
|
571
|
+
node.props,
|
|
572
|
+
lowerExpression,
|
|
573
|
+
slotPart,
|
|
574
|
+
bindRead,
|
|
575
|
+
bindWrite,
|
|
576
|
+
)
|
|
434
577
|
/* Render the child (awaited — render is async) sharing this render's `$ctx`,
|
|
435
578
|
so its `await`/`try` block ids draw from the same depth-first counter,
|
|
436
579
|
unique across page + children, and the streamed fragments resolve into the
|
|
437
580
|
right boundaries. MERGE its streaming awaits into `$awaits` and its inline
|
|
438
581
|
blocking values into `$resume`. ($awaits/$resume are captured from the
|
|
439
582
|
enclosing render body, including from branch closures.) */
|
|
583
|
+
/* The tag lowers like any reference (see generateBuild): a static import is left bare, a
|
|
584
|
+
reactive/loop/await binding derefs — SSR registers such a binding as `plain`, so it
|
|
585
|
+
reads the bare local holding the resolved component, keeping SSR and client congruent.
|
|
586
|
+
Root the child's render-path at this mount site's source-order ordinal — the same
|
|
587
|
+
segment the client's `mountChild` pushes — so the child's cells and block ids (ADR-0037)
|
|
588
|
+
get an id matching the client's. `$$withPath` sets the path across the child's awaits. */
|
|
589
|
+
const ordinal = childOrdinal++
|
|
590
|
+
const renderExpr = `$$withPath(${ordinal}, () => ${lowerExpression(node.name)}.render(${propsExpr}, $ctx))`
|
|
591
|
+
if (hoistableChildSet.has(node)) {
|
|
592
|
+
/* Hoistable (ADR-0037/0039): start the render as an isolated flight in the prefix, and
|
|
593
|
+
RESERVE its output position rather than awaiting inline. After the walk,
|
|
594
|
+
`finalizeStreamedChildren` fills the slot — inline `<!--[-->html<!--]-->` if the
|
|
595
|
+
flight already settled (byte-identical to the old inline await), or a streaming
|
|
596
|
+
`abide:await:CHILDPATH` boundary + html-only await if it is still pending. The
|
|
597
|
+
boundary id is the child's render-path (`$$renderPath(ordinal)`), congruent with the
|
|
598
|
+
client's `mountStreamedChild`. */
|
|
599
|
+
const flightName = `$cf${childFlightCounter++}`
|
|
600
|
+
childFlightDecls.push(
|
|
601
|
+
`const ${flightName} = $$flight(() => $$isolateCellBarrier(() => ${renderExpr}));`,
|
|
602
|
+
)
|
|
603
|
+
const slotVar = nextVar('$slot')
|
|
604
|
+
return (
|
|
605
|
+
anchor +
|
|
606
|
+
`${target}.push('');\n` +
|
|
607
|
+
`const ${slotVar} = ${target}.length - 1;\n` +
|
|
608
|
+
`$childSlots.push({ slot: ${slotVar}, out: ${target}, id: $$renderPath(${ordinal}), flight: ${flightName} });\n`
|
|
609
|
+
)
|
|
610
|
+
}
|
|
611
|
+
/* Non-hoistable child (off the top-level spine): await inline at its structural position,
|
|
612
|
+
sequential, as before — never streams. */
|
|
440
613
|
const result = nextVar('$child')
|
|
441
614
|
return (
|
|
442
615
|
anchor +
|
|
443
616
|
push(target, RANGE_OPEN) +
|
|
444
|
-
|
|
445
|
-
is left bare, a reactive/loop/await binding derefs — SSR registers such
|
|
446
|
-
a binding as `plain`, so it reads the bare local holding the resolved
|
|
447
|
-
component, keeping SSR and client congruent. */
|
|
448
|
-
`const ${result} = await ${lowerExpression(node.name)}.render(${propsExpr}, $ctx);\n` +
|
|
617
|
+
`const ${result} = await ${renderExpr};\n` +
|
|
449
618
|
`${target}.push(${result}.html);\n` +
|
|
450
619
|
`for (const $a of ${result}.awaits) { $awaits.push($a); }\n` +
|
|
451
620
|
`Object.assign($resume, ${result}.resume);\n` +
|
|
@@ -644,10 +813,13 @@ export function generateSSR(
|
|
|
644
813
|
`withBindings` shadow models, so a reference to the binding reads the plain
|
|
645
814
|
local rather than the (unresolved) component signal it shadows. */
|
|
646
815
|
const resolved = nextVar('$av')
|
|
647
|
-
|
|
816
|
+
/* ADR-0034: a hoisted block awaits the prefix flight const (already in-flight); a
|
|
817
|
+
non-hoisted block evaluates + awaits its promise inline as before. */
|
|
818
|
+
const hoistedPromise = flightNameByNode.get(node)
|
|
819
|
+
let code = `const ${id} = $$blockId($ctx);\n`
|
|
648
820
|
code += `${target}.push("<!--abide:await:" + ${id} + "-->");\n`
|
|
649
821
|
code += `try {\n`
|
|
650
|
-
code += `const ${resolved} = await (${lowerExpression(node.promise)});\n`
|
|
822
|
+
code += `const ${resolved} = await (${hoistedPromise ?? lowerExpression(node.promise)});\n`
|
|
651
823
|
code += `{\n`
|
|
652
824
|
code += `const ${plan.resolvedAs} = ${resolved};\n`
|
|
653
825
|
code += withBindings(withShadow, plan.resolvedBindings, ssrBindingKind, () =>
|
|
@@ -693,7 +865,7 @@ export function generateSSR(
|
|
|
693
865
|
): string {
|
|
694
866
|
const plan = awaitPlan(node)
|
|
695
867
|
const id = nextVar('$aid')
|
|
696
|
-
let code = `const ${id} = $ctx
|
|
868
|
+
let code = `const ${id} = $$blockId($ctx);\n`
|
|
697
869
|
code += `${target}.push("<!--abide:await:" + ${id} + "-->");\n`
|
|
698
870
|
code += branchContent(plan.pending, target)
|
|
699
871
|
code += `${target}.push("<!--/abide:await:" + ${id} + "-->");\n`
|
|
@@ -713,26 +885,34 @@ export function generateSSR(
|
|
|
713
885
|
const catchProp = plan.surfaceRejection
|
|
714
886
|
? ''
|
|
715
887
|
: `catch: ${settled(plan.catchAs, plan.catchBindings, plan.catchChildren)} `
|
|
888
|
+
/* ADR-0034: a hoisted streaming block's thunk returns the prefix flight const (started
|
|
889
|
+
in the prefix, so it overlaps a preceding blocking await); non-hoisted evaluates lazily
|
|
890
|
+
at drain time as before. */
|
|
891
|
+
const hoistedPromise = flightNameByNode.get(node)
|
|
716
892
|
code +=
|
|
717
893
|
`$awaits.push({ id: ${id}, ` +
|
|
718
|
-
`promise: () => (${lowerExpression(node.promise)}), ` +
|
|
894
|
+
`promise: () => (${hoistedPromise ?? lowerExpression(node.promise)}), ` +
|
|
719
895
|
`then: ${settled(plan.resolvedAs, plan.resolvedBindings, plan.resolvedChildren)}, ` +
|
|
720
896
|
`${catchProp}});\n`
|
|
721
897
|
return code
|
|
722
898
|
}
|
|
723
899
|
|
|
724
|
-
/* A
|
|
725
|
-
try/catch; on a throw, truncate the output back to the
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
900
|
+
/* A reactive error boundary: push the guarded markup (++ finally) inside a real
|
|
901
|
+
try/catch; on a throw, truncate the output back to the range start and push the
|
|
902
|
+
catch markup (++ finally) instead — so even mid-stream a render throw becomes catch
|
|
903
|
+
markup, not a broken response. No catch re-throws (propagates to an enclosing boundary
|
|
904
|
+
/ the 500 / the stream). The `abide:try:N` boundary comments let hydration discard the
|
|
905
|
+
server content if the client adoption fails; the inner `[ … ]` range markers are the
|
|
906
|
+
live range the render-many client (`tryBlock`) tracks — emitted here so a FRESH client
|
|
907
|
+
mount (which brackets its branch) and the SSR markup stay congruent. */
|
|
730
908
|
function generateTry(node: Extract<TemplateNode, { kind: 'try' }>, target: string): string {
|
|
731
909
|
const plan = tryPlan(node)
|
|
732
910
|
const id = nextVar('$tid')
|
|
733
911
|
const mark = nextVar('$trim')
|
|
734
|
-
let code = `const ${id} = $ctx
|
|
912
|
+
let code = `const ${id} = $$blockId($ctx);\n`
|
|
735
913
|
code += `${target}.push("<!--abide:try:" + ${id} + "-->");\n`
|
|
914
|
+
code += openRange(target)
|
|
915
|
+
/* Truncate back to just after the range-open marker on a catch, so `[` survives. */
|
|
736
916
|
code += `const ${mark} = ${target}.length;\n`
|
|
737
917
|
code += `try {\n`
|
|
738
918
|
code += branchContent(plan.guarded, target)
|
|
@@ -747,9 +927,28 @@ export function generateSSR(
|
|
|
747
927
|
code += `throw ${plan.catchAs};\n`
|
|
748
928
|
}
|
|
749
929
|
code += `}\n`
|
|
930
|
+
code += closeRange(target)
|
|
750
931
|
code += `${target}.push("<!--/abide:try:" + ${id} + "-->");\n`
|
|
751
932
|
return code
|
|
752
933
|
}
|
|
753
934
|
|
|
754
|
-
|
|
935
|
+
/* The body walk emits `await $flightN` / `promise: () => $flightN` for hoisted nodes; the
|
|
936
|
+
matching prefix declarations (lowered at component scope — hoisted promises reference only
|
|
937
|
+
component-scope names, never a branch shadow, so this lowering matches the body's) are
|
|
938
|
+
returned for compileSSR to place after the lowered script and before the barrier. */
|
|
939
|
+
const body = generateInto(rootNodes, '$out')
|
|
940
|
+
let flightDecls = ''
|
|
941
|
+
for (const [node, name] of flightNameByNode) {
|
|
942
|
+
flightDecls += `const ${name} = $$flight(() => (${lowerExpression(node.promise)}));\n`
|
|
943
|
+
}
|
|
944
|
+
/* Child-render flights emitted during the body walk above (ADR-0037 Phase 2) — appended after
|
|
945
|
+
the await flights; both are independent prefix promise-starts, so order between them is free. */
|
|
946
|
+
for (const decl of childFlightDecls) {
|
|
947
|
+
flightDecls += `${decl}\n`
|
|
948
|
+
}
|
|
949
|
+
/* `hasStagedChildren` tells compileSSR to declare `$childSlots` and emit the post-walk
|
|
950
|
+
`$$finalizeStreamedChildren` await (ADR-0039); a component with no hoistable child stays
|
|
951
|
+
synchronous. Each hoistable child pushed one `childFlightDecls` entry, so its length is the
|
|
952
|
+
staged-child count. */
|
|
953
|
+
return { body, flightDecls, hasStagedChildren: childFlightDecls.length > 0 }
|
|
755
954
|
}
|