octane 0.3.0 → 0.3.2
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/dist/cjs/constants.cjs +18 -25
- package/dist/cjs/css.cjs +6 -0
- package/dist/cjs/dom-binding-protocol.cjs +37 -14
- package/dist/cjs/hydration/control-capture.cjs +12 -4
- package/dist/cjs/hydration/event-capture.cjs +6 -7
- package/dist/cjs/hydration-markers.cjs +40 -2
- package/dist/cjs/index.cjs +2 -0
- package/dist/cjs/runtime-tags.cjs +55 -0
- package/dist/cjs/runtime.cjs +235 -148
- package/dist/cjs/runtime.server.cjs +110 -80
- package/dist/cjs/server/streamed-signals.cjs +102 -64
- package/dist/cjs/signals/control-binding.cjs +2 -1
- package/dist/cjs/stream-protocol.cjs +9 -8
- package/dist/cjs/version.cjs +1 -1
- package/dist/compiler/compile.js +550 -35
- package/dist/compiler/dom-bindings.js +5 -0
- package/dist/compiler/hydrate-boundaries.js +118 -0
- package/dist/compiler/private-context.js +160 -0
- package/dist/constants.d.ts +7 -18
- package/dist/constants.js +11 -9
- package/dist/css.d.ts +7 -0
- package/dist/css.js +4 -0
- package/dist/dom-binding-program.js +12 -5
- package/dist/dom-binding-protocol.d.ts +14 -1
- package/dist/dom-binding-protocol.js +40 -14
- package/dist/hydration/control-capture.d.ts +2 -0
- package/dist/hydration/control-capture.js +18 -5
- package/dist/hydration/event-capture.js +1 -2
- package/dist/hydration/independent-island.js +5 -5
- package/dist/hydration/stream-delivery.d.ts +1 -1
- package/dist/hydration/stream-delivery.js +16 -9
- package/dist/hydration/stream-result-receiver.d.ts +1 -0
- package/dist/hydration/stream-result-receiver.js +8 -4
- package/dist/hydration-markers.d.ts +30 -0
- package/dist/hydration-markers.js +27 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -0
- package/dist/react/index.js +1 -1
- package/dist/react/react-compat-shared.js +5 -3
- package/dist/react/shared.d.ts +1 -3
- package/dist/react/shared.js +1 -1
- package/dist/runtime-tags.d.ts +34 -0
- package/dist/runtime-tags.js +22 -0
- package/dist/runtime.d.ts +9 -14
- package/dist/runtime.js +213 -106
- package/dist/runtime.server.d.ts +10 -5
- package/dist/runtime.server.js +101 -56
- package/dist/server/streamed-signals.d.ts +1 -0
- package/dist/server/streamed-signals.js +102 -64
- package/dist/signals/control-binding.js +4 -2
- package/dist/stream-protocol.js +20 -9
- package/dist/universal-core.js +8 -4
- package/dist/universal-native.js +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
|
@@ -2300,6 +2300,11 @@ export function prepareDomBindings(ast, source, filename, selectedExport, helper
|
|
|
2300
2300
|
);
|
|
2301
2301
|
}
|
|
2302
2302
|
for (const [node, replacement] of rewritten) replacements.set(node, replacement);
|
|
2303
|
+
if (!plan.hostHandoff)
|
|
2304
|
+
replacements.set(node, {
|
|
2305
|
+
...mapCow(node, rewritten),
|
|
2306
|
+
_octaneScalarBindingClaims: true,
|
|
2307
|
+
});
|
|
2303
2308
|
if (plan.hostHandoff)
|
|
2304
2309
|
replacements.set(node, {
|
|
2305
2310
|
...mapCow(node, rewritten),
|
|
@@ -14,8 +14,110 @@ import {
|
|
|
14
14
|
isIdentifierReference,
|
|
15
15
|
} from './compile-universal.js';
|
|
16
16
|
|
|
17
|
+
import { findPrivateCompiledContextProofs } from './private-context.js';
|
|
18
|
+
|
|
17
19
|
export const HYDRATE_QUERY_PARAM = 'octane-hydrate';
|
|
18
20
|
|
|
21
|
+
// Only this extraction pass can certify the loader/child ABI. Keep provenance
|
|
22
|
+
// off authored attributes and parser metadata; COW rewrites preserve tag nodes.
|
|
23
|
+
const compiledSplitHydrateTags = new WeakMap();
|
|
24
|
+
|
|
25
|
+
export function compiledSplitHydrateTagsForAst(ast) {
|
|
26
|
+
return compiledSplitHydrateTags.get(ast);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isCompiledSplitHydrateBoundary(boundary) {
|
|
30
|
+
if (boundary.disabled || boundary.independent || boundary.permanentStatic) return false;
|
|
31
|
+
const opening = boundary.node.openingElement;
|
|
32
|
+
if (opening.name?.type !== 'JSXIdentifier') return false;
|
|
33
|
+
if (
|
|
34
|
+
(opening.attributes ?? []).some((attribute) => {
|
|
35
|
+
const name = attribute.name?.name;
|
|
36
|
+
return (
|
|
37
|
+
attribute.type !== 'JSXAttribute' ||
|
|
38
|
+
attribute.name?.type !== 'JSXIdentifier' ||
|
|
39
|
+
typeof name !== 'string' ||
|
|
40
|
+
name === 'children' ||
|
|
41
|
+
name === 'fallback' ||
|
|
42
|
+
name.startsWith('__')
|
|
43
|
+
);
|
|
44
|
+
})
|
|
45
|
+
)
|
|
46
|
+
return false;
|
|
47
|
+
return !(boundary.node.children ?? []).some((child) => {
|
|
48
|
+
const expression = unwrapExpression(
|
|
49
|
+
child.type === 'JSXExpressionContainer' ? child.expression : null,
|
|
50
|
+
);
|
|
51
|
+
return ['ArrowFunctionExpression', 'FunctionExpression'].includes(expression?.type);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Generated captures preserve a proven private Context identity, not an authored
|
|
56
|
+
// escape. Certificates belong only to this prepared AST and exact parser tags.
|
|
57
|
+
const privateSplitContexts = new WeakMap();
|
|
58
|
+
export function privateCompiledContextsForHydrateAst(ast) {
|
|
59
|
+
return privateSplitContexts.get(ast);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function privateContextCaptureBoundary(boundary) {
|
|
63
|
+
if (boundary.disabled || boundary.independent || boundary.permanentStatic) return false;
|
|
64
|
+
const opening = boundary.node.openingElement;
|
|
65
|
+
if (opening?.name?.type !== 'JSXIdentifier') return false;
|
|
66
|
+
return (opening.attributes ?? []).every((attribute) => {
|
|
67
|
+
const name = attribute.name?.name;
|
|
68
|
+
return (
|
|
69
|
+
attribute.type === 'JSXAttribute' &&
|
|
70
|
+
attribute.name?.type === 'JSXIdentifier' &&
|
|
71
|
+
typeof name === 'string' &&
|
|
72
|
+
name !== 'children' &&
|
|
73
|
+
!name.startsWith('__')
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function originalSplitContextProofs(analysis, moduleMovePlan) {
|
|
79
|
+
const contexts = findPrivateCompiledContextProofs(analysis.ast);
|
|
80
|
+
if (contexts.size === 0) return contexts;
|
|
81
|
+
const captured = new Set();
|
|
82
|
+
for (const boundary of analysis.boundaries) {
|
|
83
|
+
if (boundary.disabled || hasPermanentStaticAncestor(boundary)) continue;
|
|
84
|
+
const moduleBindings = moduleMovePlan.bindingsByPath.get(boundary.path) ?? new Set();
|
|
85
|
+
const names = collectCaptures(
|
|
86
|
+
boundary.node.children,
|
|
87
|
+
analysis.imports.importBindings,
|
|
88
|
+
boundary.shadowedImports,
|
|
89
|
+
moduleBindings.size === 0 ? null : moduleBindings,
|
|
90
|
+
);
|
|
91
|
+
for (const name of names) {
|
|
92
|
+
if (!contexts.has(name)) continue;
|
|
93
|
+
if (!privateContextCaptureBoundary(boundary)) contexts.delete(name);
|
|
94
|
+
else captured.add(name);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return new Map([...contexts].filter(([name]) => captured.has(name)));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function certifySplitContexts(ast, contexts, captureBindings) {
|
|
101
|
+
if (contexts.size === 0) return;
|
|
102
|
+
const byId = new Map(),
|
|
103
|
+
byTag = new Map(),
|
|
104
|
+
present = new WeakSet();
|
|
105
|
+
for (const [name, context] of contexts) {
|
|
106
|
+
byId.set(context.id, context.callee);
|
|
107
|
+
for (const tag of context.providerTags) byTag.set(tag, captureBindings.get(name) ?? context.id);
|
|
108
|
+
}
|
|
109
|
+
const callees = new Set(),
|
|
110
|
+
providers = new Map();
|
|
111
|
+
mapAstCow(ast, (node) => {
|
|
112
|
+
present.add(node);
|
|
113
|
+
if (byId.has(node)) callees.add(byId.get(node));
|
|
114
|
+
if (byTag.has(node)) providers.set(node, byTag.get(node));
|
|
115
|
+
return undefined;
|
|
116
|
+
});
|
|
117
|
+
for (const [tag, binding] of providers) if (!present.has(binding)) providers.delete(tag);
|
|
118
|
+
if (callees.size > 0 || providers.size > 0) privateSplitContexts.set(ast, { callees, providers });
|
|
119
|
+
}
|
|
120
|
+
|
|
19
121
|
const SKIP_KEYS = new Set(['type', 'loc', 'start', 'end', 'range', 'metadata', 'parent']);
|
|
20
122
|
const TRANSPARENT_TS_EXPRESSIONS = new Set([
|
|
21
123
|
'ParenthesizedExpression',
|
|
@@ -1755,6 +1857,7 @@ function extractedModuleAst(
|
|
|
1755
1857
|
request,
|
|
1756
1858
|
moduleBindingsByPath = new Map(),
|
|
1757
1859
|
moduleDeclarationsByPath = new Map(),
|
|
1860
|
+
captureBindings = new Map(),
|
|
1758
1861
|
) {
|
|
1759
1862
|
if (boundary.disabled) {
|
|
1760
1863
|
throw extractionError(
|
|
@@ -1803,6 +1906,11 @@ function extractedModuleAst(
|
|
|
1803
1906
|
boundary.node,
|
|
1804
1907
|
),
|
|
1805
1908
|
];
|
|
1909
|
+
if (setup.length > 0) {
|
|
1910
|
+
const pattern = setup[0].declarations[0].id;
|
|
1911
|
+
for (let index = 0; index < captures.length; index++)
|
|
1912
|
+
captureBindings.set(captures[index], pattern.elements[index]);
|
|
1913
|
+
}
|
|
1806
1914
|
const codeBlock = inheritGeneratedOrigin(
|
|
1807
1915
|
{
|
|
1808
1916
|
type: 'JSXCodeBlock',
|
|
@@ -2139,6 +2247,8 @@ export function prepareHydrateBoundaries(source, filename, boundaryPath = null,
|
|
|
2139
2247
|
}
|
|
2140
2248
|
const request = sameSourceRequest(filename);
|
|
2141
2249
|
const moduleMovePlan = createModuleMovePlanAst(analysis, request);
|
|
2250
|
+
const contexts = originalSplitContextProofs(analysis, moduleMovePlan);
|
|
2251
|
+
const captureBindings = new Map();
|
|
2142
2252
|
const independentWidgets = independentWidgetMetadata(analysis, filename, moduleMovePlan);
|
|
2143
2253
|
let independentIndex = 0;
|
|
2144
2254
|
for (const boundary of analysis.boundaries) {
|
|
@@ -2174,8 +2284,16 @@ export function prepareHydrateBoundaries(source, filename, boundaryPath = null,
|
|
|
2174
2284
|
request,
|
|
2175
2285
|
moduleMovePlan.bindingsByPath,
|
|
2176
2286
|
moduleMovePlan.declarationsByPath,
|
|
2287
|
+
captureBindings,
|
|
2177
2288
|
);
|
|
2178
2289
|
}
|
|
2290
|
+
const templateTags = new Set(
|
|
2291
|
+
analysis.boundaries
|
|
2292
|
+
.filter(isCompiledSplitHydrateBoundary)
|
|
2293
|
+
.map((boundary) => boundary.node.openingElement.name),
|
|
2294
|
+
);
|
|
2295
|
+
if (templateTags.size > 0) compiledSplitHydrateTags.set(ast, templateTags);
|
|
2296
|
+
certifySplitContexts(ast, contexts, captureBindings);
|
|
2179
2297
|
return {
|
|
2180
2298
|
ast,
|
|
2181
2299
|
boundaryPath,
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createLexicalAnalysis,
|
|
3
|
+
forEachRuntimeAstChild,
|
|
4
|
+
isIdentifierReference,
|
|
5
|
+
} from './compile-universal.js';
|
|
6
|
+
|
|
7
|
+
/** Only private contexts whose complete authored lifetime stays in template bodies. */
|
|
8
|
+
export function findPrivateCompiledContexts(ast) {
|
|
9
|
+
return new Map(
|
|
10
|
+
[...findPrivateCompiledContextProofs(ast)].map(([name, context]) => [name, context.callee]),
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Definition and exact provider tags accepted by the same closed-module proof. */
|
|
15
|
+
export function findPrivateCompiledContextProofs(ast) {
|
|
16
|
+
const imports = new Map();
|
|
17
|
+
for (const statement of ast.body ?? []) {
|
|
18
|
+
if (
|
|
19
|
+
statement.type !== 'ImportDeclaration' ||
|
|
20
|
+
statement.source?.value !== 'octane' ||
|
|
21
|
+
statement.importKind === 'type'
|
|
22
|
+
)
|
|
23
|
+
continue;
|
|
24
|
+
for (const specifier of statement.specifiers ?? []) {
|
|
25
|
+
const name = specifier.imported?.name ?? specifier.imported?.value;
|
|
26
|
+
if (
|
|
27
|
+
specifier.type === 'ImportSpecifier' &&
|
|
28
|
+
specifier.importKind !== 'type' &&
|
|
29
|
+
['createContext', 'use', 'useContext'].includes(name)
|
|
30
|
+
)
|
|
31
|
+
imports.set(specifier.local.name, name);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (![...imports.values()].includes('createContext')) return new Map();
|
|
35
|
+
if (
|
|
36
|
+
!(ast.body ?? []).some(
|
|
37
|
+
(statement) =>
|
|
38
|
+
statement.type === 'VariableDeclaration' &&
|
|
39
|
+
statement.kind === 'const' &&
|
|
40
|
+
statement.declarations.some(
|
|
41
|
+
(entry) => imports.get(entry.init?.callee?.name) === 'createContext',
|
|
42
|
+
),
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
return new Map();
|
|
46
|
+
const lexical = createLexicalAnalysis(ast);
|
|
47
|
+
const moduleBinding = (node) =>
|
|
48
|
+
node != null &&
|
|
49
|
+
lexical.resolveBinding(lexical.nodeScopes.get(node), node.name)?.scope === lexical.rootScope;
|
|
50
|
+
const contexts = new Map();
|
|
51
|
+
for (const statement of ast.body ?? []) {
|
|
52
|
+
if (statement.type !== 'VariableDeclaration' || statement.kind !== 'const') continue;
|
|
53
|
+
for (const entry of statement.declarations ?? []) {
|
|
54
|
+
const call = entry.init;
|
|
55
|
+
if (
|
|
56
|
+
entry.id?.type === 'Identifier' &&
|
|
57
|
+
call?.type === 'CallExpression' &&
|
|
58
|
+
call.optional !== true &&
|
|
59
|
+
call.callee?.type === 'Identifier' &&
|
|
60
|
+
imports.get(call.callee.name) === 'createContext' &&
|
|
61
|
+
moduleBinding(call.callee) &&
|
|
62
|
+
call.arguments.length === 1 &&
|
|
63
|
+
call.arguments[0].type !== 'SpreadElement'
|
|
64
|
+
)
|
|
65
|
+
contexts.set(entry.id.name, {
|
|
66
|
+
id: entry.id,
|
|
67
|
+
callee: call.callee,
|
|
68
|
+
providerTags: new Set(),
|
|
69
|
+
valid: true,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (contexts.size === 0) return new Map();
|
|
74
|
+
let opaque = false;
|
|
75
|
+
const providerChildren = (element) =>
|
|
76
|
+
!(element.openingElement.attributes ?? []).some(
|
|
77
|
+
(attribute) =>
|
|
78
|
+
attribute.type === 'JSXSpreadAttribute' ||
|
|
79
|
+
attribute.name?.type !== 'JSXIdentifier' ||
|
|
80
|
+
attribute.name.name === 'children' ||
|
|
81
|
+
attribute.name.name.startsWith('__'),
|
|
82
|
+
) &&
|
|
83
|
+
!(element.children ?? []).some((child) => {
|
|
84
|
+
let expression = child.type === 'JSXExpressionContainer' ? child.expression : null;
|
|
85
|
+
while (
|
|
86
|
+
[
|
|
87
|
+
'TSAsExpression',
|
|
88
|
+
'TSTypeAssertion',
|
|
89
|
+
'TSSatisfiesExpression',
|
|
90
|
+
'TSNonNullExpression',
|
|
91
|
+
'TSInstantiationExpression',
|
|
92
|
+
'ParenthesizedExpression',
|
|
93
|
+
].includes(expression?.type)
|
|
94
|
+
)
|
|
95
|
+
expression = expression.expression;
|
|
96
|
+
return ['ArrowFunctionExpression', 'FunctionExpression'].includes(expression?.type);
|
|
97
|
+
});
|
|
98
|
+
const inspect = (node, parent, key, grandparent, direct = false) => {
|
|
99
|
+
if (node === null || typeof node !== 'object') return;
|
|
100
|
+
const identifier = node.type === 'Identifier';
|
|
101
|
+
const reference = identifier && isIdentifierReference(node, parent, key, lexical);
|
|
102
|
+
if (
|
|
103
|
+
node.type === 'WithStatement' ||
|
|
104
|
+
(reference && node.name === 'eval') ||
|
|
105
|
+
((node.type === 'CallExpression' || node.type === 'OptionalCallExpression') &&
|
|
106
|
+
['MemberExpression', 'OptionalMemberExpression'].includes(node.callee?.type) &&
|
|
107
|
+
(node.callee.property?.name ?? node.callee.property?.value) === 'eval')
|
|
108
|
+
)
|
|
109
|
+
opaque = true;
|
|
110
|
+
const context = contexts.get(node.name);
|
|
111
|
+
if (context !== undefined) {
|
|
112
|
+
// A module-wide name is used at emitted component calls. Any shadow
|
|
113
|
+
// declines that name rather than guessing a generated body's scope.
|
|
114
|
+
if (lexical.bindingNodes.has(node) && node !== context.id) context.valid = false;
|
|
115
|
+
const tag =
|
|
116
|
+
node.type === 'JSXIdentifier' &&
|
|
117
|
+
(((parent?.type === 'JSXOpeningElement' || parent?.type === 'JSXClosingElement') &&
|
|
118
|
+
key === 'name') ||
|
|
119
|
+
(parent?.type === 'JSXMemberExpression' && key === 'object'));
|
|
120
|
+
const exported = parent?.type === 'ExportSpecifier' && key === 'local';
|
|
121
|
+
if (reference || tag || exported) {
|
|
122
|
+
if (lexical.nodeScopes.get(node) === undefined) context.valid = false;
|
|
123
|
+
else if (moduleBinding(node)) {
|
|
124
|
+
const read =
|
|
125
|
+
reference &&
|
|
126
|
+
parent?.type === 'CallExpression' &&
|
|
127
|
+
parent.optional !== true &&
|
|
128
|
+
parent.arguments[0] === node &&
|
|
129
|
+
parent.arguments.length === 1 &&
|
|
130
|
+
parent.callee?.type === 'Identifier' &&
|
|
131
|
+
['use', 'useContext'].includes(imports.get(parent.callee.name)) &&
|
|
132
|
+
moduleBinding(parent.callee);
|
|
133
|
+
const provider =
|
|
134
|
+
tag &&
|
|
135
|
+
direct &&
|
|
136
|
+
(parent?.type === 'JSXOpeningElement' || parent?.type === 'JSXClosingElement') &&
|
|
137
|
+
grandparent?.type === 'JSXElement' &&
|
|
138
|
+
providerChildren(grandparent);
|
|
139
|
+
if (!read && !provider) context.valid = false;
|
|
140
|
+
if (provider) context.providerTags.add(node);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
forEachRuntimeAstChild(node, (child, childKey) => {
|
|
145
|
+
const childDirect =
|
|
146
|
+
node.type === 'JSXCodeBlock'
|
|
147
|
+
? childKey === 'render'
|
|
148
|
+
: direct &&
|
|
149
|
+
((node.type === 'JSXElement' &&
|
|
150
|
+
['children', 'openingElement', 'closingElement'].includes(childKey)) ||
|
|
151
|
+
(node.type === 'JSXFragment' && childKey === 'children') ||
|
|
152
|
+
((node.type === 'JSXOpeningElement' || node.type === 'JSXClosingElement') &&
|
|
153
|
+
childKey === 'name'));
|
|
154
|
+
inspect(child, node, childKey, parent, childDirect);
|
|
155
|
+
});
|
|
156
|
+
};
|
|
157
|
+
inspect(ast);
|
|
158
|
+
if (opaque) return new Map();
|
|
159
|
+
return new Map([...contexts].filter(([, context]) => context.valid));
|
|
160
|
+
}
|
package/dist/constants.d.ts
CHANGED
|
@@ -10,16 +10,13 @@
|
|
|
10
10
|
*
|
|
11
11
|
* The server emit (runtime.server `ssrBlock` and friends) writes these markers
|
|
12
12
|
* around every dynamic site, and the client `hydrateRoot` cursor scans for them
|
|
13
|
-
* to align with the server output. This module is the
|
|
13
|
+
* to align with the server output. This module is the public home both import;
|
|
14
|
+
* the marker payloads and boundary attribute names themselves are declared in
|
|
15
|
+
* `hydration-markers.js`, which stays off the `dom-tables.js` graph so the
|
|
16
|
+
* pre-root capture bundle can share them, and are re-exported here unchanged.
|
|
14
17
|
*/
|
|
15
|
-
|
|
16
|
-
export
|
|
17
|
-
/** Single-character payload of a block-close comment. */
|
|
18
|
-
export declare const HYDRATION_END = "]";
|
|
19
|
-
/** @for outer-open payload: the server rendered its @empty arm. */
|
|
20
|
-
export declare const HYDRATION_FOR_EMPTY = "[f0";
|
|
21
|
-
/** @for outer-open payload: the server rendered one or more direct-host items. */
|
|
22
|
-
export declare const HYDRATION_FOR_ITEMS = "[f1";
|
|
18
|
+
import { HYDRATION_START, HYDRATION_END, HYDRATION_FOR_EMPTY, HYDRATION_FOR_ITEMS } from './hydration-markers.js';
|
|
19
|
+
export { HYDRATION_START, HYDRATION_END, HYDRATION_FOR_EMPTY, HYDRATION_FOR_ITEMS };
|
|
23
20
|
/** Opens a hydratable block (component output / control-flow branch). */
|
|
24
21
|
export declare const BLOCK_OPEN = "<!--[-->";
|
|
25
22
|
/** Closes a hydratable block. */
|
|
@@ -87,17 +84,9 @@ export declare const HYDRATION_RANGE_BOUNDARY: unique symbol;
|
|
|
87
84
|
export declare const HYDRATE_STATIC_ID_COUNT_PREFIX = "octane-static-hydrate:";
|
|
88
85
|
/** Closing comment for a wrapper-free permanent-static range. */
|
|
89
86
|
export declare const HYDRATE_STATIC_END = "/octane-static-hydrate";
|
|
90
|
-
/** Stable id of a server-rendered deferred hydration boundary. */
|
|
91
|
-
export declare const HYDRATE_ID_ATTR = "data-octane-hydrate-id";
|
|
92
|
-
/** Serialized strategy kind (`visible`, `idle`, `dynamic`, …). */
|
|
93
|
-
export declare const HYDRATE_WHEN_ATTR = "data-octane-hydrate-when";
|
|
94
|
-
/** Number of `useId()` slots consumed while rendering the deferred child. */
|
|
95
|
-
export declare const HYDRATE_ID_COUNT_ATTR = "data-octane-hydrate-id-count";
|
|
96
87
|
/** Opaque renderer stream token authenticating pending descendants owned by this boundary. */
|
|
97
88
|
export { HYDRATE_STREAM_TOKEN_ATTR } from './stream-protocol.js';
|
|
98
|
-
|
|
99
|
-
export declare const HYDRATE_SEED_ATTR = "data-octane-hydrate-seed";
|
|
100
|
-
export { INDEPENDENT_HYDRATE_MANIFEST_ATTR, HYDRATE_INDEPENDENT_ATTR, HYDRATE_INPUT_ATTR, SIGNAL_CONTROL_ATTR, } from './hydration-markers.js';
|
|
89
|
+
export { INDEPENDENT_HYDRATE_MANIFEST_ATTR, HYDRATE_INDEPENDENT_ATTR, HYDRATE_INPUT_ATTR, SIGNAL_CONTROL_ATTR, HYDRATE_ID_ATTR, HYDRATE_WHEN_ATTR, HYDRATE_ID_COUNT_ATTR, HYDRATE_SEED_ATTR, } from './hydration-markers.js';
|
|
101
90
|
/** Sentinel <template> attribute marking a pending streamed boundary. */
|
|
102
91
|
export { STREAM_BOUNDARY_ATTR } from './stream-protocol.js';
|
|
103
92
|
/** Hidden segment container attribute carrying a completed boundary's content. */
|
package/dist/constants.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import {
|
|
2
|
+
HYDRATION_START,
|
|
3
|
+
HYDRATION_END,
|
|
4
|
+
HYDRATION_FOR_EMPTY,
|
|
5
|
+
HYDRATION_FOR_ITEMS
|
|
6
|
+
} from "./hydration-markers.js";
|
|
5
7
|
const BLOCK_OPEN = `<!--${HYDRATION_START}-->`;
|
|
6
8
|
const BLOCK_CLOSE = `<!--${HYDRATION_END}-->`;
|
|
7
9
|
const FOR_BLOCK_OPEN_EMPTY = `<!--${HYDRATION_FOR_EMPTY}-->`;
|
|
@@ -23,16 +25,16 @@ const HYDRATION_RANGE_BOUNDARY = /* @__PURE__ */ Symbol.for(
|
|
|
23
25
|
);
|
|
24
26
|
const HYDRATE_STATIC_ID_COUNT_PREFIX = "octane-static-hydrate:";
|
|
25
27
|
const HYDRATE_STATIC_END = "/octane-static-hydrate";
|
|
26
|
-
const HYDRATE_ID_ATTR = "data-octane-hydrate-id";
|
|
27
|
-
const HYDRATE_WHEN_ATTR = "data-octane-hydrate-when";
|
|
28
|
-
const HYDRATE_ID_COUNT_ATTR = "data-octane-hydrate-id-count";
|
|
29
28
|
import { HYDRATE_STREAM_TOKEN_ATTR } from "./stream-protocol.js";
|
|
30
|
-
const HYDRATE_SEED_ATTR = "data-octane-hydrate-seed";
|
|
31
29
|
import {
|
|
32
30
|
INDEPENDENT_HYDRATE_MANIFEST_ATTR,
|
|
33
31
|
HYDRATE_INDEPENDENT_ATTR,
|
|
34
32
|
HYDRATE_INPUT_ATTR,
|
|
35
|
-
SIGNAL_CONTROL_ATTR
|
|
33
|
+
SIGNAL_CONTROL_ATTR,
|
|
34
|
+
HYDRATE_ID_ATTR,
|
|
35
|
+
HYDRATE_WHEN_ATTR,
|
|
36
|
+
HYDRATE_ID_COUNT_ATTR,
|
|
37
|
+
HYDRATE_SEED_ATTR
|
|
36
38
|
} from "./hydration-markers.js";
|
|
37
39
|
import { STREAM_BOUNDARY_ATTR } from "./stream-protocol.js";
|
|
38
40
|
const STREAM_SEGMENT_ATTR = "data-oct-s";
|
package/dist/css.d.ts
CHANGED
|
@@ -12,3 +12,10 @@ export declare function styleName(name: string): string;
|
|
|
12
12
|
export declare function devWarnStyleProperty(name: string, value: unknown, server: boolean): void;
|
|
13
13
|
/** @internal Development-only guidance after an existing CSS coercion fails. */
|
|
14
14
|
export declare function devWarnStyleCoercion(name: string, value: unknown): void;
|
|
15
|
+
/**
|
|
16
|
+
* The one stylesheet element-scoped `<ViewTransition>` needs. The client
|
|
17
|
+
* dedupes the injection by this id and the SSR serializer emits the same pair,
|
|
18
|
+
* so spelling either half twice ships the rule twice or drops it on one side.
|
|
19
|
+
*/
|
|
20
|
+
export declare const VIEW_TRANSITION_SCOPE_STYLE_ID = "octane-view-transition-scope";
|
|
21
|
+
export declare const VIEW_TRANSITION_SCOPE_CSS = "[vt-scope=\"element\"]{view-transition-scope:all!important}";
|
package/dist/css.js
CHANGED
|
@@ -67,7 +67,11 @@ function devWarnStyleCoercion(name, value) {
|
|
|
67
67
|
`The provided \`${name}\` CSS property is an unsupported type ${valueType}. This value must be coerced to a string before using it here.`
|
|
68
68
|
);
|
|
69
69
|
}
|
|
70
|
+
const VIEW_TRANSITION_SCOPE_STYLE_ID = "octane-view-transition-scope";
|
|
71
|
+
const VIEW_TRANSITION_SCOPE_CSS = '[vt-scope="element"]{view-transition-scope:all!important}';
|
|
70
72
|
export {
|
|
73
|
+
VIEW_TRANSITION_SCOPE_CSS,
|
|
74
|
+
VIEW_TRANSITION_SCOPE_STYLE_ID,
|
|
71
75
|
devWarnStyleCoercion,
|
|
72
76
|
devWarnStyleProperty,
|
|
73
77
|
mergeClass,
|
|
@@ -5,7 +5,13 @@ import {
|
|
|
5
5
|
__releaseBinding,
|
|
6
6
|
__writeBinding
|
|
7
7
|
} from "./dom-bindings.js";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
bindingRootMarker,
|
|
10
|
+
BINDING_OPEN_PREFIX,
|
|
11
|
+
encodeBindingKey,
|
|
12
|
+
parseBindingMarker
|
|
13
|
+
} from "./dom-binding-protocol.js";
|
|
14
|
+
import { HYDRATION_FOR_PREFIX } from "./hydration-markers.js";
|
|
9
15
|
import { moveNativeNodeBefore } from "./dom-focused-move.js";
|
|
10
16
|
import { rendererRangeClose } from "./stream-protocol.js";
|
|
11
17
|
import {
|
|
@@ -527,7 +533,7 @@ function createFragment(definition, id, document, transaction) {
|
|
|
527
533
|
const template = document.createElement("template");
|
|
528
534
|
template.innerHTML = definition.ns === 1 ? `<svg>${definition.html}</svg>` : definition.html;
|
|
529
535
|
const content = document.createDocumentFragment();
|
|
530
|
-
const start = document.createComment(
|
|
536
|
+
const start = document.createComment(bindingRootMarker(id));
|
|
531
537
|
const end = document.createComment("]");
|
|
532
538
|
content.appendChild(start);
|
|
533
539
|
if (definition.ns === 1) {
|
|
@@ -875,7 +881,8 @@ function commitRegion(plan, id, transaction) {
|
|
|
875
881
|
transaction
|
|
876
882
|
);
|
|
877
883
|
}
|
|
878
|
-
if (definition.kind === "if")
|
|
884
|
+
if (definition.kind === "if")
|
|
885
|
+
region.range.start.data = `${BINDING_OPEN_PREFIX}${id};${region.site};${plan.arm}`;
|
|
879
886
|
}
|
|
880
887
|
function writeText(region, value) {
|
|
881
888
|
if (region.text === null) {
|
|
@@ -1469,13 +1476,13 @@ function commitList(plan, id, transaction) {
|
|
|
1469
1476
|
}
|
|
1470
1477
|
let anchor = region.range.end;
|
|
1471
1478
|
for (const [key, item] of [...plan.items].reverse()) {
|
|
1472
|
-
item.instance.range.start.data =
|
|
1479
|
+
item.instance.range.start.data = `${BINDING_OPEN_PREFIX}${id};${region.site};k;${key}`;
|
|
1473
1480
|
moveRange(item.instance.range, region.range.end.parentNode, anchor, transaction);
|
|
1474
1481
|
if (transaction.disposed) return;
|
|
1475
1482
|
anchor = item.instance.range.start;
|
|
1476
1483
|
}
|
|
1477
1484
|
}
|
|
1478
|
-
region.range.start.data =
|
|
1485
|
+
region.range.start.data = `${HYDRATION_FOR_PREFIX}${plan.arm};b;${id};${region.site}`;
|
|
1479
1486
|
return;
|
|
1480
1487
|
}
|
|
1481
1488
|
function claimProgramControl(instance, index, node, transaction) {
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
/** Compiler-owned DOM presentation markers. This leaf is shared by client and SSR. */
|
|
2
1
|
export type BindingKey = string | number;
|
|
2
|
+
/** Payload prefix opening a presentation-binding range. */
|
|
3
|
+
export declare const BINDING_OPEN_PREFIX: string;
|
|
4
|
+
/**
|
|
5
|
+
* Comment payload opening an authored presentation view's own component range.
|
|
6
|
+
* The client runtime and the SSR serializer both stamp this onto the view they
|
|
7
|
+
* bind, so the two must never spell it separately.
|
|
8
|
+
*/
|
|
9
|
+
export declare function bindingRootMarker(id: string): string;
|
|
3
10
|
export declare function encodeBindingKey(key: BindingKey): string;
|
|
4
11
|
export declare function decodeBindingKey(encoded: string): BindingKey;
|
|
5
12
|
export interface BindingMarker {
|
|
@@ -11,3 +18,9 @@ export interface BindingMarker {
|
|
|
11
18
|
}
|
|
12
19
|
export declare function parseBindingMarker(data: string): BindingMarker | null;
|
|
13
20
|
export declare function isBindingOpenComment(data: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* True when a binding-open payload carries an `@for` outer arm. The prefix test
|
|
23
|
+
* alone is not a decision: `isBindingOpenComment` still requires the full
|
|
24
|
+
* receipt, so this only spares callers from spelling the arm prefixes again.
|
|
25
|
+
*/
|
|
26
|
+
export declare function isForBindingOpenComment(data: string): boolean;
|
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HYDRATION_FOR_ARM_INDEX,
|
|
3
|
+
HYDRATION_FOR_EMPTY,
|
|
4
|
+
HYDRATION_FOR_ITEMS,
|
|
5
|
+
HYDRATION_FOR_PREFIX,
|
|
6
|
+
HYDRATION_START
|
|
7
|
+
} from "./hydration-markers.js";
|
|
8
|
+
const BINDING_OPEN_PREFIX = HYDRATION_START + "b;";
|
|
9
|
+
const BINDING_RECEIPT = ";b;";
|
|
10
|
+
const BINDING_FOR_EMPTY_PREFIX = HYDRATION_FOR_EMPTY + BINDING_RECEIPT;
|
|
11
|
+
const BINDING_FOR_ITEMS_PREFIX = HYDRATION_FOR_ITEMS + BINDING_RECEIPT;
|
|
12
|
+
const BINDING_FOR_ID_START = BINDING_FOR_EMPTY_PREFIX.length;
|
|
13
|
+
const BINDING_ID_START = BINDING_OPEN_PREFIX.length;
|
|
14
|
+
const BINDING_ROOT_SITE = "root";
|
|
15
|
+
const BINDING_ROOT_SUFFIX = ";" + BINDING_ROOT_SITE;
|
|
16
|
+
function bindingRootMarker(id) {
|
|
17
|
+
return BINDING_OPEN_PREFIX + id + BINDING_ROOT_SUFFIX;
|
|
18
|
+
}
|
|
1
19
|
function encodeBindingKey(key) {
|
|
2
20
|
if (typeof key === "string")
|
|
3
21
|
return "s:" + JSON.stringify(key).replace(/-/g, "\\u002d").replace(/</g, "\\u003c");
|
|
@@ -17,21 +35,22 @@ function decodeBindingKey(encoded) {
|
|
|
17
35
|
throw new TypeError("A DOM presentation key has invalid serialized identity.");
|
|
18
36
|
}
|
|
19
37
|
function parseBindingMarker(data) {
|
|
20
|
-
if (data.startsWith(
|
|
21
|
-
const separator = data.indexOf(";",
|
|
22
|
-
if (separator <=
|
|
38
|
+
if (data.startsWith(BINDING_FOR_EMPTY_PREFIX) || data.startsWith(BINDING_FOR_ITEMS_PREFIX)) {
|
|
39
|
+
const separator = data.indexOf(";", BINDING_FOR_ID_START);
|
|
40
|
+
if (separator <= BINDING_FOR_ID_START || separator === data.length - 1) return null;
|
|
23
41
|
return {
|
|
24
|
-
id: data.slice(
|
|
42
|
+
id: data.slice(BINDING_FOR_ID_START, separator),
|
|
25
43
|
site: data.slice(separator + 1),
|
|
26
44
|
kind: "for",
|
|
27
|
-
arm: data.charCodeAt(
|
|
45
|
+
arm: data.charCodeAt(HYDRATION_FOR_ARM_INDEX) - 48
|
|
28
46
|
};
|
|
29
47
|
}
|
|
30
|
-
if (!data.startsWith(
|
|
31
|
-
const idEnd = data.indexOf(";",
|
|
32
|
-
if (idEnd <=
|
|
33
|
-
const id = data.slice(
|
|
34
|
-
if (data.slice(idEnd + 1) ===
|
|
48
|
+
if (!data.startsWith(BINDING_OPEN_PREFIX)) return null;
|
|
49
|
+
const idEnd = data.indexOf(";", BINDING_ID_START);
|
|
50
|
+
if (idEnd <= BINDING_ID_START || idEnd === data.length - 1) return null;
|
|
51
|
+
const id = data.slice(BINDING_ID_START, idEnd);
|
|
52
|
+
if (data.slice(idEnd + 1) === BINDING_ROOT_SITE)
|
|
53
|
+
return { id, site: BINDING_ROOT_SITE, kind: "root" };
|
|
35
54
|
const siteEnd = data.indexOf(";", idEnd + 1);
|
|
36
55
|
if (siteEnd <= idEnd + 1 || siteEnd === data.length - 1) return null;
|
|
37
56
|
const site = data.slice(idEnd + 1, siteEnd);
|
|
@@ -56,13 +75,14 @@ function parseBindingMarker(data) {
|
|
|
56
75
|
return null;
|
|
57
76
|
}
|
|
58
77
|
function isBindingOpenComment(data) {
|
|
59
|
-
const list = data.startsWith(
|
|
60
|
-
if (!list && !data.startsWith(
|
|
61
|
-
const idStart = list ?
|
|
78
|
+
const list = data.startsWith(BINDING_FOR_EMPTY_PREFIX) || data.startsWith(BINDING_FOR_ITEMS_PREFIX);
|
|
79
|
+
if (!list && !data.startsWith(BINDING_OPEN_PREFIX)) return false;
|
|
80
|
+
const idStart = list ? BINDING_FOR_ID_START : BINDING_ID_START;
|
|
62
81
|
const idEnd = data.indexOf(";", idStart);
|
|
63
82
|
if (idEnd <= idStart || idEnd === data.length - 1) return false;
|
|
64
83
|
if (list) return data.indexOf(";", idEnd + 1) === -1;
|
|
65
|
-
if (data.length - idEnd ===
|
|
84
|
+
if (data.length - idEnd === BINDING_ROOT_SUFFIX.length && data.endsWith(BINDING_ROOT_SUFFIX))
|
|
85
|
+
return true;
|
|
66
86
|
const siteEnd = data.indexOf(";", idEnd + 1);
|
|
67
87
|
if (siteEnd <= idEnd + 1 || siteEnd === data.length - 1) return false;
|
|
68
88
|
const tail = siteEnd + 1;
|
|
@@ -80,9 +100,15 @@ function isBindingOpenComment(data) {
|
|
|
80
100
|
}
|
|
81
101
|
return true;
|
|
82
102
|
}
|
|
103
|
+
function isForBindingOpenComment(data) {
|
|
104
|
+
return data.startsWith(HYDRATION_FOR_PREFIX) && isBindingOpenComment(data);
|
|
105
|
+
}
|
|
83
106
|
export {
|
|
107
|
+
BINDING_OPEN_PREFIX,
|
|
108
|
+
bindingRootMarker,
|
|
84
109
|
decodeBindingKey,
|
|
85
110
|
encodeBindingKey,
|
|
86
111
|
isBindingOpenComment,
|
|
112
|
+
isForBindingOpenComment,
|
|
87
113
|
parseBindingMarker
|
|
88
114
|
};
|
|
@@ -45,6 +45,8 @@ export interface HydrationControlCandidateValue {
|
|
|
45
45
|
* the state changed while ownership was being transferred.
|
|
46
46
|
*/
|
|
47
47
|
export declare function snapshotHydrationControl(control: Element): HydrationControlSnapshot | null;
|
|
48
|
+
/** @internal Only unchanged server state may yield to an eventless textarea restore. */
|
|
49
|
+
export declare function isRestoredHydrationTextarea(control: Element, value: unknown): boolean;
|
|
48
50
|
/**
|
|
49
51
|
* Capture the exact DOM/edit authority an async storage read is allowed to replace.
|
|
50
52
|
* A focus, selection, composition, input (including clear), node replacement, or
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
HYDRATE_ID_ATTR,
|
|
3
|
+
HYDRATE_INDEPENDENT_ATTR,
|
|
4
|
+
HYDRATE_INPUT_ATTR,
|
|
5
|
+
HYDRATE_MARKER_SELECTOR,
|
|
6
|
+
HYDRATE_WHEN_ATTR
|
|
7
|
+
} from "../hydration-markers.js";
|
|
2
8
|
import {
|
|
3
9
|
EARLY_HYDRATION_INTENTS_KEY,
|
|
4
10
|
HYDRATE_INTERACTION_EVENTS_ATTR
|
|
@@ -9,10 +15,9 @@ import {
|
|
|
9
15
|
publishHydrationControlSignalValues,
|
|
10
16
|
readEarlyHydrationControlRevision
|
|
11
17
|
} from "../signals/early-values.js";
|
|
12
|
-
const HYDRATE_MARKER_SELECTOR = "[data-octane-hydrate-id]";
|
|
13
18
|
const HYDRATE_CONTROL_DOCUMENTS = /* @__PURE__ */ new WeakSet();
|
|
14
19
|
function isEarlyHydrationIntentCurrent([event, target, boundary, id, when, events], ownerDocument) {
|
|
15
|
-
return event.target === target && target.isConnected && boundary.isConnected && target.ownerDocument === ownerDocument && boundary.ownerDocument === ownerDocument && target.closest(`[${HYDRATE_INDEPENDENT_ATTR}]`) === boundary && boundary.getAttribute(
|
|
20
|
+
return event.target === target && target.isConnected && boundary.isConnected && target.ownerDocument === ownerDocument && boundary.ownerDocument === ownerDocument && target.closest(`[${HYDRATE_INDEPENDENT_ATTR}]`) === boundary && boundary.getAttribute(HYDRATE_ID_ATTR) === id && boundary.getAttribute(HYDRATE_WHEN_ATTR) === when && boundary.getAttribute(HYDRATE_INTERACTION_EVENTS_ATTR) === events;
|
|
16
21
|
}
|
|
17
22
|
function isHydrationElement(target) {
|
|
18
23
|
return target !== null && target.nodeType === 1;
|
|
@@ -108,6 +113,13 @@ function snapshotHydrationControl(control) {
|
|
|
108
113
|
composing: record?.composing ?? false
|
|
109
114
|
};
|
|
110
115
|
}
|
|
116
|
+
function isRestoredHydrationTextarea(control, value) {
|
|
117
|
+
if (control.localName !== "textarea" || typeof value !== "string" || HYDRATE_CONTROL_RECORDS.get(control)?.composing === true)
|
|
118
|
+
return false;
|
|
119
|
+
const textarea = control;
|
|
120
|
+
const baseline = textarea.defaultValue.replace(/\r\n?/g, "\n");
|
|
121
|
+
return value.replace(/\r\n?/g, "\n") === baseline && textarea.value !== baseline;
|
|
122
|
+
}
|
|
111
123
|
function captureHydrationControlCandidate(control) {
|
|
112
124
|
const snapshot = snapshotHydrationControl(control);
|
|
113
125
|
if (snapshot === null) return null;
|
|
@@ -115,7 +127,7 @@ function captureHydrationControlCandidate(control) {
|
|
|
115
127
|
return {
|
|
116
128
|
control,
|
|
117
129
|
bindingId: control.getAttribute(HYDRATE_INPUT_ATTR),
|
|
118
|
-
boundaryId: control.closest(HYDRATE_MARKER_SELECTOR)?.getAttribute(
|
|
130
|
+
boundaryId: control.closest(HYDRATE_MARKER_SELECTOR)?.getAttribute(HYDRATE_ID_ATTR) ?? null,
|
|
119
131
|
snapshot
|
|
120
132
|
};
|
|
121
133
|
}
|
|
@@ -130,7 +142,7 @@ function sameHydrationControlValue(left, right) {
|
|
|
130
142
|
}
|
|
131
143
|
function applyHydrationControlCandidate(candidate, value) {
|
|
132
144
|
const control = candidate.control;
|
|
133
|
-
if (!control.isConnected || control.getAttribute(HYDRATE_INPUT_ATTR) !== candidate.bindingId || (control.closest(HYDRATE_MARKER_SELECTOR)?.getAttribute(
|
|
145
|
+
if (!control.isConnected || control.getAttribute(HYDRATE_INPUT_ATTR) !== candidate.bindingId || (control.closest(HYDRATE_MARKER_SELECTOR)?.getAttribute(HYDRATE_ID_ATTR) ?? null) !== candidate.boundaryId) {
|
|
134
146
|
return false;
|
|
135
147
|
}
|
|
136
148
|
const current = snapshotHydrationControl(control);
|
|
@@ -219,5 +231,6 @@ export {
|
|
|
219
231
|
consumeHydrationControl,
|
|
220
232
|
initializeHydrationControlCapture,
|
|
221
233
|
isEarlyHydrationIntentCurrent,
|
|
234
|
+
isRestoredHydrationTextarea,
|
|
222
235
|
snapshotHydrationControl
|
|
223
236
|
};
|