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

Conversation

patak-dev
Copy link
Member

Description

Alternative to #10273

isWorkerRequest is costly, and it was showing in the profiles. There is an internal call to parseRequest, a function we should also review because it uses a regex that for this particular case wasn't needed for example (it is useful with dynamic variables in imports)

Using cleanUrl should work here. The original issue of having .ts?query is more general than just in workers.


What is the purpose of this pull request?

  • Bug fix
  • New Feature
  • Documentation update
  • Other

@stackblitz
Copy link

stackblitz bot commented Mar 24, 2023

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@patak-dev patak-dev added the performance Performance related enhancement label Mar 24, 2023
Copy link
Member

@bluwy bluwy left a comment

Choose a reason for hiding this comment

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

Good catch! Maybe we can simplify the isTsRequest and isPossibleTsOutput functions directly here:

const knownTsRE = /\.(?:ts|mts|cts|tsx)$/
const knownTsOutputRE = /\.(?:js|mjs|cjs|jsx)$/
export const isTsRequest = (url: string): boolean => knownTsRE.test(url)
export const isPossibleTsOutput = (url: string): boolean =>
knownTsOutputRE.test(cleanUrl(url))

So we only have to apply one regex to test it.

@patak-dev patak-dev changed the title perf(resolve): avoid isWorkerRequest perf(resolve): avoid isWorkerRequest and clean up .ts imported a .js Mar 25, 2023
Comment on lines 555 to 569
const type = path.extname(file)
const fileName = file.slice(0, -type.length)
if (
(res = tryResolveRealFile(
fileName + type.replace('js', 'ts'),
preserveSymlinks,
))
)
return res
// for .js, also try .tsx
if (
type === '.js' &&
(res = tryResolveRealFile(fileName + '.tsx', preserveSymlinks))
)
return res
Copy link
Member Author

Choose a reason for hiding this comment

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

getPotentialSrcPaths was returning the following:

  • .js -> [.ts, .tsx]
  • .mjs -> [.mts, .mtsx] 👀
  • .cjs -> [.cts, .ctsx] 👁️ 👁️
  • .jsx -> [.tsx]

It was also doing a complex regex taking into account query params. This function was hiding the real checks, better to be explicit and have them expanded here.
Now we no longer check for mtsx and ctsx

@patak-dev patak-dev requested a review from bluwy March 25, 2023 08:32
Comment on lines +528 to +529
const knownTsOutputRE = /\.(?:js|mjs|cjs|jsx)$/
const isPossibleTsOutput = (url: string): boolean => knownTsOutputRE.test(url)
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

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

packages/vite/src/node/plugins/resolve.ts Outdated Show resolved Hide resolved
packages/vite/src/node/plugins/resolve.ts Outdated Show resolved Hide resolved
Copy link
Member

@bluwy bluwy left a comment

Choose a reason for hiding this comment

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

Nice cleanup 🚀

@patak-dev patak-dev enabled auto-merge (squash) March 25, 2023 09:53
@patak-dev patak-dev merged commit 8ab1438 into main Mar 25, 2023
17 of 18 checks passed
@patak-dev patak-dev deleted the perf/avoid-is-worker-request branch March 25, 2023 10:09
@@ -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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
performance Performance related enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants