octane 0.2.12 → 0.2.13
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/README.md +5 -0
- package/dist/behavior-root.d.ts +13 -3
- package/dist/behavior-root.js +28 -5
- package/dist/cjs/behavior-root.cjs +32 -6
- package/dist/cjs/class-names.cjs +49 -0
- package/dist/cjs/constants.cjs +9 -0
- package/dist/cjs/css.cjs +6 -27
- package/dist/cjs/data-encoding.cjs +160 -0
- package/dist/cjs/dom-binding-handoff.cjs +93 -0
- package/dist/cjs/dom-binding-protocol.cjs +115 -0
- package/dist/cjs/dom-binding-styles.cjs +292 -0
- package/dist/cjs/dom-bindings.cjs +700 -0
- package/dist/cjs/dom-focused-move.cjs +66 -0
- package/dist/cjs/dom-tables.cjs +4 -94
- package/dist/cjs/error-codes.client.generated.cjs +33 -0
- package/dist/cjs/error-codes.server.generated.cjs +19 -0
- package/dist/cjs/hydration/control-capture.cjs +244 -0
- package/dist/cjs/hydration/event-capture.cjs +125 -43
- package/dist/cjs/hydration/interaction-config.cjs +50 -2
- package/dist/cjs/hydration-markers.cjs +37 -0
- package/dist/cjs/independent-hydration-protocol.cjs +106 -0
- package/dist/cjs/index.cjs +6 -0
- package/dist/cjs/runtime.cjs +2791 -215
- package/dist/cjs/runtime.server.cjs +716 -76
- package/dist/cjs/server/early-signals.cjs +45 -0
- package/dist/cjs/server/index.cjs +44 -0
- package/dist/cjs/server/rpc-batch.cjs +149 -0
- package/dist/cjs/server/rpc-stream.cjs +146 -0
- package/dist/cjs/server/rpc.cjs +34 -15
- package/dist/cjs/server/signal-query-observation.cjs +158 -0
- package/dist/cjs/server/streamed-region.cjs +52 -0
- package/dist/cjs/server/streamed-signals.cjs +512 -0
- package/dist/cjs/server-call.cjs +103 -0
- package/dist/cjs/server-rpc-batch-client.cjs +141 -0
- package/dist/cjs/server-rpc-client.cjs +94 -27
- package/dist/cjs/server-rpc-protocol.cjs +141 -0
- package/dist/cjs/server-rpc-stream-client.cjs +118 -0
- package/dist/cjs/signals/actions.cjs +474 -0
- package/dist/cjs/signals/client.cjs +4 -1
- package/dist/cjs/signals/computations.cjs +460 -0
- package/dist/cjs/signals/control-binding.cjs +368 -0
- package/dist/cjs/signals/derived-facade.cjs +50 -0
- package/dist/cjs/signals/document-owner.cjs +71 -0
- package/dist/cjs/signals/early-values.cjs +155 -0
- package/dist/cjs/signals/encoding.cjs +6 -95
- package/dist/cjs/signals/engine.cjs +276 -12
- package/dist/cjs/signals/errors.cjs +18 -0
- package/dist/cjs/signals/facade.cjs +477 -0
- package/dist/cjs/signals/graph.cjs +163 -20
- package/dist/cjs/signals/handle-protocol.cjs +36 -0
- package/dist/cjs/signals/index.cjs +81 -2
- package/dist/cjs/signals/native-read-client.cjs +10 -0
- package/dist/cjs/signals/owner-context.cjs +112 -0
- package/dist/cjs/signals/query-attempt-observer.cjs +68 -0
- package/dist/cjs/signals/query-facade.cjs +72 -0
- package/dist/cjs/signals/read-protocol.cjs +32 -0
- package/dist/cjs/signals/requests.cjs +287 -14
- package/dist/cjs/signals/scalar-computations.cjs +76 -0
- package/dist/cjs/signals/server.cjs +4 -1
- package/dist/cjs/signals/transition-candidate.cjs +567 -0
- package/dist/cjs/signals/transition-state.cjs +106 -0
- package/dist/cjs/signals/types.cjs +50 -0
- package/dist/cjs/stream-protocol.cjs +3 -0
- package/dist/cjs/streamed-signals-protocol.cjs +159 -0
- package/dist/cjs/style-values.cjs +122 -0
- package/dist/cjs/version.cjs +1 -1
- package/dist/class-names.d.ts +5 -0
- package/dist/class-names.js +25 -0
- package/dist/compiler/bundler.js +34 -6
- package/dist/compiler/compile.js +2234 -559
- package/dist/compiler/dom-binding-program.js +935 -0
- package/dist/compiler/dom-binding-request.js +57 -0
- package/dist/compiler/dom-bindings.js +2245 -0
- package/dist/compiler/hook-deps.js +26 -0
- package/dist/compiler/hydrate-boundaries.js +358 -15
- package/dist/compiler/index.d.ts +38 -1
- package/dist/compiler/index.js +1 -0
- package/dist/compiler/native-attribute-reads.js +127 -0
- package/dist/compiler/native-read-diagnostics.js +48 -9
- package/dist/compiler/native-read-types.js +55 -32
- package/dist/compiler/server-context.js +118 -0
- package/dist/compiler/signal-attempt-reads.js +479 -0
- package/dist/compiler/signal-declarations.js +557 -0
- package/dist/compiler/signal-start-reads.js +413 -0
- package/dist/compiler/slot-hooks.js +57 -10
- package/dist/compiler/template-ir.js +200 -0
- package/dist/compiler/typescript.d.ts +2 -0
- package/dist/compiler/typescript.js +2 -0
- package/dist/compiler/vite.d.ts +3 -1
- package/dist/compiler/vite.js +122 -4
- package/dist/compiler/volar.d.ts +6 -1
- package/dist/compiler/volar.js +674 -143
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +10 -0
- package/dist/css.d.ts +2 -5
- package/dist/css.js +2 -23
- package/dist/data-encoding.d.ts +9 -0
- package/dist/data-encoding.js +134 -0
- package/dist/dom-binding-classes.d.ts +15 -0
- package/dist/dom-binding-classes.js +129 -0
- package/dist/dom-binding-controls.d.ts +2 -0
- package/dist/dom-binding-controls.js +6 -0
- package/dist/dom-binding-handoff.d.ts +55 -0
- package/dist/dom-binding-handoff.js +62 -0
- package/dist/dom-binding-program.d.ts +300 -0
- package/dist/dom-binding-program.js +1625 -0
- package/dist/dom-binding-projections.d.ts +15 -0
- package/dist/dom-binding-projections.js +187 -0
- package/dist/dom-binding-protocol.d.ts +13 -0
- package/dist/dom-binding-protocol.js +88 -0
- package/dist/dom-binding-signals.d.ts +21 -0
- package/dist/dom-binding-signals.js +109 -0
- package/dist/dom-binding-styles.d.ts +17 -0
- package/dist/dom-binding-styles.js +272 -0
- package/dist/dom-bindings.d.ts +89 -0
- package/dist/dom-bindings.js +670 -0
- package/dist/dom-focused-move.d.ts +6 -0
- package/dist/dom-focused-move.js +42 -0
- package/dist/dom-tables.d.ts +1 -3
- package/dist/dom-tables.js +1 -91
- package/dist/error-codes.client.generated.d.ts +9 -0
- package/dist/error-codes.client.generated.js +33 -0
- package/dist/error-codes.server.generated.d.ts +5 -0
- package/dist/error-codes.server.generated.js +19 -0
- package/dist/hydration/control-capture.d.ts +62 -0
- package/dist/hydration/control-capture.js +223 -0
- package/dist/hydration/document-lifecycle.d.ts +23 -0
- package/dist/hydration/document-lifecycle.js +80 -0
- package/dist/hydration/event-capture.d.ts +20 -1
- package/dist/hydration/event-capture.js +128 -42
- package/dist/hydration/independent-island.d.ts +45 -0
- package/dist/hydration/independent-island.js +233 -0
- package/dist/hydration/index.d.ts +8 -1
- package/dist/hydration/index.js +55 -1
- package/dist/hydration/interaction-config.d.ts +5 -0
- package/dist/hydration/interaction-config.js +44 -1
- package/dist/hydration/stream-delivery.d.ts +32 -0
- package/dist/hydration/stream-delivery.js +333 -0
- package/dist/hydration/stream-receiver.d.ts +26 -0
- package/dist/hydration/stream-receiver.js +204 -0
- package/dist/hydration/stream-result-receiver.d.ts +70 -0
- package/dist/hydration/stream-result-receiver.js +246 -0
- package/dist/hydration/streamed-signals.d.ts +44 -0
- package/dist/hydration/streamed-signals.js +186 -0
- package/dist/hydration/types.d.ts +6 -0
- package/dist/hydration-markers.d.ts +7 -0
- package/dist/hydration-markers.js +10 -0
- package/dist/independent-hydration-protocol.d.ts +31 -0
- package/dist/independent-hydration-protocol.js +80 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/internal/signal-read.d.ts +1 -0
- package/dist/internal/signal-read.js +4 -0
- package/dist/jsx-runtime.d.ts +45 -6
- package/dist/public-types.d.ts +15 -1
- package/dist/runtime.d.ts +259 -21
- package/dist/runtime.js +2812 -220
- package/dist/runtime.server.d.ts +86 -6
- package/dist/runtime.server.js +712 -76
- package/dist/server/early-signals.d.ts +14 -0
- package/dist/server/early-signals.js +34 -0
- package/dist/server/index.d.ts +10 -2
- package/dist/server/index.js +48 -1
- package/dist/server/rpc-batch.d.ts +17 -0
- package/dist/server/rpc-batch.js +129 -0
- package/dist/server/rpc-stream.d.ts +9 -0
- package/dist/server/rpc-stream.js +125 -0
- package/dist/server/rpc.d.ts +5 -1
- package/dist/server/rpc.js +37 -13
- package/dist/server/signal-query-observation.d.ts +3 -0
- package/dist/server/signal-query-observation.js +134 -0
- package/dist/server/streamed-region.d.ts +15 -0
- package/dist/server/streamed-region.js +30 -0
- package/dist/server/streamed-signals.d.ts +45 -0
- package/dist/server/streamed-signals.js +488 -0
- package/dist/server-call.d.ts +41 -0
- package/dist/server-call.js +75 -0
- package/dist/server-rpc-batch-client.d.ts +7 -0
- package/dist/server-rpc-batch-client.js +119 -0
- package/dist/server-rpc-client.d.ts +3 -1
- package/dist/server-rpc-client.js +99 -27
- package/dist/server-rpc-protocol.d.ts +47 -0
- package/dist/server-rpc-protocol.js +109 -0
- package/dist/server-rpc-stream-client.d.ts +5 -0
- package/dist/server-rpc-stream-client.js +98 -0
- package/dist/signals/actions.d.ts +12 -0
- package/dist/signals/actions.js +466 -0
- package/dist/signals/client.d.ts +2 -0
- package/dist/signals/client.js +1 -0
- package/dist/signals/computations.d.ts +37 -0
- package/dist/signals/computations.js +455 -0
- package/dist/signals/control-binding.d.ts +31 -0
- package/dist/signals/control-binding.js +353 -0
- package/dist/signals/derived-facade.d.ts +3 -0
- package/dist/signals/derived-facade.js +25 -0
- package/dist/signals/document-owner.d.ts +10 -0
- package/dist/signals/document-owner.js +43 -0
- package/dist/signals/early-values.d.ts +24 -0
- package/dist/signals/early-values.js +124 -0
- package/dist/signals/encoding.d.ts +1 -3
- package/dist/signals/encoding.js +3 -93
- package/dist/signals/engine.d.ts +53 -4
- package/dist/signals/engine.js +275 -16
- package/dist/signals/errors.d.ts +7 -0
- package/dist/signals/errors.js +16 -0
- package/dist/signals/facade.d.ts +74 -0
- package/dist/signals/facade.js +452 -0
- package/dist/signals/graph.d.ts +50 -5
- package/dist/signals/graph.js +170 -19
- package/dist/signals/handle-protocol.d.ts +4 -0
- package/dist/signals/handle-protocol.js +11 -0
- package/dist/signals/index.d.ts +12 -3
- package/dist/signals/index.js +71 -2
- package/dist/signals/native-read-client.d.ts +3 -0
- package/dist/signals/native-read-client.js +10 -0
- package/dist/signals/owner-context.d.ts +22 -0
- package/dist/signals/owner-context.js +77 -0
- package/dist/signals/query-attempt-observer.d.ts +49 -0
- package/dist/signals/query-attempt-observer.js +41 -0
- package/dist/signals/query-facade.d.ts +3 -0
- package/dist/signals/query-facade.js +49 -0
- package/dist/signals/read-protocol.d.ts +32 -3
- package/dist/signals/read-protocol.js +26 -0
- package/dist/signals/requests.d.ts +38 -5
- package/dist/signals/requests.js +298 -15
- package/dist/signals/scalar-computations.d.ts +2 -0
- package/dist/signals/scalar-computations.js +60 -0
- package/dist/signals/server.d.ts +2 -0
- package/dist/signals/server.js +1 -0
- package/dist/signals/transition-candidate.d.ts +71 -0
- package/dist/signals/transition-candidate.js +557 -0
- package/dist/signals/transition-state.d.ts +51 -0
- package/dist/signals/transition-state.js +70 -0
- package/dist/signals/types.d.ts +95 -5
- package/dist/signals/types.js +20 -0
- package/dist/stream-protocol.d.ts +0 -5
- package/dist/stream-protocol.js +3 -0
- package/dist/streamed-signals-protocol.d.ts +60 -0
- package/dist/streamed-signals-protocol.js +130 -0
- package/dist/style-values.d.ts +3 -0
- package/dist/style-values.js +96 -0
- package/dist/version.js +1 -1
- package/package.json +37 -1
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import { builders as b } from '@tsrx/core';
|
|
2
|
+
import { createLexicalAnalysis } from './compile-universal.js';
|
|
3
|
+
import { inheritHookMemoOrigin } from './inline-hook-memo.js';
|
|
4
|
+
|
|
5
|
+
const SIGNAL_MODULES = new Set([
|
|
6
|
+
'octane/signals',
|
|
7
|
+
'octane/signals/client',
|
|
8
|
+
'octane/signals/server',
|
|
9
|
+
]);
|
|
10
|
+
const METADATA = new Set([
|
|
11
|
+
'loc',
|
|
12
|
+
'start',
|
|
13
|
+
'end',
|
|
14
|
+
'range',
|
|
15
|
+
'metadata',
|
|
16
|
+
'parent',
|
|
17
|
+
'comments',
|
|
18
|
+
'leadingComments',
|
|
19
|
+
'trailingComments',
|
|
20
|
+
'innerComments',
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
function children(node, visit) {
|
|
24
|
+
for (const key in node) {
|
|
25
|
+
if (METADATA.has(key) || key.startsWith('_octane')) continue;
|
|
26
|
+
const value = node[key];
|
|
27
|
+
if (Array.isArray(value)) {
|
|
28
|
+
for (const child of value) if (child?.type) visit(child);
|
|
29
|
+
} else if (value?.type) visit(value);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function unwrap(node) {
|
|
34
|
+
while (
|
|
35
|
+
[
|
|
36
|
+
'TSAsExpression',
|
|
37
|
+
'TSTypeAssertion',
|
|
38
|
+
'TSSatisfiesExpression',
|
|
39
|
+
'TSNonNullExpression',
|
|
40
|
+
'ParenthesizedExpression',
|
|
41
|
+
].includes(node?.type)
|
|
42
|
+
)
|
|
43
|
+
node = node.expression;
|
|
44
|
+
return node;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isFunction(node) {
|
|
48
|
+
return ['FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression'].includes(
|
|
49
|
+
node.type,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function hasRender(node) {
|
|
54
|
+
if (node.type.startsWith('JSX')) return true;
|
|
55
|
+
if (isFunction(node)) return false;
|
|
56
|
+
let found = false;
|
|
57
|
+
children(node, (child) => {
|
|
58
|
+
found ||= hasRender(child);
|
|
59
|
+
});
|
|
60
|
+
return found;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Start adjacent strict reads or complete static native output reads of
|
|
65
|
+
* lexically proven immutable descriptors.
|
|
66
|
+
* Original reads remain the suspension/error/observation points. In particular,
|
|
67
|
+
* this does not hoist declarations, evaluate member receivers, cross statements,
|
|
68
|
+
* or enter a conditional arm before its authored control-flow boundary.
|
|
69
|
+
*/
|
|
70
|
+
export function startIndependentSignalReads(ast, isText = null) {
|
|
71
|
+
if (
|
|
72
|
+
!(ast.body ?? []).some(
|
|
73
|
+
(node) => node.type === 'ImportDeclaration' && SIGNAL_MODULES.has(node.source?.value),
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
return ast;
|
|
77
|
+
const lexical = createLexicalAnalysis(ast);
|
|
78
|
+
const imports = new Map();
|
|
79
|
+
const declarations = new Map();
|
|
80
|
+
const names = new Set();
|
|
81
|
+
const binding = (node) =>
|
|
82
|
+
node?.type === 'Identifier'
|
|
83
|
+
? lexical.resolveBinding(lexical.nodeScopes.get(node) ?? lexical.rootScope, node.name)
|
|
84
|
+
: null;
|
|
85
|
+
const recordFor = (node) => declarations.get(binding(node)?.scope)?.get(node.name);
|
|
86
|
+
function register(id, record, declarationScope) {
|
|
87
|
+
if (id?.type !== 'Identifier') return;
|
|
88
|
+
const scope = declarationScope
|
|
89
|
+
? lexical.resolveBinding(declarationScope, id.name)?.scope
|
|
90
|
+
: binding(id)?.scope;
|
|
91
|
+
if (!scope) return;
|
|
92
|
+
let records = declarations.get(scope);
|
|
93
|
+
if (!records) declarations.set(scope, (records = new Map()));
|
|
94
|
+
records.set(id.name, record);
|
|
95
|
+
}
|
|
96
|
+
function collect(node, ownerFunction = null) {
|
|
97
|
+
if (node.type === 'Identifier') names.add(node.name);
|
|
98
|
+
if (
|
|
99
|
+
node.type === 'ImportDeclaration' &&
|
|
100
|
+
node.importKind !== 'type' &&
|
|
101
|
+
SIGNAL_MODULES.has(node.source?.value)
|
|
102
|
+
) {
|
|
103
|
+
for (const specifier of node.specifiers ?? []) {
|
|
104
|
+
if (specifier.importKind === 'type') continue;
|
|
105
|
+
const factory = specifier.imported?.name ?? specifier.imported?.value;
|
|
106
|
+
if (
|
|
107
|
+
factory === 'query$' ||
|
|
108
|
+
factory === 'derived$' ||
|
|
109
|
+
specifier.type === 'ImportNamespaceSpecifier'
|
|
110
|
+
)
|
|
111
|
+
imports.set(specifier.local.name, {
|
|
112
|
+
factory,
|
|
113
|
+
source: node.source.value,
|
|
114
|
+
declaration: node,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (node.type === 'VariableDeclaration') {
|
|
119
|
+
for (const declaration of node.declarations)
|
|
120
|
+
register(declaration.id, {
|
|
121
|
+
init: declaration.init,
|
|
122
|
+
kind: node.kind,
|
|
123
|
+
declaration,
|
|
124
|
+
ownerFunction,
|
|
125
|
+
});
|
|
126
|
+
} else if (node.type === 'FunctionDeclaration')
|
|
127
|
+
register(
|
|
128
|
+
node.id,
|
|
129
|
+
{ init: node, kind: 'function', declaration: node },
|
|
130
|
+
lexical.nodeScopes.get(node),
|
|
131
|
+
);
|
|
132
|
+
children(node, (child) => collect(child, isFunction(node) ? node : ownerFunction));
|
|
133
|
+
}
|
|
134
|
+
collect(ast);
|
|
135
|
+
// Initializer reachability is insufficient when a selector was reassigned or
|
|
136
|
+
// installed in a mutable object. Fence any result captured by an already
|
|
137
|
+
// available closure, without attempting heap or assignment reconstruction.
|
|
138
|
+
const capturedResults = new Map();
|
|
139
|
+
const opaqueFunctions = new Set();
|
|
140
|
+
function collectCaptures(node, stack = []) {
|
|
141
|
+
const nested =
|
|
142
|
+
isFunction(node) || node.type === 'ClassDeclaration' || node.type === 'ClassExpression'
|
|
143
|
+
? [...stack, node]
|
|
144
|
+
: stack;
|
|
145
|
+
if (node.type === 'Identifier' && !lexical.bindingNodes.has(node)) {
|
|
146
|
+
const record = recordFor(node);
|
|
147
|
+
const owner = record?.ownerFunction;
|
|
148
|
+
const index = owner ? nested.indexOf(owner) : -1;
|
|
149
|
+
const closure = index < 0 ? null : nested[index + 1];
|
|
150
|
+
if (closure) {
|
|
151
|
+
let captured = capturedResults.get(record);
|
|
152
|
+
if (!captured) capturedResults.set(record, (captured = new Set()));
|
|
153
|
+
captured.add(closure);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const callee = unwrap(node.callee);
|
|
157
|
+
if (
|
|
158
|
+
(node.type === 'CallExpression' || node.type === 'NewExpression') &&
|
|
159
|
+
callee?.type === 'Identifier' &&
|
|
160
|
+
(callee.name === 'eval' || callee.name === 'Function')
|
|
161
|
+
) {
|
|
162
|
+
for (const owner of nested) opaqueFunctions.add(owner);
|
|
163
|
+
}
|
|
164
|
+
children(node, (child) => collectCaptures(child, nested));
|
|
165
|
+
}
|
|
166
|
+
collectCaptures(ast);
|
|
167
|
+
function capturedBefore(result, position) {
|
|
168
|
+
for (const closure of capturedResults.get(recordFor(result)) ?? []) {
|
|
169
|
+
if (
|
|
170
|
+
closure.type === 'FunctionDeclaration' ||
|
|
171
|
+
(closure.start ?? -Infinity) < (position ?? Infinity)
|
|
172
|
+
)
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
function factoryOf(init) {
|
|
178
|
+
const call = unwrap(init);
|
|
179
|
+
if (call?.type !== 'CallExpression' || call.optional) return null;
|
|
180
|
+
const callee = unwrap(call.callee);
|
|
181
|
+
const identifier =
|
|
182
|
+
callee?.type === 'Identifier'
|
|
183
|
+
? callee
|
|
184
|
+
: callee?.type === 'MemberExpression' && !callee.computed && !callee.optional
|
|
185
|
+
? callee.object
|
|
186
|
+
: null;
|
|
187
|
+
if (identifier?.type !== 'Identifier') return null;
|
|
188
|
+
const record = imports.get(identifier.name);
|
|
189
|
+
const resolved = binding(identifier);
|
|
190
|
+
if (
|
|
191
|
+
!record ||
|
|
192
|
+
resolved?.scope !== lexical.rootScope ||
|
|
193
|
+
resolved.importSource?.value !== record.source
|
|
194
|
+
)
|
|
195
|
+
return null;
|
|
196
|
+
const factory =
|
|
197
|
+
callee === identifier
|
|
198
|
+
? record.factory
|
|
199
|
+
: record.factory === undefined
|
|
200
|
+
? callee.property.name
|
|
201
|
+
: null;
|
|
202
|
+
return factory === 'query$' || factory === 'derived$' ? record : null;
|
|
203
|
+
}
|
|
204
|
+
function strictExpression(expression) {
|
|
205
|
+
const call = unwrap(expression);
|
|
206
|
+
if (call?.type !== 'CallExpression' || call.optional || call.arguments.length !== 0)
|
|
207
|
+
return null;
|
|
208
|
+
const member = unwrap(call.callee);
|
|
209
|
+
if (
|
|
210
|
+
member?.type !== 'MemberExpression' ||
|
|
211
|
+
member.optional ||
|
|
212
|
+
member.computed ||
|
|
213
|
+
member.property.name !== 'get' ||
|
|
214
|
+
member.object.type !== 'Identifier'
|
|
215
|
+
)
|
|
216
|
+
return null;
|
|
217
|
+
const descriptor = recordFor(member.object);
|
|
218
|
+
if (descriptor?.kind !== 'const') return null;
|
|
219
|
+
const factory = factoryOf(descriptor.init);
|
|
220
|
+
return factory ? { handle: member.object, descriptor, factory } : null;
|
|
221
|
+
}
|
|
222
|
+
function strictRead(statement) {
|
|
223
|
+
if (
|
|
224
|
+
statement.type !== 'VariableDeclaration' ||
|
|
225
|
+
statement.kind !== 'const' ||
|
|
226
|
+
statement.declarations.length !== 1
|
|
227
|
+
)
|
|
228
|
+
return null;
|
|
229
|
+
const declaration = statement.declarations[0];
|
|
230
|
+
if (declaration.id.type !== 'Identifier') return null;
|
|
231
|
+
const read = strictExpression(declaration.init);
|
|
232
|
+
return read ? { ...read, result: declaration.id } : null;
|
|
233
|
+
}
|
|
234
|
+
// Only complete static native output is traversed. Mount phases can evaluate
|
|
235
|
+
// an attribute or typed text before an earlier generic child, so a lexical
|
|
236
|
+
// prefix before an opaque node (or a mix of hole kinds) is not sufficient.
|
|
237
|
+
function outputReads(output) {
|
|
238
|
+
if (isText === null) return [];
|
|
239
|
+
const reads = [];
|
|
240
|
+
let text;
|
|
241
|
+
function visit(node) {
|
|
242
|
+
if (node.type === 'JSXText') return true;
|
|
243
|
+
if (node.type === 'JSXExpressionContainer') {
|
|
244
|
+
if (node.expression.type === 'JSXEmptyExpression') return true;
|
|
245
|
+
const read = strictExpression(node.expression);
|
|
246
|
+
if (read === null) return false;
|
|
247
|
+
const kind = isText(node.expression);
|
|
248
|
+
if (text !== undefined && text !== kind) return false;
|
|
249
|
+
text = kind;
|
|
250
|
+
reads.push(read);
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
if (node.type === 'JSXElement') {
|
|
254
|
+
const opening = node.openingElement;
|
|
255
|
+
const tag = opening.name;
|
|
256
|
+
if (
|
|
257
|
+
tag.type !== 'JSXIdentifier' ||
|
|
258
|
+
!/^[a-z][a-z0-9]*$/.test(tag.name) ||
|
|
259
|
+
/^(?:html|head|body|base|meta|link|title|style|script|template|noscript|svg|math|input|select|textarea|option|optgroup|iframe|object|embed|img|image|audio|video|source|track)$/.test(
|
|
260
|
+
tag.name,
|
|
261
|
+
)
|
|
262
|
+
)
|
|
263
|
+
return false;
|
|
264
|
+
for (const attribute of opening.attributes ?? []) {
|
|
265
|
+
if (
|
|
266
|
+
attribute.type !== 'JSXAttribute' ||
|
|
267
|
+
attribute.name.type !== 'JSXIdentifier' ||
|
|
268
|
+
/^(?:ref|key|is|style|children|innerHTML|dangerouslySetInnerHTML|on.*)$/i.test(
|
|
269
|
+
attribute.name.name,
|
|
270
|
+
) ||
|
|
271
|
+
(attribute.value !== null && attribute.value.type !== 'Literal')
|
|
272
|
+
)
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
} else if (node.type !== 'JSXFragment') return false;
|
|
276
|
+
return (node.children ?? []).every(visit);
|
|
277
|
+
}
|
|
278
|
+
return output && visit(output) ? reads : [];
|
|
279
|
+
}
|
|
280
|
+
// Follow local aliases/helpers as well as direct captures: a later selector
|
|
281
|
+
// closing over an earlier read's result must run after that result is assigned.
|
|
282
|
+
function dependsOn(node, results, seen = new Set()) {
|
|
283
|
+
if (!node || seen.has(node)) return false;
|
|
284
|
+
seen.add(node);
|
|
285
|
+
if (node.type === 'Identifier' && !lexical.bindingNodes.has(node)) {
|
|
286
|
+
const resolved = binding(node);
|
|
287
|
+
if (
|
|
288
|
+
results.some(
|
|
289
|
+
(result) => result.name === node.name && binding(result)?.scope === resolved?.scope,
|
|
290
|
+
)
|
|
291
|
+
)
|
|
292
|
+
return true;
|
|
293
|
+
if (dependsOn(recordFor(node)?.init, results, seen)) return true;
|
|
294
|
+
}
|
|
295
|
+
let found = false;
|
|
296
|
+
children(node, (child) => {
|
|
297
|
+
found ||= dependsOn(child, results, seen);
|
|
298
|
+
});
|
|
299
|
+
return found;
|
|
300
|
+
}
|
|
301
|
+
let helperName = '_$startSignalReads';
|
|
302
|
+
while (names.has(helperName)) helperName += '$';
|
|
303
|
+
let helperImport = null;
|
|
304
|
+
function start(reads, origin, primitive = false) {
|
|
305
|
+
helperImport ??= reads[0].factory.declaration;
|
|
306
|
+
return inheritHookMemoOrigin(
|
|
307
|
+
// Guard only speculative receiver evaluation (including module TDZ).
|
|
308
|
+
// The original reads retain their normal observation/error boundary.
|
|
309
|
+
b.try(
|
|
310
|
+
b.block([
|
|
311
|
+
b.stmt(
|
|
312
|
+
b.call(
|
|
313
|
+
b.id(helperName),
|
|
314
|
+
b.array(reads.map((read) => b.id(read.handle.name))),
|
|
315
|
+
...(primitive ? [b.literal(true)] : []),
|
|
316
|
+
),
|
|
317
|
+
),
|
|
318
|
+
]),
|
|
319
|
+
b.catch_clause(null, null, b.block([])),
|
|
320
|
+
),
|
|
321
|
+
origin,
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
function statements(body) {
|
|
325
|
+
let out = null;
|
|
326
|
+
for (let i = 0; i < body.length;) {
|
|
327
|
+
const first = strictRead(body[i]);
|
|
328
|
+
if (!first) {
|
|
329
|
+
const reads = outputReads(body[i]);
|
|
330
|
+
if (reads.length > 1) {
|
|
331
|
+
out ??= body.slice(0, i);
|
|
332
|
+
out.push(start(reads, body[i], true));
|
|
333
|
+
}
|
|
334
|
+
if (out) out.push(body[i]);
|
|
335
|
+
i++;
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
const run = [first];
|
|
339
|
+
let end = i + 1;
|
|
340
|
+
for (; end < body.length; end++) {
|
|
341
|
+
const next = strictRead(body[end]);
|
|
342
|
+
if (
|
|
343
|
+
!next ||
|
|
344
|
+
run.some((read) => capturedBefore(read.result, body[i].start)) ||
|
|
345
|
+
dependsOn(
|
|
346
|
+
next.descriptor.init,
|
|
347
|
+
run.map((read) => read.result),
|
|
348
|
+
)
|
|
349
|
+
)
|
|
350
|
+
break;
|
|
351
|
+
run.push(next);
|
|
352
|
+
}
|
|
353
|
+
if (run.length > 1) {
|
|
354
|
+
out ??= body.slice(0, i);
|
|
355
|
+
out.push(start(run, body[i]));
|
|
356
|
+
}
|
|
357
|
+
if (out) out.push(...body.slice(i, end));
|
|
358
|
+
i = end;
|
|
359
|
+
}
|
|
360
|
+
return out ?? body;
|
|
361
|
+
}
|
|
362
|
+
function transform(node, rendering = false, functionBody = false) {
|
|
363
|
+
if (node === null || typeof node !== 'object') return node;
|
|
364
|
+
if (Array.isArray(node)) {
|
|
365
|
+
const mapped = node.map((child) => transform(child, rendering));
|
|
366
|
+
return mapped.some((child, index) => child !== node[index]) ? mapped : node;
|
|
367
|
+
}
|
|
368
|
+
if (isFunction(node)) rendering = !opaqueFunctions.has(node) && hasRender(node.body);
|
|
369
|
+
let out = node;
|
|
370
|
+
for (const key in node) {
|
|
371
|
+
if (METADATA.has(key) || key.startsWith('_octane')) continue;
|
|
372
|
+
const mapped = transform(node[key], rendering, isFunction(node) && key === 'body');
|
|
373
|
+
if (mapped !== node[key]) {
|
|
374
|
+
if (out === node) out = { ...node };
|
|
375
|
+
out[key] = mapped;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if (
|
|
379
|
+
rendering &&
|
|
380
|
+
(node.type === 'BlockStatement' || node.type === 'JSXCodeBlock') &&
|
|
381
|
+
Array.isArray(node.body)
|
|
382
|
+
) {
|
|
383
|
+
// Analyze original nodes, whose lexical binding identities are known.
|
|
384
|
+
const body = statements(node.body);
|
|
385
|
+
if (body !== node.body) {
|
|
386
|
+
const mapped = new Map(node.body.map((statement, index) => [statement, out.body[index]]));
|
|
387
|
+
out = { ...out, body: body.map((statement) => mapped.get(statement) ?? statement) };
|
|
388
|
+
}
|
|
389
|
+
// Do not turn a transparent render-only child block into a new scope.
|
|
390
|
+
if (node.type === 'JSXCodeBlock' && (functionBody || node.body.length > 0)) {
|
|
391
|
+
const reads = outputReads(node.render);
|
|
392
|
+
if (reads.length > 1)
|
|
393
|
+
out = { ...out, body: [...out.body, start(reads, node.render, true)] };
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return out;
|
|
397
|
+
}
|
|
398
|
+
const lowered = transform(ast);
|
|
399
|
+
if (helperImport === null) return ast;
|
|
400
|
+
return {
|
|
401
|
+
...lowered,
|
|
402
|
+
body: lowered.body.flatMap((statement) => {
|
|
403
|
+
if (statement !== helperImport) return [statement];
|
|
404
|
+
const helper = inheritHookMemoOrigin(
|
|
405
|
+
b.import_specifier('__startSignalReads', helperName),
|
|
406
|
+
statement,
|
|
407
|
+
);
|
|
408
|
+
return statement.specifiers.some((specifier) => specifier.type === 'ImportNamespaceSpecifier')
|
|
409
|
+
? [statement, { ...statement, specifiers: [helper] }]
|
|
410
|
+
: [{ ...statement, specifiers: [...statement.specifiers, helper] }];
|
|
411
|
+
}),
|
|
412
|
+
};
|
|
413
|
+
}
|
|
@@ -26,6 +26,7 @@ import { nativeReadActivationIndex } from './native-read-codegen.js';
|
|
|
26
26
|
import { findManualHookProviders, manualHookWrapperParameters } from './manual-hooks.js';
|
|
27
27
|
import { findLeadingJsxImportSourcePragma } from './pragma.js';
|
|
28
28
|
import { collectProvenContextBindings, isProvenContextUse } from './context-use.js';
|
|
29
|
+
import { signalDeclarationSourceEdits } from './signal-declarations.js';
|
|
29
30
|
import {
|
|
30
31
|
hookMethodName,
|
|
31
32
|
hasHookMethods,
|
|
@@ -1337,7 +1338,7 @@ function parseHookSource(source, id) {
|
|
|
1337
1338
|
* `inlineHookMemo: true` enables the production-client whole-AST memo path;
|
|
1338
1339
|
* the default remains surgical. `manualSlots: true` permits memo and observed
|
|
1339
1340
|
* getter rewrites without injecting or changing the authored slot policy.
|
|
1340
|
-
* @returns {{ code: string, map: any, diagnostics?: any[] } | null}
|
|
1341
|
+
* @returns {{ code: string, map: any, streamedSignals?: true, diagnostics?: any[] } | null}
|
|
1341
1342
|
*/
|
|
1342
1343
|
export function slotHooks(source, id, options) {
|
|
1343
1344
|
const environment = options?.environment ?? 'client';
|
|
@@ -1381,6 +1382,7 @@ export function slotHooks(source, id, options) {
|
|
|
1381
1382
|
);
|
|
1382
1383
|
const manualProviders = options?.manualSlots ? findManualHookProviders(ast) : new Map();
|
|
1383
1384
|
const nativeReadActivation = options?.nativeReads === true && importsNativeRenderer(ast);
|
|
1385
|
+
const signalLowering = signalDeclarationSourceEdits(ast, id, source);
|
|
1384
1386
|
const canSpecializeRoot =
|
|
1385
1387
|
!options?.manualSlots &&
|
|
1386
1388
|
!options?.hmr &&
|
|
@@ -1391,6 +1393,7 @@ export function slotHooks(source, id, options) {
|
|
|
1391
1393
|
!importInfo.importsHook &&
|
|
1392
1394
|
!canSpecializeRoot &&
|
|
1393
1395
|
!nativeReadActivation &&
|
|
1396
|
+
!signalLowering.usesSignals &&
|
|
1394
1397
|
!manualProviders.size
|
|
1395
1398
|
) {
|
|
1396
1399
|
return strongAnalysis?.diagnostics.length ? { code: source, map: null, ...strongHints } : null;
|
|
@@ -1417,6 +1420,8 @@ export function slotHooks(source, id, options) {
|
|
|
1417
1420
|
const getterCalls = importInfo.importsHook ? collectStateGetterCalls(ast) : new WeakSet();
|
|
1418
1421
|
if (
|
|
1419
1422
|
!strongAnalysis?.enabled &&
|
|
1423
|
+
// The whole-AST memo path does not apply declaration identity edits.
|
|
1424
|
+
!signalLowering.usesSignals &&
|
|
1420
1425
|
canPrint &&
|
|
1421
1426
|
options?.inlineHookMemo === true &&
|
|
1422
1427
|
environment === 'client' &&
|
|
@@ -1465,7 +1470,7 @@ export function slotHooks(source, id, options) {
|
|
|
1465
1470
|
hash: hookSlotHash(id),
|
|
1466
1471
|
nextId: 0,
|
|
1467
1472
|
nextPuId: 0,
|
|
1468
|
-
edits: [],
|
|
1473
|
+
edits: [...signalLowering.edits],
|
|
1469
1474
|
decls: [],
|
|
1470
1475
|
parallelHelpers: new Map(),
|
|
1471
1476
|
provenContextBindings: collectProvenContextBindings(ast),
|
|
@@ -1485,7 +1490,7 @@ export function slotHooks(source, id, options) {
|
|
|
1485
1490
|
if (canSpecializeRoot) {
|
|
1486
1491
|
collectVoidRootEdits(ast, st, options.isVoidComponentImport);
|
|
1487
1492
|
}
|
|
1488
|
-
if (st.edits.length === 0 && !nativeReadActivation)
|
|
1493
|
+
if (st.edits.length === 0 && !nativeReadActivation && !signalLowering.usesSignals)
|
|
1489
1494
|
return strongAnalysis?.diagnostics.length ? { code: source, map: null, ...strongHints } : null;
|
|
1490
1495
|
const activation = nativeReadActivation
|
|
1491
1496
|
? requireParallelHelper(st, 'enableNativeReadCollection')
|
|
@@ -1526,6 +1531,35 @@ export function slotHooks(source, id, options) {
|
|
|
1526
1531
|
const otherHelperImports = [...otherHelpers]
|
|
1527
1532
|
.map(([request, specifiers]) => `import { ${specifiers.join(', ')} } from '${request}';\n`)
|
|
1528
1533
|
.join('');
|
|
1534
|
+
const signalHelperImports = new Map();
|
|
1535
|
+
for (const helper of signalLowering.imports) {
|
|
1536
|
+
let specifiers = signalHelperImports.get(helper.source);
|
|
1537
|
+
if (specifiers === undefined) signalHelperImports.set(helper.source, (specifiers = []));
|
|
1538
|
+
specifiers.push(`${helper.imported} as ${helper.local}`);
|
|
1539
|
+
}
|
|
1540
|
+
let signalActivation = '';
|
|
1541
|
+
if (signalLowering.usesSignals) {
|
|
1542
|
+
const request =
|
|
1543
|
+
environment === 'server'
|
|
1544
|
+
? 'octane/internal/server'
|
|
1545
|
+
: nativeReadActivation
|
|
1546
|
+
? 'octane/internal/client'
|
|
1547
|
+
: 'octane/signals';
|
|
1548
|
+
const imported =
|
|
1549
|
+
environment === 'server'
|
|
1550
|
+
? 'enableServerSignalBindings'
|
|
1551
|
+
: nativeReadActivation
|
|
1552
|
+
? 'enableSignalBindings'
|
|
1553
|
+
: '__enableSignalDocument';
|
|
1554
|
+
const local = allocSlotName(st, `_$${imported}`);
|
|
1555
|
+
let specifiers = signalHelperImports.get(request);
|
|
1556
|
+
if (specifiers === undefined) signalHelperImports.set(request, (specifiers = []));
|
|
1557
|
+
specifiers.push(`${imported} as ${local}`);
|
|
1558
|
+
signalActivation = `${local}(1);\n`;
|
|
1559
|
+
}
|
|
1560
|
+
const signalImports = [...signalHelperImports]
|
|
1561
|
+
.map(([request, specifiers]) => `import { ${specifiers.join(', ')} } from '${request}';\n`)
|
|
1562
|
+
.join('');
|
|
1529
1563
|
const profileImport =
|
|
1530
1564
|
st.profile && !st.manualSlots
|
|
1531
1565
|
? "import { __profileHook as _$__profileHook } from 'octane/profiling';\n"
|
|
@@ -1535,15 +1569,22 @@ export function slotHooks(source, id, options) {
|
|
|
1535
1569
|
? `const ${st.slotBaseName} = /* @__PURE__ */ ${st.hookSlotsName}(${st.nextId});\n`
|
|
1536
1570
|
: '';
|
|
1537
1571
|
const block =
|
|
1538
|
-
helperImport +
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1572
|
+
helperImport +
|
|
1573
|
+
otherHelperImports +
|
|
1574
|
+
signalImports +
|
|
1575
|
+
profileImport +
|
|
1576
|
+
slotBase +
|
|
1577
|
+
st.decls.join('\n') +
|
|
1578
|
+
'\n' +
|
|
1579
|
+
signalActivation;
|
|
1580
|
+
if (activation !== null || signalLowering.usesSignals) {
|
|
1581
|
+
// Plain modules may read global signals or render during evaluation.
|
|
1582
|
+
// Their document capability and any render slots must already exist.
|
|
1542
1583
|
// No newline is inserted, retaining the surgical pass's line mapping.
|
|
1543
1584
|
const index = nativeReadActivationIndex(ast.body);
|
|
1544
1585
|
st.edits.push({
|
|
1545
1586
|
pos: ast.body[index]?.start ?? source.length,
|
|
1546
|
-
text: `;${block.replace(/\n/g, ' ')}${activation}(1); `,
|
|
1587
|
+
text: `;${block.replace(/\n/g, ' ')}${activation === null ? '' : `${activation}(1); `}`,
|
|
1547
1588
|
});
|
|
1548
1589
|
}
|
|
1549
1590
|
// Assemble disjoint edits once instead of copying the growing module for
|
|
@@ -1574,6 +1615,12 @@ export function slotHooks(source, id, options) {
|
|
|
1574
1615
|
chunks.push(source.slice(0, cursor));
|
|
1575
1616
|
code = chunks.reverse().join('');
|
|
1576
1617
|
}
|
|
1577
|
-
if (activation === null
|
|
1578
|
-
|
|
1618
|
+
if (activation === null && !signalLowering.usesSignals)
|
|
1619
|
+
code = code.endsWith('\n') ? code + block : code + '\n' + block;
|
|
1620
|
+
return {
|
|
1621
|
+
code,
|
|
1622
|
+
map: null,
|
|
1623
|
+
...(signalLowering.usesSignals || nativeReadActivation ? { streamedSignals: true } : null),
|
|
1624
|
+
...strongHints,
|
|
1625
|
+
};
|
|
1579
1626
|
}
|