diff --git a/src/test/index.spec.ts b/src/test/index.spec.ts index 4f4a19c5a..927d2db1d 100644 --- a/src/test/index.spec.ts +++ b/src/test/index.spec.ts @@ -82,6 +82,10 @@ test.suite('ts-node', (test) => { const cmd = `"${BIN_PATH}" --project "${PROJECT}"`; /** Default `ts-node` invocation without `--project` */ const cmdNoProject = `"${BIN_PATH}"`; + 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); @@ -346,6 +350,19 @@ test.suite('ts-node', (test) => { expect(stdout).to.contain('Hello World!'); }); + 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); execPromise.child.stdin!.end("console.log('hello')"); @@ -1770,23 +1787,24 @@ 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`, { - 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(`${esmCmd} "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( [ @@ -1806,7 +1824,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); @@ -1817,29 +1835,35 @@ 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`, { - 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(`${esmCmd} ./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:' @@ -1847,9 +1871,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(`${esmCmd} 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( @@ -1858,16 +1885,19 @@ test.suite('ts-node', (test) => { }); test('should bypass import cache when changing search params', async () => { - const { err, stdout } = await exec(`${esmCmd} 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'); }); 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'), } @@ -1876,9 +1906,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(`${esmCmd} 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.'); } @@ -1899,7 +1932,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, diff --git a/src/transpilers/swc.ts b/src/transpilers/swc.ts index 1ece325e5..9b7361a08 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,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 moduleType = + module === ModuleKind.CommonJS + ? 'commonjs' + : module === ModuleKind.AMD + ? 'amd' + : module === ModuleKind.UMD + ? 'umd' + : undefined; return { sourceMaps: sourceMap, // isModule: true, - module: { - type: 'commonjs', - noInterop: !esModuleInterop, - }, + module: moduleType + ? ({ + noInterop: !esModuleInterop, + type: moduleType, + } as swcTypes.ModuleConfig) + : undefined, swcrc: false, jsc: { externalHelpers: importHelpers, @@ -118,3 +129,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; 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