Skip to content

Commit 26631cb

Browse files
authored
feat: NativeClass transformer now supports ts-patch and ts-loader (#10952)
1 parent 2f2f948 commit 26631cb

File tree

6 files changed

+58
-35
lines changed

6 files changed

+58
-35
lines changed

packages/vite/helpers/nativeclass-transform.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export function transformNativeClassSource(code: string, fileName: string) {
9898
module: ts.ModuleKind.ESNext,
9999
target: ts.ScriptTarget.ES5,
100100
experimentalDecorators: true,
101-
emitDecoratorMetadata: true,
101+
emitDecoratorMetadata: false,
102102
noEmitHelpers: true,
103103
useDefineForClassFields: false,
104104
},

packages/vite/tsconfig.lib.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"declaration": true,
77
"ignoreDeprecations": "5.0",
88
"removeComments": false,
9-
"emitDecoratorMetadata": true,
9+
"emitDecoratorMetadata": false,
1010
"experimentalDecorators": true,
1111
"diagnostics": true,
1212
"sourceMap": true,

packages/webpack5/src/loaders/native-class-downlevel-loader/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export default function nativeClassDownlevelLoader(
7171
target: ts.ScriptTarget.ES5,
7272
noEmitHelpers: true,
7373
experimentalDecorators: true,
74-
emitDecoratorMetadata: true,
74+
emitDecoratorMetadata: false,
7575
useDefineForClassFields: false,
7676
},
7777
fileName: this.resourcePath.endsWith('.ts')

packages/webpack5/src/transformers/NativeClass/index.ts

Lines changed: 53 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ import ts from 'typescript';
1414
//
1515
// IMPORTANT: We intentionally avoid deep traversal to reduce chance of corrupting internal TS state.
1616
// Only top-level/class-block statement arrays are rewritten.
17-
export default function (ctx: ts.TransformationContext) {
18-
const factory = ctx.factory ?? ts.factory;
17+
export default function (context: ts.TransformationContext, ...args) {
18+
const factory = context.factory ?? ts.factory;
1919
return (sourceFile: ts.SourceFile) => {
2020
if (sourceFile.isDeclarationFile) return sourceFile;
2121
let mutated = false;
22-
22+
2323
// Minimal mutable shape
2424
type MutableNode = ts.Node & {
2525
flags?: ts.NodeFlags;
@@ -78,7 +78,7 @@ export default function (ctx: ts.TransformationContext) {
7878
target: ts.ScriptTarget.ES5,
7979
noEmitHelpers: true,
8080
experimentalDecorators: true,
81-
emitDecoratorMetadata: true,
81+
emitDecoratorMetadata: false,
8282
useDefineForClassFields: false,
8383
},
8484
})
@@ -182,8 +182,8 @@ export default function (ctx: ts.TransformationContext) {
182182
}
183183

184184
function visitNode(node: ts.Node): ts.Node {
185-
// Do not traverse synthesized helper trees; leave them intact
186-
if (((node as MutableNode).flags ?? 0) & ts.NodeFlags.Synthesized) {
185+
// Do not traverse synthesized helper trees; leave them intact
186+
if (((node as MutableNode).flags ?? 0) & ts.NodeFlags.Synthesized ) {
187187
return node;
188188
}
189189
if (ts.isSourceFile(node)) {
@@ -210,71 +210,94 @@ export default function (ctx: ts.TransformationContext) {
210210
return changed ? factory.updateDefaultClause(node, stmts) : node;
211211
}
212212
// No generic deep traversal; leave unrelated subtrees intact
213-
return node;
213+
return ts.visitEachChild(node, visitNode, context);
214214
}
215215

216216
function transformStatements(
217217
statements: ts.NodeArray<ts.Statement>,
218218
isTopLevel: boolean,
219219
): [ts.NodeArray<ts.Statement>, boolean] {
220220
let changed = false;
221-
const out: ts.Statement[] = [];
222-
for (const s of statements) {
223-
if (((s as MutableNode).flags ?? 0) & ts.NodeFlags.Synthesized) {
224-
out.push(s);
221+
if (!statements) {
222+
return [null, changed];
223+
}
224+
const result: ts.Statement[] = [];
225+
for (const statement of statements) {
226+
if (((statement as MutableNode).flags ?? 0) & ts.NodeFlags.Synthesized) {
227+
result.push(statement);
225228
continue;
226229
}
227-
if (ts.isClassDeclaration(s)) {
228-
if (hasNativeClassDecorator(s)) {
230+
if (ts.isClassDeclaration(statement)) {
231+
if (hasNativeClassDecorator(statement)) {
229232
mutated = true;
230233
changed = true;
231-
out.push(...emitDownleveledClass(s));
234+
result.push(...emitDownleveledClass(statement));
232235
continue;
233236
} else {
234237
// As an extra fallback, raw text check in case decorator nodes failed and regex missed
235-
const sf = s.getSourceFile();
236-
const start = (s as any).getFullStart
237-
? (s as any).getFullStart()
238-
: (s.pos ?? s.getStart?.(sf));
239-
const raw = sf.text.slice(start, s.end);
238+
const sf = statement.getSourceFile();
239+
const start = (statement as any).getFullStart
240+
? (statement as any).getFullStart()
241+
: (statement.pos ?? statement.getStart?.(sf));
242+
const raw = sf.text.slice(start, statement.end);
240243
if (/^\s*@NativeClass\b/m.test(raw)) {
241244
mutated = true;
242245
changed = true;
243-
out.push(...emitDownleveledClass(s));
246+
result.push(...emitDownleveledClass(statement));
244247
continue;
245248
}
246249
}
247250
}
248-
if (ts.isExpressionStatement(s)) {
249-
const updated = removeNativeClassFromDecorate(s);
251+
if (ts.isExpressionStatement(statement)) {
252+
const updated = removeNativeClassFromDecorate(statement);
250253
if (!updated) {
251254
mutated = true;
252255
changed = true;
253256
continue;
254257
}
255-
out.push(updated);
258+
const visited = ts.visitEachChild(updated, visitNode, context);
259+
if (updated !== statement || visited !== statement) {
260+
mutated = true;
261+
changed = true;
262+
}
263+
result.push(visited);
256264
continue;
257265
}
258-
if (isTopLevel && ts.isImportDeclaration(s)) {
259-
const updated = removeNativeClassImport(s);
266+
if (isTopLevel && ts.isImportDeclaration(statement)) {
267+
const updated = removeNativeClassImport(statement);
260268
if (!updated) {
261269
mutated = true;
262270
changed = true;
263271
continue;
264272
}
265-
if (updated !== s) {
273+
if (updated !== statement) {
266274
mutated = true;
267275
changed = true;
268276
}
269-
out.push(updated);
277+
result.push(updated);
270278
continue;
271279
}
272280
// No deep traversal for unrelated nodes
273-
out.push(s);
281+
result.push(statement);
274282
}
275-
return [changed ? factory.createNodeArray(out) : statements, changed];
283+
return [changed ? factory.createNodeArray(result) : statements, changed];
276284
}
277285

286+
287+
// we detect ts-patch
288+
if (args.length) {
289+
const statements= ts.visitNodes(sourceFile.statements, visitNode) as unknown as ts.Statement[];
290+
if (!mutated) {
291+
return sourceFile;
292+
}
293+
const updatedSource = factory.updateSourceFile(sourceFile, statements as unknown as ts.Statement[]);
294+
// Do NOT clear or rebind the entire SourceFile here. Doing so can break TS's
295+
// import usage analysis and lead to import elision. The factory/update API
296+
// preserves parents/bindings for original nodes (like imports). We only
297+
// synthesize/bind the newly inserted class replacement statements.
298+
return updatedSource;
299+
}
300+
278301
const updated = ts.visitNode(sourceFile, visitNode) as ts.SourceFile;
279302
if (!mutated) return sourceFile;
280303
return updated;
@@ -316,4 +339,4 @@ function setSynthesizedRangeRecursive(node: ts.Node): void {
316339
function setOriginalRecursive(node: ts.Node, original: ts.Node): void {
317340
ts.setOriginalNode(node, original);
318341
ts.forEachChild(node, (child) => setOriginalRecursive(child, original));
319-
}
342+
}

packages/webpack5/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"target": "es2018",
66
"module": "commonjs",
77
"declaration": true,
8-
"emitDecoratorMetadata": true,
8+
"emitDecoratorMetadata": false,
99
"experimentalDecorators": true,
1010
"lib": ["es2018"],
1111
"sourceMap": true,

tools/transformers/native-class.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ module: ts.ModuleKind.ESNext,
135135
target: ts.ScriptTarget.ES5,
136136
noEmitHelpers: true,
137137
experimentalDecorators: true,
138-
emitDecoratorMetadata: true,
138+
emitDecoratorMetadata: false,
139139
useDefineForClassFields: false,
140140
},
141141
})

0 commit comments

Comments
 (0)