Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Respect "module" config in SWC integration #1409

Merged
merged 6 commits into from Jul 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
101 changes: 67 additions & 34 deletions src/test/index.spec.ts
Expand Up @@ -103,6 +103,10 @@ test.beforeAll(async () => {
test.suite('ts-node', (test) => {
const cmd = `"${BIN_PATH}" --project "${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 @@ -367,6 +371,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 @@ -1737,23 +1754,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 @@ -1773,7 +1791,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 @@ -1784,39 +1802,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 @@ -1825,16 +1852,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 @@ -1843,9 +1873,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 @@ -1866,7 +1899,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