diff --git a/src/test/transpilers.spec.ts b/src/test/transpilers.spec.ts index d11c368bf..03cd11f72 100644 --- a/src/test/transpilers.spec.ts +++ b/src/test/transpilers.spec.ts @@ -139,4 +139,117 @@ test.suite('swc', (test) => { ` ); }); + + test.suite('useDefineForClassFields', (test) => { + const input = outdent` + class Foo { + bar = 1; + } + `; + const outputNative = outdent` + let Foo = class Foo { + bar = 1; + }; + `; + const outputCtorAssignment = outdent` + let Foo = class Foo { + constructor(){ + this.bar = 1; + } + }; + `; + const outputDefine = outdent` + function _define_property(obj, key, value) { + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); + } else { + obj[key] = value; + } + return obj; + } + let Foo = class Foo { + constructor(){ + _define_property(this, "bar", 1); + } + }; + `; + test( + 'useDefineForClassFields unset, should default to true and emit native property assignment b/c `next` target', + compileMacro, + { + target: 'ESNext', + }, + input, + outputNative + ); + test( + 'useDefineForClassFields unset, should default to true and emit native property assignment b/c new target', + compileMacro, + { + target: 'ES2022', + }, + input, + outputNative + ); + test( + 'useDefineForClassFields unset, should default to false b/c old target', + compileMacro, + { + target: 'ES2021', + }, + input, + outputCtorAssignment + ); + test( + 'useDefineForClassFields unset, should default to false b/c no target', + compileMacro, + {}, + input, + outputCtorAssignment + ); + test( + 'useDefineForClassFields=true, should emit native property assignment b/c new target', + compileMacro, + { + useDefineForClassFields: true, + target: 'ES2022', + }, + input, + outputNative + ); + test( + 'useDefineForClassFields=true, should emit define b/c old target', + compileMacro, + { + useDefineForClassFields: true, + target: 'ES2021', + }, + input, + outputDefine + ); + test( + 'useDefineForClassFields=false, new target, should still emit legacy property assignment in ctor', + compileMacro, + { + useDefineForClassFields: false, + target: 'ES2022', + }, + input, + outputCtorAssignment + ); + test( + 'useDefineForClassFields=false, old target, should emit legacy property assignment in ctor', + compileMacro, + { + useDefineForClassFields: false, + }, + input, + outputCtorAssignment + ); + }); }); diff --git a/src/transpilers/swc.ts b/src/transpilers/swc.ts index 441a895b0..3c7f3070f 100644 --- a/src/transpilers/swc.ts +++ b/src/transpilers/swc.ts @@ -3,6 +3,7 @@ import type * as swcWasm from '@swc/wasm'; import type * as swcTypes from '@swc/core'; import type { CreateTranspilerOptions, Transpiler } from './types'; import type { NodeModuleEmitKind } from '..'; +import { getUseDefineForClassFields } from '../ts-internals'; type SwcInstance = typeof swcTypes; export interface SwcTranspilerOptions extends CreateTranspilerOptions { @@ -194,6 +195,8 @@ export function createSwcOptions( jsx === JsxEmit.ReactJSX || jsx === JsxEmit.ReactJSXDev ? 'automatic' : undefined; const jsxDevelopment: swcTypes.ReactConfig['development'] = jsx === JsxEmit.ReactJSXDev ? true : undefined; + const useDefineForClassFields = getUseDefineForClassFields(compilerOptions); + const nonTsxOptions = createVariant(false); const tsxOptions = createVariant(true); return { nonTsxOptions, tsxOptions }; @@ -233,6 +236,7 @@ export function createSwcOptions( pragmaFrag: jsxFragmentFactory!, runtime: jsxRuntime, }, + useDefineForClassFields, }, keepClassNames, experimental: { diff --git a/src/ts-internals.ts b/src/ts-internals.ts index 9c37f43e1..81fb3070c 100644 --- a/src/ts-internals.ts +++ b/src/ts-internals.ts @@ -329,3 +329,28 @@ function replaceWildcardCharacter(match: string, singleAsteriskRegexFragment: st function isImplicitGlob(lastPathComponent: string): boolean { return !/[.*?]/.test(lastPathComponent); } + +const ts_ScriptTarget_ES5 = 1; +const ts_ScriptTarget_ES2022 = 9; +const ts_ScriptTarget_ESNext = 99; +const ts_ModuleKind_Node16 = 100; +const ts_ModuleKind_NodeNext = 199; +// https://github.com/microsoft/TypeScript/blob/fc418a2e611c88cf9afa0115ff73490b2397d311/src/compiler/utilities.ts#L8761 +export function getUseDefineForClassFields(compilerOptions: _ts.CompilerOptions): boolean { + return compilerOptions.useDefineForClassFields === undefined + ? getEmitScriptTarget(compilerOptions) >= ts_ScriptTarget_ES2022 + : compilerOptions.useDefineForClassFields; +} + +// https://github.com/microsoft/TypeScript/blob/fc418a2e611c88cf9afa0115ff73490b2397d311/src/compiler/utilities.ts#L8556 +export function getEmitScriptTarget(compilerOptions: { + module?: _ts.CompilerOptions['module']; + target?: _ts.CompilerOptions['target']; +}): _ts.ScriptTarget { + return ( + compilerOptions.target ?? + ((compilerOptions.module === ts_ModuleKind_Node16 && ts_ScriptTarget_ES2022) || + (compilerOptions.module === ts_ModuleKind_NodeNext && ts_ScriptTarget_ESNext) || + ts_ScriptTarget_ES5) + ); +}