Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(resolve): support resolving TS files by JS extension specifiers in JS files #18889

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 7 additions & 28 deletions packages/vite/src/node/optimizer/scan.ts
Original file line number Diff line number Diff line change
@@ -105,11 +105,6 @@ export function devToScanEnvironment(
} as unknown as ScanEnvironment
}

type ResolveIdOptions = Omit<
Parameters<EnvironmentPluginContainer['resolveId']>[2],
'environment'
>

const debug = createDebugger('vite:deps')

const htmlTypesRE = /\.(html|vue|svelte|astro|imba)$/
@@ -383,27 +378,19 @@ function esbuildScanPlugin(
async function resolveId(
id: string,
importer?: string,
options?: ResolveIdOptions,
): Promise<PartialResolvedId | null> {
return environment.pluginContainer.resolveId(
id,
importer && normalizePath(importer),
{
...options,
scan: true,
},
{ scan: true },
)
}
const resolve = async (
id: string,
importer?: string,
options?: ResolveIdOptions,
) => {
const resolve = async (id: string, importer?: string) => {
const key = id + (importer && path.dirname(importer))
if (seen.has(key)) {
return seen.get(key)
}
const resolved = await resolveId(id, importer, options)
const resolved = await resolveId(id, importer)
const res = resolved?.id
seen.set(key, res)
return res
@@ -633,18 +620,14 @@ function esbuildScanPlugin(
// avoid matching windows volume
filter: /^[\w@][^:]/,
},
async ({ path: id, importer, pluginData }) => {
async ({ path: id, importer }) => {
if (moduleListContains(exclude, id)) {
return externalUnlessEntry({ path: id })
}
if (depImports[id]) {
return externalUnlessEntry({ path: id })
}
const resolved = await resolve(id, importer, {
custom: {
depScan: { loader: pluginData?.htmlType?.loader },
},
})
const resolved = await resolve(id, importer)
if (resolved) {
if (shouldExternalizeDep(resolved, id)) {
return externalUnlessEntry({ path: id })
@@ -706,13 +689,9 @@ function esbuildScanPlugin(
{
filter: /.*/,
},
async ({ path: id, importer, pluginData }) => {
async ({ path: id, importer }) => {
// use vite resolver to support urls and omitted extensions
const resolved = await resolve(id, importer, {
custom: {
depScan: { loader: pluginData?.htmlType?.loader },
},
})
const resolved = await resolve(id, importer)
if (resolved) {
if (
shouldExternalizeDep(resolved, id) ||
20 changes: 2 additions & 18 deletions packages/vite/src/node/plugins/resolve.ts
Original file line number Diff line number Diff line change
@@ -28,7 +28,6 @@ import {
isNonDriveRelativeAbsolutePath,
isObject,
isOptimizable,
isTsRequest,
normalizePath,
safeRealpathSync,
tryStatSync,
@@ -124,10 +123,7 @@ interface ResolvePluginOptions {
tryPrefix?: string
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.
/** @deprecated */
isFromTsImporter?: boolean
// True when resolving during the scan phase to discover dependencies
scan?: boolean
@@ -243,18 +239,6 @@ export function resolvePlugin(
}
}

if (importer) {
if (
isTsRequest(importer) ||
resolveOpts.custom?.depScan?.loader?.startsWith('ts')
) {
options.isFromTsImporter = true
} else {
const moduleLang = this.getModuleInfo(importer)?.meta.vite?.lang
options.isFromTsImporter = moduleLang && isTsRequest(`.${moduleLang}`)
}
}

let res: string | PartialResolvedId | undefined

// resolve pre-bundled deps requests, these could be resolved by
@@ -598,7 +582,7 @@ function tryCleanFsResolve(
let res: string | undefined

// If path.dirname is a valid directory, try extensions and ts resolution logic
const possibleJsToTs = options.isFromTsImporter && isPossibleTsOutput(file)
const possibleJsToTs = isPossibleTsOutput(file)
if (possibleJsToTs || options.extensions.length || tryPrefix) {
const dirPath = path.dirname(file)
if (isDirectory(dirPath)) {
3 changes: 0 additions & 3 deletions packages/vite/src/node/utils.ts
Original file line number Diff line number Diff line change
@@ -295,9 +295,6 @@ export const isJSRequest = (url: string): boolean => {
return false
}

const knownTsRE = /\.(?:ts|mts|cts|tsx)(?:$|\?)/
export const isTsRequest = (url: string): boolean => knownTsRE.test(url)

const importQueryRE = /(\?|&)import=?(?:&|$)/
const directRequestRE = /(\?|&)direct=?(?:&|$)/
const internalPrefixes = [
4 changes: 4 additions & 0 deletions playground/resolve/__tests__/resolve.spec.ts
Original file line number Diff line number Diff line change
@@ -111,6 +111,10 @@ test('a ts module can import another ts module using its corresponding js file n
expect(await page.textContent('.ts-extension')).toMatch('[success]')
})

test('a js module can import another ts module using its corresponding js file name', async () => {
expect(await page.textContent('.js-ts-extension')).toMatch('[success]')
})

test('filename with dot', async () => {
expect(await page.textContent('.dot')).toMatch('[success]')
})
6 changes: 6 additions & 0 deletions playground/resolve/index.html
Original file line number Diff line number Diff line change
@@ -113,6 +113,9 @@ <h2>
</h2>
<p class="cjs-extension">fail</p>

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

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

@@ -301,6 +304,9 @@ <h2>resolve non normalized absolute path</h2>
import { msgMjs as tsMjsExtensionWithQueryMsg } from './ts-extension?query=1'
text('.mjs-extension-with-query', tsMjsExtensionWithQueryMsg)

import { msg as jsTsExtensionMsg } from './ts-extension/index-js.js'
text('.js-ts-extension', jsTsExtensionMsg)

// filename with dot
import { bar } from './util/bar.util'
text('.dot', bar())
10 changes: 10 additions & 0 deletions playground/resolve/ts-extension/index-js.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { msg as msgJs } from './hello.js'
import { msgJsx } from './hellojsx.jsx'
import { msgTsx } from './hellotsx.js'
import { msgCjs } from './hellocjs.cjs'
import { msgMjs } from './hellomjs.mjs'

export const msg =
msgJs && msgJsx && msgTsx && msgCjs && msgMjs
? '[success] use .js / .jsx / .cjs / .mjs extension to import a TS modules'
: '[fail]'