From 0022f38055689db7fc00027bf0aa6b181c7c0d2f Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Fri, 15 Sep 2023 19:09:01 -0400 Subject: [PATCH] Fix swc issues (#2062) * Bump minimum version of swc to 1.3.84 * Fix type error caused by swc's mismatched types between @swc/wasm and @swc/core * Update swc options to match latest swc version, fix bug where swc was emitting import assertions as `with` which node does not understand * Pass correct useDefineForClassFields, derived from tsconfig * Fix bug where ts-node errored when swc or custom transpiler returned undefined for sourcemap * pass jsxImportSource to swc * Bump min swc version to 1.3.85 Fix issue introduced by swc 1.3.85 with module options that are now forbidden instead of ignored for certain module types remove failing useDefineForClassFields case because I forgot that implicit compiler options were in effect, so default target is actually much higher than I expected --- package.json | 8 +- src/index.ts | 2 +- src/test/transpilers.spec.ts | 189 ++++++++++++++++++++- src/transpilers/swc.ts | 31 ++-- src/ts-internals.ts | 25 +++ tests/1996/empty.ts | 1 + tests/1996/index.ts | 3 + tests/1996/transpiler.js | 13 ++ tests/1996/tsconfig.custom-transpiler.json | 6 + tests/1996/tsconfig.json | 5 + yarn.lock | 117 +++++++------ 11 files changed, 325 insertions(+), 75 deletions(-) create mode 100644 tests/1996/empty.ts create mode 100644 tests/1996/index.ts create mode 100644 tests/1996/transpiler.js create mode 100644 tests/1996/tsconfig.custom-transpiler.json create mode 100644 tests/1996/tsconfig.json diff --git a/package.json b/package.json index 7cc9f4ab9..49510830c 100644 --- a/package.json +++ b/package.json @@ -116,8 +116,8 @@ "@cspotcode/ava-lib": "https://github.com/cspotcode/ava-lib#805aab17b2b89c388596b6dc2b4eece403c5fb87", "@cspotcode/expect-stream": "https://github.com/cspotcode/node-expect-stream#4e425ff1eef240003af8716291e80fbaf3e3ae8f", "@microsoft/api-extractor": "^7.19.4", - "@swc/core": "1.3.32", - "@swc/wasm": "1.3.32", + "@swc/core": "1.3.85", + "@swc/wasm": "1.3.85", "@types/diff": "^4.0.2", "@types/lodash": "^4.14.151", "@types/node": "13.13.5", @@ -145,8 +145,8 @@ "workaround-broken-npm-prepack-behavior": "https://github.com/cspotcode/workaround-broken-npm-prepack-behavior#1a7adbbb8a527784daf97edad6ba42d6e96611f6" }, "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", + "@swc/core": ">=1.3.85", + "@swc/wasm": ">=1.3.85", "@types/node": "*", "typescript": ">=4.4" }, diff --git a/src/index.ts b/src/index.ts index c7f320530..894c17dba 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1158,7 +1158,7 @@ export function createFromPreloadedConfig(foundConfigResult: ReturnType { .create({ swc: true, skipProject: true, - compilerOptions: { - module: 'esnext', - ...compilerOptions, - }, + compilerOptions, }) .compile(input, 'input.tsx'); expect(code.replace(/\/\/# sourceMappingURL.*/, '').trim()).toBe(expectedOutput); @@ -93,12 +99,17 @@ test.suite('swc', (test) => { const div =
; `; - test(compileMacro, { jsx: 'react' }, input, `const div = /*#__PURE__*/ React.createElement("div", null);`); + test( + compileMacro, + { module: 'esnext', jsx: 'react' }, + input, + `const div = /*#__PURE__*/ React.createElement("div", null);` + ); test.suite('react 17 jsx factories', (test) => { test.if(tsSupportsReact17JsxFactories); test( compileMacro, - { jsx: 'react-jsx' }, + { module: 'esnext', jsx: 'react-jsx' }, input, outdent` import { jsx as _jsx } from "react/jsx-runtime"; @@ -107,7 +118,7 @@ test.suite('swc', (test) => { ); test( compileMacro, - { jsx: 'react-jsxdev' }, + { module: 'esnext', jsx: 'react-jsxdev' }, input, outdent` import { jsxDEV as _jsxDEV } from "react/jsx-dev-runtime"; @@ -139,4 +150,166 @@ 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, + { module: 'esnext', target: 'ESNext' }, + input, + outputNative + ); + test( + 'useDefineForClassFields unset, should default to true and emit native property assignment b/c new target', + compileMacro, + { module: 'esnext', target: 'ES2022' }, + input, + outputNative + ); + test( + 'useDefineForClassFields unset, should default to false b/c old target', + compileMacro, + { module: 'esnext', target: 'ES2021' }, + input, + outputCtorAssignment + ); + test( + 'useDefineForClassFields=true, should emit native property assignment b/c new target', + compileMacro, + { + module: 'esnext', + useDefineForClassFields: true, + target: 'ES2022', + }, + input, + outputNative + ); + test( + 'useDefineForClassFields=true, should emit define b/c old target', + compileMacro, + { + module: 'esnext', + useDefineForClassFields: true, + target: 'ES2021', + }, + input, + outputDefine + ); + test( + 'useDefineForClassFields=false, new target, should still emit legacy property assignment in ctor', + compileMacro, + { + module: 'esnext', + useDefineForClassFields: false, + target: 'ES2022', + }, + input, + outputCtorAssignment + ); + test( + 'useDefineForClassFields=false, old target, should emit legacy property assignment in ctor', + compileMacro, + { + module: 'esnext', + useDefineForClassFields: false, + }, + input, + outputCtorAssignment + ); + }); + + test.suite('jsx and jsxImportSource', (test) => { + test( + 'jsx=react-jsx', + compileMacro, + { + module: 'esnext', + jsx: 'react-jsx', + }, + outdent` +
+ `, + outdent` + /*#__PURE__*/ import { jsx as _jsx } from "react/jsx-runtime"; + _jsx("div", {}); + ` + ); + test( + 'jsx=react-jsx w/custom jsxImportSource', + compileMacro, + { + module: 'esnext', + jsx: 'react-jsx', + jsxImportSource: 'foo', + }, + outdent` +
+ `, + outdent` + /*#__PURE__*/ import { jsx as _jsx } from "foo/jsx-runtime"; + _jsx("div", {}); + ` + ); + }); + + test.suite( + '#1996 regression: ts-node gracefully allows swc to not return a sourcemap for type-only files', + (test) => { + // https://github.com/TypeStrong/ts-node/issues/1996 + // @swc/core 1.3.51 returned `undefined` instead of sourcemap if the file was empty or only exported types. + // Newer swc versions do not do this. But our typedefs technically allow it. + const exec = createExec({ + cwd: join(TEST_DIR, '1996'), + }); + test('import empty file w/swc', async (t) => { + const r = await exec(`${CMD_TS_NODE_WITHOUT_PROJECT_FLAG} ./index.ts`); + expect(r.err).toBe(null); + expect(r.stdout).toMatch(/#1996 regression test./); + }); + test('use custom transpiler which never returns a sourcemap', async (t) => { + const r = await exec( + `${CMD_TS_NODE_WITHOUT_PROJECT_FLAG} --project tsconfig.custom-transpiler.json ./empty.ts` + ); + expect(r.err).toBe(null); + expect(r.stdout).toMatch(/#1996 regression test with custom transpiler./); + }); + } + ); }); diff --git a/src/transpilers/swc.ts b/src/transpilers/swc.ts index 16868cad2..ea41d534c 100644 --- a/src/transpilers/swc.ts +++ b/src/transpilers/swc.ts @@ -3,8 +3,9 @@ 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 swcWasm; +type SwcInstance = typeof swcTypes; export interface SwcTranspilerOptions extends CreateTranspilerOptions { /** * swc compiler to use for compilation @@ -28,7 +29,7 @@ export function create(createOptions: SwcTranspilerOptions): Transpiler { let swcDepName: string = 'swc'; if (typeof swc === 'string') { swcDepName = swc; - swcInstance = require(transpilerConfigLocalResolveHelper(swc, true)) as typeof swcWasm; + swcInstance = require(transpilerConfigLocalResolveHelper(swc, true)) as SwcInstance; } else if (swc == null) { let swcResolved; try { @@ -44,9 +45,9 @@ export function create(createOptions: SwcTranspilerOptions): Transpiler { ); } } - swcInstance = require(swcResolved) as typeof swcWasm; + swcInstance = require(swcResolved) as SwcInstance; } else { - swcInstance = swc; + swcInstance = swc as any as SwcInstance; } // Prepare SWC options derived from typescript compiler options @@ -142,6 +143,7 @@ export function createSwcOptions( strict, alwaysStrict, noImplicitUseStrict, + jsxImportSource, } = compilerOptions; let swcTarget = targetMapping.get(target!) ?? 'es3'; @@ -194,6 +196,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 }; @@ -204,11 +208,15 @@ export function createSwcOptions( // isModule: true, module: moduleType ? { - noInterop: !esModuleInterop, type: moduleType, - strictMode, - // For NodeNext and Node12, emit as CJS but do not transform dynamic imports - ignoreDynamic: nodeModuleEmitKind === 'nodecjs', + ...(moduleType === 'amd' || moduleType === 'commonjs' || moduleType === 'umd' + ? { + noInterop: !esModuleInterop, + strictMode, + // For NodeNext and Node12, emit as CJS but do not transform dynamic imports + ignoreDynamic: nodeModuleEmitKind === 'nodecjs', + } + : {}), } : undefined, swcrc: false, @@ -232,12 +240,15 @@ export function createSwcOptions( pragma: jsxFactory!, pragmaFrag: jsxFragmentFactory!, runtime: jsxRuntime, + importSource: jsxImportSource, }, + useDefineForClassFields, }, keepClassNames, experimental: { - keepImportAssertions: true, - }, + keepImportAttributes: true, + emitAssertForImportAttributes: true, + } as swcTypes.JscConfig['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) + ); +} diff --git a/tests/1996/empty.ts b/tests/1996/empty.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/tests/1996/empty.ts @@ -0,0 +1 @@ +export {}; diff --git a/tests/1996/index.ts b/tests/1996/index.ts new file mode 100644 index 000000000..b643f71c7 --- /dev/null +++ b/tests/1996/index.ts @@ -0,0 +1,3 @@ +import * as empty from './empty'; +empty; +console.log('#1996 regression test.'); diff --git a/tests/1996/transpiler.js b/tests/1996/transpiler.js new file mode 100644 index 000000000..ba0e34080 --- /dev/null +++ b/tests/1996/transpiler.js @@ -0,0 +1,13 @@ +// A custom transpiler that returns `undefined` instead of a sourcemap, which is +// allowed according to our typedefs. + +exports.create = function () { + return { + transpile(input, options) { + return { + outputText: 'console.log("#1996 regression test with custom transpiler.")', + sourceMapText: undefined, + }; + }, + }; +}; diff --git a/tests/1996/tsconfig.custom-transpiler.json b/tests/1996/tsconfig.custom-transpiler.json new file mode 100644 index 000000000..d10e9a7b0 --- /dev/null +++ b/tests/1996/tsconfig.custom-transpiler.json @@ -0,0 +1,6 @@ +{ + "ts-node": { + "transpileOnly": true, + "transpiler": "./transpiler.js" + } +} diff --git a/tests/1996/tsconfig.json b/tests/1996/tsconfig.json new file mode 100644 index 000000000..3f9c57e6f --- /dev/null +++ b/tests/1996/tsconfig.json @@ -0,0 +1,5 @@ +{ + "ts-node": { + "swc": true + } +} diff --git a/yarn.lock b/yarn.lock index ccd9029ae..55633a4b4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -511,90 +511,93 @@ __metadata: languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.3.32": - version: 1.3.32 - resolution: "@swc/core-darwin-arm64@npm:1.3.32" +"@swc/core-darwin-arm64@npm:1.3.85": + version: 1.3.85 + resolution: "@swc/core-darwin-arm64@npm:1.3.85" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.3.32": - version: 1.3.32 - resolution: "@swc/core-darwin-x64@npm:1.3.32" +"@swc/core-darwin-x64@npm:1.3.85": + version: 1.3.85 + resolution: "@swc/core-darwin-x64@npm:1.3.85" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.3.32": - version: 1.3.32 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.32" +"@swc/core-linux-arm-gnueabihf@npm:1.3.85": + version: 1.3.85 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.85" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.3.32": - version: 1.3.32 - resolution: "@swc/core-linux-arm64-gnu@npm:1.3.32" +"@swc/core-linux-arm64-gnu@npm:1.3.85": + version: 1.3.85 + resolution: "@swc/core-linux-arm64-gnu@npm:1.3.85" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.3.32": - version: 1.3.32 - resolution: "@swc/core-linux-arm64-musl@npm:1.3.32" +"@swc/core-linux-arm64-musl@npm:1.3.85": + version: 1.3.85 + resolution: "@swc/core-linux-arm64-musl@npm:1.3.85" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.3.32": - version: 1.3.32 - resolution: "@swc/core-linux-x64-gnu@npm:1.3.32" +"@swc/core-linux-x64-gnu@npm:1.3.85": + version: 1.3.85 + resolution: "@swc/core-linux-x64-gnu@npm:1.3.85" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.3.32": - version: 1.3.32 - resolution: "@swc/core-linux-x64-musl@npm:1.3.32" +"@swc/core-linux-x64-musl@npm:1.3.85": + version: 1.3.85 + resolution: "@swc/core-linux-x64-musl@npm:1.3.85" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.3.32": - version: 1.3.32 - resolution: "@swc/core-win32-arm64-msvc@npm:1.3.32" +"@swc/core-win32-arm64-msvc@npm:1.3.85": + version: 1.3.85 + resolution: "@swc/core-win32-arm64-msvc@npm:1.3.85" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.3.32": - version: 1.3.32 - resolution: "@swc/core-win32-ia32-msvc@npm:1.3.32" +"@swc/core-win32-ia32-msvc@npm:1.3.85": + version: 1.3.85 + resolution: "@swc/core-win32-ia32-msvc@npm:1.3.85" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.3.32": - version: 1.3.32 - resolution: "@swc/core-win32-x64-msvc@npm:1.3.32" +"@swc/core-win32-x64-msvc@npm:1.3.85": + version: 1.3.85 + resolution: "@swc/core-win32-x64-msvc@npm:1.3.85" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@swc/core@npm:1.3.32": - version: 1.3.32 - resolution: "@swc/core@npm:1.3.32" +"@swc/core@npm:1.3.85": + version: 1.3.85 + resolution: "@swc/core@npm:1.3.85" dependencies: - "@swc/core-darwin-arm64": 1.3.32 - "@swc/core-darwin-x64": 1.3.32 - "@swc/core-linux-arm-gnueabihf": 1.3.32 - "@swc/core-linux-arm64-gnu": 1.3.32 - "@swc/core-linux-arm64-musl": 1.3.32 - "@swc/core-linux-x64-gnu": 1.3.32 - "@swc/core-linux-x64-musl": 1.3.32 - "@swc/core-win32-arm64-msvc": 1.3.32 - "@swc/core-win32-ia32-msvc": 1.3.32 - "@swc/core-win32-x64-msvc": 1.3.32 + "@swc/core-darwin-arm64": 1.3.85 + "@swc/core-darwin-x64": 1.3.85 + "@swc/core-linux-arm-gnueabihf": 1.3.85 + "@swc/core-linux-arm64-gnu": 1.3.85 + "@swc/core-linux-arm64-musl": 1.3.85 + "@swc/core-linux-x64-gnu": 1.3.85 + "@swc/core-linux-x64-musl": 1.3.85 + "@swc/core-win32-arm64-msvc": 1.3.85 + "@swc/core-win32-ia32-msvc": 1.3.85 + "@swc/core-win32-x64-msvc": 1.3.85 + "@swc/types": ^0.1.4 + peerDependencies: + "@swc/helpers": ^0.5.0 dependenciesMeta: "@swc/core-darwin-arm64": optional: true @@ -616,14 +619,24 @@ __metadata: optional: true "@swc/core-win32-x64-msvc": optional: true - checksum: 61d3eeee89b2dec8d128f63051cb8808b74aea42afc444a0e6477a763b145b5a7c511eab14f5ab0a6e2fa486a99316b0428cb5e02cb11dd65003b760caf85c6e + peerDependenciesMeta: + "@swc/helpers": + optional: true + checksum: af9ec7d88fd9ad3dd876c8fea812b20ba734c2ed917c9f8281fd57c68ab57d5931ccb841f4467332b84c0cd522682737a770dfc8c3f9e0cc88cdce713971978c + languageName: node + linkType: hard + +"@swc/types@npm:^0.1.4": + version: 0.1.4 + resolution: "@swc/types@npm:0.1.4" + checksum: 9b09de7dca8e4b19bfb43f9e332c771855158cb761d26000807fe858447ecbc5342a6c257b26d9aa5497f7138fc58913693e2bee222e5042e0e8f57c2979ae66 languageName: node linkType: hard -"@swc/wasm@npm:1.3.32": - version: 1.3.32 - resolution: "@swc/wasm@npm:1.3.32" - checksum: 80df9b6983f4211e049592f210762dfbbd19899a24d37bf3dad7d8c2ede6fee70c7945cca3c508dae24e6b7f4a14b0ca6d645648fab01694ac4e93fda1ced379 +"@swc/wasm@npm:1.3.85": + version: 1.3.85 + resolution: "@swc/wasm@npm:1.3.85" + checksum: e4937fa9278eb11e7fd336937b3b5043a34a9236560e45df4d587566bafb6baedce68afbf34a984398bf33cffaf05b48a7662283fec2cfc7a93057845b68acb0 languageName: node linkType: hard @@ -3815,8 +3828,8 @@ __metadata: "@cspotcode/expect-stream": "https://github.com/cspotcode/node-expect-stream#4e425ff1eef240003af8716291e80fbaf3e3ae8f" "@cspotcode/source-map-support": ^0.8.0 "@microsoft/api-extractor": ^7.19.4 - "@swc/core": 1.3.32 - "@swc/wasm": 1.3.32 + "@swc/core": 1.3.85 + "@swc/wasm": 1.3.85 "@tsconfig/node14": "*" "@tsconfig/node16": "*" "@tsconfig/node18": "*" @@ -3853,8 +3866,8 @@ __metadata: v8-compile-cache-lib: ^3.0.1 workaround-broken-npm-prepack-behavior: "https://github.com/cspotcode/workaround-broken-npm-prepack-behavior#1a7adbbb8a527784daf97edad6ba42d6e96611f6" peerDependencies: - "@swc/core": ">=1.2.50" - "@swc/wasm": ">=1.2.50" + "@swc/core": ">=1.3.85" + "@swc/wasm": ">=1.3.85" "@types/node": "*" typescript: ">=4.4" peerDependenciesMeta: