From 049fe8030aa98b23d10c85bcb69bc5b202a7e4ef Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Wed, 21 Jul 2021 16:44:29 -0400 Subject: [PATCH 1/5] Respect 'module' option in swc transpiler --- src/transpilers/swc.ts | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/transpilers/swc.ts b/src/transpilers/swc.ts index 1ece325e5..c9528c512 100644 --- a/src/transpilers/swc.ts +++ b/src/transpilers/swc.ts @@ -49,6 +49,7 @@ export function create(createOptions: SwcTranspilerOptions): Transpiler { experimentalDecorators, emitDecoratorMetadata, target, + module, jsxFactory, jsxFragmentFactory, } = compilerOptions; @@ -57,13 +58,21 @@ export function create(createOptions: SwcTranspilerOptions): Transpiler { function createSwcOptions(isTsx: boolean): swcTypes.Options { const swcTarget = targetMapping.get(target!) ?? 'es3'; const keepClassNames = target! >= /* ts.ScriptTarget.ES2016 */ 3; + const moduleConfig = { + noInterop: !esModuleInterop, + type: + module === ModuleKind.CommonJS + ? 'commonjs' + : module === ModuleKind.AMD + ? 'amd' + : module === ModuleKind.UMD + ? 'umd' + : undefined, + } as swcTypes.ModuleConfig; return { sourceMaps: sourceMap, // isModule: true, - module: { - type: 'commonjs', - noInterop: !esModuleInterop, - }, + module: moduleConfig, swcrc: false, jsc: { externalHelpers: importHelpers, @@ -118,3 +127,14 @@ targetMapping.set(/* ts.ScriptTarget.ES2018 */ 5, 'es2018'); targetMapping.set(/* ts.ScriptTarget.ES2019 */ 6, 'es2019'); targetMapping.set(/* ts.ScriptTarget.ES2020 */ 7, 'es2019'); targetMapping.set(/* ts.ScriptTarget.ESNext */ 99, 'es2019'); + +const ModuleKind = { + None: 0, + CommonJS: 1, + AMD: 2, + UMD: 3, + System: 4, + ES2015: 5, + ES2020: 6, + ESNext: 99, +} as const; From ccba5cbb239f9f1056568cafb62e4e7bcec1c945 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Wed, 21 Jul 2021 21:42:37 -0400 Subject: [PATCH 2/5] fix tests --- src/test/index.spec.ts | 12 +++++++++ src/transpilers/swc.ts | 26 ++++++++++--------- tests/transpile-only-swc-native-esm/index.ts | 2 ++ .../package.json | 3 +++ .../tsconfig.json | 13 ++++++++++ .../transpile-only-swc-via-tsconfig/index.ts | 4 +++ .../tsconfig.json | 2 +- tests/transpile-only-swc/index.ts | 4 +++ tests/transpile-only-swc/tsconfig.json | 2 +- 9 files changed, 54 insertions(+), 14 deletions(-) create mode 100644 tests/transpile-only-swc-native-esm/index.ts create mode 100644 tests/transpile-only-swc-native-esm/package.json create mode 100644 tests/transpile-only-swc-native-esm/tsconfig.json diff --git a/src/test/index.spec.ts b/src/test/index.spec.ts index a9d7b91f9..ed9f19c94 100644 --- a/src/test/index.spec.ts +++ b/src/test/index.spec.ts @@ -103,6 +103,7 @@ test.beforeAll(async () => { test.suite('ts-node', (test) => { const cmd = `"${BIN_PATH}" --project "${PROJECT}"`; const cmdNoProject = `"${BIN_PATH}"`; + const cmdEsmLoaderNoProject = `node --loader ts-node/esm`; test('should export the correct version', () => { expect(VERSION).to.equal(require('../../package.json').version); @@ -367,6 +368,17 @@ test.suite('ts-node', (test) => { expect(stdout).to.contain('Hello World!'); }); + test('swc transpiler supports native ESM emit', async () => { + const { err, stdout } = await exec( + `${cmdEsmLoaderNoProject} ./index.ts`, + { + cwd: resolve(TEST_DIR, 'transpile-only-swc-native-esm'), + } + ); + expect(err).to.equal(null); + expect(stdout).to.contain('Hello file://'); + }); + test('should pipe into `ts-node` and evaluate', async () => { const execPromise = exec(cmd); execPromise.child.stdin!.end("console.log('hello')"); diff --git a/src/transpilers/swc.ts b/src/transpilers/swc.ts index c9528c512..9b7361a08 100644 --- a/src/transpilers/swc.ts +++ b/src/transpilers/swc.ts @@ -58,21 +58,23 @@ export function create(createOptions: SwcTranspilerOptions): Transpiler { function createSwcOptions(isTsx: boolean): swcTypes.Options { const swcTarget = targetMapping.get(target!) ?? 'es3'; const keepClassNames = target! >= /* ts.ScriptTarget.ES2016 */ 3; - const moduleConfig = { - noInterop: !esModuleInterop, - type: - module === ModuleKind.CommonJS - ? 'commonjs' - : module === ModuleKind.AMD - ? 'amd' - : module === ModuleKind.UMD - ? 'umd' - : undefined, - } as swcTypes.ModuleConfig; + const moduleType = + module === ModuleKind.CommonJS + ? 'commonjs' + : module === ModuleKind.AMD + ? 'amd' + : module === ModuleKind.UMD + ? 'umd' + : undefined; return { sourceMaps: sourceMap, // isModule: true, - module: moduleConfig, + module: moduleType + ? ({ + noInterop: !esModuleInterop, + type: moduleType, + } as swcTypes.ModuleConfig) + : undefined, swcrc: false, jsc: { externalHelpers: importHelpers, diff --git a/tests/transpile-only-swc-native-esm/index.ts b/tests/transpile-only-swc-native-esm/index.ts new file mode 100644 index 000000000..8c6d0f5aa --- /dev/null +++ b/tests/transpile-only-swc-native-esm/index.ts @@ -0,0 +1,2 @@ +const x: number = `Hello ${import.meta.url.slice(0, 7)}!`; +console.log(x); diff --git a/tests/transpile-only-swc-native-esm/package.json b/tests/transpile-only-swc-native-esm/package.json new file mode 100644 index 000000000..3dbc1ca59 --- /dev/null +++ b/tests/transpile-only-swc-native-esm/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/tests/transpile-only-swc-native-esm/tsconfig.json b/tests/transpile-only-swc-native-esm/tsconfig.json new file mode 100644 index 000000000..e0e3c9a90 --- /dev/null +++ b/tests/transpile-only-swc-native-esm/tsconfig.json @@ -0,0 +1,13 @@ +{ + "ts-node": { + "transpileOnly": true, + "transpiler": "ts-node/transpilers/swc-experimental" + }, + "compilerOptions": { + "target": "ES2018", + "module": "ESNext", + "allowJs": true, + "jsx": "react", + "experimentalDecorators": true + } +} diff --git a/tests/transpile-only-swc-via-tsconfig/index.ts b/tests/transpile-only-swc-via-tsconfig/index.ts index e2f5a1e22..b7dfa09cd 100644 --- a/tests/transpile-only-swc-via-tsconfig/index.ts +++ b/tests/transpile-only-swc-via-tsconfig/index.ts @@ -7,3 +7,7 @@ class World {} parseInt(1101, 2); const x: number = `Hello ${World.name}!`; console.log(x); + +// test module type emit +import { readFileSync } from 'fs'; +readFileSync; diff --git a/tests/transpile-only-swc-via-tsconfig/tsconfig.json b/tests/transpile-only-swc-via-tsconfig/tsconfig.json index e0e3c9a90..5097d63ac 100644 --- a/tests/transpile-only-swc-via-tsconfig/tsconfig.json +++ b/tests/transpile-only-swc-via-tsconfig/tsconfig.json @@ -5,7 +5,7 @@ }, "compilerOptions": { "target": "ES2018", - "module": "ESNext", + "module": "CommonJS", "allowJs": true, "jsx": "react", "experimentalDecorators": true diff --git a/tests/transpile-only-swc/index.ts b/tests/transpile-only-swc/index.ts index e2f5a1e22..b7dfa09cd 100644 --- a/tests/transpile-only-swc/index.ts +++ b/tests/transpile-only-swc/index.ts @@ -7,3 +7,7 @@ class World {} parseInt(1101, 2); const x: number = `Hello ${World.name}!`; console.log(x); + +// test module type emit +import { readFileSync } from 'fs'; +readFileSync; diff --git a/tests/transpile-only-swc/tsconfig.json b/tests/transpile-only-swc/tsconfig.json index c1cf4bccd..78c35a9ca 100644 --- a/tests/transpile-only-swc/tsconfig.json +++ b/tests/transpile-only-swc/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "target": "ES2018", - "module": "ESNext", + "module": "CommonJS", "allowJs": true, "jsx": "react", "experimentalDecorators": true From c84c10e80d71fccc2226af472f6f1ecf4f401f60 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Thu, 22 Jul 2021 00:10:51 -0400 Subject: [PATCH 3/5] fix tests --- src/test/index.spec.ts | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/test/index.spec.ts b/src/test/index.spec.ts index ed9f19c94..0f0b05f9f 100644 --- a/src/test/index.spec.ts +++ b/src/test/index.spec.ts @@ -103,7 +103,10 @@ test.beforeAll(async () => { test.suite('ts-node', (test) => { const cmd = `"${BIN_PATH}" --project "${PROJECT}"`; const cmdNoProject = `"${BIN_PATH}"`; - const cmdEsmLoaderNoProject = `node --loader ts-node/esm`; + const experimentalModulesFlag = semver.gte(process.version, '12.17.0') + ? '' + : '--experimental-modules'; + const cmdEsmLoaderNoProject = `node ${experimentalModulesFlag} --loader ts-node/esm`; test('should export the correct version', () => { expect(VERSION).to.equal(require('../../package.json').version); @@ -1749,21 +1752,16 @@ test.suite('ts-node', (test) => { }); test.suite('esm', (test) => { - const experimentalModulesFlag = semver.gte(process.version, '12.17.0') - ? '' - : '--experimental-modules'; - const esmCmd = `node ${experimentalModulesFlag} --loader ts-node/esm`; - if (semver.gte(process.version, '12.16.0')) { test('should compile and execute as ESM', async () => { - const { err, stdout } = await exec(`${esmCmd} index.ts`, { + const { err, stdout } = await exec(`${cmdEsmLoaderNoProject} index.ts`, { cwd: join(TEST_DIR, './esm'), }); expect(err).to.equal(null); expect(stdout).to.equal('foo bar baz biff libfoo\n'); }); test('should use source maps', async () => { - const { err, stdout } = await exec(`${esmCmd} "throw error.ts"`, { + const { err, stdout } = await exec(`${cmdEsmLoaderNoProject} "throw error.ts"`, { cwd: join(TEST_DIR, './esm'), }); expect(err).not.to.equal(null); @@ -1785,7 +1783,7 @@ test.suite('ts-node', (test) => { err, stdout, } = await exec( - `${esmCmd} --experimental-specifier-resolution=node index.ts`, + `${cmdEsmLoaderNoProject} --experimental-specifier-resolution=node index.ts`, { cwd: join(TEST_DIR, './esm-node-resolver') } ); expect(err).to.equal(null); @@ -1796,14 +1794,14 @@ test.suite('ts-node', (test) => { err, stdout, } = await exec( - `${esmCmd} --experimental-modules --es-module-specifier-resolution=node index.ts`, + `${cmdEsmLoaderNoProject} --experimental-modules --es-module-specifier-resolution=node index.ts`, { cwd: join(TEST_DIR, './esm-node-resolver') } ); expect(err).to.equal(null); expect(stdout).to.equal('foo bar baz biff libfoo\n'); }); test('via NODE_OPTIONS', async () => { - const { err, stdout } = await exec(`${esmCmd} index.ts`, { + const { err, stdout } = await exec(`${cmdEsmLoaderNoProject} index.ts`, { cwd: join(TEST_DIR, './esm-node-resolver'), env: { ...process.env, @@ -1816,7 +1814,7 @@ test.suite('ts-node', (test) => { }); test('throws ERR_REQUIRE_ESM when attempting to require() an ESM script when ESM loader is enabled', async () => { - const { err, stderr } = await exec(`${esmCmd} ./index.js`, { + const { err, stderr } = await exec(`${cmdEsmLoaderNoProject} ./index.js`, { cwd: join(TEST_DIR, './esm-err-require-esm'), }); expect(err).to.not.equal(null); @@ -1826,7 +1824,7 @@ test.suite('ts-node', (test) => { }); test('defers to fallback loaders when URL should not be handled by ts-node', async () => { - const { err, stdout, stderr } = await exec(`${esmCmd} index.mjs`, { + const { err, stdout, stderr } = await exec(`${cmdEsmLoaderNoProject} index.mjs`, { cwd: join(TEST_DIR, './esm-import-http-url'), }); expect(err).to.not.equal(null); @@ -1837,7 +1835,7 @@ test.suite('ts-node', (test) => { }); test('should bypass import cache when changing search params', async () => { - const { err, stdout } = await exec(`${esmCmd} index.ts`, { + const { err, stdout } = await exec(`${cmdEsmLoaderNoProject} index.ts`, { cwd: join(TEST_DIR, './esm-import-cache'), }); expect(err).to.equal(null); @@ -1846,7 +1844,7 @@ test.suite('ts-node', (test) => { test('should support transpile only mode via dedicated loader entrypoint', async () => { const { err, stdout } = await exec( - `${esmCmd}/transpile-only index.ts`, + `${cmdEsmLoaderNoProject}/transpile-only index.ts`, { cwd: join(TEST_DIR, './esm-transpile-only'), } @@ -1855,7 +1853,7 @@ test.suite('ts-node', (test) => { expect(stdout).to.equal(''); }); test('should throw type errors without transpile-only enabled', async () => { - const { err, stdout } = await exec(`${esmCmd} index.ts`, { + const { err, stdout } = await exec(`${cmdEsmLoaderNoProject} index.ts`, { cwd: join(TEST_DIR, './esm-transpile-only'), }); if (err === null) { @@ -1878,7 +1876,7 @@ test.suite('ts-node', (test) => { async function runModuleTypeTest(project: string, ext: string) { const { err, stderr, stdout } = await exec( - `${esmCmd} ./module-types/${project}/test.${ext}`, + `${cmdEsmLoaderNoProject} ./module-types/${project}/test.${ext}`, { env: { ...process.env, From 27473ace19558c8d0b502b930674ae9f698476b8 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Thu, 22 Jul 2021 01:04:38 -0400 Subject: [PATCH 4/5] lint fix --- src/test/index.spec.ts | 71 +++++++++++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 25 deletions(-) diff --git a/src/test/index.spec.ts b/src/test/index.spec.ts index 0f0b05f9f..27634c942 100644 --- a/src/test/index.spec.ts +++ b/src/test/index.spec.ts @@ -1754,16 +1754,22 @@ test.suite('ts-node', (test) => { test.suite('esm', (test) => { if (semver.gte(process.version, '12.16.0')) { test('should compile and execute as ESM', async () => { - const { err, stdout } = await exec(`${cmdEsmLoaderNoProject} index.ts`, { - cwd: join(TEST_DIR, './esm'), - }); + const { err, stdout } = await exec( + `${cmdEsmLoaderNoProject} index.ts`, + { + cwd: join(TEST_DIR, './esm'), + } + ); expect(err).to.equal(null); expect(stdout).to.equal('foo bar baz biff libfoo\n'); }); test('should use source maps', async () => { - const { err, stdout } = await exec(`${cmdEsmLoaderNoProject} "throw error.ts"`, { - cwd: join(TEST_DIR, './esm'), - }); + const { err, stdout } = await exec( + `${cmdEsmLoaderNoProject} "throw error.ts"`, + { + cwd: join(TEST_DIR, './esm'), + } + ); expect(err).not.to.equal(null); expect(err!.message).to.contain( [ @@ -1801,22 +1807,28 @@ test.suite('ts-node', (test) => { expect(stdout).to.equal('foo bar baz biff libfoo\n'); }); test('via NODE_OPTIONS', async () => { - const { err, stdout } = await exec(`${cmdEsmLoaderNoProject} index.ts`, { - cwd: join(TEST_DIR, './esm-node-resolver'), - env: { - ...process.env, - NODE_OPTIONS: `${experimentalModulesFlag} --experimental-specifier-resolution=node`, - }, - }); + const { err, stdout } = await exec( + `${cmdEsmLoaderNoProject} index.ts`, + { + cwd: join(TEST_DIR, './esm-node-resolver'), + env: { + ...process.env, + NODE_OPTIONS: `${experimentalModulesFlag} --experimental-specifier-resolution=node`, + }, + } + ); expect(err).to.equal(null); expect(stdout).to.equal('foo bar baz biff libfoo\n'); }); }); test('throws ERR_REQUIRE_ESM when attempting to require() an ESM script when ESM loader is enabled', async () => { - const { err, stderr } = await exec(`${cmdEsmLoaderNoProject} ./index.js`, { - cwd: join(TEST_DIR, './esm-err-require-esm'), - }); + const { err, stderr } = await exec( + `${cmdEsmLoaderNoProject} ./index.js`, + { + cwd: join(TEST_DIR, './esm-err-require-esm'), + } + ); expect(err).to.not.equal(null); expect(stderr).to.contain( 'Error [ERR_REQUIRE_ESM]: Must use import to load ES Module:' @@ -1824,9 +1836,12 @@ test.suite('ts-node', (test) => { }); test('defers to fallback loaders when URL should not be handled by ts-node', async () => { - const { err, stdout, stderr } = await exec(`${cmdEsmLoaderNoProject} index.mjs`, { - cwd: join(TEST_DIR, './esm-import-http-url'), - }); + const { err, stdout, stderr } = await exec( + `${cmdEsmLoaderNoProject} index.mjs`, + { + cwd: join(TEST_DIR, './esm-import-http-url'), + } + ); expect(err).to.not.equal(null); // expect error from node's default resolver expect(stderr).to.match( @@ -1835,9 +1850,12 @@ test.suite('ts-node', (test) => { }); test('should bypass import cache when changing search params', async () => { - const { err, stdout } = await exec(`${cmdEsmLoaderNoProject} index.ts`, { - cwd: join(TEST_DIR, './esm-import-cache'), - }); + const { err, stdout } = await exec( + `${cmdEsmLoaderNoProject} index.ts`, + { + cwd: join(TEST_DIR, './esm-import-cache'), + } + ); expect(err).to.equal(null); expect(stdout).to.equal('log1\nlog2\nlog2\n'); }); @@ -1853,9 +1871,12 @@ test.suite('ts-node', (test) => { expect(stdout).to.equal(''); }); test('should throw type errors without transpile-only enabled', async () => { - const { err, stdout } = await exec(`${cmdEsmLoaderNoProject} index.ts`, { - cwd: join(TEST_DIR, './esm-transpile-only'), - }); + const { err, stdout } = await exec( + `${cmdEsmLoaderNoProject} index.ts`, + { + cwd: join(TEST_DIR, './esm-transpile-only'), + } + ); if (err === null) { throw new Error('Command was expected to fail, but it succeeded.'); } From 6ea89bd63bbe795b6c4b823795a23c221862d42d Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Thu, 22 Jul 2021 01:18:51 -0400 Subject: [PATCH 5/5] dont do esm test on really old node --- src/test/index.spec.ts | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/test/index.spec.ts b/src/test/index.spec.ts index 27634c942..d9a93c689 100644 --- a/src/test/index.spec.ts +++ b/src/test/index.spec.ts @@ -371,16 +371,18 @@ test.suite('ts-node', (test) => { expect(stdout).to.contain('Hello World!'); }); - test('swc transpiler supports native ESM emit', async () => { - const { err, stdout } = await exec( - `${cmdEsmLoaderNoProject} ./index.ts`, - { - cwd: resolve(TEST_DIR, 'transpile-only-swc-native-esm'), - } - ); - expect(err).to.equal(null); - expect(stdout).to.contain('Hello file://'); - }); + if (semver.gte(process.version, '12.16.0')) { + test('swc transpiler supports native ESM emit', async () => { + const { err, stdout } = await exec( + `${cmdEsmLoaderNoProject} ./index.ts`, + { + cwd: resolve(TEST_DIR, 'transpile-only-swc-native-esm'), + } + ); + expect(err).to.equal(null); + expect(stdout).to.contain('Hello file://'); + }); + } test('should pipe into `ts-node` and evaluate', async () => { const execPromise = exec(cmd);