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

perf(resolve): avoid isWorkerRequest and clean up .ts imported a .js #12571

Merged
merged 3 commits into from Mar 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
37 changes: 0 additions & 37 deletions packages/vite/src/node/__tests__/utils.spec.ts
Expand Up @@ -5,7 +5,6 @@ import {
asyncFlatten,
getHash,
getLocalhostAddressIfDiffersFromDNS,
getPotentialTsSrcPaths,
injectQuery,
isFileReadable,
isWindows,
Expand Down Expand Up @@ -137,42 +136,6 @@ describe('resolveHostname', () => {
})
})

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',
'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 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',
])
})
Comment on lines -140 to -174
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are testing these in the resolve playground


describe('posToNumber', () => {
test('simple', () => {
const actual = posToNumber('a\nb', { line: 2, column: 0 })
Expand Down
34 changes: 21 additions & 13 deletions packages/vite/src/node/plugins/resolve.ts
Expand Up @@ -22,15 +22,13 @@ import {
deepImportRE,
ensureVolumeInPath,
fsPathFromId,
getPotentialTsSrcPaths,
injectQuery,
isBuiltin,
isDataUrl,
isExternalUrl,
isNonDriveRelativeAbsolutePath,
isObject,
isOptimizable,
isPossibleTsOutput,
isTsRequest,
isWindows,
lookupFile,
Expand All @@ -48,7 +46,6 @@ import {
loadPackageData,
resolvePackageData,
} from '../packages'
import { isWorkerRequest } from './worker'

const normalizedClientEntry = normalizePath(CLIENT_ENTRY)
const normalizedEnvEntry = normalizePath(ENV_ENTRY)
Expand Down Expand Up @@ -177,16 +174,13 @@ export function resolvePlugin(resolveOptions: InternalResolveOptions): Plugin {
}

if (importer) {
const _importer = isWorkerRequest(importer)
? splitFileAndPostfix(importer).file
: importer
if (
isTsRequest(_importer) ||
isTsRequest(importer) ||
resolveOpts.custom?.depScan?.loader?.startsWith('ts')
) {
options.isFromTsImporter = true
} else {
const moduleLang = this.getModuleInfo(_importer)?.meta?.vite?.lang
const moduleLang = this.getModuleInfo(importer)?.meta?.vite?.lang
options.isFromTsImporter = moduleLang && isTsRequest(`.${moduleLang}`)
}
}
Expand Down Expand Up @@ -531,6 +525,9 @@ function tryFsResolve(
if (res) return res + postfix
}

const knownTsOutputRE = /\.(?:js|mjs|cjs|jsx)$/
const isPossibleTsOutput = (url: string): boolean => knownTsOutputRE.test(url)
Comment on lines +528 to +529
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this function here because it isn't a widely used util and it only works without query, that is what we need in tryCleanFsResolve. I think we are pushing too many functions to utils lately and we will be better of delaying moving them there to avoid creating abstractions that aren't really needed


function tryCleanFsResolve(
file: string,
options: InternalResolveOptions,
Expand All @@ -554,11 +551,22 @@ function tryCleanFsResolve(
const dirStat = tryStatSync(dirPath)
if (dirStat?.isDirectory()) {
if (possibleJsToTs) {
// try resolve .js, .mjs, .mts or .jsx import to typescript file
const tsSrcPaths = getPotentialTsSrcPaths(file)
for (const srcPath of tsSrcPaths) {
if ((res = tryResolveRealFile(srcPath, preserveSymlinks))) return res
}
// try resolve .js, .mjs, .cjs or .jsx import to typescript file
const fileExt = path.extname(file)
const fileName = file.slice(0, -fileExt.length)
if (
(res = tryResolveRealFile(
fileName + fileExt.replace('js', 'ts'),
preserveSymlinks,
))
)
return res
// for .js, also try .tsx
if (
fileExt === '.js' &&
(res = tryResolveRealFile(fileName + '.tsx', preserveSymlinks))
)
return res
}

if (
Expand Down
8 changes: 0 additions & 8 deletions packages/vite/src/node/plugins/worker.ts
Expand Up @@ -32,14 +32,6 @@ export type WorkerType = 'classic' | 'module' | 'ignore'
export const WORKER_FILE_ID = 'worker_file'
const workerCache = new WeakMap<ResolvedConfig, WorkerCache>()

export function isWorkerRequest(id: string): boolean {
const query = parseRequest(id)
if (query && query[WORKER_FILE_ID] != null) {
return true
}
return false
}

function saveEmitWorkerAsset(
config: ResolvedConfig,
asset: EmittedAsset,
Expand Down
15 changes: 1 addition & 14 deletions packages/vite/src/node/utils.ts
Expand Up @@ -282,21 +282,8 @@ export const isJSRequest = (url: string): boolean => {
return false
}

const knownTsRE = /\.(?:ts|mts|cts|tsx)$/
const knownTsOutputRE = /\.(?:js|mjs|cjs|jsx)$/
const knownTsRE = /\.(?:ts|mts|cts|tsx)(?:$|\?)/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI This perf PR also fixes a bug we run into in Vitest. I was just about to set up a fix PR for it but this exact line fixed it - thanks! My changes for this regexp would have been identical with this one.
Here's minimal repro for it just in case you want to set a test case to prevent this from breaking in future. The file.ts?query is not detected as TS query and fails to load file.mts.

import { writeFileSync } from "fs";
import { fileURLToPath } from "url";
import { createServer } from "vite";

writeFileSync(
  "./source.ts",
  `import { hello } from "./target.mjs"; // Change this to ".mts" or omit extension and it works too
  hello();`,
  "utf8"
);
writeFileSync(
  "./target.mts",
  `export const hello = () => "Hello from .mts";`,
  "utf8"
);

const __dirname = fileURLToPath(new URL(".", import.meta.url));

const server = await createServer({
  configFile: false,
  root: __dirname,
  server: {
    port: 1337,
  },
});
await server.listen();

const first = await server.transformRequest("./source.ts");
console.log("First worked!", first.code);

await new Promise((resolve) => setTimeout(resolve, 2000));

const second = await server
  .transformRequest("./source.ts?v=123")
  .then((result) => console.log("Second worked!", result.code))
  .catch((e) => console.error("Second failed", e));

await server.close();
process.exit();

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome! Thanks for letting us know! Maybe not a bad idea to send a PR to add a case for this to one of our playgrounds if you would like.

export const isTsRequest = (url: string): boolean => knownTsRE.test(url)
export const isPossibleTsOutput = (url: string): boolean =>
knownTsOutputRE.test(cleanUrl(url))

const splitFilePathAndQueryRE = /(\.(?:[cm]?js|jsx))(\?.*)?$/
export function getPotentialTsSrcPaths(filePath: string): string[] {
const [name, type, query = ''] = filePath.split(splitFilePathAndQueryRE)
const paths = [name + type.replace('js', 'ts') + query]
if (type[type.length - 1] !== 'x') {
paths.push(name + type.replace('js', 'tsx') + query)
}
return paths
}

const importQueryRE = /(\?|&)import=?(?:&|$)/
const directRequestRE = /(\?|&)direct=?(?:&|$)/
Expand Down