Skip to content

Commit 14a0e88

Browse files
committed
perf: importsRE quadratic moves
1 parent 68be9c7 commit 14a0e88

File tree

2 files changed

+14
-12
lines changed

2 files changed

+14
-12
lines changed

packages/vite/src/node/__tests__/scan.spec.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, expect, test } from 'vitest'
2-
import { commentRE, importsRE, scriptRE } from '../optimizer/scan'
2+
import { commentRE, extractImportPaths, scriptRE } from '../optimizer/scan'
33
import { multilineCommentsRE, singlelineCommentsRE } from '../utils'
44

55
describe('optimizer-scan:script-test', () => {
@@ -89,8 +89,7 @@ describe('optimizer-scan:script-test', () => {
8989
]
9090

9191
shouldMatchArray.forEach((str) => {
92-
importsRE.lastIndex = 0
93-
expect(importsRE.exec(str)[1]).toEqual("'vue'")
92+
expect(extractImportPaths(str)).toStrictEqual(["import 'vue'"])
9493
})
9594

9695
const shouldFailArray = [
@@ -102,7 +101,7 @@ describe('optimizer-scan:script-test', () => {
102101
`import type Bar from 'foo'`
103102
]
104103
shouldFailArray.forEach((str) => {
105-
expect(importsRE.test(str)).toBe(false)
104+
expect(extractImportPaths(str)).toStrictEqual([])
106105
})
107106
})
108107

packages/vite/src/node/optimizer/scan.ts

+11-8
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,10 @@ const htmlTypesRE = /\.(html|vue|svelte|astro|imba)$/
3939
// use Acorn because it's slow. Luckily this doesn't have to be bullet proof
4040
// since even missed imports can be caught at runtime, and false positives will
4141
// simply be ignored.
42-
export const importsRE =
43-
// eslint-disable-next-line regexp/no-super-linear-move -- TODO: FIXME backtracking
44-
/(?<!\/\/.*)(?<=^|;|\*\/)\s*import(?!\s+type)(?:[\w*{}\n\r\t, ]+from)?\s*("[^"]+"|'[^']+')\s*(?=$|;|\/\/|\/\*)/gm
42+
// `[ \f\t\v\u00a0\u1680\u180e\u2000-\u200a\u202f\u205f\u3000\ufeff]` is same with
43+
// `[\r\n\u2028\u2029]` removed from `\s`
44+
const importsRE =
45+
/(?<=^|;|\*\/)[ \f\t\v\u00a0\u1680\u180e\u2000-\u200a\u202f\u205f\u3000\ufeff]*import(?!\s+type)(?:[\w*{}\n\r\t, ]+from)?\s*("[^"]+"|'[^']+')\s*(?=$|;|\/\/|\/\*)/gm
4546

4647
export async function scanImports(config: ResolvedConfig): Promise<{
4748
deps: Record<string, string>
@@ -325,7 +326,9 @@ function esbuildScanPlugin(
325326
// since they may be used in the template
326327
const contents =
327328
content +
328-
(loader.startsWith('ts') ? extractImportPaths(content) : '')
329+
(loader.startsWith('ts')
330+
? extractImportPaths(content).join('\n')
331+
: '')
329332

330333
const key = `${path}?id=${scriptId++}`
331334
if (contents.includes('import.meta.glob')) {
@@ -524,22 +527,22 @@ function esbuildScanPlugin(
524527
* the solution is to add `import 'x'` for every source to force
525528
* esbuild to keep crawling due to potential side effects.
526529
*/
527-
function extractImportPaths(code: string) {
530+
export function extractImportPaths(code: string): string[] {
528531
// empty singleline & multiline comments to avoid matching comments
529532
code = code
530533
.replace(multilineCommentsRE, '/* */')
531534
.replace(singlelineCommentsRE, '')
532535

533-
let js = ''
536+
const imports = []
534537
let m
535538
while ((m = importsRE.exec(code)) != null) {
536539
// This is necessary to avoid infinite loops with zero-width matches
537540
if (m.index === importsRE.lastIndex) {
538541
importsRE.lastIndex++
539542
}
540-
js += `\nimport ${m[1]}`
543+
imports.push(`import ${m[1]}`)
541544
}
542-
return js
545+
return imports
543546
}
544547

545548
function shouldExternalizeDep(resolvedId: string, rawId: string): boolean {

0 commit comments

Comments
 (0)