octane 0.1.31 → 0.1.32
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/compiler/compile-universal.js +290 -22
- package/dist/compiler/compile.js +67 -22
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -0
- package/dist/runtime.d.ts +7 -0
- package/dist/runtime.js +250 -34
- package/dist/runtime.server.d.ts +1 -1
- package/dist/runtime.server.js +11 -1
- package/dist/universal-core.d.ts +40 -1
- package/dist/universal-core.js +313 -89
- package/dist/version.js +1 -2
- package/package.json +1 -1
|
@@ -1811,6 +1811,62 @@ function collectComponentNames(ast) {
|
|
|
1811
1811
|
return names;
|
|
1812
1812
|
}
|
|
1813
1813
|
|
|
1814
|
+
function collectOwnerFreeThreeHostComponents(ast, state, development) {
|
|
1815
|
+
if (
|
|
1816
|
+
development ||
|
|
1817
|
+
state.hmr ||
|
|
1818
|
+
state.profile ||
|
|
1819
|
+
state.renderer.id !== 'three' ||
|
|
1820
|
+
state.renderer.module !== '@octanejs/three/renderer'
|
|
1821
|
+
) {
|
|
1822
|
+
return null;
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
const constructors = new Set();
|
|
1826
|
+
for (const statement of ast.body ?? []) {
|
|
1827
|
+
if (
|
|
1828
|
+
statement.type !== 'ImportDeclaration' ||
|
|
1829
|
+
statement.importKind === 'type' ||
|
|
1830
|
+
statement.source?.value !== '@octanejs/three'
|
|
1831
|
+
) {
|
|
1832
|
+
continue;
|
|
1833
|
+
}
|
|
1834
|
+
for (const specifier of statement.specifiers ?? []) {
|
|
1835
|
+
if (
|
|
1836
|
+
specifier.type === 'ImportSpecifier' &&
|
|
1837
|
+
specifier.importKind !== 'type' &&
|
|
1838
|
+
(specifier.imported?.name ?? specifier.imported?.value) === 'extend' &&
|
|
1839
|
+
typeof specifier.local?.name === 'string'
|
|
1840
|
+
) {
|
|
1841
|
+
constructors.add(specifier.local.name);
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
if (constructors.size === 0) return null;
|
|
1846
|
+
|
|
1847
|
+
const names = new Set();
|
|
1848
|
+
for (const statement of ast.body ?? []) {
|
|
1849
|
+
const declaration =
|
|
1850
|
+
statement.type === 'ExportNamedDeclaration' ? statement.declaration : statement;
|
|
1851
|
+
if (declaration?.type !== 'VariableDeclaration' || declaration.kind !== 'const') continue;
|
|
1852
|
+
for (const binding of declaration.declarations ?? []) {
|
|
1853
|
+
const call = binding.init;
|
|
1854
|
+
if (
|
|
1855
|
+
binding.id?.type === 'Identifier' &&
|
|
1856
|
+
call?.type === 'CallExpression' &&
|
|
1857
|
+
call.optional !== true &&
|
|
1858
|
+
call.callee?.type === 'Identifier' &&
|
|
1859
|
+
constructors.has(call.callee.name) &&
|
|
1860
|
+
call.arguments?.length === 1 &&
|
|
1861
|
+
call.arguments[0]?.type !== 'SpreadElement'
|
|
1862
|
+
) {
|
|
1863
|
+
names.add(binding.id.name);
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
return names.size === 0 ? null : { names, lexical: createLexicalAnalysis(ast) };
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1814
1870
|
function addPatternNames(pattern, names) {
|
|
1815
1871
|
if (!pattern) return;
|
|
1816
1872
|
if (pattern.type === 'Identifier') {
|
|
@@ -1966,32 +2022,52 @@ function isOwnerFreeForAttribute(attribute) {
|
|
|
1966
2022
|
);
|
|
1967
2023
|
}
|
|
1968
2024
|
|
|
1969
|
-
function
|
|
2025
|
+
function ownerFreeForLeaf(node) {
|
|
1970
2026
|
if (node.empty != null) return null;
|
|
1971
2027
|
if (!isOwnerFreeForExpression(node.right) || !isOwnerFreeForExpression(node.key)) return null;
|
|
1972
2028
|
const body = (node.body?.body ?? []).filter(
|
|
1973
2029
|
(statement) => statement.type !== 'JSXText' || normalizeJsxText(statement.value ?? '') !== '',
|
|
1974
2030
|
);
|
|
1975
2031
|
if (body.length !== 1) return null;
|
|
1976
|
-
const
|
|
2032
|
+
const leaf = body[0];
|
|
2033
|
+
if (leaf.type !== 'JSXElement' && leaf.type !== 'Element') return null;
|
|
1977
2034
|
if (
|
|
1978
|
-
(
|
|
1979
|
-
isComponentElement(host) ||
|
|
1980
|
-
jsxName(host) === 'Activity'
|
|
1981
|
-
) {
|
|
1982
|
-
return null;
|
|
1983
|
-
}
|
|
1984
|
-
const type = jsxName(host);
|
|
1985
|
-
if (type === null || !/^[a-z]/.test(type)) return null;
|
|
1986
|
-
if (
|
|
1987
|
-
(host.children ?? []).some(
|
|
2035
|
+
(leaf.children ?? []).some(
|
|
1988
2036
|
(child) => child.type !== 'JSXText' || normalizeJsxText(child.value ?? '') !== '',
|
|
1989
2037
|
)
|
|
1990
2038
|
) {
|
|
1991
2039
|
return null;
|
|
1992
2040
|
}
|
|
1993
|
-
const attributes =
|
|
1994
|
-
return attributes.every(isOwnerFreeForAttribute) ?
|
|
2041
|
+
const attributes = leaf.openingElement?.attributes ?? leaf.attributes ?? [];
|
|
2042
|
+
return attributes.every(isOwnerFreeForAttribute) ? leaf : null;
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
function ownerFreeForHost(node) {
|
|
2046
|
+
const host = ownerFreeForLeaf(node);
|
|
2047
|
+
if (host === null || isComponentElement(host) || jsxName(host) === 'Activity') return null;
|
|
2048
|
+
const type = jsxName(host);
|
|
2049
|
+
return type !== null && /^[a-z]/.test(type) ? host : null;
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
function ownerFreeForThreeHostComponent(node, state) {
|
|
2053
|
+
const trusted = state.ownerFreeThreeHostComponents;
|
|
2054
|
+
if (trusted == null) return null;
|
|
2055
|
+
const component = ownerFreeForLeaf(node);
|
|
2056
|
+
if (component === null || !isComponentElement(component)) return null;
|
|
2057
|
+
const attributes = component.openingElement?.attributes ?? component.attributes ?? [];
|
|
2058
|
+
const attributesSeen = new Set();
|
|
2059
|
+
for (const attribute of attributes) {
|
|
2060
|
+
const attributeKey = attributeName(attribute);
|
|
2061
|
+
if (attributeKey === '__proto__' || attributesSeen.has(attributeKey)) return null;
|
|
2062
|
+
attributesSeen.add(attributeKey);
|
|
2063
|
+
}
|
|
2064
|
+
const name = component.openingElement?.name ?? component.name;
|
|
2065
|
+
if (name?.type !== 'JSXIdentifier' || !trusted.names.has(name.name)) return null;
|
|
2066
|
+
const binding = trusted.lexical.resolveBinding(
|
|
2067
|
+
trusted.lexical.nodeScopes.get(name) ?? trusted.lexical.rootScope,
|
|
2068
|
+
name.name,
|
|
2069
|
+
);
|
|
2070
|
+
return binding?.scope === trusted.lexical.rootScope ? component : null;
|
|
1995
2071
|
}
|
|
1996
2072
|
|
|
1997
2073
|
function allocPlan(state, root, origin = null) {
|
|
@@ -2407,6 +2483,85 @@ function compilePlainPropsObjectAst(attributes, state, origin) {
|
|
|
2407
2483
|
return inheritGeneratedOrigin(b.object(entries), origin);
|
|
2408
2484
|
}
|
|
2409
2485
|
|
|
2486
|
+
/**
|
|
2487
|
+
* Lower a `class={[…]}` array literal to its clsx-composed string when every
|
|
2488
|
+
* element is statically a string or falsy.
|
|
2489
|
+
*
|
|
2490
|
+
* The runtime composes class arrays clsx-style, so `['row', on && 'danger']`
|
|
2491
|
+
* is only ever observed as `'row'` or `'row danger'` — but as a slot value the
|
|
2492
|
+
* array is rebuilt on every render, and on a transported root each rebuild is
|
|
2493
|
+
* re-encoded, making the hottest per-row prop a fresh allocation per render.
|
|
2494
|
+
* Emitting the string-building expression instead makes the slot value a
|
|
2495
|
+
* primitive: identity-comparable, allocation-free, and byte-identical in the
|
|
2496
|
+
* background and main-thread programs (so first-screen adoption still sees
|
|
2497
|
+
* the same tree). An all-literal array folds further, into a static plan prop
|
|
2498
|
+
* with no slot at all. Anything not statically string-or-falsy (spreads,
|
|
2499
|
+
* nested arrays, objects, expressions with non-literal truthy arms) keeps the
|
|
2500
|
+
* authored array slot and the runtime's general composition.
|
|
2501
|
+
*
|
|
2502
|
+
* Returns `{ staticValue }` for a fully static class, `{ expression }` for a
|
|
2503
|
+
* string-building lowering, or `null` to keep the authored value.
|
|
2504
|
+
*/
|
|
2505
|
+
function loweredHostClassAst(expression, state) {
|
|
2506
|
+
if (expression.type !== 'ArrayExpression') return null;
|
|
2507
|
+
const elements = expression.elements ?? [];
|
|
2508
|
+
if (elements.length === 0) return { staticValue: '' };
|
|
2509
|
+
/** @type {({ static: string } | { test: any, value: string })[]} */
|
|
2510
|
+
const parts = [];
|
|
2511
|
+
for (const element of elements) {
|
|
2512
|
+
if (element == null || element.type === 'SpreadElement') return null;
|
|
2513
|
+
if (element.type === 'Literal' && typeof element.value === 'string') {
|
|
2514
|
+
if (element.value !== '') parts.push({ static: element.value });
|
|
2515
|
+
continue;
|
|
2516
|
+
}
|
|
2517
|
+
if (
|
|
2518
|
+
element.type === 'LogicalExpression' &&
|
|
2519
|
+
element.operator === '&&' &&
|
|
2520
|
+
element.right.type === 'Literal' &&
|
|
2521
|
+
typeof element.right.value === 'string' &&
|
|
2522
|
+
element.right.value !== ''
|
|
2523
|
+
) {
|
|
2524
|
+
parts.push({ test: element.left, value: element.right.value });
|
|
2525
|
+
continue;
|
|
2526
|
+
}
|
|
2527
|
+
return null;
|
|
2528
|
+
}
|
|
2529
|
+
// The first part must be a literal so every later piece can join with an
|
|
2530
|
+
// unconditional leading space.
|
|
2531
|
+
if (parts.length !== 0 && parts[0].static === undefined) return null;
|
|
2532
|
+
let composed = null;
|
|
2533
|
+
let pendingStatic = '';
|
|
2534
|
+
const flushStatic = () => {
|
|
2535
|
+
if (pendingStatic === '') return;
|
|
2536
|
+
const literal = b.literal(pendingStatic, JSON.stringify(pendingStatic));
|
|
2537
|
+
composed = composed === null ? literal : b.binary('+', composed, literal);
|
|
2538
|
+
pendingStatic = '';
|
|
2539
|
+
};
|
|
2540
|
+
for (const part of parts) {
|
|
2541
|
+
if (part.static !== undefined) {
|
|
2542
|
+
pendingStatic += pendingStatic === '' && composed === null ? part.static : ` ${part.static}`;
|
|
2543
|
+
continue;
|
|
2544
|
+
}
|
|
2545
|
+
flushStatic();
|
|
2546
|
+
const spaced = ` ${part.value}`;
|
|
2547
|
+
composed = b.binary(
|
|
2548
|
+
'+',
|
|
2549
|
+
composed,
|
|
2550
|
+
inheritGeneratedOrigin(
|
|
2551
|
+
b.conditional(
|
|
2552
|
+
dynamicExpressionAst(part.test, state),
|
|
2553
|
+
b.literal(spaced, JSON.stringify(spaced)),
|
|
2554
|
+
b.literal('', '""'),
|
|
2555
|
+
),
|
|
2556
|
+
part.test,
|
|
2557
|
+
),
|
|
2558
|
+
);
|
|
2559
|
+
}
|
|
2560
|
+
if (composed === null) return { staticValue: pendingStatic };
|
|
2561
|
+
flushStatic();
|
|
2562
|
+
return { expression: inheritGeneratedOrigin(composed, expression) };
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2410
2565
|
function compileAttributeAst(attribute, context, state, canonicalizeHostClass) {
|
|
2411
2566
|
if (attribute.type === 'JSXSpreadAttribute' || attribute.type === 'SpreadAttribute') {
|
|
2412
2567
|
throw universalError(
|
|
@@ -2428,6 +2583,18 @@ function compileAttributeAst(attribute, context, state, canonicalizeHostClass) {
|
|
|
2428
2583
|
if (value.type === 'Literal') return { name, staticValue: value.value };
|
|
2429
2584
|
if (value.type === 'JSXExpressionContainer') {
|
|
2430
2585
|
if (!value.expression || value.expression.type === 'JSXEmptyExpression') return null;
|
|
2586
|
+
if (name === 'class') {
|
|
2587
|
+
const lowered = loweredHostClassAst(value.expression, state);
|
|
2588
|
+
if (lowered !== null) {
|
|
2589
|
+
if (lowered.expression === undefined) return { name, staticValue: lowered.staticValue };
|
|
2590
|
+
const slot = context.values.length;
|
|
2591
|
+
context.values.push(lowered.expression);
|
|
2592
|
+
return { name, slot };
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
if (value.expression.type === 'Literal' && isStaticPropLiteral(value.expression.value)) {
|
|
2596
|
+
return { name, staticValue: value.expression.value };
|
|
2597
|
+
}
|
|
2431
2598
|
const slot = context.values.length;
|
|
2432
2599
|
context.values.push(mainThreadHostValueAst(name, value.expression, state));
|
|
2433
2600
|
return { name, slot };
|
|
@@ -2435,6 +2602,15 @@ function compileAttributeAst(attribute, context, state, canonicalizeHostClass) {
|
|
|
2435
2602
|
throw universalError(state.filename, attribute, `unsupported value for host attribute ${name}.`);
|
|
2436
2603
|
}
|
|
2437
2604
|
|
|
2605
|
+
/**
|
|
2606
|
+
* Literal values that can ride the frozen plan instead of a per-render slot.
|
|
2607
|
+
* `null` stays a slot: it is a legal "no handler" value for event props, whose
|
|
2608
|
+
* erasure in main-thread first-screen programs happens at the slot site.
|
|
2609
|
+
*/
|
|
2610
|
+
function isStaticPropLiteral(value) {
|
|
2611
|
+
return typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean';
|
|
2612
|
+
}
|
|
2613
|
+
|
|
2438
2614
|
function addDynamicAst(context, expression) {
|
|
2439
2615
|
const slot = context.values.length;
|
|
2440
2616
|
context.values.push(expression);
|
|
@@ -2710,6 +2886,55 @@ function rewriteSetupStatementAst(statement, state) {
|
|
|
2710
2886
|
return rewritten === null ? [] : [rewritten];
|
|
2711
2887
|
}
|
|
2712
2888
|
|
|
2889
|
+
function compileOwnerFreeForHostAst(host, state, itemBinding, indexBinding) {
|
|
2890
|
+
const context = { values: [] };
|
|
2891
|
+
const plan = allocPlan(state, compileHostElementAst(host, context, state), host);
|
|
2892
|
+
return {
|
|
2893
|
+
plan: generatedIdentifier(plan, host),
|
|
2894
|
+
render: generatedArrow(
|
|
2895
|
+
[itemBinding, indexBinding],
|
|
2896
|
+
inheritGeneratedOrigin(b.array(context.values), host),
|
|
2897
|
+
host,
|
|
2898
|
+
),
|
|
2899
|
+
};
|
|
2900
|
+
}
|
|
2901
|
+
|
|
2902
|
+
function compileOwnerFreeThreeHostComponentAst(component, state, itemBinding, indexBinding) {
|
|
2903
|
+
const attributes = component.openingElement?.attributes ?? component.attributes ?? [];
|
|
2904
|
+
const names = [];
|
|
2905
|
+
const values = [];
|
|
2906
|
+
for (const attribute of attributes) {
|
|
2907
|
+
const name = attributeName(attribute);
|
|
2908
|
+
const value = attribute.value;
|
|
2909
|
+
const expression =
|
|
2910
|
+
value === null
|
|
2911
|
+
? inheritGeneratedOrigin(b.literal(true), attribute)
|
|
2912
|
+
: value.type === 'Literal'
|
|
2913
|
+
? inheritGeneratedOrigin(b.literal(value.value), value)
|
|
2914
|
+
: mainThreadHostValueAst(name, value.expression, state);
|
|
2915
|
+
names.push(name);
|
|
2916
|
+
values.push(expression);
|
|
2917
|
+
}
|
|
2918
|
+
const helper = (state.helpers.hostComponentLeafPlan ??= allocName(
|
|
2919
|
+
state,
|
|
2920
|
+
'__octaneUniversalHostComponentLeafPlan',
|
|
2921
|
+
));
|
|
2922
|
+
const signature = JSON.stringify(names);
|
|
2923
|
+
return {
|
|
2924
|
+
plan: generatedCall(
|
|
2925
|
+
helper,
|
|
2926
|
+
[b.literal(state.renderer.id), jsxNameExpressionAst(component, state), b.literal(signature)],
|
|
2927
|
+
component,
|
|
2928
|
+
),
|
|
2929
|
+
render: generatedArrow(
|
|
2930
|
+
[itemBinding, indexBinding],
|
|
2931
|
+
inheritGeneratedOrigin(b.array(values), component),
|
|
2932
|
+
component,
|
|
2933
|
+
),
|
|
2934
|
+
signature: inheritGeneratedOrigin(b.literal(signature), component),
|
|
2935
|
+
};
|
|
2936
|
+
}
|
|
2937
|
+
|
|
2713
2938
|
function compileForAst(node, context, state) {
|
|
2714
2939
|
if (node.await) {
|
|
2715
2940
|
throw universalError(
|
|
@@ -2730,18 +2955,43 @@ function compileForAst(node, context, state) {
|
|
|
2730
2955
|
node.index ?? generatedIdentifier(allocName(state, '__octaneUniversalIndex'), node);
|
|
2731
2956
|
assertNoResidualTemplate(node.right, state, '@for source');
|
|
2732
2957
|
assertNoResidualTemplate(node.key, state, '@for key');
|
|
2958
|
+
const host = !state.hmr ? ownerFreeForHost(node) : null;
|
|
2959
|
+
const component = host === null ? ownerFreeForThreeHostComponent(node, state) : null;
|
|
2960
|
+
const compactHost =
|
|
2961
|
+
host === null ? null : compileOwnerFreeForHostAst(host, state, itemBinding, indexBinding);
|
|
2962
|
+
const compactComponent =
|
|
2963
|
+
component === null
|
|
2964
|
+
? null
|
|
2965
|
+
: compileOwnerFreeThreeHostComponentAst(component, state, itemBinding, indexBinding);
|
|
2733
2966
|
const args = [
|
|
2734
2967
|
rewriteSourceAst(node.right, state),
|
|
2735
2968
|
generatedArrow([itemBinding, indexBinding], rewriteSourceAst(node.key, state), node.key),
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2969
|
+
compactHost?.render ??
|
|
2970
|
+
compactComponent?.render ??
|
|
2971
|
+
compileBlockValueAst(
|
|
2972
|
+
node.body?.body ?? [],
|
|
2973
|
+
state,
|
|
2974
|
+
[itemBinding, indexBinding],
|
|
2975
|
+
node.body ?? node,
|
|
2976
|
+
),
|
|
2742
2977
|
];
|
|
2743
|
-
if (
|
|
2744
|
-
args.push(
|
|
2978
|
+
if (host !== null) {
|
|
2979
|
+
args.push(
|
|
2980
|
+
b.literal(null, 'null'),
|
|
2981
|
+
b.literal(true),
|
|
2982
|
+
b.literal(true),
|
|
2983
|
+
inheritGeneratedOrigin(b.unary('void', b.literal(0)), host),
|
|
2984
|
+
compactHost.plan,
|
|
2985
|
+
);
|
|
2986
|
+
} else if (component !== null) {
|
|
2987
|
+
args.push(
|
|
2988
|
+
b.literal(null, 'null'),
|
|
2989
|
+
b.literal(true),
|
|
2990
|
+
b.literal(true),
|
|
2991
|
+
jsxNameExpressionAst(component, state),
|
|
2992
|
+
compactComponent.plan,
|
|
2993
|
+
compactComponent.signature,
|
|
2994
|
+
);
|
|
2745
2995
|
} else if (node.empty) {
|
|
2746
2996
|
args.push(compileBlockValueAst(node.empty?.body ?? [], state, [], node.empty));
|
|
2747
2997
|
}
|
|
@@ -2828,6 +3078,16 @@ function compileChildAst(node, context, state) {
|
|
|
2828
3078
|
}
|
|
2829
3079
|
if (node.type === 'JSXExpressionContainer') {
|
|
2830
3080
|
if (!node.expression || node.expression.type === 'JSXEmptyExpression') return [];
|
|
3081
|
+
// A string-literal child is authored text with braces around it: fold it
|
|
3082
|
+
// into the plan like JSXText, so the constant stops riding every render's
|
|
3083
|
+
// slot array. Renderers without host text keep the renderable-hole slot.
|
|
3084
|
+
if (
|
|
3085
|
+
node.expression.type === 'Literal' &&
|
|
3086
|
+
typeof node.expression.value === 'string' &&
|
|
3087
|
+
state.renderer.text === 'host'
|
|
3088
|
+
) {
|
|
3089
|
+
return [withPlanOrigin({ kind: 'text', value: node.expression.value }, node)];
|
|
3090
|
+
}
|
|
2831
3091
|
return [addDynamicAst(context, dynamicExpressionAst(node.expression, state))];
|
|
2832
3092
|
}
|
|
2833
3093
|
if (node.type === 'JSXElement' || node.type === 'Element') {
|
|
@@ -3048,6 +3308,9 @@ function universalHelperImportAst(state, extraPairs = [], origin = null) {
|
|
|
3048
3308
|
['universalPlan', state.helpers.plan],
|
|
3049
3309
|
['universalValue', state.helpers.value],
|
|
3050
3310
|
['universalComponent', state.helpers.nestedComponent],
|
|
3311
|
+
...(state.helpers.hostComponentLeafPlan === undefined
|
|
3312
|
+
? []
|
|
3313
|
+
: [['universalHostComponentLeafPlan', state.helpers.hostComponentLeafPlan]]),
|
|
3051
3314
|
['universalProps', state.helpers.props],
|
|
3052
3315
|
['universalIf', state.helpers.if],
|
|
3053
3316
|
['universalSwitch', state.helpers.switch],
|
|
@@ -3696,6 +3959,11 @@ export function compileUniversal(
|
|
|
3696
3959
|
componentNames: collectComponentNames(ast),
|
|
3697
3960
|
runtimeImports: new Map(),
|
|
3698
3961
|
};
|
|
3962
|
+
state.ownerFreeThreeHostComponents = collectOwnerFreeThreeHostComponents(
|
|
3963
|
+
ast,
|
|
3964
|
+
state,
|
|
3965
|
+
options.dev === true,
|
|
3966
|
+
);
|
|
3699
3967
|
state.helpers.component = allocName(state, '__octaneDefineUniversalComponent');
|
|
3700
3968
|
state.helpers.plan = allocName(state, '__octaneUniversalPlan');
|
|
3701
3969
|
state.helpers.value = allocName(state, '__octaneUniversalValue');
|
package/dist/compiler/compile.js
CHANGED
|
@@ -1309,10 +1309,10 @@ function annotatePureLazyCalls(ast) {
|
|
|
1309
1309
|
}
|
|
1310
1310
|
|
|
1311
1311
|
/**
|
|
1312
|
-
* Lower the exact imported `<ErrorBoundary>` builtin to
|
|
1313
|
-
*
|
|
1314
|
-
*
|
|
1315
|
-
*
|
|
1312
|
+
* Lower the exact imported `<ErrorBoundary>` builtin to catch-only boundary IR
|
|
1313
|
+
* when its fallback is statically compilable. Client output uses errorBlock;
|
|
1314
|
+
* server output keeps the existing ssrTry wire contract. Dynamic props,
|
|
1315
|
+
* spreads, keys, and shadowed imports stay on the generic runtime path.
|
|
1316
1316
|
*/
|
|
1317
1317
|
function lowerImportedErrorBoundaries(ast) {
|
|
1318
1318
|
const boundaryLocals = new Set();
|
|
@@ -7515,7 +7515,11 @@ function compileInternal(source, filename, options, analyzedAst, mode, bundlerMe
|
|
|
7515
7515
|
// `const el = <App/>`) to createElement(...) before printing — esrap
|
|
7516
7516
|
// can't print raw JSX, and this is what makes root.render(<App/>) match
|
|
7517
7517
|
// React's shape.
|
|
7518
|
-
const lowered =
|
|
7518
|
+
const lowered = markSingleRootMemoInitializers(
|
|
7519
|
+
stampAnonymousDefaultFunctionLoc(rewriteModuleJsxValues(hooked, ctx), ctx),
|
|
7520
|
+
ctx,
|
|
7521
|
+
memoImportNames,
|
|
7522
|
+
);
|
|
7519
7523
|
// Top-level passthrough (imports, plain consts/functions): already a
|
|
7520
7524
|
// rewritten statement node — embedded directly in the module AST.
|
|
7521
7525
|
bodyNodes.push(lowered);
|
|
@@ -10942,6 +10946,43 @@ function singleRootInitializer(ctx, component) {
|
|
|
10942
10946
|
return markPure(b.call('_$__s', component));
|
|
10943
10947
|
}
|
|
10944
10948
|
|
|
10949
|
+
// An exact public memo wrapper preserves the already-proven host output of its
|
|
10950
|
+
// immutable local component. Stamp only the fresh compiler-owned wrapper:
|
|
10951
|
+
// probing arbitrary component metadata would invoke observable getters, and
|
|
10952
|
+
// dev/HMR, custom comparators, imported components, and renderer units remain
|
|
10953
|
+
// deliberately opaque.
|
|
10954
|
+
function markSingleRootMemoInitializers(node, ctx, memoImportNames) {
|
|
10955
|
+
if (ctx.hmr || ctx.dev || ctx.profile || memoImportNames.size === 0) return node;
|
|
10956
|
+
const exported = node.type === 'ExportNamedDeclaration';
|
|
10957
|
+
const declaration = exported ? node.declaration : node;
|
|
10958
|
+
if (declaration?.type !== 'VariableDeclaration' || declaration.kind !== 'const') return node;
|
|
10959
|
+
let changed = false;
|
|
10960
|
+
const declarations = declaration.declarations.map((item) => {
|
|
10961
|
+
const init = item.init;
|
|
10962
|
+
const wrapped = init?.arguments?.[0];
|
|
10963
|
+
if (
|
|
10964
|
+
item.id?.type !== 'Identifier' ||
|
|
10965
|
+
!ctx.defaultMemoBindings.has(item.id.name) ||
|
|
10966
|
+
init?.type !== 'CallExpression' ||
|
|
10967
|
+
init.callee?.type !== 'Identifier' ||
|
|
10968
|
+
!memoImportNames.has(init.callee.name) ||
|
|
10969
|
+
init.arguments.length !== 1 ||
|
|
10970
|
+
wrapped?.type !== 'Identifier' ||
|
|
10971
|
+
!ctx.moduleFunctionDeclarations.has(wrapped.name) ||
|
|
10972
|
+
ctx.componentInfo.get(wrapped.name)?.singleRoot !== true ||
|
|
10973
|
+
ctx._universalRuntimeUnitsByBinding.has(item.id.name) ||
|
|
10974
|
+
ctx._universalRuntimeUnitsByBinding.has(wrapped.name)
|
|
10975
|
+
) {
|
|
10976
|
+
return item;
|
|
10977
|
+
}
|
|
10978
|
+
changed = true;
|
|
10979
|
+
return { ...item, init: inheritOriginLoc(singleRootInitializer(ctx, init), init) };
|
|
10980
|
+
});
|
|
10981
|
+
if (!changed) return node;
|
|
10982
|
+
const next = { ...declaration, declarations };
|
|
10983
|
+
return exported ? { ...node, declaration: next } : next;
|
|
10984
|
+
}
|
|
10985
|
+
|
|
10945
10986
|
function finalizeComponentInitializers(ctx, bodyNodes) {
|
|
10946
10987
|
if (!ctx.componentEffectOwnership || ctx.componentOwners.length === 0) return;
|
|
10947
10988
|
|
|
@@ -18807,8 +18848,11 @@ function planJsx(
|
|
|
18807
18848
|
for (const tc of tryCalls) {
|
|
18808
18849
|
const slotIndex = tc.slotIndex;
|
|
18809
18850
|
const org = tc.origin ?? planOrigin;
|
|
18810
|
-
|
|
18811
|
-
|
|
18851
|
+
const catchOnly =
|
|
18852
|
+
tc.propagateSuspense && tc.pendingHelper === 'null' && tc.catchHelper !== 'null';
|
|
18853
|
+
const boundaryHelper = catchOnly ? 'errorBlock' : 'tryBlock';
|
|
18854
|
+
ctx.runtimeNeeded.add(boundaryHelper);
|
|
18855
|
+
registerDirectiveOrigin(ctx, org, [rtAlias(boundaryHelper), tc.tryHelper]);
|
|
18812
18856
|
registerClauseOrigin(ctx, tc.handlerKeyword, [tc.catchHelper]);
|
|
18813
18857
|
registerClauseOrigin(ctx, tc.pendingKeyword, [tc.pendingHelper]);
|
|
18814
18858
|
// Anchor selection — see anchorNodeFor (mirrors ifBlock, including the
|
|
@@ -18816,24 +18860,25 @@ function planJsx(
|
|
|
18816
18860
|
const tryAnchor = anchorNodeFor(tc, 'tryAnchor');
|
|
18817
18861
|
const tryEnv = envNodeFor(tc);
|
|
18818
18862
|
const trailing = [];
|
|
18819
|
-
if (tryAnchor || tryEnv || tc.propagateSuspense)
|
|
18820
|
-
|
|
18821
|
-
|
|
18863
|
+
if (tryAnchor || tryEnv || (!catchOnly && tc.propagateSuspense)) {
|
|
18864
|
+
trailing.push(tryAnchor || undefinedNode());
|
|
18865
|
+
}
|
|
18866
|
+
if (tryEnv || (!catchOnly && tc.propagateSuspense)) trailing.push(tryEnv || undefinedNode());
|
|
18867
|
+
if (!catchOnly && tc.propagateSuspense) trailing.push(b.literal(true));
|
|
18868
|
+
const boundaryArgs = [
|
|
18869
|
+
b.id('__s'),
|
|
18870
|
+
b.literal(slotIndex),
|
|
18871
|
+
hostNodeFor(`_tryHost$${tc.id}`),
|
|
18872
|
+
helperRefNode(tc.tryHelper),
|
|
18873
|
+
inheritOriginLoc(helperRefNode(tc.catchHelper), tc.handlerKeyword),
|
|
18874
|
+
];
|
|
18875
|
+
if (!catchOnly) {
|
|
18876
|
+
boundaryArgs.push(inheritOriginLoc(helperRefNode(tc.pendingHelper), tc.pendingKeyword));
|
|
18877
|
+
}
|
|
18822
18878
|
pushAfterStmt(
|
|
18823
18879
|
tc.id,
|
|
18824
18880
|
org,
|
|
18825
|
-
b.stmt(
|
|
18826
|
-
b.call(
|
|
18827
|
-
'_$tryBlock',
|
|
18828
|
-
b.id('__s'),
|
|
18829
|
-
b.literal(slotIndex),
|
|
18830
|
-
hostNodeFor(`_tryHost$${tc.id}`),
|
|
18831
|
-
helperRefNode(tc.tryHelper),
|
|
18832
|
-
inheritOriginLoc(helperRefNode(tc.catchHelper), tc.handlerKeyword),
|
|
18833
|
-
inheritOriginLoc(helperRefNode(tc.pendingHelper), tc.pendingKeyword),
|
|
18834
|
-
...trailing,
|
|
18835
|
-
),
|
|
18836
|
-
),
|
|
18881
|
+
b.stmt(b.call(rtAlias(boundaryHelper), ...boundaryArgs, ...trailing)),
|
|
18837
18882
|
);
|
|
18838
18883
|
}
|
|
18839
18884
|
for (const sc of ctx._switchCalls) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { version } from './version.js';
|
|
2
2
|
export { initializeHydrationEventCapture } from './hydration/event-capture.js';
|
|
3
|
-
export { createRoot, hydrateRoot, flushSync, act, type Root, type RootOptions, useState, useLinkedState, type LinkedStatePrevious, type LinkedStateOptions, useReducer, useEffect, useLayoutEffect, useInsertionEffect, useMemo, useCallback, useRef, useId, useImperativeHandle, useEffectEvent, useSyncExternalStore, useDeferredValue, useTransition, useActionState, useFormStatus, useOptimistic, useDebugValue, type FormStatus, startTransition, requestFormReset, memo, lazy, preload, preinit, preconnect, prefetchDNS, createContext, use, useContext, type Context, type ForeignHostContext, Suspense, ErrorBoundary, Hydrate, Activity, ViewTransition, addTransitionType, ViewTransition as unstable_ViewTransition, addTransitionType as unstable_addTransitionType, ViewTransitionPseudoElement, type ViewTransitionProps, type ViewTransitionInstance, Fragment, createPortal, type PortalDescriptor, createElement, cloneElement, isValidElement, isChildrenBlock, Children, type ElementDescriptor, type ComponentBody, type OctaneNode, TsrxErrorBoundary, __useStateWithGetter, __useLinkedStateWithGetter, __useReducerWithGetter, __createVoidRoot, bindRendererRegionOwner, EXTERNAL_HYDRATION_PROMISE, HYDRATION_RANGE_BOUNDARY, createHostContextRequest, __vtSeen, template, clone, drainFrag, bag0, bag1, bag2, bag3, bag4, bag5, bag6, bag7, bag8, bag9, bag10, bag11, bag12, bag13, bag14, bag15, bag16, bagOf, evt0, evt0u, evt1, evt1u, evt2, evt2u, evtN, evtNu, devEventListener, htext, htextSwap, child, sibling, setText, setScriptText, setHTML, setDangerouslySetInnerHTML, setDangerouslySetInnerHTMLSources, markDangerouslySetInnerHTMLChildren, setAttribute, setStringData, setBooleanAttribute, setAriaAttribute, setClassName, setClassAttr, normalizeClass, setStyle, setSpread, snapshotSpread, setHostPropSources, queueNativeChangeDiagnostic, markNativeChangeDiagnosticStatic, setFormAction, setValue, setFormControlSources, setChecked, setCheckedCheckable, setSelectValue, setDefaultValue, setDefaultValueUncontrolled, setDefaultChecked, setAutoFocus, attachRef, queueRefAttach, queueRefDetach, injectStyle, headBlock, namespaceHead, namespaceHeadElement, delegateEvents, delegateCaptureEvents, fastForBlock, fastKeyedForBlock, fastMapSlot, forBlock, keyedForBlock, mapSlot, ifBlock, tryBlock, switchBlock, activityBlock, componentSlot, componentSlotVoid, componentSlotLite, compilerCacheArray, compilerCacheContext, markSingleRoot, markSingleRoot as __s, markChildrenBlock, createScopedValue, createScopedElement, childSlot, positionalChildren, textSlot, textHole, childTextHole, hostComponent, renderBlock, portal, hookSlots, withSlot, useBatch, warmMemo, warmChild, puMiss, puTake0, puTake1, puTake2, puTake3, puTake4, puPub, provideContext, mountFragmentRef, FragmentInstance, hmr, HMR, hasPendingWork, type Scope, type Block, drainPassiveEffects, setIsOctaneActEnvironment, setTransitionFallbackTimeout, getTransitionFallbackTimeout, } from './runtime.js';
|
|
3
|
+
export { createRoot, hydrateRoot, flushSync, act, type Root, type RootOptions, useState, useLinkedState, type LinkedStatePrevious, type LinkedStateOptions, useReducer, useEffect, useLayoutEffect, useInsertionEffect, useMemo, useCallback, useRef, useId, useImperativeHandle, useEffectEvent, useSyncExternalStore, useDeferredValue, useTransition, useActionState, useFormStatus, useOptimistic, useDebugValue, type FormStatus, startTransition, requestFormReset, memo, lazy, preload, preinit, preconnect, prefetchDNS, createContext, use, useContext, type Context, type ForeignHostContext, Suspense, ErrorBoundary, Hydrate, Activity, ViewTransition, addTransitionType, ViewTransition as unstable_ViewTransition, addTransitionType as unstable_addTransitionType, ViewTransitionPseudoElement, type ViewTransitionProps, type ViewTransitionInstance, Fragment, createPortal, type PortalDescriptor, createElement, cloneElement, isValidElement, isChildrenBlock, Children, type ElementDescriptor, type ComponentBody, type OctaneNode, TsrxErrorBoundary, __useStateWithGetter, __useLinkedStateWithGetter, __useReducerWithGetter, __createVoidRoot, bindRendererRegionOwner, EXTERNAL_HYDRATION_PROMISE, HYDRATION_RANGE_BOUNDARY, createHostContextRequest, __vtSeen, template, clone, drainFrag, bag0, bag1, bag2, bag3, bag4, bag5, bag6, bag7, bag8, bag9, bag10, bag11, bag12, bag13, bag14, bag15, bag16, bagOf, evt0, evt0u, evt1, evt1u, evt2, evt2u, evtN, evtNu, devEventListener, htext, htextSwap, child, sibling, setText, setScriptText, setHTML, setDangerouslySetInnerHTML, setDangerouslySetInnerHTMLSources, markDangerouslySetInnerHTMLChildren, setAttribute, setStringData, setBooleanAttribute, setAriaAttribute, setClassName, setClassAttr, normalizeClass, setStyle, setSpread, snapshotSpread, setHostPropSources, queueNativeChangeDiagnostic, markNativeChangeDiagnosticStatic, setFormAction, setValue, setFormControlSources, setChecked, setCheckedCheckable, setSelectValue, setDefaultValue, setDefaultValueUncontrolled, setDefaultChecked, setAutoFocus, attachRef, queueRefAttach, queueRefDetach, injectStyle, headBlock, namespaceHead, namespaceHeadElement, delegateEvents, delegateCaptureEvents, fastForBlock, fastKeyedForBlock, fastMapSlot, forBlock, keyedForBlock, mapSlot, ifBlock, errorBlock, tryBlock, switchBlock, activityBlock, componentSlot, componentSlotVoid, componentSlotLite, compilerCacheArray, compilerCacheContext, markSingleRoot, markSingleRoot as __s, markChildrenBlock, createScopedValue, createScopedElement, childSlot, positionalChildren, textSlot, textHole, childTextHole, hostComponent, renderBlock, portal, hookSlots, withSlot, useBatch, warmMemo, warmChild, puMiss, puTake0, puTake1, puTake2, puTake3, puTake4, puPub, provideContext, mountFragmentRef, FragmentInstance, hmr, HMR, hasPendingWork, type Scope, type Block, drainPassiveEffects, setIsOctaneActEnvironment, setTransitionFallbackTimeout, getTransitionFallbackTimeout, } from './runtime.js';
|
|
4
4
|
export type { HydrateOptions, HydrateProps, HydrateWhen, HydrationInteractionEvent, HydrationInteractionEvents, HydrationPrefetchContext, HydrationPrefetchFunction, HydrationPrefetchStrategy, HydrationPrefetchWaitReason, HydrationStrategy, HydrationWhen, } from './hydration/types.js';
|
|
5
5
|
export { __serverRpc } from './server-rpc-client.js';
|
|
6
6
|
export { __methodDep } from './method-dep.js';
|
package/dist/index.js
CHANGED
|
@@ -140,6 +140,7 @@ import {
|
|
|
140
140
|
keyedForBlock,
|
|
141
141
|
mapSlot,
|
|
142
142
|
ifBlock,
|
|
143
|
+
errorBlock,
|
|
143
144
|
tryBlock,
|
|
144
145
|
switchBlock,
|
|
145
146
|
activityBlock,
|
|
@@ -253,6 +254,7 @@ export {
|
|
|
253
254
|
devEventListener,
|
|
254
255
|
drainFrag,
|
|
255
256
|
drainPassiveEffects,
|
|
257
|
+
errorBlock,
|
|
256
258
|
evt0,
|
|
257
259
|
evt0u,
|
|
258
260
|
evt1,
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1352,6 +1352,13 @@ export declare const HMR: unique symbol;
|
|
|
1352
1352
|
export declare function hmr<P>(fn: ComponentBody<P>): ComponentBody<P>;
|
|
1353
1353
|
export declare function setTransitionFallbackTimeout(ms: number): void;
|
|
1354
1354
|
export declare function getTransitionFallbackTimeout(): number;
|
|
1355
|
+
/**
|
|
1356
|
+
* Exact imported JSX ErrorBoundary lowering. Unlike @try, these boundaries
|
|
1357
|
+
* only catch application errors: suspension belongs to an enclosing Suspense.
|
|
1358
|
+
* Keeping their state independent lets catch-only applications discard the
|
|
1359
|
+
* hidden-primary, transition-hold, and off-screen rendering implementations.
|
|
1360
|
+
*/
|
|
1361
|
+
export declare function errorBlock(parentScope: Scope, slotKey: number, domParent: Node, tryBody: ComponentBody, catchBody: ComponentBody, anchor?: Node | null, env?: any[]): () => void;
|
|
1355
1362
|
export declare function tryBlock(parentScope: Scope, slotKey: number, domParent: Node, tryBody: ComponentBody, catchBody: ComponentBody | null, pendingBody: ComponentBody | null, anchor?: Node | null, env?: any[], propagateSuspense?: boolean): () => void;
|
|
1356
1363
|
export declare function startTransition(fn: () => void | Promise<unknown>): void;
|
|
1357
1364
|
export declare function useTransition(slot?: symbol): [boolean, (fn: () => void | Promise<unknown>) => void];
|