From bbe431fe2206e7f447ca8bcfe6e3a38391ad5a36 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Fri, 17 Jun 2022 17:41:14 -0400 Subject: [PATCH 1/5] refactor swc plugin to make it more test-able; also throw helpful error when users swc dep is too old --- src/transpilers/swc.ts | 171 ++++++++++++++++++++++++----------------- 1 file changed, 102 insertions(+), 69 deletions(-) diff --git a/src/transpilers/swc.ts b/src/transpilers/swc.ts index 246b70f40..5aed56fcf 100644 --- a/src/transpilers/swc.ts +++ b/src/transpilers/swc.ts @@ -2,7 +2,9 @@ import type * as ts from 'typescript'; import type * as swcWasm from '@swc/wasm'; import type * as swcTypes from '@swc/core'; import type { CreateTranspilerOptions, Transpiler } from './types'; +import type { NodeModuleEmitKind } from '..'; +type SwcInstance = typeof swcWasm; export interface SwcTranspilerOptions extends CreateTranspilerOptions { /** * swc compiler to use for compilation @@ -21,8 +23,11 @@ export function create(createOptions: SwcTranspilerOptions): Transpiler { } = createOptions; // Load swc compiler - let swcInstance: typeof swcWasm; + let swcInstance: SwcInstance; + // Used later in diagnostics; merely needs to be human-readable. + let swcDepName: string = 'swc'; if (typeof swc === 'string') { + swcDepName = swc; swcInstance = require(transpilerConfigLocalResolveHelper( swc, true @@ -30,10 +35,12 @@ export function create(createOptions: SwcTranspilerOptions): Transpiler { } else if (swc == null) { let swcResolved; try { - swcResolved = transpilerConfigLocalResolveHelper('@swc/core', true); + swcDepName = '@swc/core'; + swcResolved = transpilerConfigLocalResolveHelper(swcDepName, true); } catch (e) { try { - swcResolved = transpilerConfigLocalResolveHelper('@swc/wasm', true); + swcDepName = '@swc/wasm'; + swcResolved = transpilerConfigLocalResolveHelper(swcDepName, true); } catch (e) { throw new Error( 'swc compiler requires either @swc/core or @swc/wasm to be installed as a dependency. See https://typestrong.org/ts-node/docs/transpilers' @@ -46,7 +53,76 @@ export function create(createOptions: SwcTranspilerOptions): Transpiler { } // Prepare SWC options derived from typescript compiler options - const compilerOptions = config.options; + const {nonTsxOptions, tsxOptions} = createSwcOptions(config.options, nodeModuleEmitKind, swcInstance, swcDepName); + + const transpile: Transpiler['transpile'] = (input, transpileOptions) => { + const { fileName } = transpileOptions; + const swcOptions = + fileName.endsWith('.tsx') || fileName.endsWith('.jsx') + ? tsxOptions + : nonTsxOptions; + const { code, map } = swcInstance.transformSync(input, { + ...swcOptions, + filename: fileName, + }); + return { outputText: code, sourceMapText: map }; + }; + + return { + transpile, + }; +} + +/** @internal */ +export const targetMapping = new Map(); +targetMapping.set(/* ts.ScriptTarget.ES3 */ 0, 'es3'); +targetMapping.set(/* ts.ScriptTarget.ES5 */ 1, 'es5'); +targetMapping.set(/* ts.ScriptTarget.ES2015 */ 2, 'es2015'); +targetMapping.set(/* ts.ScriptTarget.ES2016 */ 3, 'es2016'); +targetMapping.set(/* ts.ScriptTarget.ES2017 */ 4, 'es2017'); +targetMapping.set(/* ts.ScriptTarget.ES2018 */ 5, 'es2018'); +targetMapping.set(/* ts.ScriptTarget.ES2019 */ 6, 'es2019'); +targetMapping.set(/* ts.ScriptTarget.ES2020 */ 7, 'es2020'); +targetMapping.set(/* ts.ScriptTarget.ES2021 */ 8, 'es2021'); +targetMapping.set(/* ts.ScriptTarget.ES2022 */ 9, 'es2022'); +targetMapping.set(/* ts.ScriptTarget.ESNext */ 99, 'es2022'); + +type SwcTarget = typeof swcTargets[number]; +/** + * @internal + * We use this list to downgrade to a prior target when we probe swc to detect if it supports a particular target + */ +const swcTargets = [ + 'es3', + 'es5', + 'es2015', + 'es2016', + 'es2017', + 'es2018', + 'es2019', + 'es2020', + 'es2021', + 'es2022', +] as const; + +const ModuleKind = { + None: 0, + CommonJS: 1, + AMD: 2, + UMD: 3, + System: 4, + ES2015: 5, + ES2020: 6, + ESNext: 99, + Node12: 100, + NodeNext: 199, +} as const; + +/** + * Prepare SWC options derived from typescript compiler options. + * @internal exported for testing + */ +export function createSwcOptions(compilerOptions: ts.CompilerOptions, nodeModuleEmitKind: NodeModuleEmitKind | undefined, swcInstance: SwcInstance, swcDepName: string) { const { esModuleInterop, sourceMap, @@ -61,9 +137,7 @@ export function create(createOptions: SwcTranspilerOptions): Transpiler { alwaysStrict, noImplicitUseStrict, } = compilerOptions; - const nonTsxOptions = createSwcOptions(false); - const tsxOptions = createSwcOptions(true); - function createSwcOptions(isTsx: boolean): swcTypes.Options { + let swcTarget = targetMapping.get(target!) ?? 'es3'; // Downgrade to lower target if swc does not support the selected target. // Perhaps project has an older version of swc. @@ -110,7 +184,13 @@ export function create(createOptions: SwcTranspilerOptions): Transpiler { noImplicitUseStrict === true ? false : true; - return { + + const nonTsxOptions = createVariant(false); + const tsxOptions = createVariant(true); + return {nonTsxOptions, tsxOptions}; + + function createVariant(isTsx: boolean): swcTypes.Options { + const swcOptions: swcTypes.Options = { sourceMaps: sourceMap, // isModule: true, module: moduleType @@ -146,67 +226,20 @@ export function create(createOptions: SwcTranspilerOptions): Transpiler { keepClassNames, } as swcTypes.JscConfig, }; - } - const transpile: Transpiler['transpile'] = (input, transpileOptions) => { - const { fileName } = transpileOptions; - const swcOptions = - fileName.endsWith('.tsx') || fileName.endsWith('.jsx') - ? tsxOptions - : nonTsxOptions; - const { code, map } = swcInstance.transformSync(input, { - ...swcOptions, - filename: fileName, - }); - return { outputText: code, sourceMapText: map }; - }; + // Throw a helpful error if swc version is old, for example, if it rejects `ignoreDynamic` + try { + swcInstance.transformSync('', swcOptions); + } catch(e) { + throw new Error( + `${swcDepName} threw an error when attempting to validate swc compiler options.\n` + + 'You may be using an old version of swc which does not support the options used by ts-node.\n' + + 'Try upgrading to the latest version of swc.\n' + + 'Error message from swc:\n' + + (e as Error)?.message + ); + } - return { - transpile, - }; + return swcOptions; + } } - -/** @internal */ -export const targetMapping = new Map(); -targetMapping.set(/* ts.ScriptTarget.ES3 */ 0, 'es3'); -targetMapping.set(/* ts.ScriptTarget.ES5 */ 1, 'es5'); -targetMapping.set(/* ts.ScriptTarget.ES2015 */ 2, 'es2015'); -targetMapping.set(/* ts.ScriptTarget.ES2016 */ 3, 'es2016'); -targetMapping.set(/* ts.ScriptTarget.ES2017 */ 4, 'es2017'); -targetMapping.set(/* ts.ScriptTarget.ES2018 */ 5, 'es2018'); -targetMapping.set(/* ts.ScriptTarget.ES2019 */ 6, 'es2019'); -targetMapping.set(/* ts.ScriptTarget.ES2020 */ 7, 'es2020'); -targetMapping.set(/* ts.ScriptTarget.ES2021 */ 8, 'es2021'); -targetMapping.set(/* ts.ScriptTarget.ES2022 */ 9, 'es2022'); -targetMapping.set(/* ts.ScriptTarget.ESNext */ 99, 'es2022'); - -type SwcTarget = typeof swcTargets[number]; -/** - * @internal - * We use this list to downgrade to a prior target when we probe swc to detect if it supports a particular target - */ -const swcTargets = [ - 'es3', - 'es5', - 'es2015', - 'es2016', - 'es2017', - 'es2018', - 'es2019', - 'es2020', - 'es2021', - 'es2022', -] as const; - -const ModuleKind = { - None: 0, - CommonJS: 1, - AMD: 2, - UMD: 3, - System: 4, - ES2015: 5, - ES2020: 6, - ESNext: 99, - Node12: 100, - NodeNext: 199, -} as const; From 45a3732374682a49cb2fdf4fd29f9d49652c3f98 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Fri, 17 Jun 2022 19:55:38 -0400 Subject: [PATCH 2/5] fmt --- src/transpilers/swc.ts | 120 ++++++++++++++++++++++------------------- 1 file changed, 65 insertions(+), 55 deletions(-) diff --git a/src/transpilers/swc.ts b/src/transpilers/swc.ts index 5aed56fcf..68093a6d8 100644 --- a/src/transpilers/swc.ts +++ b/src/transpilers/swc.ts @@ -53,7 +53,12 @@ export function create(createOptions: SwcTranspilerOptions): Transpiler { } // Prepare SWC options derived from typescript compiler options - const {nonTsxOptions, tsxOptions} = createSwcOptions(config.options, nodeModuleEmitKind, swcInstance, swcDepName); + const { nonTsxOptions, tsxOptions } = createSwcOptions( + config.options, + nodeModuleEmitKind, + swcInstance, + swcDepName + ); const transpile: Transpiler['transpile'] = (input, transpileOptions) => { const { fileName } = transpileOptions; @@ -114,7 +119,7 @@ const ModuleKind = { ES2015: 5, ES2020: 6, ESNext: 99, - Node12: 100, + Node16: 100, NodeNext: 199, } as const; @@ -122,7 +127,12 @@ const ModuleKind = { * Prepare SWC options derived from typescript compiler options. * @internal exported for testing */ -export function createSwcOptions(compilerOptions: ts.CompilerOptions, nodeModuleEmitKind: NodeModuleEmitKind | undefined, swcInstance: SwcInstance, swcDepName: string) { +export function createSwcOptions( + compilerOptions: ts.CompilerOptions, + nodeModuleEmitKind: NodeModuleEmitKind | undefined, + swcInstance: SwcInstance, + swcDepName: string +) { const { esModuleInterop, sourceMap, @@ -138,56 +148,56 @@ export function createSwcOptions(compilerOptions: ts.CompilerOptions, nodeModule noImplicitUseStrict, } = compilerOptions; - let swcTarget = targetMapping.get(target!) ?? 'es3'; - // Downgrade to lower target if swc does not support the selected target. - // Perhaps project has an older version of swc. - // TODO cache the results of this; slightly faster - let swcTargetIndex = swcTargets.indexOf(swcTarget); - for (; swcTargetIndex >= 0; swcTargetIndex--) { - try { - swcInstance.transformSync('', { - jsc: { target: swcTargets[swcTargetIndex] }, - }); - break; - } catch (e) {} - } - swcTarget = swcTargets[swcTargetIndex]; - const keepClassNames = target! >= /* ts.ScriptTarget.ES2016 */ 3; - const isNodeModuleKind = - module === ModuleKind.Node12 || module === ModuleKind.NodeNext; - // swc only supports these 4x module options [MUST_UPDATE_FOR_NEW_MODULEKIND] - const moduleType = - module === ModuleKind.CommonJS - ? 'commonjs' - : module === ModuleKind.AMD - ? 'amd' - : module === ModuleKind.UMD - ? 'umd' - : isNodeModuleKind && nodeModuleEmitKind === 'nodecjs' - ? 'commonjs' - : isNodeModuleKind && nodeModuleEmitKind === 'nodeesm' - ? 'es6' - : 'es6'; - // In swc: - // strictMode means `"use strict"` is *always* emitted for non-ES module, *never* for ES module where it is assumed it can be omitted. - // (this assumption is invalid, but that's the way swc behaves) - // tsc is a bit more complex: - // alwaysStrict will force emitting it always unless `import`/`export` syntax is emitted which implies it per the JS spec. - // if not alwaysStrict, will emit implicitly whenever module target is non-ES *and* transformed module syntax is emitted. - // For node, best option is to assume that all scripts are modules (commonjs or esm) and thus should get tsc's implicit strict behavior. - - // Always set strictMode, *unless* alwaysStrict is disabled and noImplicitUseStrict is enabled - const strictMode = - // if `alwaysStrict` is disabled, remembering that `strict` defaults `alwaysStrict` to true - (alwaysStrict === false || (alwaysStrict !== true && strict !== true)) && - // if noImplicitUseStrict is enabled - noImplicitUseStrict === true - ? false - : true; + let swcTarget = targetMapping.get(target!) ?? 'es3'; + // Downgrade to lower target if swc does not support the selected target. + // Perhaps project has an older version of swc. + // TODO cache the results of this; slightly faster + let swcTargetIndex = swcTargets.indexOf(swcTarget); + for (; swcTargetIndex >= 0; swcTargetIndex--) { + try { + swcInstance.transformSync('', { + jsc: { target: swcTargets[swcTargetIndex] }, + }); + break; + } catch (e) {} + } + swcTarget = swcTargets[swcTargetIndex]; + const keepClassNames = target! >= /* ts.ScriptTarget.ES2016 */ 3; + const isNodeModuleKind = + module === ModuleKind.Node16 || module === ModuleKind.NodeNext; + // swc only supports these 4x module options [MUST_UPDATE_FOR_NEW_MODULEKIND] + const moduleType = + module === ModuleKind.CommonJS + ? 'commonjs' + : module === ModuleKind.AMD + ? 'amd' + : module === ModuleKind.UMD + ? 'umd' + : isNodeModuleKind && nodeModuleEmitKind === 'nodecjs' + ? 'commonjs' + : isNodeModuleKind && nodeModuleEmitKind === 'nodeesm' + ? 'es6' + : 'es6'; + // In swc: + // strictMode means `"use strict"` is *always* emitted for non-ES module, *never* for ES module where it is assumed it can be omitted. + // (this assumption is invalid, but that's the way swc behaves) + // tsc is a bit more complex: + // alwaysStrict will force emitting it always unless `import`/`export` syntax is emitted which implies it per the JS spec. + // if not alwaysStrict, will emit implicitly whenever module target is non-ES *and* transformed module syntax is emitted. + // For node, best option is to assume that all scripts are modules (commonjs or esm) and thus should get tsc's implicit strict behavior. + + // Always set strictMode, *unless* alwaysStrict is disabled and noImplicitUseStrict is enabled + const strictMode = + // if `alwaysStrict` is disabled, remembering that `strict` defaults `alwaysStrict` to true + (alwaysStrict === false || (alwaysStrict !== true && strict !== true)) && + // if noImplicitUseStrict is enabled + noImplicitUseStrict === true + ? false + : true; const nonTsxOptions = createVariant(false); const tsxOptions = createVariant(true); - return {nonTsxOptions, tsxOptions}; + return { nonTsxOptions, tsxOptions }; function createVariant(isTsx: boolean): swcTypes.Options { const swcOptions: swcTypes.Options = { @@ -230,13 +240,13 @@ export function createSwcOptions(compilerOptions: ts.CompilerOptions, nodeModule // Throw a helpful error if swc version is old, for example, if it rejects `ignoreDynamic` try { swcInstance.transformSync('', swcOptions); - } catch(e) { + } catch (e) { throw new Error( `${swcDepName} threw an error when attempting to validate swc compiler options.\n` + - 'You may be using an old version of swc which does not support the options used by ts-node.\n' + - 'Try upgrading to the latest version of swc.\n' + - 'Error message from swc:\n' + - (e as Error)?.message + 'You may be using an old version of swc which does not support the options used by ts-node.\n' + + 'Try upgrading to the latest version of swc.\n' + + 'Error message from swc:\n' + + (e as Error)?.message ); } From dee455616409459eef890e6991e37bd8738fa3d8 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Sun, 26 Jun 2022 17:38:03 -0400 Subject: [PATCH 3/5] fix --- package-lock.json | 150 +++++++++++++++++------------------ package.json | 7 +- src/test/transpilers.spec.ts | 89 +++++++++++++++++++++ src/transpilers/swc.ts | 16 +++- 4 files changed, 182 insertions(+), 80 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9722c5fd8..27ef91d22 100644 --- a/package-lock.json +++ b/package-lock.json @@ -508,21 +508,6 @@ } } }, - "@napi-rs/triples": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@napi-rs/triples/-/triples-1.0.3.tgz", - "integrity": "sha512-jDJTpta+P4p1NZTFVLHJ/TLFVYVcOqv6l8xwOeBKNPMgY/zDYH/YH7SJbvrr/h1RcS9GzbPcLKGzpuK9cV56UA==", - "dev": true - }, - "@node-rs/helper": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@node-rs/helper/-/helper-1.2.1.tgz", - "integrity": "sha512-R5wEmm8nbuQU0YGGmYVjEc0OHtYsuXdpRG+Ut/3wZ9XAvQWyThN08bTh2cBJgoZxHQUPtvRfeQuxcAgLuiBISg==", - "dev": true, - "requires": { - "@napi-rs/triples": "^1.0.3" - } - }, "@nodelib/fs.scandir": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", @@ -644,114 +629,121 @@ "dev": true }, "@swc/core": { - "version": "1.2.106", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.2.106.tgz", - "integrity": "sha512-9uw8gqU+lsk7KROAcSNhsrnBgNiC5H4MIaps5LlnnEevJmKu/o1ws22tXc2qjJg+F4/V1ynUbh8E0rYlmo1XGw==", - "dev": true, - "requires": { - "@node-rs/helper": "^1.0.0", - "@swc/core-android-arm64": "^1.2.106", - "@swc/core-darwin-arm64": "^1.2.106", - "@swc/core-darwin-x64": "^1.2.106", - "@swc/core-freebsd-x64": "^1.2.106", - "@swc/core-linux-arm-gnueabihf": "^1.2.106", - "@swc/core-linux-arm64-gnu": "^1.2.106", - "@swc/core-linux-arm64-musl": "^1.2.106", - "@swc/core-linux-x64-gnu": "^1.2.106", - "@swc/core-linux-x64-musl": "^1.2.106", - "@swc/core-win32-arm64-msvc": "^1.2.106", - "@swc/core-win32-ia32-msvc": "^1.2.106", - "@swc/core-win32-x64-msvc": "^1.2.106" - } + "version": "1.2.205", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.2.205.tgz", + "integrity": "sha512-evq0/tFyYdYgOhKb//+G93fxe9zwFxtme7NL7wSiEF8+4/ON4Y5AI9eHLoqddXqs3W8Y0HQi+rJmlrkCibrseA==", + "dev": true, + "requires": { + "@swc/core-android-arm-eabi": "1.2.205", + "@swc/core-android-arm64": "1.2.205", + "@swc/core-darwin-arm64": "1.2.205", + "@swc/core-darwin-x64": "1.2.205", + "@swc/core-freebsd-x64": "1.2.205", + "@swc/core-linux-arm-gnueabihf": "1.2.205", + "@swc/core-linux-arm64-gnu": "1.2.205", + "@swc/core-linux-arm64-musl": "1.2.205", + "@swc/core-linux-x64-gnu": "1.2.205", + "@swc/core-linux-x64-musl": "1.2.205", + "@swc/core-win32-arm64-msvc": "1.2.205", + "@swc/core-win32-ia32-msvc": "1.2.205", + "@swc/core-win32-x64-msvc": "1.2.205" + } + }, + "@swc/core-android-arm-eabi": { + "version": "1.2.205", + "resolved": "https://registry.npmjs.org/@swc/core-android-arm-eabi/-/core-android-arm-eabi-1.2.205.tgz", + "integrity": "sha512-HfiuVA1JDHMSRQ8nE1DcemUgZ1PKaPwit4i7q3xin0NVbVHY1xkJyQFuLVh3VxTvGKKkF3hi8GJMVQgOXWL6kg==", + "dev": true, + "optional": true }, "@swc/core-android-arm64": { - "version": "1.2.106", - "resolved": "https://registry.npmjs.org/@swc/core-android-arm64/-/core-android-arm64-1.2.106.tgz", - "integrity": "sha512-F5T6kP3yV9S0/oXyco305QaAyE6rLNsNSdR0QI4CtACwKadiPwTOptwNIDCiTNLNgWlWLQmIRkPpxg+G4doT6Q==", + "version": "1.2.205", + "resolved": "https://registry.npmjs.org/@swc/core-android-arm64/-/core-android-arm64-1.2.205.tgz", + "integrity": "sha512-sRGZBV2dOnmh8gWWFo9HVOHdKa33zIsF8/8oYEGtq+2/s96UlAKltO2AA7HH9RaO/fT1tzBZStp+fEMUhDk/FA==", "dev": true, "optional": true }, "@swc/core-darwin-arm64": { - "version": "1.2.106", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.2.106.tgz", - "integrity": "sha512-bgKzzYLFnc+mv2mDS/DLwzBvx5DCC9ZCKYY46b4dAnBfasr+SMHj+v/WI84HtilbjLBMUfYZ2hgYKls3CebIIQ==", + "version": "1.2.205", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.2.205.tgz", + "integrity": "sha512-JwVDfKS7vp7zzOQXWNwwcF41h4r3DWEpK6DQjz18WJyS1VVOcpVQGyuE7kSPjcnG01ZxBL9JPwwT353i/8IwDg==", "dev": true, "optional": true }, "@swc/core-darwin-x64": { - "version": "1.2.106", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.2.106.tgz", - "integrity": "sha512-I5Uhit5RqbXaMIV2+v9jL+MIQeR3lT1DqVwzxZs1bTARclAheFZQpTmg+h6QmichjCiUT74SXQb6Apc/vqYKog==", + "version": "1.2.205", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.2.205.tgz", + "integrity": "sha512-malz2I+w6xFF1QyTmPGt0Y0NEMbUcrvfr5gUfZDGjxMhPPlS7k6fXucuZxVr9VVaM+JGq1SidVODmZ84jb1qHg==", "dev": true, "optional": true }, "@swc/core-freebsd-x64": { - "version": "1.2.106", - "resolved": "https://registry.npmjs.org/@swc/core-freebsd-x64/-/core-freebsd-x64-1.2.106.tgz", - "integrity": "sha512-ZSK3vgzbA2Pkpw2LgHlAkUdx4okIpdXXTbLXuc5jkZMw1KhRWpeQaDlwbrN7XVynAYjkj2qgGQ7wv1tD43vQig==", + "version": "1.2.205", + "resolved": "https://registry.npmjs.org/@swc/core-freebsd-x64/-/core-freebsd-x64-1.2.205.tgz", + "integrity": "sha512-/nZrG1z0T7h97AsOb/wOtYlnh4WEuNppv3XKQIMPj32YNQdMBVgpybVTVRIs1GQGZMd1/7jAy5BVQcwQjUbrLg==", "dev": true, "optional": true }, "@swc/core-linux-arm-gnueabihf": { - "version": "1.2.106", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.2.106.tgz", - "integrity": "sha512-WZh6XV8cQ9Fh3IQNX9z87Tv68+sLtfnT51ghMQxceRhfvc5gIaYW+PCppezDDdlPJnWXhybGWNPAl5SHppWb2g==", + "version": "1.2.205", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.2.205.tgz", + "integrity": "sha512-mTA3vETMdBmpecUyI9waZYsp7FABhew4e81psspmFpDyfty0SLISWZDnvPAn0pSnb2fWhzKwDC5kdXHKUmLJuA==", "dev": true, "optional": true }, "@swc/core-linux-arm64-gnu": { - "version": "1.2.106", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.2.106.tgz", - "integrity": "sha512-OSI9VUWPsRrCbUlRQ4KdYqdwV63VYBC5ahSNq+72DXhtRwVbLSFuF7MNsnXgUSMHidxbc0No3/bPPamshqHdsQ==", + "version": "1.2.205", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.2.205.tgz", + "integrity": "sha512-qGzFGryeQE+O5SFK7Nn2ESqCEnv00rnzhf11WZF9V71EZ15amIhmbcwHqvFpoRSDw8hZnqoGqfPRfoJbouptnA==", "dev": true, "optional": true }, "@swc/core-linux-arm64-musl": { - "version": "1.2.106", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.2.106.tgz", - "integrity": "sha512-de8AAUOP8D2/tZIpQ399xw+pGGKlR1+l5Jmy4lW7ixarEI4xKkBSF4bS9eXtC1jckmenzrLPiK/5sSbQSf6BWQ==", + "version": "1.2.205", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.2.205.tgz", + "integrity": "sha512-uLJoX9L/4Xg3sLMjAbIhzbTe5gD/MBA8VETBeEkLtgb7a0ys1kvn9xQ6qLw6A71amEPlI+VABnoTRdUEaBSV9Q==", "dev": true, "optional": true }, "@swc/core-linux-x64-gnu": { - "version": "1.2.106", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.2.106.tgz", - "integrity": "sha512-QzFC7+lBSuVBmX5tS2pdM+74voiJcGgIMJ+x9pcjUu3GkDl3ow6WC6ta2WUzlgGopCGNp6IdZaFemKRzjLr3lw==", + "version": "1.2.205", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.2.205.tgz", + "integrity": "sha512-gQsjcYlkWKP1kceQIsoHGrOrG7ygW3ojNsSnYoZ5DG5PipRA4eeUfO9YIfrmoa29LiVNjmRPfUJa8O1UHDG5ew==", "dev": true, "optional": true }, "@swc/core-linux-x64-musl": { - "version": "1.2.106", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.2.106.tgz", - "integrity": "sha512-QZ1gFqNiCJefkNMihbmYc7nr5stERyjoQpWgAIN6dzrgMUzRHXHGDRl/p1qsXW2VKos+okSdLwPFEmRT94H+1A==", + "version": "1.2.205", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.2.205.tgz", + "integrity": "sha512-LR5ukqBltQc++2eX3qEj/H8KtOt0V3CmtgXNOiNCUxvPDT8mYz/8MJhYOrofonND0RKfXyyPW7dRxg62ceTLSQ==", "dev": true, "optional": true }, "@swc/core-win32-arm64-msvc": { - "version": "1.2.106", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.2.106.tgz", - "integrity": "sha512-MbuQwk+s43bfBNnAZTKnoQlfo4UPSOsy6t9F15yU4P3rVUuFtcxdZg6CpDnUqNPbojILXujp8z4SSigRYh5cgg==", + "version": "1.2.205", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.2.205.tgz", + "integrity": "sha512-NjcLWm4mOy78LAEt7pqFl+SLcCyqnSlUP729XRd1uRvKwt1Cwch5SQRdoaFqwf1DaEQy4H4iuGPynkfarlb1kQ==", "dev": true, "optional": true }, "@swc/core-win32-ia32-msvc": { - "version": "1.2.106", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.2.106.tgz", - "integrity": "sha512-BFxWpcPxsG2LLQZ+8K8ma45rbTckjpPbnvOOhybQ0hEhLgoVzMVPp3RIUGmC+RMZe6DkGSaEQf/Rjn2cbMdQhw==", + "version": "1.2.205", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.2.205.tgz", + "integrity": "sha512-+6byrRxIXgZ0zmLL6ZeX1HBBrAqvCy8MR5Yz0SO26jR8OPZXJCgZXL9BTsZO+YEG4f32ZOlZh3nnHCl6Dcb4GA==", "dev": true, "optional": true }, "@swc/core-win32-x64-msvc": { - "version": "1.2.106", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.2.106.tgz", - "integrity": "sha512-Emn5akqApGXzPsA7ntSXEohL0AH0WjQMHy6mT3MS9Yil42yTJ96dJGf68ejKVptxwg7Iz798mT+J9r1JbAFBgg==", + "version": "1.2.205", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.2.205.tgz", + "integrity": "sha512-RRSkyAol0c7sU9gejtrpF8TLmdYdBjLutcmQHtLKbWTm74ZLidZpF28G0J2tD7HNmzQnMpLzyoT1jW9JgLwzVg==", "dev": true, "optional": true }, "@swc/wasm": { - "version": "1.2.58", - "resolved": "https://registry.npmjs.org/@swc/wasm/-/wasm-1.2.58.tgz", - "integrity": "sha512-3PAMVT+clB2xZsaVtQK2WjgeCftCxDO/0q8JCwW5g3CrLL/WBOCHyCKME3CzGz7V9zfw84QZZMpZY26gohhF/w==", + "version": "1.2.205", + "resolved": "https://registry.npmjs.org/@swc/wasm/-/wasm-1.2.205.tgz", + "integrity": "sha512-xRI7Lrg/v18EnJIHRDflD09bif4Ivc2W0dhRGtTmjdsSrCjJFrArDGaGttD2Fwuv7q1S4/uAWMLFHncwwSyO3Q==", "dev": true }, "@szmarczak/http-timer": { @@ -3583,6 +3575,12 @@ } } }, + "outdent": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/outdent/-/outdent-0.8.0.tgz", + "integrity": "sha512-KiOAIsdpUTcAXuykya5fnVVT+/5uS0Q1mrkRHcF89tpieSmY33O/tmc54CqwA+bfhbtEfZUNLHaPUiB9X3jt1A==", + "dev": true + }, "p-cancelable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", @@ -4670,9 +4668,9 @@ } }, "typescript": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.2.tgz", - "integrity": "sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A==", + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", "dev": true }, "typescript-json-schema": { diff --git a/package.json b/package.json index 96cc40187..5350063f4 100644 --- a/package.json +++ b/package.json @@ -112,8 +112,8 @@ "homepage": "https://typestrong.org/ts-node", "devDependencies": { "@microsoft/api-extractor": "^7.19.4", - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", + "@swc/core": ">=1.2.205", + "@swc/wasm": ">=1.2.205", "@types/diff": "^4.0.2", "@types/lodash": "^4.14.151", "@types/node": "13.13.5", @@ -131,6 +131,7 @@ "lodash": "^4.17.15", "ntypescript": "^1.201507091536.1", "nyc": "^15.0.1", + "outdent": "^0.8.0", "proper-lockfile": "^4.1.2", "proxyquire": "^2.0.0", "react": "^16.14.0", @@ -138,7 +139,7 @@ "semver": "^7.1.3", "throat": "^6.0.1", "typedoc": "^0.22.10", - "typescript": "4.7.2", + "typescript": "4.7.4", "typescript-json-schema": "^0.53.0", "util.promisify": "^1.0.1" }, diff --git a/src/test/transpilers.spec.ts b/src/test/transpilers.spec.ts index 57174f5e8..dfac3307f 100644 --- a/src/test/transpilers.spec.ts +++ b/src/test/transpilers.spec.ts @@ -4,7 +4,9 @@ import { context } from './testlib'; import { ctxTsNode, testsDirRequire } from './helpers'; +import { createSwcOptions } from '../transpilers/swc'; import * as expect from 'expect'; +import { outdent } from 'outdent'; const test = context(ctxTsNode); @@ -44,4 +46,91 @@ test.suite('swc', (test) => { expect([...swcTranspiler.targetMapping.values()]).toContain(target); } }); + + test.suite('converts TS config to swc config', (test) => { + test.suite('jsx', (test) => { + const macro = test.macro( + (jsx: string, runtime?: string, development?: boolean) => [ + () => `jsx=${jsx}`, + async (t) => { + const tsNode = t.context.tsNodeUnderTest.create({ + compilerOptions: { + jsx, + }, + }); + const swcOptions = createSwcOptions( + tsNode.config.options, + undefined, + require('@swc/core'), + '@swc/core' + ); + expect(swcOptions.tsxOptions.jsc?.transform?.react).toBeDefined(); + expect( + swcOptions.tsxOptions.jsc?.transform?.react?.development + ).toBe(development); + expect(swcOptions.tsxOptions.jsc?.transform?.react?.runtime).toBe( + runtime + ); + }, + ] + ); + + test(macro, 'react', undefined, undefined); + test(macro, 'react-jsx', 'automatic', undefined); + test(macro, 'react-jsxdev', 'automatic', true); + }); + }); + + test.suite('transforms various forms of jsx', (test) => { + const macro = test.macro( + (compilerOptions: object, expectedOutput: string) => [ + () => `${JSON.stringify(compilerOptions)}`, + async (t) => { + const code = t.context.tsNodeUnderTest + .create({ + swc: true, + skipProject: true, + compilerOptions: { + module: 'esnext', + ...compilerOptions, + }, + }) + .compile(input, 'input.tsx'); + expect(code.replace(/\/\/# sourceMappingURL.*/, '').trim()).toBe( + expectedOutput + ); + }, + ] + ); + + const input = outdent` + const div =
; + `; + + test( + macro, + { jsx: 'react' }, + `const div = /*#__PURE__*/ React.createElement("div", null);` + ); + test( + macro, + { jsx: 'react-jsx' }, + outdent` + import { jsx as _jsx } from "react/jsx-runtime"; + const div = /*#__PURE__*/ _jsx("div", {}); + ` + ); + test( + macro, + { jsx: 'react-jsxdev' }, + outdent` + import { jsxDEV as _jsxDEV } from "react/jsx-dev-runtime"; + const div = /*#__PURE__*/ _jsxDEV("div", {}, void 0, false, { + fileName: "input.tsx", + lineNumber: 1, + columnNumber: 13 + }, this); + ` + ); + }); }); diff --git a/src/transpilers/swc.ts b/src/transpilers/swc.ts index 68093a6d8..35af844db 100644 --- a/src/transpilers/swc.ts +++ b/src/transpilers/swc.ts @@ -123,6 +123,11 @@ const ModuleKind = { NodeNext: 199, } as const; +const JsxEmit = { + ReactJSX: /* ts.JsxEmit.ReactJSX */ 4, + ReactJSXDev: /* ts.JsxEmit.ReactJSXDev */ 5, +} as const; + /** * Prepare SWC options derived from typescript compiler options. * @internal exported for testing @@ -141,6 +146,7 @@ export function createSwcOptions( emitDecoratorMetadata, target, module, + jsx, jsxFactory, jsxFragmentFactory, strict, @@ -195,6 +201,13 @@ export function createSwcOptions( ? false : true; + const jsxRuntime: swcTypes.ReactConfig['runtime'] = + jsx === JsxEmit.ReactJSX || jsx === JsxEmit.ReactJSXDev + ? 'automatic' + : undefined; + const jsxDevelopment: swcTypes.ReactConfig['development'] = + jsx === JsxEmit.ReactJSXDev ? true : undefined; + const nonTsxOptions = createVariant(false); const tsxOptions = createVariant(true); return { nonTsxOptions, tsxOptions }; @@ -227,10 +240,11 @@ export function createSwcOptions( legacyDecorator: true, react: { throwIfNamespace: false, - development: false, + development: jsxDevelopment, useBuiltins: false, pragma: jsxFactory!, pragmaFrag: jsxFragmentFactory!, + runtime: jsxRuntime, } as swcTypes.ReactConfig, }, keepClassNames, From 25db65634502bb02a4961b48d517cd79ac1a3944 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Mon, 27 Jun 2022 00:25:09 -0400 Subject: [PATCH 4/5] fix import assertions --- src/test/transpilers.spec.ts | 69 +++++++++++++++++++++++------------- src/transpilers/swc.ts | 4 +++ 2 files changed, 49 insertions(+), 24 deletions(-) diff --git a/src/test/transpilers.spec.ts b/src/test/transpilers.spec.ts index dfac3307f..6920a2747 100644 --- a/src/test/transpilers.spec.ts +++ b/src/test/transpilers.spec.ts @@ -81,48 +81,51 @@ test.suite('swc', (test) => { }); }); - test.suite('transforms various forms of jsx', (test) => { - const macro = test.macro( - (compilerOptions: object, expectedOutput: string) => [ - () => `${JSON.stringify(compilerOptions)}`, - async (t) => { - const code = t.context.tsNodeUnderTest - .create({ - swc: true, - skipProject: true, - compilerOptions: { - module: 'esnext', - ...compilerOptions, - }, - }) - .compile(input, 'input.tsx'); - expect(code.replace(/\/\/# sourceMappingURL.*/, '').trim()).toBe( - expectedOutput - ); - }, - ] - ); + const compileMacro = test.macro( + (compilerOptions: object, input: string, expectedOutput: string) => [ + (title?: string) => title ?? `${JSON.stringify(compilerOptions)}`, + async (t) => { + const code = t.context.tsNodeUnderTest + .create({ + swc: true, + skipProject: true, + compilerOptions: { + module: 'esnext', + ...compilerOptions, + }, + }) + .compile(input, 'input.tsx'); + expect(code.replace(/\/\/# sourceMappingURL.*/, '').trim()).toBe( + expectedOutput + ); + }, + ] + ); + test.suite('transforms various forms of jsx', (test) => { const input = outdent` const div =
; `; test( - macro, + compileMacro, { jsx: 'react' }, + input, `const div = /*#__PURE__*/ React.createElement("div", null);` ); test( - macro, + compileMacro, { jsx: 'react-jsx' }, + input, outdent` import { jsx as _jsx } from "react/jsx-runtime"; const div = /*#__PURE__*/ _jsx("div", {}); ` ); test( - macro, + compileMacro, { jsx: 'react-jsxdev' }, + input, outdent` import { jsxDEV as _jsxDEV } from "react/jsx-dev-runtime"; const div = /*#__PURE__*/ _jsxDEV("div", {}, void 0, false, { @@ -133,4 +136,22 @@ test.suite('swc', (test) => { ` ); }); + + test.suite('preserves import assertions for json imports', (test) => { + test( + 'basic json import', + compileMacro, + { module: 'esnext' }, + outdent` + import document from './document.json' assert {type: 'json'}; + document; + `, + outdent` + import document from './document.json' assert { + type: 'json' + }; + document; + ` + ); + }); }); diff --git a/src/transpilers/swc.ts b/src/transpilers/swc.ts index 35af844db..89e9bd496 100644 --- a/src/transpilers/swc.ts +++ b/src/transpilers/swc.ts @@ -233,6 +233,7 @@ export function createSwcOptions( tsx: isTsx, decorators: experimentalDecorators, dynamicImport: true, + importAssertions: true, }, target: swcTarget, transform: { @@ -248,6 +249,9 @@ export function createSwcOptions( } as swcTypes.ReactConfig, }, keepClassNames, + experimental: { + keepImportAssertions: true, + }, } as swcTypes.JscConfig, }; From 7eda9c2da3b6f7f8a3fed3e2607ddc941d3d0135 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Mon, 27 Jun 2022 00:40:12 -0400 Subject: [PATCH 5/5] fix --- src/test/helpers.ts | 2 ++ src/test/transpilers.spec.ts | 62 +++++++++++++++++++++--------------- 2 files changed, 39 insertions(+), 25 deletions(-) diff --git a/src/test/helpers.ts b/src/test/helpers.ts index c4ea9e8a4..da86bddc2 100644 --- a/src/test/helpers.ts +++ b/src/test/helpers.ts @@ -99,6 +99,8 @@ export const tsSupportsStableNodeNextNode16 = // TS 4.5 is first version to understand .cts, .mts, .cjs, and .mjs extensions export const tsSupportsMtsCtsExtensions = semver.gte(ts.version, '4.5.0'); export const tsSupportsImportAssertions = semver.gte(ts.version, '4.5.0'); +// TS 4.1 added jsx=react-jsx and react-jsxdev: https://devblogs.microsoft.com/typescript/announcing-typescript-4-1/#react-17-jsx-factories +export const tsSupportsReact17JsxFactories = semver.gte(ts.version, '4.1.0'); //#endregion export const xfs = new NodeFS(fs); diff --git a/src/test/transpilers.spec.ts b/src/test/transpilers.spec.ts index 6920a2747..1d64186a0 100644 --- a/src/test/transpilers.spec.ts +++ b/src/test/transpilers.spec.ts @@ -3,7 +3,12 @@ // Should consolidate them here. import { context } from './testlib'; -import { ctxTsNode, testsDirRequire } from './helpers'; +import { + ctxTsNode, + testsDirRequire, + tsSupportsImportAssertions, + tsSupportsReact17JsxFactories, +} from './helpers'; import { createSwcOptions } from '../transpilers/swc'; import * as expect from 'expect'; import { outdent } from 'outdent'; @@ -76,8 +81,11 @@ test.suite('swc', (test) => { ); test(macro, 'react', undefined, undefined); - test(macro, 'react-jsx', 'automatic', undefined); - test(macro, 'react-jsxdev', 'automatic', true); + test.suite('react 17 jsx factories', (test) => { + test.runIf(tsSupportsReact17JsxFactories); + test(macro, 'react-jsx', 'automatic', undefined); + test(macro, 'react-jsxdev', 'automatic', true); + }); }); }); @@ -113,31 +121,35 @@ test.suite('swc', (test) => { input, `const div = /*#__PURE__*/ React.createElement("div", null);` ); - test( - compileMacro, - { jsx: 'react-jsx' }, - input, - outdent` - import { jsx as _jsx } from "react/jsx-runtime"; - const div = /*#__PURE__*/ _jsx("div", {}); - ` - ); - test( - compileMacro, - { jsx: 'react-jsxdev' }, - input, - outdent` - import { jsxDEV as _jsxDEV } from "react/jsx-dev-runtime"; - const div = /*#__PURE__*/ _jsxDEV("div", {}, void 0, false, { - fileName: "input.tsx", - lineNumber: 1, - columnNumber: 13 - }, this); - ` - ); + test.suite('react 17 jsx factories', (test) => { + test.runIf(tsSupportsReact17JsxFactories); + test( + compileMacro, + { jsx: 'react-jsx' }, + input, + outdent` + import { jsx as _jsx } from "react/jsx-runtime"; + const div = /*#__PURE__*/ _jsx("div", {}); + ` + ); + test( + compileMacro, + { jsx: 'react-jsxdev' }, + input, + outdent` + import { jsxDEV as _jsxDEV } from "react/jsx-dev-runtime"; + const div = /*#__PURE__*/ _jsxDEV("div", {}, void 0, false, { + fileName: "input.tsx", + lineNumber: 1, + columnNumber: 13 + }, this); + ` + ); + }); }); test.suite('preserves import assertions for json imports', (test) => { + test.runIf(tsSupportsImportAssertions); test( 'basic json import', compileMacro,