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(), '')
+ })
})