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

feat(node-resolve): Resolve js/jsx/mjs/cjs imports from TypeScript files #1498

Merged
merged 1 commit into from
May 30, 2023
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
17 changes: 12 additions & 5 deletions packages/node-resolve/src/index.js
Expand Up @@ -146,11 +146,18 @@ export function nodeResolve(opts = {}) {
importSpecifierList.push(`./${importee}`);
}

// TypeScript files may import '.js' to refer to either '.ts' or '.tsx'
if (importer && importee.endsWith('.js')) {
for (const ext of ['.ts', '.tsx']) {
if (importer.endsWith(ext) && extensions.includes(ext)) {
importSpecifierList.push(importee.replace(/.js$/, ext));
// TypeScript files may import '.mjs' or '.cjs' to refer to either '.mts' or '.cts'.
// They may also import .js to refer to either .ts or .tsx, and .jsx to refer to .tsx.
if (importer && /\.(ts|mts|cts|tsx)$/.test(importer)) {
for (const [importeeExt, resolvedExt] of [
['.js', '.ts'],
['.js', '.tsx'],
['.jsx', '.tsx'],
['.mjs', '.mts'],
['.cjs', '.cts']
]) {
if (importee.endsWith(importeeExt) && extensions.includes(resolvedExt)) {
importSpecifierList.push(importee.slice(0, -importeeExt.length) + resolvedExt);
}
}
}
Expand Down
@@ -0,0 +1,4 @@
import { main } from './main.cjs';
// This resolves as main.cts and _not_ main.cjs, despite the extension
const mainResult = main();
export default mainResult;
@@ -0,0 +1,11 @@
// To make this very clearly TypeScript and not just CJS with a CTS extension
type TestType = string | string[];
interface Main {
(): string;
propertyCall(input?: TestType): TestType;
}

const main: Main = () => 'It works!';
main.propertyCall = () => '';

export { main };
@@ -0,0 +1,4 @@
import { main } from './main.mjs';
// This resolves as main.mts and _not_ main.mjs, despite the extension
const mainResult = main();
export default mainResult;
@@ -0,0 +1,11 @@
// To make this very clearly TypeScript and not just MJS with a MTS extension
type TestType = string | string[];
interface Main {
(): string;
propertyCall(input?: TestType): TestType;
}

const main: Main = () => 'It works!';
main.propertyCall = () => '';

export { main };
@@ -0,0 +1,7 @@
// To make this very clearly TypeScript and not just JS with a TS extension
type TestType = string | string[];
function MyComponent() {
return 'It works!';
}

export { MyComponent };
@@ -0,0 +1,4 @@
import { MyComponent } from './MyComponent.js';
// This resolves as MyComponent.tsx and _not_ MyComponent.js, despite the extension
const componentResult = MyComponent();
export default componentResult;
@@ -0,0 +1,7 @@
// To make this very clearly TypeScript and not just JS with a TS extension
type TestType = string | string[];
function MyComponent() {
return 'It works!';
}

export { MyComponent };
@@ -0,0 +1,4 @@
import { MyComponent } from './MyComponent.jsx';
// This resolves as MyComponent.tsx and _not_ MyComponent.jsx, despite the extension
const componentResult = MyComponent();
export default componentResult;
76 changes: 76 additions & 0 deletions packages/node-resolve/test/test.mjs
Expand Up @@ -168,6 +168,82 @@ test('supports JS extensions in TS when referring to TS imports', async (t) => {
t.is(module.exports, 'It works!');
});

test('supports JS extensions in TS when referring to TSX imports', async (t) => {
const bundle = await rollup({
input: 'tsx-import-js-extension/import-tsx-with-js-extension.ts',
onwarn: failOnWarn(t),
plugins: [
nodeResolve({
extensions: ['.js', '.ts', '.tsx']
}),
babel({
babelHelpers: 'bundled',
plugins: ['@babel/plugin-transform-typescript'],
extensions: ['.js', '.ts', '.tsx']
})
]
});
const { module } = await testBundle(t, bundle);
t.is(module.exports, 'It works!');
});

test('supports JSX extensions in TS when referring to TSX imports', async (t) => {
const bundle = await rollup({
input: 'tsx-import-jsx-extension/import-tsx-with-jsx-extension.ts',
onwarn: failOnWarn(t),
plugins: [
nodeResolve({
extensions: ['.js', '.ts', '.tsx']
}),
babel({
babelHelpers: 'bundled',
plugins: ['@babel/plugin-transform-typescript'],
extensions: ['.js', '.ts', '.tsx']
})
]
});
const { module } = await testBundle(t, bundle);
t.is(module.exports, 'It works!');
});

test('supports MJS extensions in TS when referring to MTS imports', async (t) => {
const bundle = await rollup({
input: 'ts-import-mjs-extension/import-ts-with-mjs-extension.ts',
onwarn: failOnWarn(t),
plugins: [
nodeResolve({
extensions: ['.js', '.ts', '.mjs', '.mts']
}),
babel({
babelHelpers: 'bundled',
plugins: ['@babel/plugin-transform-typescript'],
extensions: ['.js', '.ts', '.mjs', '.mts']
})
]
});
const { module } = await testBundle(t, bundle);
t.is(module.exports, 'It works!');
});

test('supports CJS extensions in TS when referring to CTS imports', async (t) => {
const bundle = await rollup({
input: 'ts-import-cjs-extension/import-ts-with-cjs-extension.ts',
onwarn: failOnWarn(t),
plugins: [
nodeResolve({
extensions: ['.js', '.ts', '.cjs', '.cts']
}),
babel({
babelHelpers: 'bundled',
plugins: ['@babel/plugin-transform-typescript'],
extensions: ['.js', '.ts', '.cjs', '.cts']
})
]
});
const { module } = await testBundle(t, bundle);
t.is(module.exports, 'It works!');
});

test('supports JS extensions in TS actually importing JS with export map', async (t) => {
const bundle = await rollup({
input: 'ts-import-js-extension-for-js-file-export-map/import-js-with-js-extension.ts',
Expand Down