Skip to content

Commit

Permalink
feat: importing ts files using their corresponding js extesions (#5510)
Browse files Browse the repository at this point in the history
  • Loading branch information
sodatea committed Nov 2, 2021
1 parent 518da44 commit 7977e92
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 12 deletions.
4 changes: 4 additions & 0 deletions packages/playground/resolve/__tests__/resolve.spec.ts
Expand Up @@ -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]')
})
Expand Down
8 changes: 8 additions & 0 deletions packages/playground/resolve/index.html
Expand Up @@ -33,6 +33,11 @@ <h2>Resolve to non-duplicated file extension</h2>
<h2>Don't add extensions to directory names</h2>
<p class="dir-with-ext">fail</p>

<h2>
A ts module can import another ts module using its corresponding js file name
</h2>
<p class="ts-extension">fail</p>

<h2>Resolve file name containing dot</h2>
<p class="dot">fail</p>

Expand Down Expand Up @@ -119,6 +124,9 @@ <h2>resolve package that contains # in path</h2>
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())
Expand Down
1 change: 1 addition & 0 deletions packages/playground/resolve/ts-extension/hello.ts
@@ -0,0 +1 @@
export const msg = '[success] use .js extension to import a ts module'
3 changes: 3 additions & 0 deletions packages/playground/resolve/ts-extension/index.ts
@@ -0,0 +1,3 @@
import { msg } from './hello.js'

export { msg }
2 changes: 1 addition & 1 deletion packages/playground/resolve/vite.config.js
Expand Up @@ -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']
},
Expand Down
44 changes: 33 additions & 11 deletions packages/vite/src/node/plugins/resolve.ts
Expand Up @@ -25,7 +25,10 @@ import {
cleanUrl,
slash,
nestedResolveFrom,
isFileReadable
isFileReadable,
isTsRequest,
isPossibleTsOutput,
getTsSrcPath
} from '../utils'
import { ViteDevServer, SSROptions } from '..'
import { createFilter } from '@rollup/pluginutils'
Expand Down Expand Up @@ -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 {
Expand All @@ -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 ?? {}
Expand All @@ -104,13 +108,15 @@ 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,

const options = isRequire ? requireOptions : baseOptions
isRequire,
isFromTsImporter: isTsRequest(importer ?? '')
}

const preserveSymlinks = !!server?.config.resolve.preserveSymlinks

Expand Down Expand Up @@ -450,6 +456,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(
Expand Down
8 changes: 8 additions & 0 deletions packages/vite/src/node/utils.ts
Expand Up @@ -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,
Expand Down

0 comments on commit 7977e92

Please sign in to comment.