From f36fcb3b1216a761214ae1779f72576f5a45c549 Mon Sep 17 00:00:00 2001 From: aadamcik Date: Sun, 1 Feb 2026 10:48:13 +0000 Subject: [PATCH] fix: import .tsx using import mapping --- src/loader.ts | 29 ++++++++++++++++---------- tests/loader.spec.ts | 48 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 11 deletions(-) diff --git a/src/loader.ts b/src/loader.ts index 9b7945b..6aa3376 100644 --- a/src/loader.ts +++ b/src/loader.ts @@ -112,22 +112,29 @@ export const resolve: ResolveHook = async (specifier, context, nextResolve) => { * production build. */ if (error.code === 'ERR_MODULE_NOT_FOUND' && error.url && isLocalPath(error.url)) { - let [urlPathname, ...urlQs] = error.url.split('?') + let [urlPathname, ...urlQs] = (error.url as string).split('?') const fileExtension = extname(urlPathname) + const testExtensions: string[] = [] + + if (['.js', '.mjs', '.cjs', '.jsx'].includes(fileExtension)) { + testExtensions.push(fileExtension.replace('js', 'ts')) + } if (fileExtension === '.js') { - urlPathname = urlPathname.replace('.js', '.ts') - } else if (fileExtension === '.mjs') { - urlPathname = urlPathname.replace('.mjs', '.mts') - } else if (fileExtension === '.cjs') { - urlPathname = urlPathname.replace('.cjs', '.cts') - } else if (fileExtension === '.jsx') { - urlPathname = urlPathname.replace('.jsx', '.tsx') + testExtensions.push('.tsx') } - const url = urlQs.length ? `${urlPathname}?${urlQs.join('?')}` : urlPathname - debug('retrying to resolve file with specifier %s', url) - return nextResolve(url, context) + for (const ext of testExtensions) { + const tryPath = urlPathname.slice(0, -fileExtension.length) + ext + + try { + const url = urlQs.length ? `${tryPath}?${urlQs.join('?')}` : tryPath + debug('retrying to resolve file with specifier %s', url) + return await nextResolve(url, context) + } catch { + // ignore and try next extension + } + } } throw error diff --git a/tests/loader.spec.ts b/tests/loader.spec.ts index 7f5e5f1..785a168 100644 --- a/tests/loader.spec.ts +++ b/tests/loader.spec.ts @@ -707,4 +707,52 @@ test.group('Loader', (group) => { assert.equal(result.stdout.trim(), '5000') assert.equal(result.stderr.trim(), '') }) + + test('allow importing files with .tsx extension using import mapping', async ({ assert, fs }) => { + await fs.createJson('tsconfig.json', { + compilerOptions: { + rewriteRelativeImportExtensions: true, + jsx: 'react-jsx', + jsxImportSource: 'preact', + }, + }) + + await fs.createJson('package.json', { + imports: { + '#components/*': './components/*.js', + }, + }) + + await fs.create( + 'index.ts', + ` + import render from 'preact-render-to-string' + import { Button } from '#components/button' + + console.log(render(Button({ type: 'submit', text: 'Login' }))) + ` + ) + + await fs.create( + 'components/button.tsx', + ` + interface ChildrenProps { + type: 'submit' | 'button'; + text: string + } + + export function Button(props: ChildrenProps) { + return + } + ` + ) + + const result = await spawnPromisified( + process.execPath, + ['--no-warnings', '--import', './build/index.js', join(fs.basePath, 'index.ts')], + {} + ) + + assert.equal(result.stdout.trim(), '') + }) })