From ab6504514d23e74dd5ef0c653238fb36c48e689a Mon Sep 17 00:00:00 2001 From: Haoqun Jiang Date: Mon, 1 Nov 2021 16:43:03 +0800 Subject: [PATCH 1/4] test: add a test case for issue #3040 --- packages/playground/resolve/__tests__/resolve.spec.ts | 4 ++++ packages/playground/resolve/index.html | 8 ++++++++ packages/playground/resolve/ts-extension/hello.ts | 1 + packages/playground/resolve/ts-extension/index.ts | 3 +++ packages/playground/resolve/vite.config.js | 2 +- 5 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 packages/playground/resolve/ts-extension/hello.ts create mode 100644 packages/playground/resolve/ts-extension/index.ts diff --git a/packages/playground/resolve/__tests__/resolve.spec.ts b/packages/playground/resolve/__tests__/resolve.spec.ts index b94be689371b22..b1524e1e42aa08 100644 --- a/packages/playground/resolve/__tests__/resolve.spec.ts +++ b/packages/playground/resolve/__tests__/resolve.spec.ts @@ -54,6 +54,10 @@ test('dont add extension to directory name (./dir-with-ext.js/index.js)', async expect(await page.textContent('.dir-with-ext')).toMatch('[success]') }) +test('a ts module can import another ts module using its corresponding js file name', async () => { + expect(await page.textContent('.ts-extension')).toMatch('[success]') +}) + test('filename with dot', async () => { expect(await page.textContent('.dot')).toMatch('[success]') }) diff --git a/packages/playground/resolve/index.html b/packages/playground/resolve/index.html index a121c5c8a68ca1..9dc6525fcd7a43 100644 --- a/packages/playground/resolve/index.html +++ b/packages/playground/resolve/index.html @@ -33,6 +33,11 @@

Resolve to non-duplicated file extension

Don't add extensions to directory names

fail

+

+ A ts module can import another ts module using its corresponding js file name +

+

fail

+

Resolve file name containing dot

fail

@@ -119,6 +124,9 @@

resolve package that contains # in path

import { file as dirWithExtMsg } from './dir-with-ext' text('.dir-with-ext', dirWithExtMsg) + import { msg as tsExtensionMsg } from './ts-extension' + text('.ts-extension', tsExtensionMsg) + // filename with dot import { bar } from './util/bar.util' text('.dot', bar()) diff --git a/packages/playground/resolve/ts-extension/hello.ts b/packages/playground/resolve/ts-extension/hello.ts new file mode 100644 index 00000000000000..0189355c3fe06f --- /dev/null +++ b/packages/playground/resolve/ts-extension/hello.ts @@ -0,0 +1 @@ +export const msg = '[success] use .js extension to import a ts module' diff --git a/packages/playground/resolve/ts-extension/index.ts b/packages/playground/resolve/ts-extension/index.ts new file mode 100644 index 00000000000000..e095619ee4d716 --- /dev/null +++ b/packages/playground/resolve/ts-extension/index.ts @@ -0,0 +1,3 @@ +import { msg } from './hello.js' + +export { msg } diff --git a/packages/playground/resolve/vite.config.js b/packages/playground/resolve/vite.config.js index a05d51bb5b8e45..e7d531097add7c 100644 --- a/packages/playground/resolve/vite.config.js +++ b/packages/playground/resolve/vite.config.js @@ -2,7 +2,7 @@ const virtualFile = '@virtual-file' module.exports = { resolve: { - extensions: ['.mjs', '.js', '.es'], + extensions: ['.mjs', '.js', '.es', '.ts'], mainFields: ['custom', 'module'], conditions: ['custom'] }, From 739c6fc063cb4e2964a6cfb3ea9a003f603267e0 Mon Sep 17 00:00:00 2001 From: Haoqun Jiang Date: Tue, 2 Nov 2021 13:06:57 +0800 Subject: [PATCH 2/4] feat: support importing ts files using their corresponding js extesions To align with `tsc` default behavior, mentioned in https://github.com/microsoft/TypeScript/issues/46452 Fixes #3040 --- packages/vite/src/node/plugins/resolve.ts | 40 +++++++++++++++++++---- packages/vite/src/node/utils.ts | 8 +++++ 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts index 5b6fd6c59e9f72..4c3f72457b7766 100644 --- a/packages/vite/src/node/plugins/resolve.ts +++ b/packages/vite/src/node/plugins/resolve.ts @@ -25,7 +25,10 @@ import { cleanUrl, slash, nestedResolveFrom, - isFileReadable + isFileReadable, + isTsRequest, + isPossibleTsOutput, + getTsSrcPath } from '../utils' import { ViteDevServer, SSROptions } from '..' import { createFilter } from '@rollup/pluginutils' @@ -65,6 +68,11 @@ export interface InternalResolveOptions extends ResolveOptions { skipPackageJson?: boolean preferRelative?: boolean isRequire?: boolean + // #3040 + // when the importer is a ts module, + // if the specifier requests a non-existent `.js/jsx/mjs/cjs` file, + // should also try import from `.ts/tsx/mts/cts` source file as fallback, + isFromTsImporter?: boolean } export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin { @@ -75,10 +83,6 @@ export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin { ssrConfig, preferRelative = false } = baseOptions - const requireOptions: InternalResolveOptions = { - ...baseOptions, - isRequire: true - } let server: ViteDevServer | undefined const { target: ssrTarget, noExternal: ssrNoExternal } = ssrConfig ?? {} @@ -104,13 +108,19 @@ export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin { const targetWeb = !ssr || ssrTarget === 'webworker' // this is passed by @rollup/plugin-commonjs - const isRequire = + const isRequire = !!( resolveOpts && resolveOpts.custom && resolveOpts.custom['node-resolve'] && resolveOpts.custom['node-resolve'].isRequire + ) - const options = isRequire ? requireOptions : baseOptions + const options: InternalResolveOptions = { + ...baseOptions, + + isRequire, + isFromTsImporter: isTsRequest(importer ?? '') + } const preserveSymlinks = !!server?.config.resolve.preserveSymlinks @@ -450,6 +460,22 @@ function tryResolveFile( if (index) return index + postfix } } + + const tryTsExtension = options.isFromTsImporter && isPossibleTsOutput(file) + if (tryTsExtension) { + const tsSrcPath = getTsSrcPath(file) + return tryResolveFile( + tsSrcPath, + postfix, + options, + tryIndex, + targetWeb, + preserveSymlinks, + tryPrefix, + skipPackageJson + ) + } + if (tryPrefix) { const prefixed = `${path.dirname(file)}/${tryPrefix}${path.basename(file)}` return tryResolveFile( diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index 4d6d4436b4248b..bdbbce4faa166e 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -164,6 +164,14 @@ export const isJSRequest = (url: string): boolean => { return false } +const knownTsRE = /\.(ts|mts|cts|tsx)$/ +const knownTsOutputRE = /\.(js|mjs|cjs|jsx)$/ +export const isTsRequest = (url: string) => knownTsRE.test(cleanUrl(url)) +export const isPossibleTsOutput = (url: string) => + knownTsOutputRE.test(cleanUrl(url)) +export const getTsSrcPath = (filename: string) => + filename.replace(/\.([cm])?(js)(x?)$/, '.$1ts$3') + const importQueryRE = /(\?|&)import=?(?:&|$)/ const internalPrefixes = [ FS_PREFIX, From 86b2bb8b6c0ba715aa94bf6f38e0a76513e0bbb1 Mon Sep 17 00:00:00 2001 From: Haoqun Jiang Date: Tue, 2 Nov 2021 13:12:46 +0800 Subject: [PATCH 3/4] chore: correct punctuation --- packages/vite/src/node/plugins/resolve.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts index 4c3f72457b7766..4ce550cd6263b4 100644 --- a/packages/vite/src/node/plugins/resolve.ts +++ b/packages/vite/src/node/plugins/resolve.ts @@ -71,7 +71,7 @@ export interface InternalResolveOptions extends ResolveOptions { // #3040 // when the importer is a ts module, // if the specifier requests a non-existent `.js/jsx/mjs/cjs` file, - // should also try import from `.ts/tsx/mts/cts` source file as fallback, + // should also try import from `.ts/tsx/mts/cts` source file as fallback. isFromTsImporter?: boolean } From 3b5d856c82e4697f5dd7b8dd182f44ffa1c603a3 Mon Sep 17 00:00:00 2001 From: Haoqun Jiang Date: Tue, 2 Nov 2021 16:28:04 +0800 Subject: [PATCH 4/4] refactor: use optional chaining --- packages/vite/src/node/plugins/resolve.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts index 4ce550cd6263b4..49ffcec3bd33e5 100644 --- a/packages/vite/src/node/plugins/resolve.ts +++ b/packages/vite/src/node/plugins/resolve.ts @@ -108,12 +108,8 @@ export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin { const targetWeb = !ssr || ssrTarget === 'webworker' // this is passed by @rollup/plugin-commonjs - const isRequire = !!( - resolveOpts && - resolveOpts.custom && - resolveOpts.custom['node-resolve'] && - resolveOpts.custom['node-resolve'].isRequire - ) + const isRequire: boolean = + resolveOpts?.custom?.['node-resolve']?.isRequire ?? false const options: InternalResolveOptions = { ...baseOptions,