octane 0.2.2 → 0.2.3
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/compatibility.cjs +35 -0
- package/dist/cjs/component-flags.cjs +8 -1
- package/dist/cjs/dom-tables.cjs +2 -0
- package/dist/cjs/error-codes.client.generated.cjs +15 -2
- package/dist/cjs/error-codes.server.generated.cjs +5 -0
- package/dist/cjs/event-names.cjs +39 -0
- package/dist/cjs/index.cjs +13 -0
- package/dist/cjs/runtime.cjs +1223 -332
- package/dist/cjs/runtime.server.cjs +362 -120
- package/dist/cjs/server/index.cjs +22 -0
- package/dist/cjs/version.cjs +1 -1
- package/dist/compatibility.d.ts +7 -0
- package/dist/compatibility.js +10 -0
- package/dist/compiler/bundler.js +13 -30
- package/dist/compiler/compile-renderer-boundaries.js +1 -0
- package/dist/compiler/compile-universal.js +3 -0
- package/dist/compiler/compile.js +619 -99
- package/dist/compiler/hook-deps.js +100 -3
- package/dist/compiler/hook-names.js +1 -0
- package/dist/compiler/manual-hooks.js +171 -0
- package/dist/compiler/plain-hook-memo.js +54 -5
- package/dist/compiler/slot-hooks.d.ts +43 -0
- package/dist/compiler/slot-hooks.js +154 -35
- package/dist/component-flags.d.ts +1 -1
- package/dist/component-flags.js +8 -1
- package/dist/dom-tables.js +2 -0
- package/dist/error-codes.client.generated.d.ts +3 -1
- package/dist/error-codes.client.generated.js +15 -2
- package/dist/error-codes.server.generated.d.ts +1 -0
- package/dist/error-codes.server.generated.js +5 -0
- package/dist/event-names.d.ts +1 -0
- package/dist/event-names.js +15 -0
- package/dist/html-tree-validation.d.ts +17 -34
- package/dist/index.d.ts +3 -1
- package/dist/index.js +12 -1
- package/dist/jsx-runtime.d.ts +6 -1
- package/dist/public-types.d.ts +188 -0
- package/dist/public-types.js +0 -0
- package/dist/runtime.d.ts +109 -66
- package/dist/runtime.js +1220 -332
- package/dist/runtime.server.d.ts +39 -14
- package/dist/runtime.server.js +356 -120
- package/dist/server/index.d.ts +4 -2
- package/dist/server/index.js +19 -0
- package/dist/static/index.d.ts +2 -1
- package/dist/static/index.js +3 -1
- package/dist/universal-core.d.ts +8 -1
- package/dist/universal-core.js +95 -9
- package/dist/version.js +1 -1
- package/package.json +2 -1
|
@@ -24,6 +24,7 @@ const STABLE_TUPLE_RESULTS = new Map([
|
|
|
24
24
|
['useReducer', new Set([1, 2])],
|
|
25
25
|
['useTransition', new Set([1])],
|
|
26
26
|
['useActionState', new Set([1])],
|
|
27
|
+
['useFormState', new Set([1])],
|
|
27
28
|
['useOptimistic', new Set([1])],
|
|
28
29
|
]);
|
|
29
30
|
|
|
@@ -57,6 +58,7 @@ function declareName(scope, name, details = null) {
|
|
|
57
58
|
octaneNamespace: false,
|
|
58
59
|
hookRuntimeImport: null,
|
|
59
60
|
hookRuntimeNamespace: false,
|
|
61
|
+
customHookImport: null,
|
|
60
62
|
};
|
|
61
63
|
scope.bindings.set(name, binding);
|
|
62
64
|
}
|
|
@@ -66,6 +68,7 @@ function declareName(scope, name, details = null) {
|
|
|
66
68
|
if (details?.octaneNamespace) binding.octaneNamespace = true;
|
|
67
69
|
if (details?.hookRuntimeImport) binding.hookRuntimeImport = details.hookRuntimeImport;
|
|
68
70
|
if (details?.hookRuntimeNamespace) binding.hookRuntimeNamespace = true;
|
|
71
|
+
if (details?.customHookImport) binding.customHookImport = details.customHookImport;
|
|
69
72
|
return binding;
|
|
70
73
|
}
|
|
71
74
|
|
|
@@ -133,6 +136,13 @@ function predeclareDirect(statements, scope, hookRuntimeModules, bindingsOnly =
|
|
|
133
136
|
octaneNamespace: isOctane && specifier.type === 'ImportNamespaceSpecifier',
|
|
134
137
|
hookRuntimeImport: isHookRuntime ? imported : null,
|
|
135
138
|
hookRuntimeNamespace: isHookRuntime && specifier.type === 'ImportNamespaceSpecifier',
|
|
139
|
+
customHookImport:
|
|
140
|
+
!isHookRuntime &&
|
|
141
|
+
original.importKind !== 'type' &&
|
|
142
|
+
specifier.importKind !== 'type' &&
|
|
143
|
+
(/^use[A-Z]/.test(imported ?? '') || /^use[A-Z]/.test(specifier.local.name))
|
|
144
|
+
? (imported ?? specifier.local.name)
|
|
145
|
+
: null,
|
|
136
146
|
});
|
|
137
147
|
}
|
|
138
148
|
continue;
|
|
@@ -579,10 +589,20 @@ function buildScopes(ast, onlyImported, hookRuntimeModules, bindingsOnly = false
|
|
|
579
589
|
const name = canonicalHookName(node, scope, onlyImported);
|
|
580
590
|
const config = DEPENDENCY_HOOKS.get(name);
|
|
581
591
|
const hookRuntimeImportedName = canonicalHookName(node, scope, true);
|
|
582
|
-
|
|
592
|
+
const customHookImport =
|
|
593
|
+
node.optional !== true && callee?.type === 'Identifier'
|
|
594
|
+
? resolveBinding(scope, callee.name)?.customHookImport
|
|
595
|
+
: null;
|
|
596
|
+
if (
|
|
597
|
+
octaneImportedName !== null ||
|
|
598
|
+
unboundCallee ||
|
|
599
|
+
hookRuntimeImportedName !== null ||
|
|
600
|
+
customHookImport
|
|
601
|
+
) {
|
|
583
602
|
const props = {};
|
|
584
603
|
if (octaneImportedName !== null) props._octaneImportedHook = octaneImportedName;
|
|
585
604
|
if (unboundCallee) props._octaneUnboundCallee = true;
|
|
605
|
+
if (customHookImport) props._octaneCustomHookCall = customHookImport;
|
|
586
606
|
if (octaneImportedName === null && hookRuntimeImportedName !== null) {
|
|
587
607
|
props._octaneHookRuntimeImportedHook = hookRuntimeImportedName;
|
|
588
608
|
}
|
|
@@ -665,8 +685,9 @@ function buildScopes(ast, onlyImported, hookRuntimeModules, bindingsOnly = false
|
|
|
665
685
|
declarationBindings,
|
|
666
686
|
};
|
|
667
687
|
if (bindingsOnly) return analysis;
|
|
668
|
-
|
|
669
|
-
//
|
|
688
|
+
if (onlyImported) annotateLocalHookAliases(analysis);
|
|
689
|
+
// The plain-TS pass slots base hooks and imported/aliased hook values. Local
|
|
690
|
+
// custom-hook functions still own their authored slot boundaries.
|
|
670
691
|
// Restrict custom-call inference to the full TSRX/TSX compiler, which emits
|
|
671
692
|
// that boundary for every plain-identifier custom hook call.
|
|
672
693
|
const customHooks = onlyImported ? new Map() : discoverCustomDependencyHooks(analysis);
|
|
@@ -687,6 +708,82 @@ function buildScopes(ast, onlyImported, hookRuntimeModules, bindingsOnly = false
|
|
|
687
708
|
return analysis;
|
|
688
709
|
}
|
|
689
710
|
|
|
711
|
+
// An immutable alias may select a base hook directly, or a zero-argument local
|
|
712
|
+
// factory may return that selection (the usual isomorphic-effect pattern).
|
|
713
|
+
// Follow only value aliases, never the body of an ordinary custom hook. This
|
|
714
|
+
// proof needs the completed lexical graph so declaration order and shadowing
|
|
715
|
+
// cannot change a call's identity.
|
|
716
|
+
function annotateLocalHookAliases(analysis) {
|
|
717
|
+
// Ordinary modules with only direct imported hooks need no alias tables.
|
|
718
|
+
if (
|
|
719
|
+
!analysis.calls.some(({ call, scope }) => {
|
|
720
|
+
const binding = directCallBinding(call, scope);
|
|
721
|
+
return binding !== null && !binding.imported;
|
|
722
|
+
})
|
|
723
|
+
)
|
|
724
|
+
return;
|
|
725
|
+
const values = new Map();
|
|
726
|
+
const factories = new Map();
|
|
727
|
+
for (const { decl, bindings, kind } of analysis.declarators) {
|
|
728
|
+
if (kind !== 'const' || decl.id.type !== 'Identifier') continue;
|
|
729
|
+
const binding = bindings[0]?.binding;
|
|
730
|
+
if (binding && !binding.reassigned) values.set(binding, unwrapValue(decl.init));
|
|
731
|
+
}
|
|
732
|
+
if (values.size === 0) return;
|
|
733
|
+
for (const record of analysis.functions) {
|
|
734
|
+
if (record.binding && record.stableDefinition && !record.binding.reassigned) {
|
|
735
|
+
factories.set(record.binding, record.node);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
function referencesHook(node, seen) {
|
|
739
|
+
const value = unwrapValue(node);
|
|
740
|
+
if (!value) return false;
|
|
741
|
+
if (value.type === 'Identifier') {
|
|
742
|
+
const binding = resolveBinding(analysis.nodeScopes.get(value), value.name);
|
|
743
|
+
if (!binding || binding.reassigned || seen.has(binding)) return false;
|
|
744
|
+
if (/^use[A-Z]/.test(binding.hookRuntimeImport ?? '') || binding.customHookImport)
|
|
745
|
+
return true;
|
|
746
|
+
seen.add(binding);
|
|
747
|
+
return referencesHook(values.get(binding), seen);
|
|
748
|
+
}
|
|
749
|
+
if (value.type === 'ConditionalExpression') {
|
|
750
|
+
return referencesHook(value.consequent, seen) || referencesHook(value.alternate, seen);
|
|
751
|
+
}
|
|
752
|
+
if (value.type === 'LogicalExpression') {
|
|
753
|
+
return referencesHook(value.left, seen) || referencesHook(value.right, seen);
|
|
754
|
+
}
|
|
755
|
+
if (
|
|
756
|
+
value.type === 'CallExpression' &&
|
|
757
|
+
value.optional !== true &&
|
|
758
|
+
value.arguments.length === 0
|
|
759
|
+
) {
|
|
760
|
+
const binding = directCallBinding(value, analysis.nodeScopes.get(value));
|
|
761
|
+
const factory = factories.get(binding);
|
|
762
|
+
if (!factory || factory.params.length !== 0 || seen.has(binding)) return false;
|
|
763
|
+
seen.add(binding);
|
|
764
|
+
const body = factory.body;
|
|
765
|
+
return referencesHook(
|
|
766
|
+
body.type === 'BlockStatement'
|
|
767
|
+
? body.body.length === 1 && body.body[0].type === 'ReturnStatement'
|
|
768
|
+
? body.body[0].argument
|
|
769
|
+
: null
|
|
770
|
+
: body,
|
|
771
|
+
seen,
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
return false;
|
|
775
|
+
}
|
|
776
|
+
for (const { call, scope } of analysis.calls) {
|
|
777
|
+
if (call.optional === true || call.callee.type !== 'Identifier') continue;
|
|
778
|
+
const binding = directCallBinding(call, scope);
|
|
779
|
+
if (!values.has(binding) || !referencesHook(values.get(binding), new Set([binding]))) continue;
|
|
780
|
+
analysis.callAnnotations.set(call, {
|
|
781
|
+
...analysis.callAnnotations.get(call),
|
|
782
|
+
_octaneCustomHookCall: binding.name,
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
690
787
|
function collectPatternBindings(pattern, scope, into) {
|
|
691
788
|
if (!pattern) return;
|
|
692
789
|
if (pattern.type === 'Identifier') {
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// Manual hook packages own their explicit sub-slot layout. Adapt exported or
|
|
2
|
+
// escaping hook definitions at their owner, so bound/forwarded imports retain
|
|
3
|
+
// that protocol without changing ordinary custom-hook argument lists.
|
|
4
|
+
import { builders as b } from '@tsrx/core';
|
|
5
|
+
|
|
6
|
+
const META = new Set([
|
|
7
|
+
'loc',
|
|
8
|
+
'start',
|
|
9
|
+
'end',
|
|
10
|
+
'range',
|
|
11
|
+
'metadata',
|
|
12
|
+
'parent',
|
|
13
|
+
'comments',
|
|
14
|
+
'leadingComments',
|
|
15
|
+
'trailingComments',
|
|
16
|
+
'innerComments',
|
|
17
|
+
]);
|
|
18
|
+
const isHook = (name) =>
|
|
19
|
+
typeof name === 'string' && /^(?:(?:experimental|unstable|UNSTABLE)_)?use[A-Z]/.test(name);
|
|
20
|
+
const isFunction = (node) =>
|
|
21
|
+
node?.type === 'FunctionDeclaration' ||
|
|
22
|
+
node?.type === 'FunctionExpression' ||
|
|
23
|
+
node?.type === 'ArrowFunctionExpression';
|
|
24
|
+
|
|
25
|
+
function children(node, visit) {
|
|
26
|
+
for (const key in node) {
|
|
27
|
+
if (META.has(key) || key.startsWith('_octane')) continue;
|
|
28
|
+
const value = node[key];
|
|
29
|
+
if (Array.isArray(value)) for (const child of value) visit(child, node, key);
|
|
30
|
+
else if (value && typeof value === 'object') visit(value, node, key);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function findManualHookProviders(ast) {
|
|
35
|
+
const escaping = new Set();
|
|
36
|
+
const definitions = new Map();
|
|
37
|
+
function scan(node, parent, key) {
|
|
38
|
+
if (!node || typeof node !== 'object') return;
|
|
39
|
+
if (isFunction(node) && node.body) {
|
|
40
|
+
const bindingName = parent?.type === 'VariableDeclarator' ? parent.id?.name : undefined;
|
|
41
|
+
const name = isHook(bindingName) ? bindingName : node.id?.name;
|
|
42
|
+
if (isHook(name)) {
|
|
43
|
+
// A named expression can have a private implementation name while its
|
|
44
|
+
// exported binding establishes the hook contract. Keep its public
|
|
45
|
+
// Function.name when installing the provider boundary.
|
|
46
|
+
definitions.set(node, { name, displayName: node.id?.name ?? name });
|
|
47
|
+
if (
|
|
48
|
+
parent?.type === 'ExportNamedDeclaration' ||
|
|
49
|
+
parent?.type === 'ExportDefaultDeclaration'
|
|
50
|
+
)
|
|
51
|
+
escaping.add(name);
|
|
52
|
+
if (parent?.type === 'ReturnStatement') escaping.add(name);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (node.type === 'Identifier' && isHook(node.name)) {
|
|
56
|
+
const binding =
|
|
57
|
+
(isFunction(parent) && (key === 'id' || key === 'params')) ||
|
|
58
|
+
(parent?.type === 'VariableDeclarator' && key === 'id') ||
|
|
59
|
+
parent?.type === 'ImportSpecifier' ||
|
|
60
|
+
parent?.type === 'ImportDefaultSpecifier' ||
|
|
61
|
+
parent?.type === 'ImportNamespaceSpecifier';
|
|
62
|
+
const directCall = parent?.type === 'CallExpression' && key === 'callee';
|
|
63
|
+
const property =
|
|
64
|
+
(parent?.type === 'MemberExpression' && key === 'property' && !parent.computed) ||
|
|
65
|
+
(parent?.type === 'Property' && key === 'key' && !parent.computed);
|
|
66
|
+
if (!binding && !directCall && !property) escaping.add(node.name);
|
|
67
|
+
}
|
|
68
|
+
if (
|
|
69
|
+
node.type === 'ExportNamedDeclaration' &&
|
|
70
|
+
node.declaration?.type === 'VariableDeclaration'
|
|
71
|
+
) {
|
|
72
|
+
for (const declaration of node.declaration.declarations) {
|
|
73
|
+
if (isHook(declaration.id?.name)) escaping.add(declaration.id.name);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
children(node, scan);
|
|
77
|
+
}
|
|
78
|
+
scan(ast, null, null);
|
|
79
|
+
return new Map(
|
|
80
|
+
[...definitions]
|
|
81
|
+
.filter(([, { name }]) => escaping.has(name))
|
|
82
|
+
.map(([node, { displayName }]) => [node, displayName]),
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function declarationOf(statement) {
|
|
87
|
+
return statement?.type === 'ExportNamedDeclaration' ||
|
|
88
|
+
statement?.type === 'ExportDefaultDeclaration'
|
|
89
|
+
? statement.declaration
|
|
90
|
+
: statement;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Only the parameters before the first default/rest contribute to Function.length.
|
|
94
|
+
// The public wrapper must not evaluate authored defaults or destructuring; the
|
|
95
|
+
// private implementation receives the original arguments after slot adaptation.
|
|
96
|
+
export function manualHookWrapperParameters(node) {
|
|
97
|
+
const parameters = [];
|
|
98
|
+
for (const param of node.params) {
|
|
99
|
+
if (param.type === 'Identifier' && param.name === 'this') continue;
|
|
100
|
+
if (param.type === 'AssignmentPattern' || param.type === 'RestElement') break;
|
|
101
|
+
parameters.push(b.id(`_$arg${parameters.length}`));
|
|
102
|
+
}
|
|
103
|
+
return parameters;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function adaptManualHookProviders(ast, requireHelper, allocateName) {
|
|
107
|
+
const providers = findManualHookProviders(ast);
|
|
108
|
+
if (!providers.size) return ast;
|
|
109
|
+
function visit(node) {
|
|
110
|
+
if (!node || typeof node !== 'object') return node;
|
|
111
|
+
if (Array.isArray(node)) {
|
|
112
|
+
let changed = false;
|
|
113
|
+
const result = node.map((item) => {
|
|
114
|
+
const next = visit(item);
|
|
115
|
+
changed ||= next !== item;
|
|
116
|
+
return next;
|
|
117
|
+
});
|
|
118
|
+
return changed ? result : node;
|
|
119
|
+
}
|
|
120
|
+
let result = node;
|
|
121
|
+
for (const key in node) {
|
|
122
|
+
if (META.has(key) || key.startsWith('_octane')) continue;
|
|
123
|
+
const value = node[key];
|
|
124
|
+
if (!value || typeof value !== 'object') continue;
|
|
125
|
+
const next = visit(value);
|
|
126
|
+
if (next !== value) {
|
|
127
|
+
if (result === node) result = { ...node };
|
|
128
|
+
result[key] = next;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (node.type === 'Program' || node.type === 'BlockStatement') {
|
|
132
|
+
let changed = false;
|
|
133
|
+
const body = result.body.flatMap((statement, index) => {
|
|
134
|
+
const original = declarationOf(node.body[index]);
|
|
135
|
+
if (original?.type !== 'FunctionDeclaration' || !providers.has(original))
|
|
136
|
+
return [statement];
|
|
137
|
+
changed = true;
|
|
138
|
+
const declaration = declarationOf(statement);
|
|
139
|
+
const implementation = allocateName(`_$manual_${original.id.name}`);
|
|
140
|
+
const wrapper = b.function_declaration(
|
|
141
|
+
original.id,
|
|
142
|
+
manualHookWrapperParameters(original),
|
|
143
|
+
b.block([
|
|
144
|
+
b.return(
|
|
145
|
+
b.call(
|
|
146
|
+
requireHelper('invokeManualHook'),
|
|
147
|
+
b.id(implementation),
|
|
148
|
+
b.this,
|
|
149
|
+
b.id('arguments'),
|
|
150
|
+
),
|
|
151
|
+
),
|
|
152
|
+
]),
|
|
153
|
+
);
|
|
154
|
+
// Both declarations hoist. Keep the exported binding stable even for
|
|
155
|
+
// cyclic imports and aliases captured before the textual declaration.
|
|
156
|
+
return [
|
|
157
|
+
statement === declaration ? wrapper : { ...statement, declaration: wrapper },
|
|
158
|
+
{ ...declaration, id: b.id(implementation) },
|
|
159
|
+
];
|
|
160
|
+
});
|
|
161
|
+
if (changed) result = { ...result, body };
|
|
162
|
+
}
|
|
163
|
+
return providers.has(node) && node.type !== 'FunctionDeclaration'
|
|
164
|
+
? {
|
|
165
|
+
...b.call(requireHelper('manualHook'), result, b.literal(providers.get(node))),
|
|
166
|
+
__octanePure: true,
|
|
167
|
+
}
|
|
168
|
+
: result;
|
|
169
|
+
}
|
|
170
|
+
return visit(ast);
|
|
171
|
+
}
|
|
@@ -7,6 +7,7 @@ import { print as esrapPrint } from 'esrap';
|
|
|
7
7
|
import esrapTsx from 'esrap/languages/tsx';
|
|
8
8
|
import { METHOD_DEP_IMPORT } from './hook-deps.js';
|
|
9
9
|
import { nativeReadActivationIndex } from './native-read-codegen.js';
|
|
10
|
+
import { adaptManualHookProviders } from './manual-hooks.js';
|
|
10
11
|
import {
|
|
11
12
|
hasInlineMemoDirectEval,
|
|
12
13
|
inheritHookMemoOrigin,
|
|
@@ -126,13 +127,32 @@ function inferredDependencyArray(inferred, state, origin) {
|
|
|
126
127
|
);
|
|
127
128
|
}
|
|
128
129
|
|
|
129
|
-
// Match the surgical pass's
|
|
130
|
-
//
|
|
130
|
+
// Match the surgical pass's base-hook and imported-hook slot policy. Local
|
|
131
|
+
// custom helpers keep their authored boundaries. Existing explicit memo slots are
|
|
131
132
|
// already the effective third argument, so no unused fourth argument is added.
|
|
132
133
|
function slotBaseHooks(ast, state, options) {
|
|
133
134
|
function visit(node) {
|
|
134
135
|
if (node === null || typeof node !== 'object') return node;
|
|
135
136
|
if (Array.isArray(node)) return mapChildren(node, visit);
|
|
137
|
+
if (!options.manualSlots && node.type === 'CallExpression' && node._octaneCustomHookCall) {
|
|
138
|
+
const slot = allocateHookSlot(state, node);
|
|
139
|
+
const mapped = mapChildren(node, visit);
|
|
140
|
+
const callee = mapped.typeArguments
|
|
141
|
+
? {
|
|
142
|
+
type: 'TSInstantiationExpression',
|
|
143
|
+
expression: mapped.callee,
|
|
144
|
+
typeArguments: mapped.typeArguments,
|
|
145
|
+
}
|
|
146
|
+
: mapped.callee;
|
|
147
|
+
return {
|
|
148
|
+
...mapped,
|
|
149
|
+
callee: b.id(requireHelper(state, 'withSlot', 'octane'), node),
|
|
150
|
+
typeArguments: null,
|
|
151
|
+
// Keep foreign/default parameters and arguments.length unchanged;
|
|
152
|
+
// Octane base aliases resolve their slot from this call's path.
|
|
153
|
+
arguments: [slot, callee, ...mapped.arguments],
|
|
154
|
+
};
|
|
155
|
+
}
|
|
136
156
|
const imported =
|
|
137
157
|
node.type === 'CallExpression'
|
|
138
158
|
? (node._octaneImportedHook ??
|
|
@@ -147,13 +167,12 @@ function slotBaseHooks(ast, state, options) {
|
|
|
147
167
|
inferred === undefined &&
|
|
148
168
|
node.arguments.length === 3 &&
|
|
149
169
|
!node.arguments.some((argument) => argument.type === 'SpreadElement');
|
|
150
|
-
const slot = explicitMemoSlot ? null : allocateHookSlot(state, node);
|
|
170
|
+
const slot = options.manualSlots || explicitMemoSlot ? null : allocateHookSlot(state, node);
|
|
151
171
|
const mapped = mapChildren(node, visit);
|
|
152
172
|
const args = mapped.arguments.slice();
|
|
153
173
|
if (inferred !== undefined) {
|
|
154
174
|
args.splice(inferred.depsIndex, 0, inferredDependencyArray(inferred, state, node));
|
|
155
175
|
}
|
|
156
|
-
if (slot !== null) args.push(slot);
|
|
157
176
|
let callee = mapped.callee;
|
|
158
177
|
if (options.getterCalls.has(node) && options.stateGetterHelpers[imported]) {
|
|
159
178
|
callee = b.id(requireHelper(state, options.stateGetterHelpers[imported], 'octane'), node);
|
|
@@ -161,6 +180,29 @@ function slotBaseHooks(ast, state, options) {
|
|
|
161
180
|
if (node._octaneNativeInferredMemo === true) {
|
|
162
181
|
callee = b.id(requireHelper(state, 'nativePuMemo'), node);
|
|
163
182
|
}
|
|
183
|
+
if (slot !== null) {
|
|
184
|
+
if (
|
|
185
|
+
(imported === 'useState' || imported === 'useRef') &&
|
|
186
|
+
args.some((arg) => arg.type === 'SpreadElement')
|
|
187
|
+
) {
|
|
188
|
+
const fn = mapped.typeArguments
|
|
189
|
+
? {
|
|
190
|
+
type: 'TSInstantiationExpression',
|
|
191
|
+
expression: callee,
|
|
192
|
+
typeArguments: mapped.typeArguments,
|
|
193
|
+
}
|
|
194
|
+
: callee;
|
|
195
|
+
return {
|
|
196
|
+
...mapped,
|
|
197
|
+
callee: b.id(requireHelper(state, 'withSlot', 'octane'), node),
|
|
198
|
+
typeArguments: null,
|
|
199
|
+
arguments: [slot, fn, ...args],
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
if (args.length === 0 && (imported === 'useState' || imported === 'useRef'))
|
|
203
|
+
args.push(b.id('undefined', node));
|
|
204
|
+
args.push(slot);
|
|
205
|
+
}
|
|
164
206
|
return { ...mapped, callee, arguments: args };
|
|
165
207
|
}
|
|
166
208
|
return visit(ast);
|
|
@@ -235,7 +277,7 @@ export function inlinePlainHookMemos(ast, source, id, options) {
|
|
|
235
277
|
slotBase: null,
|
|
236
278
|
slotDeclarations: [],
|
|
237
279
|
};
|
|
238
|
-
let transformed =
|
|
280
|
+
let transformed = slotBaseHooks(ast, state, options);
|
|
239
281
|
const lowered = lowerSlotMemoFunctions(transformed, {
|
|
240
282
|
allocateName: (preferred) => allocName(state, preferred),
|
|
241
283
|
requireRuntime: (imported) => requireHelper(state, imported),
|
|
@@ -243,6 +285,13 @@ export function inlinePlainHookMemos(ast, source, id, options) {
|
|
|
243
285
|
});
|
|
244
286
|
if (lowered.lowered === 0) return null;
|
|
245
287
|
transformed = lowered.ast;
|
|
288
|
+
if (options.manualSlots) {
|
|
289
|
+
transformed = adaptManualHookProviders(
|
|
290
|
+
transformed,
|
|
291
|
+
(name) => requireHelper(state, name, 'octane'),
|
|
292
|
+
(preferred) => allocName(state, preferred),
|
|
293
|
+
);
|
|
294
|
+
}
|
|
246
295
|
const origin = ast.body[0] ?? ast;
|
|
247
296
|
const activation = options.nativeReadActivation
|
|
248
297
|
? inheritHookMemoOrigin(
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { CompilerProgram, CompileSourceMap } from './index.js';
|
|
2
|
+
|
|
3
|
+
/** Options for the plain-module hook transform used by compiler adapters. */
|
|
4
|
+
export interface SlotHooksOptions {
|
|
5
|
+
environment?: 'client' | 'server';
|
|
6
|
+
strong?: boolean;
|
|
7
|
+
nativeReads?: boolean;
|
|
8
|
+
hmr?: boolean;
|
|
9
|
+
dev?: boolean;
|
|
10
|
+
profile?: boolean;
|
|
11
|
+
profileFilename?: string;
|
|
12
|
+
inlineHookMemo?: boolean;
|
|
13
|
+
manualSlots?: boolean;
|
|
14
|
+
/** Opaque activation signal; the transform only checks its presence. */
|
|
15
|
+
universalRuntime?: unknown;
|
|
16
|
+
renderer?: { target?: string };
|
|
17
|
+
isVoidComponentImport?: (request: string, imported: string) => boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface SlotHooksResult {
|
|
21
|
+
code: string;
|
|
22
|
+
/** The surgical transform preserves source lines and returns no map. */
|
|
23
|
+
map: CompileSourceMap | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface VoidComponentImport {
|
|
27
|
+
request: string;
|
|
28
|
+
imported: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Returns null when the module can pass through unchanged. */
|
|
32
|
+
export function slotHooks(
|
|
33
|
+
source: string,
|
|
34
|
+
id: string,
|
|
35
|
+
options?: SlotHooksOptions,
|
|
36
|
+
): SlotHooksResult | null;
|
|
37
|
+
|
|
38
|
+
export function findVoidRootImports(source: string, id: string): VoidComponentImport[];
|
|
39
|
+
|
|
40
|
+
export function findVoidComponentImports(
|
|
41
|
+
source: string | CompilerProgram,
|
|
42
|
+
id: string,
|
|
43
|
+
): VoidComponentImport[];
|