Skip to content

Commit

Permalink
Respect "module" config in SWC integration (#1409)
Browse files Browse the repository at this point in the history
* Respect 'module' option in swc transpiler

* fix tests

* fix tests

* lint fix

* dont do esm test on really old node
  • Loading branch information
cspotcode committed Jul 22, 2021
1 parent 08585b9 commit e8a4d76
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 40 deletions.
101 changes: 67 additions & 34 deletions src/test/index.spec.ts
Expand Up @@ -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);
Expand Down Expand Up @@ -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')");
Expand Down Expand Up @@ -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(
[
Expand All @@ -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);
Expand All @@ -1817,39 +1835,48 @@ 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:'
);
});

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(
Expand All @@ -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'),
}
Expand All @@ -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.');
}
Expand All @@ -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,
Expand Down
30 changes: 26 additions & 4 deletions src/transpilers/swc.ts
Expand Up @@ -49,6 +49,7 @@ export function create(createOptions: SwcTranspilerOptions): Transpiler {
experimentalDecorators,
emitDecoratorMetadata,
target,
module,
jsxFactory,
jsxFragmentFactory,
} = compilerOptions;
Expand All @@ -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,
Expand Down Expand Up @@ -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;
2 changes: 2 additions & 0 deletions 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);
3 changes: 3 additions & 0 deletions tests/transpile-only-swc-native-esm/package.json
@@ -0,0 +1,3 @@
{
"type": "module"
}
13 changes: 13 additions & 0 deletions 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
}
}
4 changes: 4 additions & 0 deletions tests/transpile-only-swc-via-tsconfig/index.ts
Expand Up @@ -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;
2 changes: 1 addition & 1 deletion tests/transpile-only-swc-via-tsconfig/tsconfig.json
Expand Up @@ -5,7 +5,7 @@
},
"compilerOptions": {
"target": "ES2018",
"module": "ESNext",
"module": "CommonJS",
"allowJs": true,
"jsx": "react",
"experimentalDecorators": true
Expand Down
4 changes: 4 additions & 0 deletions tests/transpile-only-swc/index.ts
Expand Up @@ -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;
2 changes: 1 addition & 1 deletion tests/transpile-only-swc/tsconfig.json
@@ -1,7 +1,7 @@
{
"compilerOptions": {
"target": "ES2018",
"module": "ESNext",
"module": "CommonJS",
"allowJs": true,
"jsx": "react",
"experimentalDecorators": true
Expand Down

0 comments on commit e8a4d76

Please sign in to comment.