Skip to content

Commit e8a4d76

Browse files
authored
Respect "module" config in SWC integration (#1409)
* Respect 'module' option in swc transpiler * fix tests * fix tests * lint fix * dont do esm test on really old node
1 parent 08585b9 commit e8a4d76

File tree

9 files changed

+121
-40
lines changed

9 files changed

+121
-40
lines changed

src/test/index.spec.ts

Lines changed: 67 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ test.suite('ts-node', (test) => {
8282
const cmd = `"${BIN_PATH}" --project "${PROJECT}"`;
8383
/** Default `ts-node` invocation without `--project` */
8484
const cmdNoProject = `"${BIN_PATH}"`;
85+
const experimentalModulesFlag = semver.gte(process.version, '12.17.0')
86+
? ''
87+
: '--experimental-modules';
88+
const cmdEsmLoaderNoProject = `node ${experimentalModulesFlag} --loader ts-node/esm`;
8589

8690
test('should export the correct version', () => {
8791
expect(VERSION).to.equal(require('../../package.json').version);
@@ -346,6 +350,19 @@ test.suite('ts-node', (test) => {
346350
expect(stdout).to.contain('Hello World!');
347351
});
348352

353+
if (semver.gte(process.version, '12.16.0')) {
354+
test('swc transpiler supports native ESM emit', async () => {
355+
const { err, stdout } = await exec(
356+
`${cmdEsmLoaderNoProject} ./index.ts`,
357+
{
358+
cwd: resolve(TEST_DIR, 'transpile-only-swc-native-esm'),
359+
}
360+
);
361+
expect(err).to.equal(null);
362+
expect(stdout).to.contain('Hello file://');
363+
});
364+
}
365+
349366
test('should pipe into `ts-node` and evaluate', async () => {
350367
const execPromise = exec(cmd);
351368
execPromise.child.stdin!.end("console.log('hello')");
@@ -1770,23 +1787,24 @@ test.suite('ts-node', (test) => {
17701787
});
17711788

17721789
test.suite('esm', (test) => {
1773-
const experimentalModulesFlag = semver.gte(process.version, '12.17.0')
1774-
? ''
1775-
: '--experimental-modules';
1776-
const esmCmd = `node ${experimentalModulesFlag} --loader ts-node/esm`;
1777-
17781790
if (semver.gte(process.version, '12.16.0')) {
17791791
test('should compile and execute as ESM', async () => {
1780-
const { err, stdout } = await exec(`${esmCmd} index.ts`, {
1781-
cwd: join(TEST_DIR, './esm'),
1782-
});
1792+
const { err, stdout } = await exec(
1793+
`${cmdEsmLoaderNoProject} index.ts`,
1794+
{
1795+
cwd: join(TEST_DIR, './esm'),
1796+
}
1797+
);
17831798
expect(err).to.equal(null);
17841799
expect(stdout).to.equal('foo bar baz biff libfoo\n');
17851800
});
17861801
test('should use source maps', async () => {
1787-
const { err, stdout } = await exec(`${esmCmd} "throw error.ts"`, {
1788-
cwd: join(TEST_DIR, './esm'),
1789-
});
1802+
const { err, stdout } = await exec(
1803+
`${cmdEsmLoaderNoProject} "throw error.ts"`,
1804+
{
1805+
cwd: join(TEST_DIR, './esm'),
1806+
}
1807+
);
17901808
expect(err).not.to.equal(null);
17911809
expect(err!.message).to.contain(
17921810
[
@@ -1806,7 +1824,7 @@ test.suite('ts-node', (test) => {
18061824
err,
18071825
stdout,
18081826
} = await exec(
1809-
`${esmCmd} --experimental-specifier-resolution=node index.ts`,
1827+
`${cmdEsmLoaderNoProject} --experimental-specifier-resolution=node index.ts`,
18101828
{ cwd: join(TEST_DIR, './esm-node-resolver') }
18111829
);
18121830
expect(err).to.equal(null);
@@ -1817,39 +1835,48 @@ test.suite('ts-node', (test) => {
18171835
err,
18181836
stdout,
18191837
} = await exec(
1820-
`${esmCmd} --experimental-modules --es-module-specifier-resolution=node index.ts`,
1838+
`${cmdEsmLoaderNoProject} --experimental-modules --es-module-specifier-resolution=node index.ts`,
18211839
{ cwd: join(TEST_DIR, './esm-node-resolver') }
18221840
);
18231841
expect(err).to.equal(null);
18241842
expect(stdout).to.equal('foo bar baz biff libfoo\n');
18251843
});
18261844
test('via NODE_OPTIONS', async () => {
1827-
const { err, stdout } = await exec(`${esmCmd} index.ts`, {
1828-
cwd: join(TEST_DIR, './esm-node-resolver'),
1829-
env: {
1830-
...process.env,
1831-
NODE_OPTIONS: `${experimentalModulesFlag} --experimental-specifier-resolution=node`,
1832-
},
1833-
});
1845+
const { err, stdout } = await exec(
1846+
`${cmdEsmLoaderNoProject} index.ts`,
1847+
{
1848+
cwd: join(TEST_DIR, './esm-node-resolver'),
1849+
env: {
1850+
...process.env,
1851+
NODE_OPTIONS: `${experimentalModulesFlag} --experimental-specifier-resolution=node`,
1852+
},
1853+
}
1854+
);
18341855
expect(err).to.equal(null);
18351856
expect(stdout).to.equal('foo bar baz biff libfoo\n');
18361857
});
18371858
});
18381859

18391860
test('throws ERR_REQUIRE_ESM when attempting to require() an ESM script when ESM loader is enabled', async () => {
1840-
const { err, stderr } = await exec(`${esmCmd} ./index.js`, {
1841-
cwd: join(TEST_DIR, './esm-err-require-esm'),
1842-
});
1861+
const { err, stderr } = await exec(
1862+
`${cmdEsmLoaderNoProject} ./index.js`,
1863+
{
1864+
cwd: join(TEST_DIR, './esm-err-require-esm'),
1865+
}
1866+
);
18431867
expect(err).to.not.equal(null);
18441868
expect(stderr).to.contain(
18451869
'Error [ERR_REQUIRE_ESM]: Must use import to load ES Module:'
18461870
);
18471871
});
18481872

18491873
test('defers to fallback loaders when URL should not be handled by ts-node', async () => {
1850-
const { err, stdout, stderr } = await exec(`${esmCmd} index.mjs`, {
1851-
cwd: join(TEST_DIR, './esm-import-http-url'),
1852-
});
1874+
const { err, stdout, stderr } = await exec(
1875+
`${cmdEsmLoaderNoProject} index.mjs`,
1876+
{
1877+
cwd: join(TEST_DIR, './esm-import-http-url'),
1878+
}
1879+
);
18531880
expect(err).to.not.equal(null);
18541881
// expect error from node's default resolver
18551882
expect(stderr).to.match(
@@ -1858,16 +1885,19 @@ test.suite('ts-node', (test) => {
18581885
});
18591886

18601887
test('should bypass import cache when changing search params', async () => {
1861-
const { err, stdout } = await exec(`${esmCmd} index.ts`, {
1862-
cwd: join(TEST_DIR, './esm-import-cache'),
1863-
});
1888+
const { err, stdout } = await exec(
1889+
`${cmdEsmLoaderNoProject} index.ts`,
1890+
{
1891+
cwd: join(TEST_DIR, './esm-import-cache'),
1892+
}
1893+
);
18641894
expect(err).to.equal(null);
18651895
expect(stdout).to.equal('log1\nlog2\nlog2\n');
18661896
});
18671897

18681898
test('should support transpile only mode via dedicated loader entrypoint', async () => {
18691899
const { err, stdout } = await exec(
1870-
`${esmCmd}/transpile-only index.ts`,
1900+
`${cmdEsmLoaderNoProject}/transpile-only index.ts`,
18711901
{
18721902
cwd: join(TEST_DIR, './esm-transpile-only'),
18731903
}
@@ -1876,9 +1906,12 @@ test.suite('ts-node', (test) => {
18761906
expect(stdout).to.equal('');
18771907
});
18781908
test('should throw type errors without transpile-only enabled', async () => {
1879-
const { err, stdout } = await exec(`${esmCmd} index.ts`, {
1880-
cwd: join(TEST_DIR, './esm-transpile-only'),
1881-
});
1909+
const { err, stdout } = await exec(
1910+
`${cmdEsmLoaderNoProject} index.ts`,
1911+
{
1912+
cwd: join(TEST_DIR, './esm-transpile-only'),
1913+
}
1914+
);
18821915
if (err === null) {
18831916
throw new Error('Command was expected to fail, but it succeeded.');
18841917
}
@@ -1899,7 +1932,7 @@ test.suite('ts-node', (test) => {
18991932

19001933
async function runModuleTypeTest(project: string, ext: string) {
19011934
const { err, stderr, stdout } = await exec(
1902-
`${esmCmd} ./module-types/${project}/test.${ext}`,
1935+
`${cmdEsmLoaderNoProject} ./module-types/${project}/test.${ext}`,
19031936
{
19041937
env: {
19051938
...process.env,

src/transpilers/swc.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export function create(createOptions: SwcTranspilerOptions): Transpiler {
4949
experimentalDecorators,
5050
emitDecoratorMetadata,
5151
target,
52+
module,
5253
jsxFactory,
5354
jsxFragmentFactory,
5455
} = compilerOptions;
@@ -57,13 +58,23 @@ export function create(createOptions: SwcTranspilerOptions): Transpiler {
5758
function createSwcOptions(isTsx: boolean): swcTypes.Options {
5859
const swcTarget = targetMapping.get(target!) ?? 'es3';
5960
const keepClassNames = target! >= /* ts.ScriptTarget.ES2016 */ 3;
61+
const moduleType =
62+
module === ModuleKind.CommonJS
63+
? 'commonjs'
64+
: module === ModuleKind.AMD
65+
? 'amd'
66+
: module === ModuleKind.UMD
67+
? 'umd'
68+
: undefined;
6069
return {
6170
sourceMaps: sourceMap,
6271
// isModule: true,
63-
module: {
64-
type: 'commonjs',
65-
noInterop: !esModuleInterop,
66-
},
72+
module: moduleType
73+
? ({
74+
noInterop: !esModuleInterop,
75+
type: moduleType,
76+
} as swcTypes.ModuleConfig)
77+
: undefined,
6778
swcrc: false,
6879
jsc: {
6980
externalHelpers: importHelpers,
@@ -118,3 +129,14 @@ targetMapping.set(/* ts.ScriptTarget.ES2018 */ 5, 'es2018');
118129
targetMapping.set(/* ts.ScriptTarget.ES2019 */ 6, 'es2019');
119130
targetMapping.set(/* ts.ScriptTarget.ES2020 */ 7, 'es2019');
120131
targetMapping.set(/* ts.ScriptTarget.ESNext */ 99, 'es2019');
132+
133+
const ModuleKind = {
134+
None: 0,
135+
CommonJS: 1,
136+
AMD: 2,
137+
UMD: 3,
138+
System: 4,
139+
ES2015: 5,
140+
ES2020: 6,
141+
ESNext: 99,
142+
} as const;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
const x: number = `Hello ${import.meta.url.slice(0, 7)}!`;
2+
console.log(x);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"type": "module"
3+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"ts-node": {
3+
"transpileOnly": true,
4+
"transpiler": "ts-node/transpilers/swc-experimental"
5+
},
6+
"compilerOptions": {
7+
"target": "ES2018",
8+
"module": "ESNext",
9+
"allowJs": true,
10+
"jsx": "react",
11+
"experimentalDecorators": true
12+
}
13+
}

tests/transpile-only-swc-via-tsconfig/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,7 @@ class World {}
77
parseInt(1101, 2);
88
const x: number = `Hello ${World.name}!`;
99
console.log(x);
10+
11+
// test module type emit
12+
import { readFileSync } from 'fs';
13+
readFileSync;

tests/transpile-only-swc-via-tsconfig/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
},
66
"compilerOptions": {
77
"target": "ES2018",
8-
"module": "ESNext",
8+
"module": "CommonJS",
99
"allowJs": true,
1010
"jsx": "react",
1111
"experimentalDecorators": true

tests/transpile-only-swc/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,7 @@ class World {}
77
parseInt(1101, 2);
88
const x: number = `Hello ${World.name}!`;
99
console.log(x);
10+
11+
// test module type emit
12+
import { readFileSync } from 'fs';
13+
readFileSync;

tests/transpile-only-swc/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"compilerOptions": {
33
"target": "ES2018",
4-
"module": "ESNext",
4+
"module": "CommonJS",
55
"allowJs": true,
66
"jsx": "react",
77
"experimentalDecorators": true

0 commit comments

Comments
 (0)