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
Show file tree
Hide file tree
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
Expand Up @@ -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)$/
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 })
Expand Down Expand Up @@ -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) ||
Expand Down
20 changes: 2 additions & 18 deletions packages/vite/src/node/plugins/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import {
isNonDriveRelativeAbsolutePath,
isObject,
isOptimizable,
isTsRequest,
normalizePath,
safeRealpathSync,
tryStatSync,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)) {
Expand Down
3 changes: 0 additions & 3 deletions packages/vite/src/node/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
4 changes: 4 additions & 0 deletions playground/resolve/__tests__/resolve.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]')
})
Expand Down
6 changes: 6 additions & 0 deletions playground/resolve/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down Expand Up @@ -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())
Expand Down
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]'
Loading