From 393719d50fa017f0771728c0269059e9b97d34fa Mon Sep 17 00:00:00 2001 From: Lee Beydoun Date: Mon, 21 Feb 2022 19:58:18 +0000 Subject: [PATCH 1/7] fix: ensure typescript resolution considers both .js and .jsx extensions. --- .../vite/src/node/__tests__/utils.spec.ts | 24 ++++++++++++++++- packages/vite/src/node/plugins/resolve.ts | 26 +++++++++++-------- packages/vite/src/node/utils.ts | 12 +++++++-- 3 files changed, 48 insertions(+), 14 deletions(-) diff --git a/packages/vite/src/node/__tests__/utils.spec.ts b/packages/vite/src/node/__tests__/utils.spec.ts index 3bf82f6b86f84b..6e8f083306fb74 100644 --- a/packages/vite/src/node/__tests__/utils.spec.ts +++ b/packages/vite/src/node/__tests__/utils.spec.ts @@ -1,4 +1,4 @@ -import { injectQuery, isWindows } from '../utils' +import { getPotentialTsSrcPaths, injectQuery, isWindows } from '../utils' if (isWindows) { // this test will work incorrectly on unix systems @@ -38,3 +38,25 @@ test('path with unicode, space, and %', () => { '/usr/vite/東京 %20 hello?direct' ) }) + +test('ts import of file with .js extension', () => { + expect(getPotentialTsSrcPaths('test-file.js')).toEqual([ + 'test-file.ts', + 'test-file.tsx' + ]) +}) + +test('ts import of file with .jsx extension', () => { + expect(getPotentialTsSrcPaths('test-file.jsx')).toEqual(['test-file.tsx']) +}) + +test('ts import of file .mjs,.cjs extension', () => { + expect(getPotentialTsSrcPaths('test-file.cjs')).toEqual(['test-file.cts']) + expect(getPotentialTsSrcPaths('test-file.mjs')).toEqual(['test-file.mts']) +}) + +test('ts import should not match .js that is not extension', () => { + expect(getPotentialTsSrcPaths('test-file.js.mjs')).toEqual([ + 'test-file.js.mts' + ]) +}) diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts index 55afd92df1a8fb..1dacf4ab15a11d 100644 --- a/packages/vite/src/node/plugins/resolve.ts +++ b/packages/vite/src/node/plugins/resolve.ts @@ -27,7 +27,7 @@ import { isFileReadable, isTsRequest, isPossibleTsOutput, - getTsSrcPath + getPotentialTsSrcPaths } from '../utils' import type { ViteDevServer, SSROptions } from '..' import type { PartialResolvedId } from 'rollup' @@ -436,16 +436,20 @@ function tryResolveFile( const tryTsExtension = options.isFromTsImporter && isPossibleTsOutput(file) if (tryTsExtension) { - const tsSrcPath = getTsSrcPath(file) - return tryResolveFile( - tsSrcPath, - postfix, - options, - tryIndex, - targetWeb, - tryPrefix, - skipPackageJson - ) + const tsSrcPaths = getPotentialTsSrcPaths(file) + for (const srcPath of tsSrcPaths) { + const res = tryResolveFile( + srcPath, + postfix, + options, + tryIndex, + targetWeb, + tryPrefix, + skipPackageJson + ) + if (res) return res + } + return } if (tryPrefix) { diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index 3012e6d57e6c65..6dfe4a970a9669 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -189,8 +189,16 @@ 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') +export const getPotentialTsSrcPaths = (filename: string) => { + // Special handling for filenames that end in `.js`, since they may actually + // refer to a `.ts` or `.tsx` file in TypeScript. + if (filename.endsWith('.js')) { + const extensionLessFile = filename.slice(0, -3) + return [`${extensionLessFile}.ts`, `${extensionLessFile}.tsx`] + } + + return [filename.replace(/\.([cm])?(js)(x?)(\?|$)/, '.$1ts$3')] +} const importQueryRE = /(\?|&)import=?(?:&|$)/ const internalPrefixes = [ From 92c32c16afecf3a8e6bdbc6d499953764e71d8f1 Mon Sep 17 00:00:00 2001 From: Lee Beydoun Date: Fri, 25 Feb 2022 00:12:13 +0000 Subject: [PATCH 2/7] fix: switch two separate regex expressions. --- packages/vite/src/node/utils.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index 6dfe4a970a9669..0cd84ae708c41b 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -190,11 +190,16 @@ export const isTsRequest = (url: string) => knownTsRE.test(cleanUrl(url)) export const isPossibleTsOutput = (url: string) => knownTsOutputRE.test(cleanUrl(url)) export const getPotentialTsSrcPaths = (filename: string) => { - // Special handling for filenames that end in `.js`, since they may actually - // refer to a `.ts` or `.tsx` file in TypeScript. - if (filename.endsWith('.js')) { - const extensionLessFile = filename.slice(0, -3) - return [`${extensionLessFile}.ts`, `${extensionLessFile}.tsx`] + // TODO: @Lee fully test this out. + const fileParts = filename.split(/(\.jsx?\?|\.jsx?$)/) + if (fileParts.length === 3) { + const [tsPart, tsxPart] = fileParts[2].length + ? ['.ts?', '.tsx?'] + : ['.ts', '.tsx'] + return [ + `${fileParts[0]}${tsPart}${fileParts[2]}`, + `${fileParts[0]}${tsxPart}${fileParts[2]}` + ] } return [filename.replace(/\.([cm])?(js)(x?)(\?|$)/, '.$1ts$3')] From f5753da17585ffe085821f2100d153d6c1f7df3d Mon Sep 17 00:00:00 2001 From: Lee Beydoun Date: Fri, 25 Feb 2022 22:07:59 +0000 Subject: [PATCH 3/7] fix: added more tests for getPotentialTsSrcPaths and made code work for .mjs and .cjs files. --- .../vite/src/node/__tests__/utils.spec.ts | 32 ++++++++++++++++--- packages/vite/src/node/utils.ts | 15 +++++---- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/packages/vite/src/node/__tests__/utils.spec.ts b/packages/vite/src/node/__tests__/utils.spec.ts index 6e8f083306fb74..9cc6d78397ea65 100644 --- a/packages/vite/src/node/__tests__/utils.spec.ts +++ b/packages/vite/src/node/__tests__/utils.spec.ts @@ -51,12 +51,34 @@ test('ts import of file with .jsx extension', () => { }) test('ts import of file .mjs,.cjs extension', () => { - expect(getPotentialTsSrcPaths('test-file.cjs')).toEqual(['test-file.cts']) - expect(getPotentialTsSrcPaths('test-file.mjs')).toEqual(['test-file.mts']) + expect(getPotentialTsSrcPaths('test-file.cjs')).toEqual([ + 'test-file.cts', + 'test-file.ctsx' + ]) + expect(getPotentialTsSrcPaths('test-file.mjs')).toEqual([ + 'test-file.mts', + 'test-file.mtsx' + ]) +}) + +test('ts import of file with .js before extension', () => { + expect(getPotentialTsSrcPaths('test-file.js.js')).toEqual([ + 'test-file.js.ts', + 'test-file.js.tsx' + ]) }) -test('ts import should not match .js that is not extension', () => { - expect(getPotentialTsSrcPaths('test-file.js.mjs')).toEqual([ - 'test-file.js.mts' +test('ts import of file with .js and query param', () => { + expect(getPotentialTsSrcPaths('test-file.js.js?lee=123')).toEqual([ + 'test-file.js.ts?lee=123', + 'test-file.js.tsx?lee=123' ]) }) + +test('ts import of non js file', () => { + expect(getPotentialTsSrcPaths('test-file.wasm')).toEqual(['test-file.wasm']) +}) + +test('ts import without any extension', () => { + expect(getPotentialTsSrcPaths('test-file')).toEqual(['test-file']) +}) diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index 0cd84ae708c41b..a2e936f4835c3c 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -190,15 +190,16 @@ export const isTsRequest = (url: string) => knownTsRE.test(cleanUrl(url)) export const isPossibleTsOutput = (url: string) => knownTsOutputRE.test(cleanUrl(url)) export const getPotentialTsSrcPaths = (filename: string) => { - // TODO: @Lee fully test this out. - const fileParts = filename.split(/(\.jsx?\?|\.jsx?$)/) + const fileParts = filename.split(/(\.[cm]?js?\?|\.[cm]?js?$)/) + + // If there are three parts, the file uses a .js, .mjs or .cjs extension. + // We replace the 'js' part with 'ts' and 'tsx'. if (fileParts.length === 3) { - const [tsPart, tsxPart] = fileParts[2].length - ? ['.ts?', '.tsx?'] - : ['.ts', '.tsx'] + const [namePart, extension, queryPart] = fileParts + return [ - `${fileParts[0]}${tsPart}${fileParts[2]}`, - `${fileParts[0]}${tsxPart}${fileParts[2]}` + `${namePart}${extension.replace('js', 'ts')}${queryPart}`, + `${namePart}${extension.replace('js', 'tsx')}${queryPart}` ] } From be38af313d76a890f84a6bcf2181df8b9e72262a Mon Sep 17 00:00:00 2001 From: Lee Beydoun Date: Sat, 26 Feb 2022 17:31:52 +0000 Subject: [PATCH 4/7] fix: removed optional specifier from regex. --- packages/vite/src/node/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index a2e936f4835c3c..edb3fd3195f455 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -190,7 +190,7 @@ export const isTsRequest = (url: string) => knownTsRE.test(cleanUrl(url)) export const isPossibleTsOutput = (url: string) => knownTsOutputRE.test(cleanUrl(url)) export const getPotentialTsSrcPaths = (filename: string) => { - const fileParts = filename.split(/(\.[cm]?js?\?|\.[cm]?js?$)/) + const fileParts = filename.split(/(\.[cm]?js\?|\.[cm]?js$)/) // If there are three parts, the file uses a .js, .mjs or .cjs extension. // We replace the 'js' part with 'ts' and 'tsx'. From af4e0e701345906ddb1265a70c37a961c0eb44b2 Mon Sep 17 00:00:00 2001 From: Lee Beydoun Date: Sun, 27 Feb 2022 09:00:04 +0000 Subject: [PATCH 5/7] fix: add resolve ts tests. --- packages/playground/resolve/index.html | 17 +++++++++++++++++ .../resolve/ts-extension/hellojsx.tsx | 1 + .../resolve/ts-extension/hellotsx.tsx | 1 + .../playground/resolve/ts-extension/index.ts | 4 +++- 4 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 packages/playground/resolve/ts-extension/hellojsx.tsx create mode 100644 packages/playground/resolve/ts-extension/hellotsx.tsx diff --git a/packages/playground/resolve/index.html b/packages/playground/resolve/index.html index db0a4bc54f1ad7..100fd60ff653c1 100644 --- a/packages/playground/resolve/index.html +++ b/packages/playground/resolve/index.html @@ -38,6 +38,17 @@

fail

+

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

+

fail

+ +

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

+

fail

+

Resolve file name containing dot

fail

@@ -130,6 +141,12 @@

resolve package that contains # in path

import { msg as tsExtensionMsg } from './ts-extension' text('.ts-extension', tsExtensionMsg) + import { msgJsx as tsJsxExtensionMsg } from './ts-extension' + text('.jsx-extension', tsJsxExtensionMsg) + + import { msgTsx as tsTsxExtensionMsg } from './ts-extension' + text('.tsx-extension', tsTsxExtensionMsg) + // filename with dot import { bar } from './util/bar.util' text('.dot', bar()) diff --git a/packages/playground/resolve/ts-extension/hellojsx.tsx b/packages/playground/resolve/ts-extension/hellojsx.tsx new file mode 100644 index 00000000000000..a8f610b399b17a --- /dev/null +++ b/packages/playground/resolve/ts-extension/hellojsx.tsx @@ -0,0 +1 @@ +export const msgJsx = '[success] use .jsx extension to import a tsx module' diff --git a/packages/playground/resolve/ts-extension/hellotsx.tsx b/packages/playground/resolve/ts-extension/hellotsx.tsx new file mode 100644 index 00000000000000..b7461ca71ded6c --- /dev/null +++ b/packages/playground/resolve/ts-extension/hellotsx.tsx @@ -0,0 +1 @@ +export const msgTsx = '[success] use .js extension to import a tsx module' diff --git a/packages/playground/resolve/ts-extension/index.ts b/packages/playground/resolve/ts-extension/index.ts index e095619ee4d716..bdb326f8778e64 100644 --- a/packages/playground/resolve/ts-extension/index.ts +++ b/packages/playground/resolve/ts-extension/index.ts @@ -1,3 +1,5 @@ import { msg } from './hello.js' +import { msgJsx } from './hellojsx.jsx' +import { msgTsx } from './hellotsx.js' -export { msg } +export { msg, msgJsx, msgTsx } From e7613ffe91b26e7ec2d271402ee39f9518219e18 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Sun, 27 Feb 2022 15:35:16 -0500 Subject: [PATCH 6/7] chore: some tweaks --- packages/vite/src/node/utils.ts | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index edb3fd3195f455..4fdae99f268bf8 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -189,21 +189,13 @@ 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 getPotentialTsSrcPaths = (filename: string) => { - const fileParts = filename.split(/(\.[cm]?js\?|\.[cm]?js$)/) - - // If there are three parts, the file uses a .js, .mjs or .cjs extension. - // We replace the 'js' part with 'ts' and 'tsx'. - if (fileParts.length === 3) { - const [namePart, extension, queryPart] = fileParts - - return [ - `${namePart}${extension.replace('js', 'ts')}${queryPart}`, - `${namePart}${extension.replace('js', 'tsx')}${queryPart}` - ] +export function getPotentialTsSrcPaths(filePath: string) { + const [name, type, query = ''] = filePath.split(/(\.(?:[cm]?js|jsx))(\?.*)?$/) + const paths = [name + type.replace('js', 'ts') + query] + if (!type.endsWith('x')) { + paths.push(name + type.replace('js', 'tsx') + query) } - - return [filename.replace(/\.([cm])?(js)(x?)(\?|$)/, '.$1ts$3')] + return paths } const importQueryRE = /(\?|&)import=?(?:&|$)/ From 6182de6bfac1a6b7207828038096b702ecacc362 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Sun, 27 Feb 2022 15:48:14 -0500 Subject: [PATCH 7/7] chore: remove invalid tests --- packages/vite/src/node/__tests__/utils.spec.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/vite/src/node/__tests__/utils.spec.ts b/packages/vite/src/node/__tests__/utils.spec.ts index 9cc6d78397ea65..26b149cd48b8af 100644 --- a/packages/vite/src/node/__tests__/utils.spec.ts +++ b/packages/vite/src/node/__tests__/utils.spec.ts @@ -74,11 +74,3 @@ test('ts import of file with .js and query param', () => { 'test-file.js.tsx?lee=123' ]) }) - -test('ts import of non js file', () => { - expect(getPotentialTsSrcPaths('test-file.wasm')).toEqual(['test-file.wasm']) -}) - -test('ts import without any extension', () => { - expect(getPotentialTsSrcPaths('test-file')).toEqual(['test-file']) -})