octane 0.4.2 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/context-identity.cjs +29 -0
- package/dist/cjs/dom-stage.cjs +24 -18
- package/dist/cjs/error-codes.server.generated.cjs +5 -0
- package/dist/cjs/hydration-markers.cjs +12 -0
- package/dist/cjs/index.cjs +2 -0
- package/dist/cjs/runtime.cjs +253 -393
- package/dist/cjs/runtime.server.cjs +137 -35
- package/dist/cjs/server/index.cjs +2 -0
- package/dist/cjs/server/streamed-signals.cjs +41 -9
- package/dist/cjs/signals/client.cjs +1 -6
- package/dist/cjs/signals/engine.cjs +1 -12
- package/dist/cjs/version.cjs +1 -1
- package/dist/compiler/client-only-server.js +1 -28
- package/dist/compiler/compile-renderer-boundaries.js +1 -22
- package/dist/compiler/compile-universal.js +1 -22
- package/dist/compiler/compile.js +782 -359
- package/dist/compiler/dom-binding-fixed-props.js +11 -1
- package/dist/compiler/generated-origin.js +46 -0
- package/dist/compiler/hydrate-boundaries.js +88 -52
- package/dist/compiler/plain-hook-memo.js +5 -1
- package/dist/compiler/pure-factories.js +66 -0
- package/dist/compiler/register.js +4 -1
- package/dist/compiler/slot-hooks.js +113 -186
- package/dist/compiler/typescript-lowering.js +763 -0
- package/dist/compiler/void-roots.js +239 -0
- package/dist/compiler/volar.js +91 -38
- package/dist/context-identity.d.ts +14 -0
- package/dist/context-identity.js +28 -0
- package/dist/dom-stage.js +24 -18
- package/dist/error-codes.server.generated.d.ts +1 -0
- package/dist/error-codes.server.generated.js +5 -0
- package/dist/hydration/idle.js +1 -0
- package/dist/hydration/independent-island.js +53 -2
- package/dist/hydration/media.js +1 -0
- package/dist/hydration/types.d.ts +5 -0
- package/dist/hydration/visible.js +1 -0
- package/dist/hydration-markers.d.ts +8 -0
- package/dist/hydration-markers.js +8 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -0
- package/dist/runtime.d.ts +25 -40
- package/dist/runtime.js +254 -398
- package/dist/runtime.server.d.ts +23 -2
- package/dist/runtime.server.js +142 -36
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +2 -0
- package/dist/server/streamed-signals.js +41 -9
- package/dist/signals/client.js +3 -8
- package/dist/signals/engine.d.ts +0 -2
- package/dist/signals/engine.js +1 -13
- package/dist/universal-core.js +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/dist/cjs/signals/diagnostic-local-hook.cjs +0 -37
- package/dist/compiler/local-void-roots.js +0 -154
- package/dist/signals/diagnostic-local-hook.d.ts +0 -8
- package/dist/signals/diagnostic-local-hook.js +0 -12
|
@@ -239,8 +239,18 @@ export function fixedBindingProps(fn, fixed, lexical, localDeclaration, isRefere
|
|
|
239
239
|
result.type === 'Property' &&
|
|
240
240
|
result.shorthand &&
|
|
241
241
|
result.value !== node.value
|
|
242
|
-
)
|
|
242
|
+
) {
|
|
243
|
+
// Expanding this shorthand must retain an own data property, not
|
|
244
|
+
// introduce the object-literal prototype setter syntax.
|
|
245
|
+
if (!node.computed && node.key.name === '__proto__')
|
|
246
|
+
return {
|
|
247
|
+
...result,
|
|
248
|
+
key: literal('__proto__', node.key),
|
|
249
|
+
computed: true,
|
|
250
|
+
shorthand: false,
|
|
251
|
+
};
|
|
243
252
|
return { ...result, shorthand: false };
|
|
253
|
+
}
|
|
244
254
|
return result;
|
|
245
255
|
};
|
|
246
256
|
return { fold, known, unknown: UNKNOWN, checks, literal };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const SKIP_KEYS = new Set(['type', 'loc', 'start', 'end', 'range', 'metadata', 'parent']);
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Give every location-less node under `root` the authored origin of `origin`,
|
|
5
|
+
* so generated scaffolding still maps into the source. Copy-on-write: adopted
|
|
6
|
+
* parser nodes are never mutated (tests deep-freeze them), and the returned
|
|
7
|
+
* tree must replace `root`. Stylesheet descendants use offsets into their own
|
|
8
|
+
* CSS source rather than JavaScript locations, so they are left intact.
|
|
9
|
+
*/
|
|
10
|
+
export function inheritGeneratedOrigin(root, origin) {
|
|
11
|
+
if (origin?.loc == null) return root;
|
|
12
|
+
const seen = new WeakMap();
|
|
13
|
+
const visit = (value) => {
|
|
14
|
+
if (!value || typeof value !== 'object') return value;
|
|
15
|
+
if (value.type === 'StyleSheet') return value;
|
|
16
|
+
if (seen.has(value)) return seen.get(value);
|
|
17
|
+
seen.set(value, value);
|
|
18
|
+
if (Array.isArray(value)) {
|
|
19
|
+
let output = null;
|
|
20
|
+
for (let index = 0; index < value.length; index++) {
|
|
21
|
+
const mapped = visit(value[index]);
|
|
22
|
+
if (output === null && mapped !== value[index]) output = value.slice(0, index);
|
|
23
|
+
if (output !== null) output.push(mapped);
|
|
24
|
+
}
|
|
25
|
+
const result = output ?? value;
|
|
26
|
+
seen.set(value, result);
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
let output = null;
|
|
30
|
+
if (typeof value.type === 'string' && value.loc == null) {
|
|
31
|
+
output = { ...value, start: origin.start, end: origin.end, loc: origin.loc };
|
|
32
|
+
}
|
|
33
|
+
for (const [key, child] of Object.entries(value)) {
|
|
34
|
+
if (SKIP_KEYS.has(key)) continue;
|
|
35
|
+
const mapped = visit(child);
|
|
36
|
+
if (mapped !== child) {
|
|
37
|
+
if (output === null) output = { ...value };
|
|
38
|
+
output[key] = mapped;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const result = output ?? value;
|
|
42
|
+
seen.set(value, result);
|
|
43
|
+
return result;
|
|
44
|
+
};
|
|
45
|
+
return visit(root);
|
|
46
|
+
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* intentionally instantiated afresh for every resource query.
|
|
9
9
|
*/
|
|
10
10
|
import { builders as b, parseModule, strongHash } from '@tsrx/core';
|
|
11
|
+
import { inheritGeneratedOrigin } from './generated-origin.js';
|
|
11
12
|
import {
|
|
12
13
|
createLexicalAnalysis,
|
|
13
14
|
forEachRuntimeAstChild,
|
|
@@ -128,45 +129,6 @@ const TRANSPARENT_TS_EXPRESSIONS = new Set([
|
|
|
128
129
|
'TSTypeAssertion',
|
|
129
130
|
]);
|
|
130
131
|
|
|
131
|
-
function inheritGeneratedOrigin(root, origin) {
|
|
132
|
-
const seen = new WeakMap();
|
|
133
|
-
const visit = (value) => {
|
|
134
|
-
if (!value || typeof value !== 'object') return value;
|
|
135
|
-
// Stylesheet descendants use offsets into their own CSS source and do
|
|
136
|
-
// not carry JavaScript locations. Keep that adopted grammar intact.
|
|
137
|
-
if (value.type === 'StyleSheet') return value;
|
|
138
|
-
if (seen.has(value)) return seen.get(value);
|
|
139
|
-
seen.set(value, value);
|
|
140
|
-
if (Array.isArray(value)) {
|
|
141
|
-
let output = null;
|
|
142
|
-
for (let index = 0; index < value.length; index++) {
|
|
143
|
-
const mapped = visit(value[index]);
|
|
144
|
-
if (output === null && mapped !== value[index]) output = value.slice(0, index);
|
|
145
|
-
if (output !== null) output.push(mapped);
|
|
146
|
-
}
|
|
147
|
-
const result = output ?? value;
|
|
148
|
-
seen.set(value, result);
|
|
149
|
-
return result;
|
|
150
|
-
}
|
|
151
|
-
let output = null;
|
|
152
|
-
if (typeof value.type === 'string' && value.loc == null && origin?.loc != null) {
|
|
153
|
-
output = { ...value, start: origin.start, end: origin.end, loc: origin.loc };
|
|
154
|
-
}
|
|
155
|
-
for (const [key, child] of Object.entries(value)) {
|
|
156
|
-
if (SKIP_KEYS.has(key)) continue;
|
|
157
|
-
const mapped = visit(child);
|
|
158
|
-
if (mapped !== child) {
|
|
159
|
-
if (output === null) output = { ...value };
|
|
160
|
-
output[key] = mapped;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
const result = output ?? value;
|
|
164
|
-
seen.set(value, result);
|
|
165
|
-
return result;
|
|
166
|
-
};
|
|
167
|
-
return visit(root);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
132
|
function mapAstCow(value, replace) {
|
|
171
133
|
if (!value || typeof value !== 'object') return value;
|
|
172
134
|
if (Array.isArray(value)) {
|
|
@@ -416,6 +378,7 @@ function collectImports(ast) {
|
|
|
416
378
|
const hydrateNames = new Set();
|
|
417
379
|
const hookNames = new Set();
|
|
418
380
|
const neverNames = new Set();
|
|
381
|
+
const conditionNames = new Set();
|
|
419
382
|
const importBindings = new Set();
|
|
420
383
|
const declarations = [];
|
|
421
384
|
for (const statement of ast.body ?? []) {
|
|
@@ -448,14 +411,16 @@ function collectImports(ast) {
|
|
|
448
411
|
statement.importKind !== 'type' &&
|
|
449
412
|
specifier.type === 'ImportSpecifier' &&
|
|
450
413
|
specifier.importKind !== 'type' &&
|
|
451
|
-
nameOf(specifier.imported) === 'never' &&
|
|
414
|
+
(nameOf(specifier.imported) === 'never' || nameOf(specifier.imported) === 'condition') &&
|
|
452
415
|
specifier.local?.name
|
|
453
416
|
) {
|
|
454
|
-
neverNames.add(
|
|
417
|
+
(nameOf(specifier.imported) === 'never' ? neverNames : conditionNames).add(
|
|
418
|
+
specifier.local.name,
|
|
419
|
+
);
|
|
455
420
|
}
|
|
456
421
|
}
|
|
457
422
|
}
|
|
458
|
-
return { declarations, hookNames, hydrateNames, neverNames, importBindings };
|
|
423
|
+
return { declarations, hookNames, hydrateNames, neverNames, conditionNames, importBindings };
|
|
459
424
|
}
|
|
460
425
|
|
|
461
426
|
function addRelevantBindings(pattern, shadowed, relevant) {
|
|
@@ -546,6 +511,41 @@ function literalIndependentEnabled(node, filename) {
|
|
|
546
511
|
return enabled;
|
|
547
512
|
}
|
|
548
513
|
|
|
514
|
+
/**
|
|
515
|
+
* An independent island activates without its lexical parent, so the client
|
|
516
|
+
* cannot re-evaluate `when`. The server serializes each built-in automatic
|
|
517
|
+
* strategy's parameters; reject the authored forms that need the parent:
|
|
518
|
+
* function-form `when` and `condition()`. Opaque values are checked at SSR.
|
|
519
|
+
*/
|
|
520
|
+
function assertIndependentWhen(node, conditionNames, shadowedImports, filename) {
|
|
521
|
+
for (const attribute of node.openingElement?.attributes ?? []) {
|
|
522
|
+
if (jsxAttributeName(attribute) !== 'when') continue;
|
|
523
|
+
const raw =
|
|
524
|
+
attribute.value?.type === 'JSXExpressionContainer'
|
|
525
|
+
? attribute.value.expression
|
|
526
|
+
: attribute.value;
|
|
527
|
+
const expression = unwrapExpression(raw);
|
|
528
|
+
const callee =
|
|
529
|
+
expression?.type === 'CallExpression' ? unwrapExpression(expression.callee) : null;
|
|
530
|
+
const kind =
|
|
531
|
+
expression?.type === 'ArrowFunctionExpression' || expression?.type === 'FunctionExpression'
|
|
532
|
+
? 'a function-form `when`'
|
|
533
|
+
: callee?.type === 'Identifier' &&
|
|
534
|
+
conditionNames.has(callee.name) &&
|
|
535
|
+
!shadowedImports.has(callee.name)
|
|
536
|
+
? '`condition()`'
|
|
537
|
+
: null;
|
|
538
|
+
if (kind !== null) {
|
|
539
|
+
throw extractionError(
|
|
540
|
+
'OCTANE_HYDRATE_INDEPENDENT_WHEN',
|
|
541
|
+
filename,
|
|
542
|
+
attribute,
|
|
543
|
+
`an independent boundary cannot use ${kind} because its lexical parent never runs on the client to re-evaluate it. Use load(), idle(), visible(), media(), interaction(), or never(), or remove \`independent\``,
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
549
|
/**
|
|
550
550
|
* Recognize only the exact server-only form whose client subtree may be erased.
|
|
551
551
|
* Spreads and indirect strategies stay on the ordinary persistent-wrapper path:
|
|
@@ -926,6 +926,7 @@ export function analyzeHydrateBoundaries(source, filename = 'unknown.tsrx', pars
|
|
|
926
926
|
const index = siblings.length;
|
|
927
927
|
const path = parentBoundary === null ? String(index) : `${parentBoundary.path}.${index}`;
|
|
928
928
|
const independent = literalIndependentEnabled(node, filename);
|
|
929
|
+
if (independent) assertIndependentWhen(node, imports.conditionNames, shadowed, filename);
|
|
929
930
|
boundary = {
|
|
930
931
|
children: [],
|
|
931
932
|
disabled: literalSplitDisabled(node),
|
|
@@ -1239,9 +1240,41 @@ function captureBindingScope(lexical, node, name) {
|
|
|
1239
1240
|
);
|
|
1240
1241
|
}
|
|
1241
1242
|
|
|
1243
|
+
// A pattern binding's value is its declarator initializer or, when that part is
|
|
1244
|
+
// undefined, any default on the path to it (`const { value = new Map() } = props`).
|
|
1245
|
+
function recordPatternInitializers(pattern, scopeNode, values, record) {
|
|
1246
|
+
if (!pattern) return;
|
|
1247
|
+
if (pattern.type === 'TSParameterProperty') {
|
|
1248
|
+
recordPatternInitializers(pattern.parameter, scopeNode, values, record);
|
|
1249
|
+
} else if (pattern.type === 'Identifier') {
|
|
1250
|
+
for (const value of values) record(pattern.name, scopeNode, value);
|
|
1251
|
+
if (values.length === 0) record(pattern.name, scopeNode, null);
|
|
1252
|
+
} else if (pattern.type === 'AssignmentPattern') {
|
|
1253
|
+
recordPatternInitializers(pattern.left, scopeNode, [...values, pattern.right], record);
|
|
1254
|
+
} else if (pattern.type === 'RestElement') {
|
|
1255
|
+
recordPatternInitializers(pattern.argument, scopeNode, values, record);
|
|
1256
|
+
} else if (pattern.type === 'ArrayPattern') {
|
|
1257
|
+
for (const element of pattern.elements ?? []) {
|
|
1258
|
+
recordPatternInitializers(element, scopeNode, values, record);
|
|
1259
|
+
}
|
|
1260
|
+
} else if (pattern.type === 'ObjectPattern') {
|
|
1261
|
+
for (const property of pattern.properties ?? []) {
|
|
1262
|
+
recordPatternInitializers(property.argument ?? property.value, scopeNode, values, record);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1242
1267
|
function bindingInitializers(ast, lexical) {
|
|
1243
1268
|
const initializers = new WeakMap();
|
|
1244
1269
|
const seen = new WeakSet();
|
|
1270
|
+
const record = (name, node, value) => {
|
|
1271
|
+
const scope = captureBindingScope(lexical, node, name);
|
|
1272
|
+
let bindings = initializers.get(scope);
|
|
1273
|
+
if (bindings === undefined) initializers.set(scope, (bindings = new Map()));
|
|
1274
|
+
let values = bindings.get(name);
|
|
1275
|
+
if (values === undefined) bindings.set(name, (values = []));
|
|
1276
|
+
values.push(value);
|
|
1277
|
+
};
|
|
1245
1278
|
const visit = (node) => {
|
|
1246
1279
|
if (!node || typeof node !== 'object' || seen.has(node)) return;
|
|
1247
1280
|
seen.add(node);
|
|
@@ -1250,16 +1283,18 @@ function bindingInitializers(ast, lexical) {
|
|
|
1250
1283
|
return;
|
|
1251
1284
|
}
|
|
1252
1285
|
if (node.type === 'VariableDeclarator') {
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1286
|
+
recordPatternInitializers(node.id, node, [node.init ?? null], record);
|
|
1287
|
+
} else if (
|
|
1288
|
+
(node.type === 'FunctionDeclaration' || node.type === 'ClassDeclaration') &&
|
|
1289
|
+
node.id?.type === 'Identifier'
|
|
1290
|
+
) {
|
|
1291
|
+
// A declaration is its own initializer: the parent creates that identity.
|
|
1292
|
+
record(node.id.name, node, node);
|
|
1293
|
+
}
|
|
1294
|
+
if (isFunction(node)) {
|
|
1295
|
+
// Parameter defaults run in the owner when the caller omits the value.
|
|
1296
|
+
for (const param of node.params ?? [])
|
|
1297
|
+
recordPatternInitializers(param, node.body, [], record);
|
|
1263
1298
|
}
|
|
1264
1299
|
for (const [key, child] of Object.entries(node)) {
|
|
1265
1300
|
if (!SKIP_KEYS.has(key)) visit(child);
|
|
@@ -1317,6 +1352,7 @@ function unsupportedIndependentInitializer(node, lexical, initializers, assigned
|
|
|
1317
1352
|
if (
|
|
1318
1353
|
isFunction(node) ||
|
|
1319
1354
|
node.type === 'ClassExpression' ||
|
|
1355
|
+
node.type === 'ClassDeclaration' ||
|
|
1320
1356
|
node.type === 'NewExpression' ||
|
|
1321
1357
|
node.type === 'CallExpression' ||
|
|
1322
1358
|
node.type === 'OptionalCallExpression'
|
|
@@ -301,7 +301,11 @@ export function inlinePlainHookMemos(ast, source, id, options) {
|
|
|
301
301
|
if (!hasMemo || hasUse) return null;
|
|
302
302
|
const visitors = esrapTsx({
|
|
303
303
|
comments: collectComments(ast),
|
|
304
|
-
getLeadingComments: (node) =>
|
|
304
|
+
getLeadingComments: (node) =>
|
|
305
|
+
node.__octanePure ||
|
|
306
|
+
(node.type === 'CallExpression' && options.pureCalls?.get(node.start) === node.end)
|
|
307
|
+
? PURE_COMMENTS
|
|
308
|
+
: undefined,
|
|
305
309
|
});
|
|
306
310
|
if (!canPrintProgram(ast, visitors)) return null;
|
|
307
311
|
const state = {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Octane factories whose runtime declarations carry `/* @__NO_SIDE_EFFECTS__ */`.
|
|
2
|
+
//
|
|
3
|
+
// Rollup (and so Vite) propagates that declaration annotation to call sites in
|
|
4
|
+
// other modules, but esbuild deliberately does not: its tree-shaking decisions
|
|
5
|
+
// are local to the file containing the call (esbuild CHANGELOG 0.18.1). An
|
|
6
|
+
// unused module-scope `const Ctx = createContext(…)` therefore counts as a
|
|
7
|
+
// side effect under esbuild and retains the whole client runtime through
|
|
8
|
+
// createContext's provider body. The compiler owns every Octane module's
|
|
9
|
+
// output, so it annotates these call sites with the portable `/* @__PURE__ */`
|
|
10
|
+
// convention that every bundler honors.
|
|
11
|
+
//
|
|
12
|
+
// Only the client DOM runtime declares all four factories side-effect-free.
|
|
13
|
+
// The server `createContext` registers the context, and universal renderers
|
|
14
|
+
// ship their own factories, so those targets keep the `lazy`-only set.
|
|
15
|
+
const CLIENT_DOM_PURE_FACTORIES = new Set(['createContext', 'createPortal', 'lazy', 'memo']);
|
|
16
|
+
const PORTABLE_PURE_FACTORIES = new Set(['lazy']);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {{ clientDom: boolean }} target
|
|
20
|
+
* @returns {ReadonlySet<string>}
|
|
21
|
+
*/
|
|
22
|
+
export function octanePureFactoryNames({ clientDom }) {
|
|
23
|
+
return clientDom ? CLIENT_DOM_PURE_FACTORIES : PORTABLE_PURE_FACTORIES;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Local names bound by `import { … } from 'octane'` to a pure factory.
|
|
28
|
+
* @param {any[]} body
|
|
29
|
+
* @param {ReadonlySet<string>} names
|
|
30
|
+
* @returns {Set<string>}
|
|
31
|
+
*/
|
|
32
|
+
export function collectPureFactoryLocals(body, names) {
|
|
33
|
+
const locals = new Set();
|
|
34
|
+
for (const node of body || []) {
|
|
35
|
+
if (node.type !== 'ImportDeclaration' || node.source?.value !== 'octane') continue;
|
|
36
|
+
if (node.importKind === 'type') continue;
|
|
37
|
+
for (const specifier of node.specifiers || []) {
|
|
38
|
+
if (
|
|
39
|
+
specifier.type === 'ImportSpecifier' &&
|
|
40
|
+
specifier.importKind !== 'type' &&
|
|
41
|
+
names.has(specifier.imported?.name ?? specifier.imported?.value) &&
|
|
42
|
+
specifier.local?.name
|
|
43
|
+
) {
|
|
44
|
+
locals.add(specifier.local.name);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return locals;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Whether `node` is a direct call to one of `locals`. */
|
|
52
|
+
export function isPureFactoryCall(node, locals) {
|
|
53
|
+
return (
|
|
54
|
+
node?.type === 'CallExpression' &&
|
|
55
|
+
node.callee?.type === 'Identifier' &&
|
|
56
|
+
locals.has(node.callee.name)
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Authored annotations stay authoritative; never stack a second one.
|
|
61
|
+
const AUTHORED_PURE_BEFORE = /\/\*\s*[#@]__PURE__\s*\*\/\s*$/;
|
|
62
|
+
|
|
63
|
+
/** Whether authored source already annotates the call starting at `start`. */
|
|
64
|
+
export function hasAuthoredPureAnnotation(source, start) {
|
|
65
|
+
return AUTHORED_PURE_BEFORE.test(source.slice(Math.max(0, start - 64), start));
|
|
66
|
+
}
|
|
@@ -131,9 +131,12 @@ function registerBunPlugin() {
|
|
|
131
131
|
? 'ts'
|
|
132
132
|
: 'js';
|
|
133
133
|
if (result === null || result.kind === 'none') return { contents: source, loader };
|
|
134
|
+
// Compiled `.ts`/`.tsx` output keeps TypeScript with runtime semantics
|
|
135
|
+
// (`enum`, value namespaces), exactly as Vite's own TS transform then
|
|
136
|
+
// consumes it, so it keeps its source loader. `.tsrx` output is JavaScript.
|
|
134
137
|
return {
|
|
135
138
|
contents: result.code,
|
|
136
|
-
loader: result.kind === '
|
|
139
|
+
loader: result.kind === 'compile' && path.endsWith('.tsrx') ? 'js' : loader,
|
|
137
140
|
};
|
|
138
141
|
});
|
|
139
142
|
},
|