octane 0.2.4 → 0.2.6
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 +9 -0
- package/dist/cjs/error-codes.client.generated.cjs +2 -0
- package/dist/cjs/host-property-diagnostics.cjs +2 -0
- package/dist/cjs/index.cjs +2 -0
- package/dist/cjs/runtime.cjs +482 -51
- package/dist/cjs/runtime.server.cjs +23 -6
- package/dist/cjs/version.cjs +1 -1
- package/dist/compiler/bundler.js +7 -1
- package/dist/compiler/compile.js +12 -0
- package/dist/compiler/hook-methods.js +164 -0
- package/dist/compiler/parser-output.js +37 -0
- package/dist/compiler/parser.browser.js +145 -1
- package/dist/compiler/parser.node.js +1 -35
- package/dist/compiler/plain-hook-memo.js +37 -0
- package/dist/compiler/slot-hooks.js +129 -5
- package/dist/compiler/volar.LICENSES.txt +1 -1
- package/dist/compiler/volar.js +186 -43
- package/dist/constants.d.ts +3 -0
- package/dist/constants.js +6 -0
- package/dist/error-codes.client.generated.d.ts +1 -0
- package/dist/error-codes.client.generated.js +2 -0
- package/dist/host-property-diagnostics.js +2 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -0
- package/dist/runtime.d.ts +16 -12
- package/dist/runtime.js +483 -51
- package/dist/runtime.server.d.ts +2 -0
- package/dist/runtime.server.js +25 -6
- package/dist/version.js +1 -1
- package/package.json +5 -5
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// production it reserves a collision-free runtime range because these arbitrary
|
|
14
14
|
// helpers can execute in a Scope alongside code from any other source module.
|
|
15
15
|
|
|
16
|
-
import { parseModule } from '@tsrx/core';
|
|
16
|
+
import { parseModule, builders as b } from '@tsrx/core';
|
|
17
17
|
import { parseModule as parseFallbackModule } from '#octane/compiler-parser';
|
|
18
18
|
import { HOOK_NAMES, hookSlotHash } from './compile.js';
|
|
19
19
|
import { NATIVE_SIGNAL_HOOK_NAMES } from './hook-names.js';
|
|
@@ -23,6 +23,13 @@ import { assertStrongMode } from './strong-mode.js';
|
|
|
23
23
|
import { assertNativeReadDiagnostics, assertNativeReadOptions } from './native-read-diagnostics.js';
|
|
24
24
|
import { nativeReadActivationIndex } from './native-read-codegen.js';
|
|
25
25
|
import { findManualHookProviders, manualHookWrapperParameters } from './manual-hooks.js';
|
|
26
|
+
import { findLeadingJsxImportSourcePragma } from './pragma.js';
|
|
27
|
+
import {
|
|
28
|
+
hookMethodName,
|
|
29
|
+
hasHookMethods,
|
|
30
|
+
assertSynchronousHookMethod,
|
|
31
|
+
lowerHookMethodChain,
|
|
32
|
+
} from './hook-methods.js';
|
|
26
33
|
|
|
27
34
|
function importsNativeRenderer(ast) {
|
|
28
35
|
return ast.body.some(
|
|
@@ -40,7 +47,7 @@ function importsNativeRenderer(ast) {
|
|
|
40
47
|
// Build a cheap import-presence gate. Precise call identity is annotated by the
|
|
41
48
|
// lexical scope analysis in analyzeHookDependencies below; this gate only avoids
|
|
42
49
|
// doing the surgical edit walk for modules that cannot contain an Octane hook.
|
|
43
|
-
function octaneHookLocals(ast, nativeReads = false) {
|
|
50
|
+
function octaneHookLocals(ast, nativeReads = false, explicitlyOwned = false) {
|
|
44
51
|
const locals = new Map();
|
|
45
52
|
let importsHook = false;
|
|
46
53
|
let hasOctaneImport = false;
|
|
@@ -90,7 +97,9 @@ function octaneHookLocals(ast, nativeReads = false) {
|
|
|
90
97
|
}
|
|
91
98
|
return {
|
|
92
99
|
locals,
|
|
93
|
-
importsHook:
|
|
100
|
+
importsHook:
|
|
101
|
+
importsHook ||
|
|
102
|
+
((hasOctaneImport || explicitlyOwned) && (importsCustomHook || hasHookMethods(ast))),
|
|
94
103
|
hasOctaneImport,
|
|
95
104
|
};
|
|
96
105
|
}
|
|
@@ -836,7 +845,9 @@ function parallelUseCallOfStatement(statement) {
|
|
|
836
845
|
|
|
837
846
|
function requireParallelHelper(st, imported) {
|
|
838
847
|
const request =
|
|
839
|
-
imported === 'nativePuMemo' ||
|
|
848
|
+
imported === 'nativePuMemo' ||
|
|
849
|
+
imported === 'enableNativeReadCollection' ||
|
|
850
|
+
imported === 'callWithReceiver'
|
|
840
851
|
? st.environment === 'server'
|
|
841
852
|
? 'octane/internal/server'
|
|
842
853
|
: 'octane/internal/client'
|
|
@@ -997,6 +1008,102 @@ function callOpenParen(node, source) {
|
|
|
997
1008
|
return -1;
|
|
998
1009
|
}
|
|
999
1010
|
|
|
1011
|
+
// Keep this line-preserving text edit within the surgical pass. Authored leaves
|
|
1012
|
+
// remain source slices; generated guards never pass through an AST printer.
|
|
1013
|
+
function emitHookMethodChain(node, owner, st) {
|
|
1014
|
+
const deleting = node.type === 'UnaryExpression' && node.operator === 'delete';
|
|
1015
|
+
if ((deleting ? node.argument : node)?.type !== 'ChainExpression') return false;
|
|
1016
|
+
const leaves = new WeakMap();
|
|
1017
|
+
const visit = (leaf) => {
|
|
1018
|
+
if (leaf.type === 'SpreadElement') return { ...leaf, argument: visit(leaf.argument) };
|
|
1019
|
+
const outerEdits = st.edits;
|
|
1020
|
+
st.edits = [];
|
|
1021
|
+
let edits;
|
|
1022
|
+
try {
|
|
1023
|
+
walk(leaf, owner, st);
|
|
1024
|
+
edits = st.edits;
|
|
1025
|
+
} finally {
|
|
1026
|
+
st.edits = outerEdits;
|
|
1027
|
+
}
|
|
1028
|
+
let source = st.source.slice(leaf.start, leaf.end);
|
|
1029
|
+
for (const edit of edits.sort((a, b) => b.pos - a.pos)) {
|
|
1030
|
+
source =
|
|
1031
|
+
source.slice(0, edit.pos - leaf.start) +
|
|
1032
|
+
edit.text +
|
|
1033
|
+
source.slice((edit.end ?? edit.pos) - leaf.start);
|
|
1034
|
+
}
|
|
1035
|
+
const opaque = b.id('_source');
|
|
1036
|
+
leaves.set(opaque, source);
|
|
1037
|
+
return opaque;
|
|
1038
|
+
};
|
|
1039
|
+
const lowered = lowerHookMethodChain(
|
|
1040
|
+
deleting ? node.argument : node,
|
|
1041
|
+
{
|
|
1042
|
+
locals: st.locals,
|
|
1043
|
+
allocateName: (name) => allocSlotName(st, name),
|
|
1044
|
+
visit,
|
|
1045
|
+
requireReceiver: () => requireParallelHelper(st, 'callWithReceiver'),
|
|
1046
|
+
wrap: (call, origin, method) =>
|
|
1047
|
+
b.call(
|
|
1048
|
+
requireParallelHelper(st, 'withSlot'),
|
|
1049
|
+
b.id(allocHookSymbol(st, owner, method, method, origin)),
|
|
1050
|
+
b.arrow([], call),
|
|
1051
|
+
),
|
|
1052
|
+
},
|
|
1053
|
+
deleting,
|
|
1054
|
+
);
|
|
1055
|
+
if (lowered === null) return false;
|
|
1056
|
+
// The lowerer produces only these guard/call shapes. Opaque authored leaves
|
|
1057
|
+
// are keyed by node identity, so strings and property names cannot be replaced.
|
|
1058
|
+
function emit(value) {
|
|
1059
|
+
if (leaves.has(value)) return `(${leaves.get(value)})`;
|
|
1060
|
+
switch (value.type) {
|
|
1061
|
+
case 'Identifier':
|
|
1062
|
+
return value.name;
|
|
1063
|
+
case 'PrivateIdentifier':
|
|
1064
|
+
return `#${value.name}`;
|
|
1065
|
+
case 'Super':
|
|
1066
|
+
return 'super';
|
|
1067
|
+
case 'ThisExpression':
|
|
1068
|
+
return 'this';
|
|
1069
|
+
case 'Literal':
|
|
1070
|
+
return JSON.stringify(value.value);
|
|
1071
|
+
case 'SpreadElement':
|
|
1072
|
+
return `...${emit(value.argument)}`;
|
|
1073
|
+
case 'TSNonNullExpression':
|
|
1074
|
+
return `(${emit(value.expression)})!`;
|
|
1075
|
+
case 'MemberExpression': {
|
|
1076
|
+
const object = value.object.type === 'Super' ? 'super' : `(${emit(value.object)})`;
|
|
1077
|
+
return value.computed
|
|
1078
|
+
? `${object}[${emit(value.property)}]`
|
|
1079
|
+
: `${object}.${emit(value.property)}`;
|
|
1080
|
+
}
|
|
1081
|
+
case 'CallExpression': {
|
|
1082
|
+
const types = value.typeArguments
|
|
1083
|
+
? st.source.slice(value.typeArguments.start, value.typeArguments.end)
|
|
1084
|
+
: '';
|
|
1085
|
+
return `(${emit(value.callee)})${types}(${value.arguments.map(emit).join(', ')})`;
|
|
1086
|
+
}
|
|
1087
|
+
case 'ArrowFunctionExpression':
|
|
1088
|
+
return `((${value.params.map(emit).join(', ')}) => ${emit(value.body)})`;
|
|
1089
|
+
case 'BinaryExpression':
|
|
1090
|
+
case 'LogicalExpression':
|
|
1091
|
+
return `(${emit(value.left)} ${value.operator} ${emit(value.right)})`;
|
|
1092
|
+
case 'ConditionalExpression':
|
|
1093
|
+
return `(${emit(value.test)} ? ${emit(value.consequent)} : ${emit(value.alternate)})`;
|
|
1094
|
+
case 'UnaryExpression':
|
|
1095
|
+
return `(${value.operator} ${emit(value.argument)})`;
|
|
1096
|
+
default:
|
|
1097
|
+
throw new Error(`Unexpected optional hook-chain node: ${value.type}`);
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
let text = emit(lowered);
|
|
1101
|
+
const originalLines = st.source.slice(node.start, node.end).split('\n').length;
|
|
1102
|
+
text += '\n'.repeat(Math.max(0, originalLines - text.split('\n').length));
|
|
1103
|
+
st.edits.push({ pos: node.start, end: node.end, text });
|
|
1104
|
+
return true;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1000
1107
|
function walk(node, owner, st) {
|
|
1001
1108
|
if (!node || typeof node !== 'object') return;
|
|
1002
1109
|
if (Array.isArray(node)) {
|
|
@@ -1018,7 +1125,19 @@ function walk(node, owner, st) {
|
|
|
1018
1125
|
const childOwner =
|
|
1019
1126
|
node.type === 'FunctionDeclaration' && node.id ? hookOwner(node, node.id.name) : owner;
|
|
1020
1127
|
|
|
1128
|
+
if (!st.manualSlots && emitHookMethodChain(node, childOwner, st)) return;
|
|
1129
|
+
|
|
1021
1130
|
if (node.type === 'CallExpression') {
|
|
1131
|
+
const method = hookMethodName(node, st.locals);
|
|
1132
|
+
if (!st.manualSlots && method !== null) {
|
|
1133
|
+
assertSynchronousHookMethod(node);
|
|
1134
|
+
const sym = allocHookSymbol(st, owner, method, method, node);
|
|
1135
|
+
const helper = requireParallelHelper(st, 'withSlot');
|
|
1136
|
+
// Keep the complete method expression inside the boundary: receivers,
|
|
1137
|
+
// getters, optional calls, and argument counts retain their semantics.
|
|
1138
|
+
st.edits.push({ pos: node.start, text: `${helper}(${sym}, () => ` });
|
|
1139
|
+
st.edits.push({ pos: node.end, text: ')' });
|
|
1140
|
+
}
|
|
1022
1141
|
if (!st.manualSlots && node._octaneCustomHookCall) {
|
|
1023
1142
|
const open = callOpenParen(node, st.source);
|
|
1024
1143
|
if (open !== -1) {
|
|
@@ -1192,7 +1311,11 @@ export function slotHooks(source, id, options) {
|
|
|
1192
1311
|
}
|
|
1193
1312
|
assertStrongMode(ast, source, id, options);
|
|
1194
1313
|
assertNativeReadDiagnostics(ast, source, id, options);
|
|
1195
|
-
const importInfo = octaneHookLocals(
|
|
1314
|
+
const importInfo = octaneHookLocals(
|
|
1315
|
+
ast,
|
|
1316
|
+
options?.nativeReads === true,
|
|
1317
|
+
findLeadingJsxImportSourcePragma(source) === 'octane',
|
|
1318
|
+
);
|
|
1196
1319
|
const manualProviders = options?.manualSlots ? findManualHookProviders(ast) : new Map();
|
|
1197
1320
|
const nativeReadActivation = options?.nativeReads === true && importsNativeRenderer(ast);
|
|
1198
1321
|
const canSpecializeRoot =
|
|
@@ -1240,6 +1363,7 @@ export function slotHooks(source, id, options) {
|
|
|
1240
1363
|
!(canSpecializeRoot && collectVoidRootCandidates(ast).length > 0)
|
|
1241
1364
|
) {
|
|
1242
1365
|
const inlined = inlinePlainHookMemos(ast, source, id, {
|
|
1366
|
+
hookLocals: importInfo.locals,
|
|
1243
1367
|
manualSlots: options?.manualSlots === true,
|
|
1244
1368
|
hookNames:
|
|
1245
1369
|
options?.nativeReads === true
|