octane 0.2.6 → 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/README.md +5 -0
- package/dist/cjs/index.cjs +2 -0
- package/dist/cjs/runtime.cjs +773 -385
- package/dist/cjs/runtime.server.cjs +319 -76
- package/dist/cjs/version.cjs +1 -1
- package/dist/compiler/bundler.js +6 -6
- package/dist/compiler/compile-universal.js +1 -1
- package/dist/compiler/compile.js +407 -126
- package/dist/compiler/context-use.js +154 -0
- package/dist/compiler/hook-deps.js +17 -1
- package/dist/compiler/index.d.ts +1 -3
- package/dist/compiler/native-read-diagnostics.js +21 -34
- package/dist/compiler/slot-hooks.d.ts +0 -1
- package/dist/compiler/slot-hooks.js +9 -3
- package/dist/compiler/strong-mode.js +716 -39
- package/dist/compiler/text-type-facts.js +38 -24
- package/dist/compiler/typescript.d.ts +8 -1
- package/dist/compiler/typescript.js +48 -20
- package/dist/compiler/vite.d.ts +6 -2
- package/dist/compiler/vite.js +85 -5
- package/dist/compiler/volar.LICENSES.txt +1 -1
- package/dist/compiler/volar.d.ts +1 -1
- package/dist/compiler/volar.js +3498 -3484
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -0
- package/dist/runtime.d.ts +19 -5
- package/dist/runtime.js +770 -385
- package/dist/runtime.server.d.ts +7 -6
- package/dist/runtime.server.js +319 -76
- package/dist/signals/index.d.ts +1 -1
- package/dist/signals/types.d.ts +1 -1
- package/dist/universal-core.d.ts +15 -0
- package/dist/universal-core.js +575 -83
- package/dist/version.js +1 -1
- package/package.json +3 -3
package/dist/compiler/compile.js
CHANGED
|
@@ -81,13 +81,18 @@ import { assertNoLiveClientOnlyImports } from './client-only-server.js';
|
|
|
81
81
|
import { nsForChildren, nsForSelf } from './jsx-namespace.js';
|
|
82
82
|
import { analyzeNativeChangeDiagnostics } from './native-change-diagnostics.js';
|
|
83
83
|
import { assertStrongMode } from './strong-mode.js';
|
|
84
|
-
import {
|
|
84
|
+
import {
|
|
85
|
+
assertNativeReadDiagnostics,
|
|
86
|
+
assertNativeReadOptions,
|
|
87
|
+
nativeReadOptions,
|
|
88
|
+
} from './native-read-diagnostics.js';
|
|
85
89
|
import {
|
|
86
90
|
captureNativeReadWitness,
|
|
87
91
|
wrapNativeReadScope,
|
|
88
92
|
wrapNativeWarmScope,
|
|
89
93
|
} from './native-read-codegen.js';
|
|
90
94
|
import { createTextTypeFactsLookup } from './text-type-facts.js';
|
|
95
|
+
import { collectProvenContextBindings, isProvenContextUse } from './context-use.js';
|
|
91
96
|
import { applyCssModuleConstants } from './css-module-constants.js';
|
|
92
97
|
import { assertUniversalRuntimeTarget, normalizeUniversalRuntime } from './universal-runtime.js';
|
|
93
98
|
|
|
@@ -444,8 +449,8 @@ function attrBindingHelper(bind) {
|
|
|
444
449
|
// repeating the guard and previous-value publication in every component.
|
|
445
450
|
// Certified repeated host rows select the ordinary writer and emit an inline
|
|
446
451
|
// guard instead, avoiding a call and cache publication on unchanged rows.
|
|
447
|
-
// Fresh classes
|
|
448
|
-
//
|
|
452
|
+
// Fresh classes compare their composed string in emitBindingUpdate; controlled
|
|
453
|
+
// form properties must reassert the live DOM value on every render.
|
|
449
454
|
function attrBindingUpdateHelper(bind, inlineBindingGuards = false) {
|
|
450
455
|
const helper = attrBindingHelper(bind);
|
|
451
456
|
if (inlineBindingGuards) return helper;
|
|
@@ -456,7 +461,11 @@ function attrBindingUpdateHelper(bind, inlineBindingGuards = false) {
|
|
|
456
461
|
case 'ariaAttr':
|
|
457
462
|
return `${helper}IfChanged`;
|
|
458
463
|
case 'class':
|
|
459
|
-
return bind.fresh
|
|
464
|
+
return bind.fresh
|
|
465
|
+
? bind.ns && bind.ns !== 'html'
|
|
466
|
+
? 'updateFreshClassAttr'
|
|
467
|
+
: 'updateFreshClassName'
|
|
468
|
+
: `${helper}IfChanged`;
|
|
460
469
|
default:
|
|
461
470
|
return helper;
|
|
462
471
|
}
|
|
@@ -1172,6 +1181,8 @@ const INTERNAL_CLIENT_RUNTIME_HELPERS = new Set([
|
|
|
1172
1181
|
'setAriaAttributeIfChanged',
|
|
1173
1182
|
'setClassNameIfChanged',
|
|
1174
1183
|
'setClassAttrIfChanged',
|
|
1184
|
+
'updateFreshClassName',
|
|
1185
|
+
'updateFreshClassAttr',
|
|
1175
1186
|
'textHoleUpdate',
|
|
1176
1187
|
'childTextHoleUpdate',
|
|
1177
1188
|
...HOOK_MEMO_RUNTIME_HELPERS,
|
|
@@ -5100,7 +5111,9 @@ function collectImmutableModuleFunctions(body) {
|
|
|
5100
5111
|
const declared = new Map();
|
|
5101
5112
|
for (const statement of body) {
|
|
5102
5113
|
const declaration =
|
|
5103
|
-
statement.type === 'ExportNamedDeclaration'
|
|
5114
|
+
statement.type === 'ExportNamedDeclaration' || statement.type === 'ExportDefaultDeclaration'
|
|
5115
|
+
? statement.declaration
|
|
5116
|
+
: statement;
|
|
5104
5117
|
if (declaration?.type === 'FunctionDeclaration' && declaration.id?.type === 'Identifier') {
|
|
5105
5118
|
declared.set(declaration.id.name, declaration);
|
|
5106
5119
|
}
|
|
@@ -5115,10 +5128,31 @@ function collectImmutableModuleFunctions(body) {
|
|
|
5115
5128
|
}
|
|
5116
5129
|
if (seen.has(n)) return;
|
|
5117
5130
|
seen.add(n);
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
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();
|
|
5122
5156
|
}
|
|
5123
5157
|
for (const key in n) {
|
|
5124
5158
|
if (AST_WALK_SKIP_KEYS.has(key)) continue;
|
|
@@ -8586,7 +8620,7 @@ function instrumentProfileComponents(ast, ctx) {
|
|
|
8586
8620
|
* Compile a .tsrx source string into JS targeting `octane`.
|
|
8587
8621
|
* @param {string} source
|
|
8588
8622
|
* @param {string} filename
|
|
8589
|
-
* @param {{ hmr?: boolean | 'vite' | 'webpack', mode?: 'client' | 'server', dev?: boolean, strong?: boolean, profile?: boolean, profileFilename?: string, autoMemo?: boolean, inlineHookMemo?: boolean, dataCallbackHooks?: readonly string[], valdiWriterFacts?: import('./compile-valdi.js').ValdiWriterFacts, textTypeFacts?: { version: 1, filename: string, sourceVersion: string, projectVersion: string, stringChildRanges: readonly (readonly [number, number])[] }, renderer?: { id: string, module: string, target: 'dom' | 'universal' | 'valdi', server?: string }, rendererBoundaries?: Readonly<Record<string, Readonly<Record<string, { ownerRenderer: string, childRenderer: string, prop: string, server?: string }>>>>, rendererRegistry?: Readonly<Record<string, { module: string, target: 'dom' | 'universal' | 'valdi', server?: string }>>, clientOnlyImports?: readonly unknown[], __hydratePrepared?: boolean, __hydrateBoundaryModule?: boolean, __nativeChangeDiagnostics?: readonly unknown[], __nativeChangeAnalysis?: { diagnostics: readonly unknown[], classifications: Map<number, string> } }} [options] —
|
|
8623
|
+
* @param {{ hmr?: boolean | 'vite' | 'webpack', mode?: 'client' | 'server', dev?: boolean, strong?: boolean, profile?: boolean, profileFilename?: string, autoMemo?: boolean, inlineHookMemo?: boolean, dataCallbackHooks?: readonly string[], valdiWriterFacts?: import('./compile-valdi.js').ValdiWriterFacts, textTypeFacts?: { version: 1, filename: string, sourceVersion: string, projectVersion: string, stringChildRanges: readonly (readonly [number, number])[], primitiveTextChildRanges?: readonly (readonly [number, number])[] }, renderer?: { id: string, module: string, target: 'dom' | 'universal' | 'valdi', server?: string }, rendererBoundaries?: Readonly<Record<string, Readonly<Record<string, { ownerRenderer: string, childRenderer: string, prop: string, server?: string }>>>>, rendererRegistry?: Readonly<Record<string, { module: string, target: 'dom' | 'universal' | 'valdi', server?: string }>>, clientOnlyImports?: readonly unknown[], __hydratePrepared?: boolean, __hydrateBoundaryModule?: boolean, __nativeChangeDiagnostics?: readonly unknown[], __nativeChangeAnalysis?: { diagnostics: readonly unknown[], classifications: Map<number, string> } }} [options] —
|
|
8590
8624
|
* `dev: true` emits client hydration source-location metadata (per-component
|
|
8591
8625
|
* `__s.locs`/`__s.locFile`) and, in server mode, source-located native-element
|
|
8592
8626
|
* scopes for invalid HTML nesting diagnostics. Both are strictly gated so
|
|
@@ -8634,7 +8668,6 @@ export function compileForBundler(source, filename, options) {
|
|
|
8634
8668
|
}
|
|
8635
8669
|
|
|
8636
8670
|
function compileAuthored(source, filename, options, bundlerMetadata) {
|
|
8637
|
-
assertNativeReadOptions(options);
|
|
8638
8671
|
const mode = (options && options.mode) || 'client';
|
|
8639
8672
|
if (mode !== 'client' && mode !== 'server') {
|
|
8640
8673
|
throw new Error(`Unknown compile mode "${mode}" — expected 'client' or 'server'.`);
|
|
@@ -8645,6 +8678,7 @@ function compileAuthored(source, filename, options, bundlerMetadata) {
|
|
|
8645
8678
|
);
|
|
8646
8679
|
analyzeTsrx(analyzedAst, cleanFilename);
|
|
8647
8680
|
adoptParserAst(analyzedAst);
|
|
8681
|
+
options = nativeReadOptions(analyzedAst, options);
|
|
8648
8682
|
assertNativeReadDiagnostics(analyzedAst, source, cleanFilename, options);
|
|
8649
8683
|
const strongModeEnabled =
|
|
8650
8684
|
assertStrongMode(analyzedAst, source, cleanFilename, options)?.enabled === true;
|
|
@@ -9175,6 +9209,7 @@ function compileInternal(
|
|
|
9175
9209
|
ctx.octaneImportLocals = imports.locals;
|
|
9176
9210
|
ctx.octaneImportNamespaces = imports.namespaces;
|
|
9177
9211
|
ctx.foreignImportLocals = imports.foreignLocals;
|
|
9212
|
+
ctx.provenContextBindings = collectProvenContextBindings(ast);
|
|
9178
9213
|
ctx.activityModuleAst = ast;
|
|
9179
9214
|
}
|
|
9180
9215
|
if (ctx.dev) {
|
|
@@ -10446,6 +10481,7 @@ function compileServer(
|
|
|
10446
10481
|
ctx.octaneImportLocals = imports.locals;
|
|
10447
10482
|
ctx.octaneImportNamespaces = imports.namespaces;
|
|
10448
10483
|
ctx.foreignImportLocals = imports.foreignLocals;
|
|
10484
|
+
ctx.provenContextBindings = collectProvenContextBindings(ast);
|
|
10449
10485
|
ctx.activityModuleAst = ast;
|
|
10450
10486
|
}
|
|
10451
10487
|
// M3 inherit-range exclusion set — must match the client compile's
|
|
@@ -11050,7 +11086,7 @@ function ssrCompileBodyWithMapTemps(
|
|
|
11050
11086
|
// produce mergeable text nodes:
|
|
11051
11087
|
// 'static' — a static string-literal Text (bakes / serializes as literal text)
|
|
11052
11088
|
// 'empty' — a static literal that renders NOTHING (adjacency-transparent)
|
|
11053
|
-
// 'dyn' — a
|
|
11089
|
+
// 'dyn' — a proven primitive text hole (client `<!>` + htextSwap,
|
|
11054
11090
|
// server markerless ssrText)
|
|
11055
11091
|
// 'other' — everything else (elements, renderable `{expr}` holes, control
|
|
11056
11092
|
// flow — all serialize elements or `<!--[-->…<!--]-->` ranges
|
|
@@ -11059,9 +11095,7 @@ function textAdjacencyKind(node, ctx) {
|
|
|
11059
11095
|
if (node.type !== 'Text') return 'other';
|
|
11060
11096
|
const lit = staticTextLiteral(node.expression);
|
|
11061
11097
|
if (lit !== null) return lit === '' ? 'empty' : 'static';
|
|
11062
|
-
return
|
|
11063
|
-
? 'dyn'
|
|
11064
|
-
: 'other';
|
|
11098
|
+
return isKnownTextChildExpression(node.expression, ctx.knownStringChildLocals) ? 'dyn' : 'other';
|
|
11065
11099
|
}
|
|
11066
11100
|
|
|
11067
11101
|
// Does the child at `i` have a text-producing sibling next to it (looking
|
|
@@ -11361,13 +11395,14 @@ function ssrEmitNode(
|
|
|
11361
11395
|
: null,
|
|
11362
11396
|
);
|
|
11363
11397
|
}
|
|
11364
|
-
//
|
|
11398
|
+
// String assertions / templates / string concats / proven numeric results
|
|
11399
|
+
// → a text binding on both client and server.
|
|
11365
11400
|
// Everything else (`{children}`, `{<Comp/>}`, possibly-renderable values)
|
|
11366
11401
|
// → ssrChild, which RENDERS a component/element child (and coerces a
|
|
11367
11402
|
// primitive to text) — mirrors Ripple's `{expr}` vs `{expr as string}`.
|
|
11368
11403
|
// rewriteHookCalls: a `use(thenable)` in this hole bypasses the setup
|
|
11369
11404
|
// rewrite, so key it here too (else it collides with sibling/nested use()).
|
|
11370
|
-
if (
|
|
11405
|
+
if (isKnownTextChildExpression(expr, ctx.knownStringChildLocals)) {
|
|
11371
11406
|
// ssrTextPre = ssrText + the runtime leading-'\n' protection (the value
|
|
11372
11407
|
// isn't known at compile time here).
|
|
11373
11408
|
const fn =
|
|
@@ -12253,7 +12288,7 @@ function ssrEmitElement(node, ctx, name, inlinedSubs, parentNs, cssHash, compone
|
|
|
12253
12288
|
// markerless `childTextHole` mount: a primitive is the host's bare text, an object
|
|
12254
12289
|
// still gets a `<!--[-->…<!--]-->` block). Must match the client's only-child
|
|
12255
12290
|
// markerless condition exactly so both sides agree for hydration: a single `Text`
|
|
12256
|
-
// child that is neither a static literal (baked into HTML) nor
|
|
12291
|
+
// child that is neither a static literal (baked into HTML) nor proven text
|
|
12257
12292
|
// (emitted via `ssrText`).
|
|
12258
12293
|
const onlyChild0 =
|
|
12259
12294
|
normChildren.length === 1 && normChildren[0].type === 'Text' ? normChildren[0] : null;
|
|
@@ -12265,7 +12300,7 @@ function ssrEmitElement(node, ctx, name, inlinedSubs, parentNs, cssHash, compone
|
|
|
12265
12300
|
htmlSources.length === 0 &&
|
|
12266
12301
|
onlyChild0 !== null &&
|
|
12267
12302
|
staticTextLiteral(onlyChild0.expression) === null &&
|
|
12268
|
-
!
|
|
12303
|
+
!isKnownTextChildExpression(onlyChild0.expression, ctx.knownStringChildLocals)
|
|
12269
12304
|
) {
|
|
12270
12305
|
const childHelper =
|
|
12271
12306
|
tag === 'pre' || tag === 'textarea' || tag === 'listing' ? 'ssrChildTextPre' : 'ssrChildText';
|
|
@@ -14027,6 +14062,7 @@ function compileFunctionBody(node, ctx, name, parentNs = 'html', cssHash = null,
|
|
|
14027
14062
|
ctx.inlineHookMemo && ctx.mode !== 'server' && ctx._universalRuntimeUnit == null;
|
|
14028
14063
|
ctx._puInlineLowering = inlineLowering;
|
|
14029
14064
|
let warmThunk = null;
|
|
14065
|
+
let staticChildOnly = false;
|
|
14030
14066
|
if (options && options.autoCallback) {
|
|
14031
14067
|
const creations = [];
|
|
14032
14068
|
const warmChildren = [];
|
|
@@ -14085,9 +14121,21 @@ function compileFunctionBody(node, ctx, name, parentNs = 'html', cssHash = null,
|
|
|
14085
14121
|
jsxNodes = parallelUseWalkJsx(jsxNodes, ctx, name, creations, warmChildren, [], new Set());
|
|
14086
14122
|
const warm = buildWarmArtifacts(node, ctx, name, creations, warmChildren);
|
|
14087
14123
|
warmThunk = warm.thunk;
|
|
14124
|
+
staticChildOnly = warm.staticChildOnly === true;
|
|
14088
14125
|
ctx._pendingWarm = warm.warmNode;
|
|
14089
14126
|
}
|
|
14090
|
-
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
|
+
);
|
|
14091
14139
|
|
|
14092
14140
|
// De-callback the hook memo tier (production client compile). Authored and
|
|
14093
14141
|
// auto-generated useMemo/useCallback declarations become inline flat-cache
|
|
@@ -14813,6 +14861,7 @@ function transformUniversalParallelUse(ast, ctx, metadata) {
|
|
|
14813
14861
|
// Names bound by a binding pattern (params, declarator ids). Mirrors the
|
|
14814
14862
|
// pattern handling of collectFreeIdentifiers' collectBindings.
|
|
14815
14863
|
function collectPatternNames(pat, into) {
|
|
14864
|
+
pat = unwrapTsExpr(pat);
|
|
14816
14865
|
if (!pat) return into;
|
|
14817
14866
|
switch (pat.type) {
|
|
14818
14867
|
case 'Identifier':
|
|
@@ -14929,10 +14978,8 @@ function isMountStableInlineHandler(node, ctx) {
|
|
|
14929
14978
|
return true;
|
|
14930
14979
|
}
|
|
14931
14980
|
|
|
14932
|
-
// Object/array/function literals allocate a new identity on every evaluation
|
|
14933
|
-
//
|
|
14934
|
-
// class binding path, where dropping the dead previous-value field preserves
|
|
14935
|
-
// the exact setter frequency while shrinking both code and the binding bag.
|
|
14981
|
+
// Object/array/function literals allocate a new identity on every evaluation.
|
|
14982
|
+
// Class bindings compare their composed string instead of that fresh identity.
|
|
14936
14983
|
function isFreshBindingExpr(node) {
|
|
14937
14984
|
const value = unwrapTsExpr(node);
|
|
14938
14985
|
return (
|
|
@@ -16114,8 +16161,61 @@ function isWarmSafeExpr(expr, paramNames, componentLocals, armLocals) {
|
|
|
16114
16161
|
return true;
|
|
16115
16162
|
}
|
|
16116
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
|
+
|
|
16117
16217
|
// ── Pass B: run detection + hoist + batch ───────────────────────────────────
|
|
16118
|
-
function rewriteParallelUse(statements, ctx, componentName, warmThunk) {
|
|
16218
|
+
function rewriteParallelUse(statements, ctx, componentName, warmThunk, staticPlanProps = null) {
|
|
16119
16219
|
let firstBatch = true;
|
|
16120
16220
|
const output = transformList(statements);
|
|
16121
16221
|
if (warmThunk) {
|
|
@@ -16125,17 +16225,57 @@ function rewriteParallelUse(statements, ctx, componentName, warmThunk) {
|
|
|
16125
16225
|
// setup preserves early-return reachability: an alternate returned tree must
|
|
16126
16226
|
// not activate the final template's plan. The first direct batch receives the
|
|
16127
16227
|
// same thunk below so it can still warm before setup reaches this registration.
|
|
16128
|
-
|
|
16129
|
-
|
|
16130
|
-
|
|
16131
|
-
|
|
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
|
+
),
|
|
16132
16248
|
warmThunk,
|
|
16133
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
|
+
}
|
|
16134
16274
|
const finalIndex =
|
|
16135
16275
|
output.length > 0 && output[output.length - 1].type === 'ReturnStatement'
|
|
16136
16276
|
? output.length - 1
|
|
16137
16277
|
: output.length;
|
|
16138
|
-
output.splice(finalIndex, 0, registration);
|
|
16278
|
+
output.splice(finalIndex, 0, ...registration);
|
|
16139
16279
|
}
|
|
16140
16280
|
return output;
|
|
16141
16281
|
|
|
@@ -16229,6 +16369,19 @@ function rewriteParallelUse(statements, ctx, componentName, warmThunk) {
|
|
|
16229
16369
|
for (const m of run.members) out.push(m.stmt);
|
|
16230
16370
|
return;
|
|
16231
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
|
+
}
|
|
16232
16385
|
// Hoist each creation into a temp, batch, then re-emit the original
|
|
16233
16386
|
// statements with creations swapped for the temps. Unwrap order (and
|
|
16234
16387
|
// hydration-seed order with it) is untouched.
|
|
@@ -16419,7 +16572,19 @@ function buildWarmArtifacts(node, ctx, componentName, creations, warmChildren) {
|
|
|
16419
16572
|
node,
|
|
16420
16573
|
);
|
|
16421
16574
|
}
|
|
16422
|
-
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
|
+
};
|
|
16423
16588
|
}
|
|
16424
16589
|
|
|
16425
16590
|
// ===========================================================================
|
|
@@ -18348,22 +18513,24 @@ function extractFragment(node, ctx, holeProps, parentNs = 'html') {
|
|
|
18348
18513
|
}
|
|
18349
18514
|
const hn = `h${holeProps.length}`;
|
|
18350
18515
|
if (expr && expr.type === 'TSAsExpression') {
|
|
18351
|
-
// Preserve
|
|
18516
|
+
// Preserve explicit string assertions. Numeric assertions and typed
|
|
18517
|
+
// primitive proofs need a synthetic text assertion on the renderer's
|
|
18518
|
+
// `props.hN` because it cannot see the original expression's proof.
|
|
18352
18519
|
holeProps.push(objectProp(hn, rewriteExtractedFragmentHole(expr.expression, ctx, childNs)));
|
|
18520
|
+
const annotation = isKnownTextChildExpression(expr, ctx.knownStringChildLocals)
|
|
18521
|
+
? b.ts_keyword_type('string')
|
|
18522
|
+
: expr.typeAnnotation;
|
|
18353
18523
|
newChildren.push(
|
|
18354
|
-
b.jsx_expression_container(
|
|
18355
|
-
b.ts_as(memberProps(hn, expr.expression), expr.typeAnnotation),
|
|
18356
|
-
),
|
|
18524
|
+
b.jsx_expression_container(b.ts_as(memberProps(hn, expr.expression), annotation)),
|
|
18357
18525
|
);
|
|
18358
18526
|
} else {
|
|
18359
18527
|
holeProps.push(objectProp(hn, rewriteExtractedFragmentHole(expr, ctx, childNs)));
|
|
18360
|
-
//
|
|
18361
|
-
//
|
|
18362
|
-
//
|
|
18363
|
-
//
|
|
18364
|
-
// a renderable childSlot, preserving byte-equality with the inline form.
|
|
18528
|
+
// The renderer only sees `props.hN`, so it cannot rediscover an inline
|
|
18529
|
+
// string or primitive-number proof. This synthetic cast preserves the
|
|
18530
|
+
// text binding across extraction; it is stripped from emitted JS and
|
|
18531
|
+
// does not change the authored expression or its runtime value.
|
|
18365
18532
|
const member = memberProps(hn, expr);
|
|
18366
|
-
const rendered =
|
|
18533
|
+
const rendered = isKnownTextChildExpression(expr, ctx.knownStringChildLocals)
|
|
18367
18534
|
? b.ts_as(member, b.ts_keyword_type('string'))
|
|
18368
18535
|
: member;
|
|
18369
18536
|
newChildren.push(b.jsx_expression_container(rendered));
|
|
@@ -20563,15 +20730,14 @@ function headElementArgNodes(node, index, ctx) {
|
|
|
20563
20730
|
];
|
|
20564
20731
|
}
|
|
20565
20732
|
|
|
20566
|
-
//
|
|
20567
|
-
//
|
|
20568
|
-
// selectors and
|
|
20569
|
-
// which is invisible to the resource contract (identity is the href).
|
|
20733
|
+
// Float resources contain global CSS. Preserve the parser's CSS source rather
|
|
20734
|
+
// than sending it through the scoped stylesheet renderer, which prunes unused
|
|
20735
|
+
// selectors and renames keyframes even without the scoping analysis pass.
|
|
20570
20736
|
/** @param {any} el @returns {any} */
|
|
20571
20737
|
function styleResourceCssExpression(el) {
|
|
20572
20738
|
const sheet = (el.children || []).find((c) => c && c.type === 'StyleSheet');
|
|
20573
20739
|
if (!sheet) return b.literal('');
|
|
20574
|
-
const css =
|
|
20740
|
+
const css = sheet.source;
|
|
20575
20741
|
return b.literal(css, JSON.stringify(css), el);
|
|
20576
20742
|
}
|
|
20577
20743
|
|
|
@@ -21180,17 +21346,30 @@ function staticTextLiteral(node) {
|
|
|
21180
21346
|
// to inherit a type proof. Ordinary copy-on-write expression rewrites preserve
|
|
21181
21347
|
// the metadata, and extractFragment explicitly carries it onto its props hole.
|
|
21182
21348
|
function applyStringChildProofs(ast, source, filename, facts) {
|
|
21183
|
-
const
|
|
21349
|
+
const typeRanges = createTextTypeFactsLookup(facts, filename, source);
|
|
21350
|
+
const typedRanges = typeRanges?.stringRanges ?? null;
|
|
21351
|
+
const primitiveRanges = typeRanges?.primitiveRanges ?? null;
|
|
21184
21352
|
// Avoid another AST walk for the overwhelmingly common syntax-only module.
|
|
21185
|
-
// Escaped identifiers can spell
|
|
21186
|
-
//
|
|
21187
|
-
const
|
|
21188
|
-
|
|
21189
|
-
|
|
21190
|
-
|
|
21191
|
-
|
|
21353
|
+
// Escaped identifiers can spell these built-ins too. The AST remains the
|
|
21354
|
+
// authority for calls; these substring checks only skip common files.
|
|
21355
|
+
const inspectIntrinsicCalls =
|
|
21356
|
+
source.includes('String') ||
|
|
21357
|
+
source.includes('Number') ||
|
|
21358
|
+
source.includes('BigInt') ||
|
|
21359
|
+
source.includes('Date') ||
|
|
21360
|
+
source.includes('\\u');
|
|
21361
|
+
if (
|
|
21362
|
+
(typedRanges === null || typedRanges.size === 0) &&
|
|
21363
|
+
(primitiveRanges === null || primitiveRanges.size === 0) &&
|
|
21364
|
+
!inspectIntrinsicCalls
|
|
21365
|
+
) {
|
|
21366
|
+
return ast;
|
|
21367
|
+
}
|
|
21368
|
+
const stringProofs = new Set();
|
|
21369
|
+
const primitiveProofs = new Set();
|
|
21370
|
+
const intrinsicCalls = [];
|
|
21192
21371
|
const writes = [];
|
|
21193
|
-
|
|
21372
|
+
const ambientIntrinsicValues = new Set();
|
|
21194
21373
|
const seen = new WeakSet();
|
|
21195
21374
|
const collect = (node, parent = null, key = null) => {
|
|
21196
21375
|
if (node === null || typeof node !== 'object') return;
|
|
@@ -21201,7 +21380,7 @@ function applyStringChildProofs(ast, source, filename, facts) {
|
|
|
21201
21380
|
if (seen.has(node)) return;
|
|
21202
21381
|
seen.add(node);
|
|
21203
21382
|
if (
|
|
21204
|
-
|
|
21383
|
+
(typedRanges !== null || primitiveRanges !== null) &&
|
|
21205
21384
|
node.type === 'JSXExpressionContainer' &&
|
|
21206
21385
|
((key === 'children' &&
|
|
21207
21386
|
(parent?.type === 'JSXElement' ||
|
|
@@ -21212,16 +21391,17 @@ function applyStringChildProofs(ast, source, filename, facts) {
|
|
|
21212
21391
|
) {
|
|
21213
21392
|
const expr = node.expression;
|
|
21214
21393
|
const range = `${expr?.start}:${expr?.end}`;
|
|
21215
|
-
if (
|
|
21394
|
+
if (typedRanges?.delete(range)) stringProofs.add(expr);
|
|
21395
|
+
if (primitiveRanges?.delete(range)) primitiveProofs.add(expr);
|
|
21216
21396
|
}
|
|
21217
|
-
if (
|
|
21397
|
+
if (inspectIntrinsicCalls) {
|
|
21218
21398
|
if (
|
|
21219
21399
|
node.type === 'CallExpression' &&
|
|
21220
21400
|
node.optional !== true &&
|
|
21221
21401
|
node.callee?.type === 'Identifier' &&
|
|
21222
|
-
node.callee.name
|
|
21402
|
+
TEXT_INTRINSICS.has(node.callee.name)
|
|
21223
21403
|
) {
|
|
21224
|
-
|
|
21404
|
+
intrinsicCalls.push(node);
|
|
21225
21405
|
}
|
|
21226
21406
|
if (node.type === 'AssignmentExpression') writes.push(node.left);
|
|
21227
21407
|
else if (node.type === 'UpdateExpression') writes.push(node.argument);
|
|
@@ -21236,11 +21416,13 @@ function applyStringChildProofs(ast, source, filename, facts) {
|
|
|
21236
21416
|
// Ambient VALUE declarations can describe a replaced global. Type-only
|
|
21237
21417
|
// imports/interfaces do not shadow the JavaScript built-in.
|
|
21238
21418
|
if (node.type === 'TSDeclareFunction' || node.declare === true) {
|
|
21239
|
-
if (node.id?.name
|
|
21419
|
+
if (TEXT_INTRINSICS.has(node.id?.name)) ambientIntrinsicValues.add(node.id.name);
|
|
21240
21420
|
if (node.type === 'VariableDeclaration') {
|
|
21241
21421
|
const names = new Set();
|
|
21242
21422
|
for (const declaration of node.declarations || []) collectBindings(declaration.id, names);
|
|
21243
|
-
|
|
21423
|
+
for (const name of names) {
|
|
21424
|
+
if (TEXT_INTRINSICS.has(name)) ambientIntrinsicValues.add(name);
|
|
21425
|
+
}
|
|
21244
21426
|
}
|
|
21245
21427
|
}
|
|
21246
21428
|
}
|
|
@@ -21251,33 +21433,43 @@ function applyStringChildProofs(ast, source, filename, facts) {
|
|
|
21251
21433
|
}
|
|
21252
21434
|
};
|
|
21253
21435
|
collect(ast);
|
|
21254
|
-
|
|
21436
|
+
const remainingRange =
|
|
21437
|
+
typedRanges?.values().next().value ?? primitiveRanges?.values().next().value;
|
|
21438
|
+
if (remainingRange !== undefined) {
|
|
21255
21439
|
throw new Error(
|
|
21256
|
-
`Invalid textTypeFacts for ${JSON.stringify(filename)}: range ${
|
|
21440
|
+
`Invalid textTypeFacts for ${JSON.stringify(filename)}: range ${remainingRange} is not an authored JSX child expression.`,
|
|
21257
21441
|
);
|
|
21258
21442
|
}
|
|
21259
21443
|
let lexical = null;
|
|
21260
|
-
if (
|
|
21444
|
+
if (
|
|
21445
|
+
intrinsicCalls.length > 0 ||
|
|
21446
|
+
((stringProofs.size > 0 || primitiveProofs.size > 0) && writes.length > 0)
|
|
21447
|
+
) {
|
|
21261
21448
|
// The existing scope analysis understands var hoisting, parameter defaults,
|
|
21262
21449
|
// imports, catch bindings, and TSRX @for/@try scopes. Pay for it only when
|
|
21263
21450
|
// an intrinsic candidate or a possible proof-invalidating write needs it.
|
|
21264
21451
|
lexical = createLexicalAnalysis(ast);
|
|
21265
|
-
if (writes.some((target) =>
|
|
21266
|
-
// TypeScript still
|
|
21267
|
-
// after an asserted replacement. Decline
|
|
21452
|
+
if (writes.some((target) => writesGlobalTextIntrinsic(target, lexical))) {
|
|
21453
|
+
// TypeScript still uses a built-in constructor's declared primitive
|
|
21454
|
+
// return type after an asserted replacement. Decline all new proofs in this
|
|
21268
21455
|
// rare module rather than attempting incomplete value-taint analysis.
|
|
21269
21456
|
// Supplied ranges were still validated above; explicit text syntax keeps
|
|
21270
21457
|
// its existing coercion meaning through isKnownStringExpression.
|
|
21271
21458
|
return ast;
|
|
21272
21459
|
}
|
|
21273
21460
|
}
|
|
21274
|
-
if (lexical !== null
|
|
21275
|
-
for (const call of
|
|
21461
|
+
if (lexical !== null) {
|
|
21462
|
+
for (const call of intrinsicCalls) {
|
|
21463
|
+
const name = call.callee.name;
|
|
21464
|
+
if (ambientIntrinsicValues.has(name)) continue;
|
|
21276
21465
|
const scope = lexical.nodeScopes.get(call);
|
|
21277
|
-
if (scope !== undefined && !lexical.isBound(scope,
|
|
21466
|
+
if (scope !== undefined && !lexical.isBound(scope, name)) {
|
|
21467
|
+
if (name === 'String' || name === 'Date') stringProofs.add(call);
|
|
21468
|
+
else primitiveProofs.add(call);
|
|
21469
|
+
}
|
|
21278
21470
|
}
|
|
21279
21471
|
}
|
|
21280
|
-
if (
|
|
21472
|
+
if (stringProofs.size === 0 && primitiveProofs.size === 0) return ast;
|
|
21281
21473
|
const rewritten = new WeakMap();
|
|
21282
21474
|
const rewrite = (node) => {
|
|
21283
21475
|
if (node === null || typeof node !== 'object') return node;
|
|
@@ -21303,8 +21495,10 @@ function applyStringChildProofs(ast, source, filename, facts) {
|
|
|
21303
21495
|
out[key] = next;
|
|
21304
21496
|
}
|
|
21305
21497
|
}
|
|
21306
|
-
if (
|
|
21498
|
+
if (stringProofs.has(node)) {
|
|
21307
21499
|
out = { ...out, metadata: { ...out.metadata, octane_string_child: true } };
|
|
21500
|
+
} else if (primitiveProofs.has(node)) {
|
|
21501
|
+
out = { ...out, metadata: { ...out.metadata, octane_primitive_text_child: true } };
|
|
21308
21502
|
}
|
|
21309
21503
|
}
|
|
21310
21504
|
rewritten.set(node, out);
|
|
@@ -21313,10 +21507,12 @@ function applyStringChildProofs(ast, source, filename, facts) {
|
|
|
21313
21507
|
return rewrite(ast);
|
|
21314
21508
|
}
|
|
21315
21509
|
|
|
21316
|
-
|
|
21317
|
-
|
|
21318
|
-
//
|
|
21319
|
-
|
|
21510
|
+
const TEXT_INTRINSICS = new Set(['String', 'Number', 'BigInt', 'Date']);
|
|
21511
|
+
|
|
21512
|
+
// A visible replacement of a global constructor invalidates inferred text
|
|
21513
|
+
// proofs for the module. Calls still evaluate the authored callee: a replacement
|
|
21514
|
+
// may return an element rather than the built-in's primitive result.
|
|
21515
|
+
function writesGlobalTextIntrinsic(target, lexical) {
|
|
21320
21516
|
if (!target || typeof target !== 'object') return false;
|
|
21321
21517
|
if (
|
|
21322
21518
|
target.type === 'TSAsExpression' ||
|
|
@@ -21324,20 +21520,20 @@ function writesGlobalString(target, lexical) {
|
|
|
21324
21520
|
target.type === 'TSNonNullExpression' ||
|
|
21325
21521
|
target.type === 'ParenthesizedExpression'
|
|
21326
21522
|
) {
|
|
21327
|
-
return
|
|
21523
|
+
return writesGlobalTextIntrinsic(target.expression, lexical);
|
|
21328
21524
|
}
|
|
21329
21525
|
const unbound = (node, name) => {
|
|
21330
21526
|
const scope = lexical.nodeScopes.get(node);
|
|
21331
21527
|
return scope !== undefined && !lexical.isBound(scope, name);
|
|
21332
21528
|
};
|
|
21333
21529
|
if (target.type === 'Identifier') {
|
|
21334
|
-
return target.name
|
|
21530
|
+
return TEXT_INTRINSICS.has(target.name) && unbound(target, target.name);
|
|
21335
21531
|
}
|
|
21336
21532
|
if (target.type === 'MemberExpression') {
|
|
21337
21533
|
const name = target.computed ? target.property?.value : target.property?.name;
|
|
21338
21534
|
const object = target.object;
|
|
21339
21535
|
return (
|
|
21340
|
-
name
|
|
21536
|
+
TEXT_INTRINSICS.has(name) &&
|
|
21341
21537
|
object?.type === 'Identifier' &&
|
|
21342
21538
|
(object.name === 'globalThis' ||
|
|
21343
21539
|
object.name === 'window' ||
|
|
@@ -21346,14 +21542,14 @@ function writesGlobalString(target, lexical) {
|
|
|
21346
21542
|
unbound(object, object.name)
|
|
21347
21543
|
);
|
|
21348
21544
|
}
|
|
21349
|
-
if (target.type === 'AssignmentPattern') return
|
|
21350
|
-
if (target.type === 'RestElement') return
|
|
21545
|
+
if (target.type === 'AssignmentPattern') return writesGlobalTextIntrinsic(target.left, lexical);
|
|
21546
|
+
if (target.type === 'RestElement') return writesGlobalTextIntrinsic(target.argument, lexical);
|
|
21351
21547
|
if (target.type === 'ArrayPattern') {
|
|
21352
|
-
return (target.elements || []).some((element) =>
|
|
21548
|
+
return (target.elements || []).some((element) => writesGlobalTextIntrinsic(element, lexical));
|
|
21353
21549
|
}
|
|
21354
21550
|
if (target.type === 'ObjectPattern') {
|
|
21355
21551
|
return (target.properties || []).some((property) =>
|
|
21356
|
-
|
|
21552
|
+
writesGlobalTextIntrinsic(property.argument ?? property.value, lexical),
|
|
21357
21553
|
);
|
|
21358
21554
|
}
|
|
21359
21555
|
return false;
|
|
@@ -21366,6 +21562,7 @@ function writesGlobalString(target, lexical) {
|
|
|
21366
21562
|
// Recognised shapes:
|
|
21367
21563
|
// - String Literal: 'foo' / "bar"
|
|
21368
21564
|
// - TemplateLiteral: `${x}-${y}` (always coerces to string)
|
|
21565
|
+
// - `typeof x`: always a string, even for unknown x
|
|
21369
21566
|
// - `as string` / `<string>x`: user-asserted string-typed expression
|
|
21370
21567
|
// - `satisfies string`: same intent
|
|
21371
21568
|
// - Wrappers (`!`, instantiation): peel and check inside
|
|
@@ -21380,6 +21577,7 @@ function isKnownStringExpression(node, locals, childProofs = false) {
|
|
|
21380
21577
|
return typeof node.value === 'string';
|
|
21381
21578
|
}
|
|
21382
21579
|
if (node.type === 'TemplateLiteral') return true;
|
|
21580
|
+
if (node.type === 'UnaryExpression' && node.operator === 'typeof') return true;
|
|
21383
21581
|
// An identifier the compiler has tracked back to a string in this component's
|
|
21384
21582
|
// scope: a `const` bound to a provably-string expression, a `const x: string`,
|
|
21385
21583
|
// or a `string`-typed param — see collectKnownStringLocals. `locals` is
|
|
@@ -21423,6 +21621,15 @@ function isKnownStringExpression(node, locals, childProofs = false) {
|
|
|
21423
21621
|
isKnownStringExpression(node.alternate, locals, childProofs)
|
|
21424
21622
|
);
|
|
21425
21623
|
}
|
|
21624
|
+
if (node.type === 'LogicalExpression') {
|
|
21625
|
+
return (
|
|
21626
|
+
isKnownStringExpression(node.left, locals, childProofs) &&
|
|
21627
|
+
isKnownStringExpression(node.right, locals, childProofs)
|
|
21628
|
+
);
|
|
21629
|
+
}
|
|
21630
|
+
if (node.type === 'SequenceExpression') {
|
|
21631
|
+
return isKnownStringExpression(node.expressions.at(-1), locals, childProofs);
|
|
21632
|
+
}
|
|
21426
21633
|
return false;
|
|
21427
21634
|
}
|
|
21428
21635
|
|
|
@@ -21433,6 +21640,75 @@ function isKnownStringChildExpression(node, locals) {
|
|
|
21433
21640
|
return isKnownStringExpression(node, locals, true);
|
|
21434
21641
|
}
|
|
21435
21642
|
|
|
21643
|
+
// Only child positions may use the primitive-text binding for numbers and
|
|
21644
|
+
// bigints. These expressions are primitives (or throw before returning) under
|
|
21645
|
+
// JavaScript's own operators. Keep the proof distinct from "known string":
|
|
21646
|
+
// numeric results cannot establish that `value + anotherValue` concatenates.
|
|
21647
|
+
function isKnownTextChildExpression(node, stringLocals) {
|
|
21648
|
+
if (isKnownStringChildExpression(node, stringLocals)) return true;
|
|
21649
|
+
if (node == null || typeof node !== 'object') return false;
|
|
21650
|
+
if (node.metadata?.octane_primitive_text_child === true) return true;
|
|
21651
|
+
if (node.type === 'Literal' || node.type === 'NumericLiteral' || node.type === 'BigIntLiteral') {
|
|
21652
|
+
return (
|
|
21653
|
+
typeof node.value === 'number' ||
|
|
21654
|
+
typeof node.value === 'bigint' ||
|
|
21655
|
+
node.type === 'BigIntLiteral'
|
|
21656
|
+
);
|
|
21657
|
+
}
|
|
21658
|
+
if (node.type === 'UnaryExpression') {
|
|
21659
|
+
return node.operator === '+' || node.operator === '-' || node.operator === '~';
|
|
21660
|
+
}
|
|
21661
|
+
if (node.type === 'BinaryExpression') {
|
|
21662
|
+
if (node.operator === '+') {
|
|
21663
|
+
return (
|
|
21664
|
+
isKnownTextChildExpression(node.left, stringLocals) &&
|
|
21665
|
+
isKnownTextChildExpression(node.right, stringLocals)
|
|
21666
|
+
);
|
|
21667
|
+
}
|
|
21668
|
+
return NUMERIC_TEXT_OPERATORS.has(node.operator);
|
|
21669
|
+
}
|
|
21670
|
+
if (node.type === 'ConditionalExpression') {
|
|
21671
|
+
return (
|
|
21672
|
+
isKnownTextChildExpression(node.consequent, stringLocals) &&
|
|
21673
|
+
isKnownTextChildExpression(node.alternate, stringLocals)
|
|
21674
|
+
);
|
|
21675
|
+
}
|
|
21676
|
+
if (node.type === 'LogicalExpression') {
|
|
21677
|
+
return (
|
|
21678
|
+
isKnownTextChildExpression(node.left, stringLocals) &&
|
|
21679
|
+
isKnownTextChildExpression(node.right, stringLocals)
|
|
21680
|
+
);
|
|
21681
|
+
}
|
|
21682
|
+
if (node.type === 'SequenceExpression') {
|
|
21683
|
+
return isKnownTextChildExpression(node.expressions.at(-1), stringLocals);
|
|
21684
|
+
}
|
|
21685
|
+
if (
|
|
21686
|
+
node.type === 'TSAsExpression' ||
|
|
21687
|
+
node.type === 'TSTypeAssertion' ||
|
|
21688
|
+
node.type === 'TSSatisfiesExpression' ||
|
|
21689
|
+
node.type === 'TSNonNullExpression' ||
|
|
21690
|
+
node.type === 'TSInstantiationExpression' ||
|
|
21691
|
+
node.type === 'ParenthesizedExpression'
|
|
21692
|
+
) {
|
|
21693
|
+
return isKnownTextChildExpression(node.expression, stringLocals);
|
|
21694
|
+
}
|
|
21695
|
+
return false;
|
|
21696
|
+
}
|
|
21697
|
+
|
|
21698
|
+
const NUMERIC_TEXT_OPERATORS = new Set([
|
|
21699
|
+
'-',
|
|
21700
|
+
'*',
|
|
21701
|
+
'/',
|
|
21702
|
+
'%',
|
|
21703
|
+
'**',
|
|
21704
|
+
'<<',
|
|
21705
|
+
'>>',
|
|
21706
|
+
'>>>',
|
|
21707
|
+
'|',
|
|
21708
|
+
'&',
|
|
21709
|
+
'^',
|
|
21710
|
+
]);
|
|
21711
|
+
|
|
21436
21712
|
// Annotation check: does a TS type annotation resolve to `string`? Accepts both a
|
|
21437
21713
|
// bare type node and a `TSTypeAnnotation` wrapper (`x: string`).
|
|
21438
21714
|
function isStringTypeAnnotation(ann) {
|
|
@@ -22020,7 +22296,7 @@ function planJsx(
|
|
|
22020
22296
|
const coalesceChildRoot =
|
|
22021
22297
|
jsxNodes.length === 1 &&
|
|
22022
22298
|
((jsxNodes[0].type === 'Text' &&
|
|
22023
|
-
!
|
|
22299
|
+
!isKnownTextChildExpression(jsxNodes[0].expression, ctx.knownStringChildLocals)) ||
|
|
22024
22300
|
jsxNodes[0].type === 'TSRXExpression');
|
|
22025
22301
|
// Top-level control-flow directives (@if/@for/@switch/@try/<Activity>). In a
|
|
22026
22302
|
// body that ALSO has static template roots, each construct emits a `<!>`
|
|
@@ -22467,6 +22743,8 @@ function planJsx(
|
|
|
22467
22743
|
// a mount-only import for deferred writes.
|
|
22468
22744
|
const attrHelper = attrBindingHelper(b);
|
|
22469
22745
|
if (attrHelper !== null) {
|
|
22746
|
+
if (b.kind === 'class' && b.fresh && (!b.deferred || (!b.mountOnly && inlineBindingGuards)))
|
|
22747
|
+
ctx.runtimeNeeded.add('normalizeClass');
|
|
22470
22748
|
if (!b.deferred) {
|
|
22471
22749
|
ctx.runtimeNeeded.add(attrHelper);
|
|
22472
22750
|
registerAttrLoweringOrigin(ctx, b.nameOrigin, attrHelper, b.name);
|
|
@@ -23876,12 +24154,10 @@ function commitSourceRows(sources, valueOf) {
|
|
|
23876
24154
|
// is byte-identical to the old unconditional mount write).
|
|
23877
24155
|
function emitDeferredMount(bind, elVar, bag) {
|
|
23878
24156
|
// Whole-object `style` diffs on `_sty`; scalar styles and other values on `_prev`.
|
|
23879
|
-
|
|
23880
|
-
|
|
23881
|
-
|
|
23882
|
-
|
|
23883
|
-
);
|
|
23884
|
-
}
|
|
24157
|
+
bag.constField(
|
|
24158
|
+
bind.kind === 'style' ? `_sty$${bind.id}` : `_prev$${bind.id}`,
|
|
24159
|
+
bind.kind === 'styleProperty' ? 'style-unset' : 'undefined',
|
|
24160
|
+
);
|
|
23885
24161
|
const key = `_el$${bind.id}`;
|
|
23886
24162
|
if (!bag.host(key, elVar)) return null;
|
|
23887
24163
|
return inheritOriginLoc(
|
|
@@ -24089,9 +24365,15 @@ function emitBindingMount(bind, elVar, bag) {
|
|
|
24089
24365
|
}
|
|
24090
24366
|
case 'class': {
|
|
24091
24367
|
// On SVG/MathML hosts the `className` property is read-only — fall back
|
|
24092
|
-
// to setAttribute.
|
|
24093
|
-
|
|
24094
|
-
|
|
24368
|
+
// to setAttribute. A fresh literal is truthy even when it composes to
|
|
24369
|
+
// an empty string, so caching its composition preserves class="".
|
|
24370
|
+
const value = bind.fresh ? b.call('_$normalizeClass', bind.expr) : bind.expr;
|
|
24371
|
+
const body = [
|
|
24372
|
+
b.const('_v', value),
|
|
24373
|
+
b.stmt(b.call(callee(), el(), V())),
|
|
24374
|
+
...mountHost(),
|
|
24375
|
+
b.stmt(b.assignment('=', local(`_prev$${bind.id}`), V())),
|
|
24376
|
+
];
|
|
24095
24377
|
return st(b.block(body));
|
|
24096
24378
|
}
|
|
24097
24379
|
case 'style': {
|
|
@@ -24386,13 +24668,14 @@ function emitBindingUpdate(bind, bag, inlineBindingGuards = false) {
|
|
|
24386
24668
|
return st(b.stmt(b.call(callee(), F('_el'), bind.expr)));
|
|
24387
24669
|
}
|
|
24388
24670
|
case 'class': {
|
|
24389
|
-
|
|
24390
|
-
|
|
24391
|
-
|
|
24671
|
+
// Recompose fresh literal values on every render, including when their
|
|
24672
|
+
// members have getters, but skip the DOM write for an unchanged class.
|
|
24673
|
+
const value =
|
|
24674
|
+
bind.fresh && inlineBindingGuards ? b.call('_$normalizeClass', bind.expr) : bind.expr;
|
|
24392
24675
|
if (inlineBindingGuards) {
|
|
24393
24676
|
return st(
|
|
24394
24677
|
b.block([
|
|
24395
|
-
b.const('_v',
|
|
24678
|
+
b.const('_v', value),
|
|
24396
24679
|
b.if(
|
|
24397
24680
|
b.binary('!==', F('_prev'), V()),
|
|
24398
24681
|
b.block([
|
|
@@ -24405,7 +24688,7 @@ function emitBindingUpdate(bind, bag, inlineBindingGuards = false) {
|
|
|
24405
24688
|
);
|
|
24406
24689
|
}
|
|
24407
24690
|
return st(
|
|
24408
|
-
b.stmt(b.assignment('=', F('_prev'), b.call(callee(),
|
|
24691
|
+
b.stmt(b.assignment('=', F('_prev'), b.call(callee(), value, F('_prev'), F('_el')))),
|
|
24409
24692
|
);
|
|
24410
24693
|
}
|
|
24411
24694
|
case 'style': {
|
|
@@ -24847,12 +25130,11 @@ function emitNodeHtml(
|
|
|
24847
25130
|
cssHash = null,
|
|
24848
25131
|
) {
|
|
24849
25132
|
if (node.type === 'Text') {
|
|
24850
|
-
if (
|
|
25133
|
+
if (isKnownTextChildExpression(node.expression, ctx.knownStringChildLocals)) {
|
|
24851
25134
|
bindings.push({
|
|
24852
25135
|
id: bindings.length,
|
|
24853
25136
|
kind: 'text',
|
|
24854
25137
|
expr: resolveStyleExpr(node.expression, cssHash),
|
|
24855
|
-
knownString: true,
|
|
24856
25138
|
path: path.slice(0, -1),
|
|
24857
25139
|
childIndex: path[path.length - 1],
|
|
24858
25140
|
});
|
|
@@ -26050,12 +26332,11 @@ function emitElementHtml(
|
|
|
26050
26332
|
]
|
|
26051
26333
|
: null;
|
|
26052
26334
|
appendTemplatePart(html, escaped, 'text', origins);
|
|
26053
|
-
} else if (
|
|
26335
|
+
} else if (isKnownTextChildExpression(txtChild.expression, ctx.knownStringChildLocals)) {
|
|
26054
26336
|
bindings.push({
|
|
26055
26337
|
id: bindings.length,
|
|
26056
26338
|
kind: 'textOnlyChild',
|
|
26057
26339
|
expr: resolveStyleExpr(txtChild.expression, cssHash),
|
|
26058
|
-
knownString: true,
|
|
26059
26340
|
path,
|
|
26060
26341
|
});
|
|
26061
26342
|
// The element stays empty in the template — runtime appends a Text node.
|
|
@@ -26224,12 +26505,11 @@ function emitElementHtml(
|
|
|
26224
26505
|
appendTemplatePart(html, escaped, 'text', origins);
|
|
26225
26506
|
prevBakedText = true;
|
|
26226
26507
|
if (!prevBaked) childIdx++;
|
|
26227
|
-
} else if (
|
|
26508
|
+
} else if (isKnownTextChildExpression(child.expression, ctx.knownStringChildLocals)) {
|
|
26228
26509
|
bindings.push({
|
|
26229
26510
|
id: bindings.length,
|
|
26230
26511
|
kind: 'text',
|
|
26231
26512
|
expr: resolveStyleExpr(child.expression, cssHash),
|
|
26232
|
-
knownString: true,
|
|
26233
26513
|
path,
|
|
26234
26514
|
childIndex: childIdx,
|
|
26235
26515
|
// A text-producing neighbour → the server emits a `<!-- -->`
|
|
@@ -26396,7 +26676,7 @@ function emitElementHtml(
|
|
|
26396
26676
|
// - `{createPortal(BODY, TARGET, PROPS?)}` → portal() call
|
|
26397
26677
|
// - `{cond ? <JSX/> : <JSX/>}` → lowered to ifBlock (so the branches
|
|
26398
26678
|
// mount real DOM, not stringified text)
|
|
26399
|
-
// - a
|
|
26679
|
+
// - a proven primitive text expression → text-hole binding
|
|
26400
26680
|
// - anything else → renderable childSlot hole
|
|
26401
26681
|
const expr = child.expression;
|
|
26402
26682
|
if (isCreatePortalCall(expr)) {
|
|
@@ -26424,12 +26704,11 @@ function emitElementHtml(
|
|
|
26424
26704
|
ifCalls.push(ic);
|
|
26425
26705
|
appendTemplatePart(html, '<!>', 'anchor');
|
|
26426
26706
|
childIdx++;
|
|
26427
|
-
} else if (
|
|
26707
|
+
} else if (isKnownTextChildExpression(expr, ctx.knownStringChildLocals)) {
|
|
26428
26708
|
bindings.push({
|
|
26429
26709
|
id: bindings.length,
|
|
26430
26710
|
kind: 'text',
|
|
26431
26711
|
expr: tsrxExprNode(resolveStyleExpr(expr, cssHash), ctx, componentName, inlinedSubs),
|
|
26432
|
-
knownString: true,
|
|
26433
26712
|
path,
|
|
26434
26713
|
childIndex: childIdx,
|
|
26435
26714
|
});
|
|
@@ -26555,7 +26834,7 @@ function helperCaptures(ctx, stmts, params) {
|
|
|
26555
26834
|
|
|
26556
26835
|
// A construct's helpers (then+else, all switch cases, try+pending+catch,
|
|
26557
26836
|
// item+empty) share ONE env tuple — `block.extra` is per construct block and
|
|
26558
|
-
// every helper
|
|
26837
|
+
// every helper reads from the same layout — so the emitted array is the
|
|
26559
26838
|
// sorted UNION of each body's captures. Null propagates (no component
|
|
26560
26839
|
// context → all of the construct's helpers stay inline).
|
|
26561
26840
|
function unionEnv(ctx, bodies) {
|
|
@@ -26578,7 +26857,7 @@ function unionEnv(ctx, bodies) {
|
|
|
26578
26857
|
// `__extra` ABI slot (renderBlock forwards `block.extra` as the third body
|
|
26579
26858
|
// arg; the compiled call site passes the current values every parent render):
|
|
26580
26859
|
//
|
|
26581
|
-
// function __then$0(__props, __s, __extra) { const
|
|
26860
|
+
// function __then$0(__props, __s, __extra) { const label = __extra[0]; … }
|
|
26582
26861
|
//
|
|
26583
26862
|
// `envNames: null` keeps the legacy placement — the helper is emitted INSIDE
|
|
26584
26863
|
// the component function so its closures capture the parent's locals
|
|
@@ -26603,25 +26882,27 @@ function hoistBodyHelper(
|
|
|
26603
26882
|
const ownEnv = new Set(ownEnvNames || []);
|
|
26604
26883
|
let bodyStmts = stmts;
|
|
26605
26884
|
if (envNames && envNames.length > 0) {
|
|
26606
|
-
//
|
|
26607
|
-
//
|
|
26608
|
-
//
|
|
26609
|
-
//
|
|
26610
|
-
//
|
|
26885
|
+
// Read only this helper's captures at their positions in the construct's
|
|
26886
|
+
// shared UNION tuple. Union-only names must not become local bindings: an
|
|
26887
|
+
// arm may have a same-named local shadowing another arm's capture. Direct
|
|
26888
|
+
// indexed reads avoid iterator work before these helpers optimize.
|
|
26889
|
+
// Each binding maps to the construct body it feeds.
|
|
26611
26890
|
const envOrigin =
|
|
26612
26891
|
(Array.isArray(stmts) && stmts.find((s) => s != null && s.loc != null)) ||
|
|
26613
26892
|
(params || []).find((p) => p != null && p.loc != null) ||
|
|
26614
26893
|
null;
|
|
26615
|
-
|
|
26616
|
-
|
|
26617
|
-
|
|
26618
|
-
|
|
26619
|
-
|
|
26894
|
+
const captures = [];
|
|
26895
|
+
for (let i = 0; i < envNames.length; i++) {
|
|
26896
|
+
const name = envNames[i];
|
|
26897
|
+
if (!ownEnv.has(name)) continue;
|
|
26898
|
+
captures.push(
|
|
26899
|
+
inheritOriginLoc(
|
|
26900
|
+
b.const(b.id(name), b.member(b.id('__extra'), b.literal(i), true)),
|
|
26901
|
+
envOrigin,
|
|
26620
26902
|
),
|
|
26621
|
-
|
|
26622
|
-
|
|
26623
|
-
|
|
26624
|
-
];
|
|
26903
|
+
);
|
|
26904
|
+
}
|
|
26905
|
+
bodyStmts = [...captures, ...stmts];
|
|
26625
26906
|
}
|
|
26626
26907
|
// The synthetic helper shell maps to the construct body it hoists.
|
|
26627
26908
|
const fakeOrigin =
|
|
@@ -26650,7 +26931,7 @@ function hoistBodyHelper(
|
|
|
26650
26931
|
// Module-scope placement. Nested constructs compiled INSIDE this body
|
|
26651
26932
|
// compute THEIR captures against the names visible at their call sites —
|
|
26652
26933
|
// which live in THIS compiled body: the component's locals extended with
|
|
26653
|
-
// this helper's params, env
|
|
26934
|
+
// this helper's params, env bindings, and body-level locals. (A nested
|
|
26654
26935
|
// helper's env values are emitted as plain identifiers at its call site.)
|
|
26655
26936
|
const prevLocals = ctx.currentComponentLocals;
|
|
26656
26937
|
const prevInvariantLocals = ctx.currentInvariantLocals;
|
|
@@ -28249,7 +28530,7 @@ function makeForCall(node, ctx, inlinedSubs, parentNs = 'html', cssHash = null)
|
|
|
28249
28530
|
// event-row captures in the same order improves the compressed fragment.
|
|
28250
28531
|
envNames = [envNames[1], envNames[0], ...envNames.slice(2)];
|
|
28251
28532
|
}
|
|
28252
|
-
// The helper
|
|
28533
|
+
// The helper reads only component-local captures (`envNames`) from the
|
|
28253
28534
|
// tuple prefix. Compiler memoization may additionally need live imported
|
|
28254
28535
|
// bindings as dependency witnesses; append them without disturbing that ABI.
|
|
28255
28536
|
const runtimeDepNames =
|