octane 0.2.7 → 0.2.8
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/index.cjs +2 -0
- package/dist/cjs/runtime.cjs +338 -111
- package/dist/cjs/runtime.server.cjs +307 -66
- package/dist/cjs/version.cjs +1 -1
- package/dist/compiler/compile.js +171 -13
- package/dist/compiler/context-use.js +154 -0
- package/dist/compiler/hook-deps.js +17 -1
- package/dist/compiler/native-read-diagnostics.js +11 -32
- package/dist/compiler/slot-hooks.js +6 -0
- package/dist/compiler/strong-mode.js +716 -39
- package/dist/compiler/volar.LICENSES.txt +1 -1
- package/dist/compiler/volar.js +3485 -3476
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -0
- package/dist/runtime.d.ts +8 -3
- package/dist/runtime.js +337 -111
- package/dist/runtime.server.d.ts +6 -5
- package/dist/runtime.server.js +307 -66
- package/dist/universal-core.d.ts +15 -0
- package/dist/universal-core.js +516 -32
- package/dist/version.js +1 -1
- package/package.json +3 -3
package/dist/compiler/compile.js
CHANGED
|
@@ -92,6 +92,7 @@ import {
|
|
|
92
92
|
wrapNativeWarmScope,
|
|
93
93
|
} from './native-read-codegen.js';
|
|
94
94
|
import { createTextTypeFactsLookup } from './text-type-facts.js';
|
|
95
|
+
import { collectProvenContextBindings, isProvenContextUse } from './context-use.js';
|
|
95
96
|
import { applyCssModuleConstants } from './css-module-constants.js';
|
|
96
97
|
import { assertUniversalRuntimeTarget, normalizeUniversalRuntime } from './universal-runtime.js';
|
|
97
98
|
|
|
@@ -5110,7 +5111,9 @@ function collectImmutableModuleFunctions(body) {
|
|
|
5110
5111
|
const declared = new Map();
|
|
5111
5112
|
for (const statement of body) {
|
|
5112
5113
|
const declaration =
|
|
5113
|
-
statement.type === 'ExportNamedDeclaration'
|
|
5114
|
+
statement.type === 'ExportNamedDeclaration' || statement.type === 'ExportDefaultDeclaration'
|
|
5115
|
+
? statement.declaration
|
|
5116
|
+
: statement;
|
|
5114
5117
|
if (declaration?.type === 'FunctionDeclaration' && declaration.id?.type === 'Identifier') {
|
|
5115
5118
|
declared.set(declaration.id.name, declaration);
|
|
5116
5119
|
}
|
|
@@ -5125,10 +5128,31 @@ function collectImmutableModuleFunctions(body) {
|
|
|
5125
5128
|
}
|
|
5126
5129
|
if (seen.has(n)) return;
|
|
5127
5130
|
seen.add(n);
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5131
|
+
const writeTarget =
|
|
5132
|
+
n.type === 'AssignmentExpression'
|
|
5133
|
+
? n.left
|
|
5134
|
+
: n.type === 'UpdateExpression'
|
|
5135
|
+
? n.argument
|
|
5136
|
+
: n.type === 'VariableDeclarator'
|
|
5137
|
+
? n.id
|
|
5138
|
+
: (n.type === 'ForOfStatement' || n.type === 'ForInStatement') &&
|
|
5139
|
+
n.left?.type !== 'VariableDeclaration'
|
|
5140
|
+
? n.left
|
|
5141
|
+
: null;
|
|
5142
|
+
if (writeTarget?.type === 'Identifier') {
|
|
5143
|
+
declared.delete(writeTarget.name);
|
|
5144
|
+
} else if (writeTarget !== null) {
|
|
5145
|
+
const names = new Set();
|
|
5146
|
+
collectPatternNames(unwrapTsExpr(writeTarget), names);
|
|
5147
|
+
for (const name of names) declared.delete(name);
|
|
5148
|
+
} else if (
|
|
5149
|
+
n.type === 'CallExpression' &&
|
|
5150
|
+
unwrapTsExpr(n.callee)?.type === 'Identifier' &&
|
|
5151
|
+
unwrapTsExpr(n.callee).name === 'eval'
|
|
5152
|
+
) {
|
|
5153
|
+
// Direct eval can write a module function binding without a visible
|
|
5154
|
+
// assignment node. Exclude those names from identity proofs.
|
|
5155
|
+
declared.clear();
|
|
5132
5156
|
}
|
|
5133
5157
|
for (const key in n) {
|
|
5134
5158
|
if (AST_WALK_SKIP_KEYS.has(key)) continue;
|
|
@@ -9185,6 +9209,7 @@ function compileInternal(
|
|
|
9185
9209
|
ctx.octaneImportLocals = imports.locals;
|
|
9186
9210
|
ctx.octaneImportNamespaces = imports.namespaces;
|
|
9187
9211
|
ctx.foreignImportLocals = imports.foreignLocals;
|
|
9212
|
+
ctx.provenContextBindings = collectProvenContextBindings(ast);
|
|
9188
9213
|
ctx.activityModuleAst = ast;
|
|
9189
9214
|
}
|
|
9190
9215
|
if (ctx.dev) {
|
|
@@ -10456,6 +10481,7 @@ function compileServer(
|
|
|
10456
10481
|
ctx.octaneImportLocals = imports.locals;
|
|
10457
10482
|
ctx.octaneImportNamespaces = imports.namespaces;
|
|
10458
10483
|
ctx.foreignImportLocals = imports.foreignLocals;
|
|
10484
|
+
ctx.provenContextBindings = collectProvenContextBindings(ast);
|
|
10459
10485
|
ctx.activityModuleAst = ast;
|
|
10460
10486
|
}
|
|
10461
10487
|
// M3 inherit-range exclusion set — must match the client compile's
|
|
@@ -14036,6 +14062,7 @@ function compileFunctionBody(node, ctx, name, parentNs = 'html', cssHash = null,
|
|
|
14036
14062
|
ctx.inlineHookMemo && ctx.mode !== 'server' && ctx._universalRuntimeUnit == null;
|
|
14037
14063
|
ctx._puInlineLowering = inlineLowering;
|
|
14038
14064
|
let warmThunk = null;
|
|
14065
|
+
let staticChildOnly = false;
|
|
14039
14066
|
if (options && options.autoCallback) {
|
|
14040
14067
|
const creations = [];
|
|
14041
14068
|
const warmChildren = [];
|
|
@@ -14094,9 +14121,21 @@ function compileFunctionBody(node, ctx, name, parentNs = 'html', cssHash = null,
|
|
|
14094
14121
|
jsxNodes = parallelUseWalkJsx(jsxNodes, ctx, name, creations, warmChildren, [], new Set());
|
|
14095
14122
|
const warm = buildWarmArtifacts(node, ctx, name, creations, warmChildren);
|
|
14096
14123
|
warmThunk = warm.thunk;
|
|
14124
|
+
staticChildOnly = warm.staticChildOnly === true;
|
|
14097
14125
|
ctx._pendingWarm = warm.warmNode;
|
|
14098
14126
|
}
|
|
14099
|
-
workingStatements = rewriteParallelUse(
|
|
14127
|
+
workingStatements = rewriteParallelUse(
|
|
14128
|
+
workingStatements,
|
|
14129
|
+
ctx,
|
|
14130
|
+
name,
|
|
14131
|
+
warmThunk,
|
|
14132
|
+
staticChildOnly &&
|
|
14133
|
+
ctx.mode !== 'server' &&
|
|
14134
|
+
ctx._universalRuntimeUnit == null &&
|
|
14135
|
+
ctx.moduleFunctionDeclarations.get(name)?.start === node.start
|
|
14136
|
+
? staticChildWarmPlanProps(node, name)
|
|
14137
|
+
: null,
|
|
14138
|
+
);
|
|
14100
14139
|
|
|
14101
14140
|
// De-callback the hook memo tier (production client compile). Authored and
|
|
14102
14141
|
// auto-generated useMemo/useCallback declarations become inline flat-cache
|
|
@@ -14822,6 +14861,7 @@ function transformUniversalParallelUse(ast, ctx, metadata) {
|
|
|
14822
14861
|
// Names bound by a binding pattern (params, declarator ids). Mirrors the
|
|
14823
14862
|
// pattern handling of collectFreeIdentifiers' collectBindings.
|
|
14824
14863
|
function collectPatternNames(pat, into) {
|
|
14864
|
+
pat = unwrapTsExpr(pat);
|
|
14825
14865
|
if (!pat) return into;
|
|
14826
14866
|
switch (pat.type) {
|
|
14827
14867
|
case 'Identifier':
|
|
@@ -16121,8 +16161,61 @@ function isWarmSafeExpr(expr, paramNames, componentLocals, armLocals) {
|
|
|
16121
16161
|
return true;
|
|
16122
16162
|
}
|
|
16123
16163
|
|
|
16164
|
+
// The registration records props now and invokes the already-attached module
|
|
16165
|
+
// plan only if a descendant suspends. A reassigned parameter would differ from
|
|
16166
|
+
// the old thunk's live lexical capture, including writes in JSX expressions or
|
|
16167
|
+
// nested callbacks. A same-name write in a shadowed scope merely declines the
|
|
16168
|
+
// optimization. Dynamic bindings are conservatively excluded as well.
|
|
16169
|
+
function staticChildWarmPlanProps(node, componentName) {
|
|
16170
|
+
const params = node.params || [];
|
|
16171
|
+
if (params.length > 1 || (params.length === 1 && params[0].type !== 'Identifier')) {
|
|
16172
|
+
return null;
|
|
16173
|
+
}
|
|
16174
|
+
const name = params[0]?.name ?? null;
|
|
16175
|
+
if (name === componentName) return null;
|
|
16176
|
+
const seen = new WeakSet();
|
|
16177
|
+
const bindsName = (pattern, binding) => {
|
|
16178
|
+
if (binding === null || pattern === null) return false;
|
|
16179
|
+
if (pattern.type === 'Identifier') return pattern.name === binding;
|
|
16180
|
+
const names = new Set();
|
|
16181
|
+
collectPatternNames(unwrapTsExpr(pattern), names);
|
|
16182
|
+
return names.has(binding);
|
|
16183
|
+
};
|
|
16184
|
+
const unsafe = (value) => {
|
|
16185
|
+
if (value === null || typeof value !== 'object') return false;
|
|
16186
|
+
if (Array.isArray(value)) return value.some(unsafe);
|
|
16187
|
+
if (seen.has(value)) return false;
|
|
16188
|
+
seen.add(value);
|
|
16189
|
+
if (
|
|
16190
|
+
(value.type === 'AssignmentExpression' && bindsName(value.left, name)) ||
|
|
16191
|
+
(value.type === 'UpdateExpression' && bindsName(value.argument, name)) ||
|
|
16192
|
+
(value.type === 'VariableDeclarator' &&
|
|
16193
|
+
(bindsName(value.id, name) || bindsName(value.id, componentName))) ||
|
|
16194
|
+
((value.type === 'FunctionDeclaration' || value.type === 'ClassDeclaration') &&
|
|
16195
|
+
value.id?.name === componentName) ||
|
|
16196
|
+
(value.type === 'CatchClause' && bindsName(value.param, componentName)) ||
|
|
16197
|
+
((value.type === 'ForOfStatement' || value.type === 'ForInStatement') &&
|
|
16198
|
+
bindsName(value.left, name)) ||
|
|
16199
|
+
(value.type === 'CallExpression' &&
|
|
16200
|
+
unwrapTsExpr(value.callee)?.type === 'Identifier' &&
|
|
16201
|
+
unwrapTsExpr(value.callee).name === 'eval') ||
|
|
16202
|
+
(value.type === 'Identifier' && value.name === 'arguments') ||
|
|
16203
|
+
value.type === 'ThisExpression' ||
|
|
16204
|
+
value.type === 'Super' ||
|
|
16205
|
+
value.type === 'MetaProperty'
|
|
16206
|
+
) {
|
|
16207
|
+
return true;
|
|
16208
|
+
}
|
|
16209
|
+
for (const key in value) {
|
|
16210
|
+
if (!AST_WALK_SKIP_KEYS.has(key) && unsafe(value[key])) return true;
|
|
16211
|
+
}
|
|
16212
|
+
return false;
|
|
16213
|
+
};
|
|
16214
|
+
return unsafe(node.body) ? null : name === null ? b.unary('void', b.literal(0, '0')) : b.id(name);
|
|
16215
|
+
}
|
|
16216
|
+
|
|
16124
16217
|
// ── Pass B: run detection + hoist + batch ───────────────────────────────────
|
|
16125
|
-
function rewriteParallelUse(statements, ctx, componentName, warmThunk) {
|
|
16218
|
+
function rewriteParallelUse(statements, ctx, componentName, warmThunk, staticPlanProps = null) {
|
|
16126
16219
|
let firstBatch = true;
|
|
16127
16220
|
const output = transformList(statements);
|
|
16128
16221
|
if (warmThunk) {
|
|
@@ -16132,17 +16225,57 @@ function rewriteParallelUse(statements, ctx, componentName, warmThunk) {
|
|
|
16132
16225
|
// setup preserves early-return reachability: an alternate returned tree must
|
|
16133
16226
|
// not activate the final template's plan. The first direct batch receives the
|
|
16134
16227
|
// same thunk below so it can still warm before setup reaches this registration.
|
|
16135
|
-
|
|
16136
|
-
|
|
16137
|
-
|
|
16138
|
-
|
|
16228
|
+
// Only the DOM client has registerWarmPlan. Its two-argument form pushes
|
|
16229
|
+
// the same block, thunk, and undefined props as an empty useBatch call,
|
|
16230
|
+
// without creating an array on every render. Server and universal units
|
|
16231
|
+
// continue to use their own batch helpers.
|
|
16232
|
+
const fallbackHelper =
|
|
16233
|
+
ctx.mode === 'client' && ctx._universalRuntimeUnit == null
|
|
16234
|
+
? 'registerWarmPlan'
|
|
16235
|
+
: ctx.mode === 'server'
|
|
16236
|
+
? 'puBatch'
|
|
16237
|
+
: 'useBatch';
|
|
16238
|
+
const fallback = inheritOriginLoc(
|
|
16239
|
+
b.stmt(
|
|
16240
|
+
fallbackHelper === 'registerWarmPlan'
|
|
16241
|
+
? b.call(
|
|
16242
|
+
requireRuntimeForContext(ctx, fallbackHelper),
|
|
16243
|
+
warmThunk,
|
|
16244
|
+
b.unary('void', b.literal(0, '0')),
|
|
16245
|
+
)
|
|
16246
|
+
: b.call(requireRuntimeForContext(ctx, fallbackHelper), b.array([]), warmThunk),
|
|
16247
|
+
),
|
|
16139
16248
|
warmThunk,
|
|
16140
16249
|
);
|
|
16250
|
+
let registration = [fallback];
|
|
16251
|
+
if (staticPlanProps !== null) {
|
|
16252
|
+
// Hoisted functions can render before the module-tail markWarm stamp,
|
|
16253
|
+
// and route extraction can leave that stamp behind. Only the common
|
|
16254
|
+
// attached-plan path may omit the in-body closure and array.
|
|
16255
|
+
const planName = allocCompilerName(ctx, '__warmPlan');
|
|
16256
|
+
registration = [
|
|
16257
|
+
inheritOriginLoc(b.const(planName, b.member(b.id(componentName), '__warm')), warmThunk),
|
|
16258
|
+
inheritOriginLoc(
|
|
16259
|
+
b.if(
|
|
16260
|
+
b.binary('===', b.unary('typeof', b.id(planName)), b.literal('function', "'function'")),
|
|
16261
|
+
b.stmt(
|
|
16262
|
+
b.call(
|
|
16263
|
+
requireRuntimeForContext(ctx, 'registerWarmPlan'),
|
|
16264
|
+
b.id(planName),
|
|
16265
|
+
staticPlanProps,
|
|
16266
|
+
),
|
|
16267
|
+
),
|
|
16268
|
+
fallback,
|
|
16269
|
+
),
|
|
16270
|
+
warmThunk,
|
|
16271
|
+
),
|
|
16272
|
+
];
|
|
16273
|
+
}
|
|
16141
16274
|
const finalIndex =
|
|
16142
16275
|
output.length > 0 && output[output.length - 1].type === 'ReturnStatement'
|
|
16143
16276
|
? output.length - 1
|
|
16144
16277
|
: output.length;
|
|
16145
|
-
output.splice(finalIndex, 0, registration);
|
|
16278
|
+
output.splice(finalIndex, 0, ...registration);
|
|
16146
16279
|
}
|
|
16147
16280
|
return output;
|
|
16148
16281
|
|
|
@@ -16236,6 +16369,19 @@ function rewriteParallelUse(statements, ctx, componentName, warmThunk) {
|
|
|
16236
16369
|
for (const m of run.members) out.push(m.stmt);
|
|
16237
16370
|
return;
|
|
16238
16371
|
}
|
|
16372
|
+
if (
|
|
16373
|
+
uses.every(
|
|
16374
|
+
(m) =>
|
|
16375
|
+
m.call._octaneImportedHook === 'use' &&
|
|
16376
|
+
isProvenContextUse(m.creation, ctx.provenContextBindings),
|
|
16377
|
+
)
|
|
16378
|
+
) {
|
|
16379
|
+
// Context reads cannot suspend. Preserve the authored statements in
|
|
16380
|
+
// their original order, and leave the first warm thunk available for
|
|
16381
|
+
// the next real data batch. The trailing child-only registration stays.
|
|
16382
|
+
for (const m of run.members) out.push(m.stmt);
|
|
16383
|
+
return;
|
|
16384
|
+
}
|
|
16239
16385
|
// Hoist each creation into a temp, batch, then re-emit the original
|
|
16240
16386
|
// statements with creations swapped for the temps. Unwrap order (and
|
|
16241
16387
|
// hydration-seed order with it) is untouched.
|
|
@@ -16426,7 +16572,19 @@ function buildWarmArtifacts(node, ctx, componentName, creations, warmChildren) {
|
|
|
16426
16572
|
node,
|
|
16427
16573
|
);
|
|
16428
16574
|
}
|
|
16429
|
-
return {
|
|
16575
|
+
return {
|
|
16576
|
+
thunk,
|
|
16577
|
+
warmNode,
|
|
16578
|
+
// The module plan contains own creations before child calls. Reuse it
|
|
16579
|
+
// for this component's registration only when it is child-only like the
|
|
16580
|
+
// original thunk; otherwise a descendant suspend could refetch its own
|
|
16581
|
+
// earlier memo. Memoized child props stay on the existing path too.
|
|
16582
|
+
staticChildOnly:
|
|
16583
|
+
warmNode !== null &&
|
|
16584
|
+
thunk !== null &&
|
|
16585
|
+
warmMemos.length === 0 &&
|
|
16586
|
+
warmKids.every((child) => child.props.every((prop) => prop.memo == null)),
|
|
16587
|
+
};
|
|
16430
16588
|
}
|
|
16431
16589
|
|
|
16432
16590
|
// ===========================================================================
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// A direct module const initialized by Octane's createContext is the only
|
|
2
|
+
// context identity we can prove without following values between modules.
|
|
3
|
+
// Reject a name if any nested scope binds it: both compiler pipelines can
|
|
4
|
+
// then use this module-wide proof without guessing which scope owns a use().
|
|
5
|
+
export function collectProvenContextBindings(ast) {
|
|
6
|
+
const factories = new Set();
|
|
7
|
+
for (const statement of ast.body || []) {
|
|
8
|
+
if (
|
|
9
|
+
statement.type !== 'ImportDeclaration' ||
|
|
10
|
+
statement.importKind === 'type' ||
|
|
11
|
+
statement.source?.value !== 'octane'
|
|
12
|
+
)
|
|
13
|
+
continue;
|
|
14
|
+
for (const specifier of statement.specifiers || []) {
|
|
15
|
+
if (
|
|
16
|
+
specifier.type === 'ImportSpecifier' &&
|
|
17
|
+
specifier.importKind !== 'type' &&
|
|
18
|
+
(specifier.imported?.name ?? specifier.imported?.value) === 'createContext' &&
|
|
19
|
+
specifier.local?.name
|
|
20
|
+
)
|
|
21
|
+
factories.add(specifier.local.name);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (factories.size === 0) return new Set();
|
|
25
|
+
|
|
26
|
+
const contexts = new Set();
|
|
27
|
+
for (const original of ast.body || []) {
|
|
28
|
+
const declaration = unwrapExport(original);
|
|
29
|
+
if (declaration?.type !== 'VariableDeclaration' || declaration.kind !== 'const') continue;
|
|
30
|
+
for (const entry of declaration.declarations || []) {
|
|
31
|
+
if (
|
|
32
|
+
entry.id?.type === 'Identifier' &&
|
|
33
|
+
entry.init?.type === 'CallExpression' &&
|
|
34
|
+
entry.init.optional !== true &&
|
|
35
|
+
entry.init.callee?.type === 'Identifier' &&
|
|
36
|
+
factories.has(entry.init.callee.name)
|
|
37
|
+
)
|
|
38
|
+
contexts.add(entry.id.name);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (contexts.size === 0) return contexts;
|
|
42
|
+
|
|
43
|
+
const shadowed = new Set();
|
|
44
|
+
const seen = new WeakSet();
|
|
45
|
+
const bind = (pattern) => {
|
|
46
|
+
if (!pattern) return;
|
|
47
|
+
switch (pattern.type) {
|
|
48
|
+
case 'Identifier':
|
|
49
|
+
if (contexts.has(pattern.name)) shadowed.add(pattern.name);
|
|
50
|
+
break;
|
|
51
|
+
case 'ArrayPattern':
|
|
52
|
+
for (const item of pattern.elements || []) bind(item);
|
|
53
|
+
break;
|
|
54
|
+
case 'ObjectPattern':
|
|
55
|
+
for (const item of pattern.properties || [])
|
|
56
|
+
bind(item.type === 'RestElement' ? item.argument : item.value);
|
|
57
|
+
break;
|
|
58
|
+
case 'AssignmentPattern':
|
|
59
|
+
bind(pattern.left);
|
|
60
|
+
break;
|
|
61
|
+
case 'RestElement':
|
|
62
|
+
bind(pattern.argument);
|
|
63
|
+
break;
|
|
64
|
+
case 'TSParameterProperty':
|
|
65
|
+
bind(pattern.parameter);
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const scan = (node) => {
|
|
70
|
+
if (!node || typeof node !== 'object') return;
|
|
71
|
+
if (Array.isArray(node)) {
|
|
72
|
+
for (const item of node) scan(item);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (seen.has(node)) return;
|
|
76
|
+
seen.add(node);
|
|
77
|
+
if (node.type === 'ImportDeclaration') return;
|
|
78
|
+
switch (node.type) {
|
|
79
|
+
case 'VariableDeclarator':
|
|
80
|
+
bind(node.id);
|
|
81
|
+
break;
|
|
82
|
+
case 'FunctionDeclaration':
|
|
83
|
+
case 'FunctionExpression':
|
|
84
|
+
case 'ArrowFunctionExpression':
|
|
85
|
+
case 'TSDeclareFunction':
|
|
86
|
+
bind(node.id);
|
|
87
|
+
for (const param of node.params || []) bind(param);
|
|
88
|
+
break;
|
|
89
|
+
case 'ClassDeclaration':
|
|
90
|
+
case 'ClassExpression':
|
|
91
|
+
case 'TSEnumDeclaration':
|
|
92
|
+
case 'TSModuleDeclaration':
|
|
93
|
+
bind(node.id);
|
|
94
|
+
break;
|
|
95
|
+
case 'CatchClause':
|
|
96
|
+
bind(node.param);
|
|
97
|
+
break;
|
|
98
|
+
case 'JSXForExpression':
|
|
99
|
+
if (node.left?.type !== 'VariableDeclaration') bind(node.left);
|
|
100
|
+
bind(node.index);
|
|
101
|
+
break;
|
|
102
|
+
case 'JSXTryExpression':
|
|
103
|
+
bind(node.handler?.param);
|
|
104
|
+
bind(node.handler?.resetParam);
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
for (const key in node) {
|
|
108
|
+
if (
|
|
109
|
+
key === 'loc' ||
|
|
110
|
+
key === 'start' ||
|
|
111
|
+
key === 'end' ||
|
|
112
|
+
key === 'range' ||
|
|
113
|
+
key === 'parent' ||
|
|
114
|
+
key === 'metadata' ||
|
|
115
|
+
key.startsWith('_octane')
|
|
116
|
+
)
|
|
117
|
+
continue;
|
|
118
|
+
scan(node[key]);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
for (const original of ast.body || []) {
|
|
122
|
+
if (original.type === 'ImportDeclaration') continue;
|
|
123
|
+
const declaration = unwrapExport(original);
|
|
124
|
+
if (declaration?.type === 'VariableDeclaration') {
|
|
125
|
+
// These IDs belong to the module itself, including the proven
|
|
126
|
+
// contexts. Their initializers can still contain nested shadows.
|
|
127
|
+
for (const entry of declaration.declarations || []) scan(entry.init);
|
|
128
|
+
} else {
|
|
129
|
+
scan(declaration);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
for (const name of shadowed) contexts.delete(name);
|
|
133
|
+
return contexts;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function isProvenContextUse(argument, contexts) {
|
|
137
|
+
let value = argument;
|
|
138
|
+
while (
|
|
139
|
+
value?.type === 'TSAsExpression' ||
|
|
140
|
+
value?.type === 'TSTypeAssertion' ||
|
|
141
|
+
value?.type === 'TSSatisfiesExpression' ||
|
|
142
|
+
value?.type === 'TSNonNullExpression' ||
|
|
143
|
+
value?.type === 'TSInstantiationExpression' ||
|
|
144
|
+
value?.type === 'ParenthesizedExpression'
|
|
145
|
+
)
|
|
146
|
+
value = value.expression;
|
|
147
|
+
return value?.type === 'Identifier' && contexts.has(value.name);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function unwrapExport(node) {
|
|
151
|
+
return node?.type === 'ExportNamedDeclaration' || node?.type === 'ExportDefaultDeclaration'
|
|
152
|
+
? node.declaration
|
|
153
|
+
: node;
|
|
154
|
+
}
|
|
@@ -178,6 +178,7 @@ function collectHoistedVars(node, functionScope, root = true, bindingsOnly = fal
|
|
|
178
178
|
if (
|
|
179
179
|
bindingsOnly &&
|
|
180
180
|
(isFunction(node) ||
|
|
181
|
+
node.type === 'JSXSwitchExpression' ||
|
|
181
182
|
(!root && node.type === 'StaticBlock') ||
|
|
182
183
|
node.type === 'ClassDeclaration' ||
|
|
183
184
|
node.type === 'ClassExpression' ||
|
|
@@ -479,7 +480,22 @@ function buildScopes(ast, onlyImported, hookRuntimeModules, bindingsOnly = false
|
|
|
479
480
|
return;
|
|
480
481
|
}
|
|
481
482
|
|
|
482
|
-
if (
|
|
483
|
+
if (bindingsOnly && node.type === 'JSXSwitchExpression') {
|
|
484
|
+
// Each template arm is a separate callback. Its `var` declarations
|
|
485
|
+
// belong to that callback, rather than to the enclosing function or
|
|
486
|
+
// another arm with a declaration of the same name.
|
|
487
|
+
walk(node.discriminant, scope);
|
|
488
|
+
for (const switchCase of node.cases || []) {
|
|
489
|
+
walk(switchCase.test, scope);
|
|
490
|
+
const caseScope = createScope(scope, 'function');
|
|
491
|
+
collectHoistedVars(switchCase.consequent, caseScope, true, true);
|
|
492
|
+
predeclareDirect(switchCase.consequent, caseScope, hookRuntimeModules, true);
|
|
493
|
+
for (const statement of switchCase.consequent || []) walk(statement, caseScope);
|
|
494
|
+
}
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (node.type === 'SwitchStatement') {
|
|
483
499
|
// A switch body is one lexical scope shared by every unbraced case.
|
|
484
500
|
// Predeclare the direct case statements together so let/const/function
|
|
485
501
|
// captures resolve even when their declaration appears in a case list
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createLexicalAnalysis
|
|
1
|
+
import { createLexicalAnalysis } from './compile-universal.js';
|
|
2
2
|
import { analyzeRendererBoundaries } from './renderer-boundaries.js';
|
|
3
3
|
import { NATIVE_SIGNAL_NAME, NATIVE_MEMO_READ, nativeReadDiagnostic } from './native-read-facts.js';
|
|
4
4
|
export { NATIVE_SIGNAL_NAME, NATIVE_MEMO_READ } from './native-read-facts.js';
|
|
@@ -65,40 +65,19 @@ function propertyName(node, computed = false) {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* Once selected, the entire module captures actual reads, including opaque calls.
|
|
72
|
-
* Inspect runtime syntax rather than text so erased types, comments, and string
|
|
73
|
-
* contents do not opt an ordinary module into native memoization and its adapter.
|
|
68
|
+
* Native reads are an explicit module capability. A runtime signals import,
|
|
69
|
+
* including a bare import for opaque readers, opts the entire module into
|
|
70
|
+
* capturing actual reads. Type-only imports and `$` names alone do not.
|
|
74
71
|
*/
|
|
75
72
|
export function nativeReadOptions(ast, options) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (nativeReads || !node || typeof node !== 'object') return;
|
|
79
|
-
if (node.importKind === 'type' || node.exportKind === 'type') return;
|
|
80
|
-
if (
|
|
73
|
+
const nativeReads = ast.body.some(
|
|
74
|
+
(node) =>
|
|
81
75
|
node.type === 'ImportDeclaration' &&
|
|
82
|
-
node.
|
|
83
|
-
node.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (
|
|
88
|
-
((node.type === 'Identifier' || node.type === 'JSXIdentifier') && node.name.endsWith('$')) ||
|
|
89
|
-
(node.type === 'ImportDeclaration' &&
|
|
90
|
-
(node.source?.value === SIGNALS_MODULE || LOCAL_MODULES.has(node.source?.value))) ||
|
|
91
|
-
((node.type === 'MemberExpression' || node.type === 'OptionalMemberExpression') &&
|
|
92
|
-
String(propertyName(node.property, node.computed) ?? '').endsWith('$')) ||
|
|
93
|
-
(node.type === 'Property' &&
|
|
94
|
-
String(propertyName(node.key, node.computed) ?? '').endsWith('$'))
|
|
95
|
-
) {
|
|
96
|
-
nativeReads = true;
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
forEachRuntimeAstChild(node, visit);
|
|
100
|
-
}
|
|
101
|
-
visit(ast);
|
|
76
|
+
node.importKind !== 'type' &&
|
|
77
|
+
(node.source?.value === SIGNALS_MODULE || LOCAL_MODULES.has(node.source?.value)) &&
|
|
78
|
+
(node.specifiers.length === 0 ||
|
|
79
|
+
node.specifiers.some((specifier) => specifier.importKind !== 'type')),
|
|
80
|
+
);
|
|
102
81
|
return { ...options, nativeReads };
|
|
103
82
|
}
|
|
104
83
|
|
|
@@ -24,6 +24,7 @@ import { assertNativeReadDiagnostics, nativeReadOptions } from './native-read-di
|
|
|
24
24
|
import { nativeReadActivationIndex } from './native-read-codegen.js';
|
|
25
25
|
import { findManualHookProviders, manualHookWrapperParameters } from './manual-hooks.js';
|
|
26
26
|
import { findLeadingJsxImportSourcePragma } from './pragma.js';
|
|
27
|
+
import { collectProvenContextBindings, isProvenContextUse } from './context-use.js';
|
|
27
28
|
import {
|
|
28
29
|
hookMethodName,
|
|
29
30
|
hasHookMethods,
|
|
@@ -868,6 +869,10 @@ function requireParallelHelper(st, imported) {
|
|
|
868
869
|
|
|
869
870
|
function emitParallelUseRun(run, owner, st) {
|
|
870
871
|
if (run.uses.length === 0) return;
|
|
872
|
+
if (run.uses.every((entry) => isProvenContextUse(entry.arg, st.provenContextBindings))) {
|
|
873
|
+
// Leave plain source untouched when every read is a proven context.
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
871
876
|
const memoName = st.nativeReads
|
|
872
877
|
? 'nativePuMemo'
|
|
873
878
|
: st.environment === 'server'
|
|
@@ -1396,6 +1401,7 @@ export function slotHooks(source, id, options) {
|
|
|
1396
1401
|
edits: [],
|
|
1397
1402
|
decls: [],
|
|
1398
1403
|
parallelHelpers: new Map(),
|
|
1404
|
+
provenContextBindings: collectProvenContextBindings(ast),
|
|
1399
1405
|
usedNames: collectIdentifierNames(ast),
|
|
1400
1406
|
slotBaseName: null,
|
|
1401
1407
|
hookSlotsName: null,
|