Skip to content

Commit 68be9c7

Browse files
committed
perf: regex perf (super-linear-move)
1 parent 9963f71 commit 68be9c7

File tree

15 files changed

+127
-53
lines changed

15 files changed

+127
-53
lines changed

.eslintrc.cjs

+7-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,13 @@ module.exports = defineConfig({
107107
files: ['packages/**'],
108108
excludedFiles: '**/__tests__/**',
109109
rules: {
110-
'no-restricted-globals': ['error', 'require', '__dirname', '__filename']
110+
'no-restricted-globals': [
111+
'error',
112+
'require',
113+
'__dirname',
114+
'__filename'
115+
],
116+
'regexp/no-super-linear-move': 'error'
111117
}
112118
},
113119
{

packages/create-vite/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,7 @@ async function init() {
375375
}
376376

377377
function formatTargetDir(targetDir: string | undefined) {
378+
// eslint-disable-next-line regexp/no-super-linear-move -- `targetDir` won't be so long
378379
return targetDir?.trim().replace(/\/+$/g, '')
379380
}
380381

packages/plugin-legacy/src/index.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ function viteLegacyPlugin(options: Options = {}): Plugin[] {
313313
fileName = fileName.replace('[name]', '[name]-legacy')
314314
} else {
315315
// entry.js -> entry-legacy.js
316+
// eslint-disable-next-line regexp/no-super-linear-move -- fileName won't be so long
316317
fileName = fileName.replace(/(.+)\.(.+)/, '$1-legacy.$2')
317318
}
318319

@@ -453,7 +454,9 @@ function viteLegacyPlugin(options: Options = {}): Plugin[] {
453454
}
454455

455456
const tags: HtmlTagDescriptor[] = []
456-
const htmlFilename = chunk.facadeModuleId?.replace(/\?.*$/, '')
457+
const htmlFilename = chunk.facadeModuleId
458+
? trimQuery(chunk.facadeModuleId)
459+
: undefined
457460

458461
// 1. inject modern polyfills
459462
const modernPolyfillFilename = facadeToModernPolyfillMap.get(
@@ -793,6 +796,11 @@ function wrapIIFEBabelPlugin(): BabelPlugin {
793796
}
794797
}
795798

799+
function trimQuery(id: string) {
800+
const pos = id.lastIndexOf('?')
801+
return pos < 0 ? id : id.slice(0, pos)
802+
}
803+
796804
export const cspHashes = [
797805
createHash('sha256').update(safari10NoModuleFix).digest('base64'),
798806
createHash('sha256').update(systemJSInlineCode).digest('base64'),

packages/plugin-react/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
113113
const importReactRE = /(?:^|\n)import\s+(?:\*\s+as\s+)?React(?:,|\s+)/
114114

115115
// Any extension, including compound ones like '.bs.js'
116+
// eslint-disable-next-line regexp/no-super-linear-move -- id won't be so long
116117
const fileExtensionRE = /\.[^/\s?]+$/
117118

118119
const viteBabel: Plugin = {

packages/plugin-vue/src/main.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,12 @@ async function linkSrcToDescriptor(
473473
(await pluginContext.resolve(src, descriptor.filename))?.id || src
474474
// #1812 if the src points to a dep file, the resolved id may contain a
475475
// version query.
476-
setSrcDescriptor(srcFile.replace(/\?.*$/, ''), descriptor, scoped)
476+
setSrcDescriptor(trimQuery(srcFile), descriptor, scoped)
477+
}
478+
479+
function trimQuery(id: string) {
480+
const pos = id.lastIndexOf('?')
481+
return pos < 0 ? id : id.slice(0, pos)
477482
}
478483

479484
// these are built-in query parameters so should be ignored

packages/vite/src/client/client.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -593,12 +593,20 @@ export function injectQuery(url: string, queryToInject: string): string {
593593
}
594594

595595
// can't use pathname from URL since it may be relative like ../
596-
const pathname = url.replace(/#.*$/, '').replace(/\?.*$/, '')
596+
const pathname = trimEndFromLastOfThatChar(
597+
trimEndFromLastOfThatChar(url, '#'),
598+
'?'
599+
)
597600
const { search, hash } = new URL(url, 'http://vitejs.dev')
598601

599602
return `${pathname}?${queryToInject}${search ? `&` + search.slice(1) : ''}${
600603
hash || ''
601604
}`
602605
}
603606

607+
function trimEndFromLastOfThatChar(input: string, char: string) {
608+
const pos = input.lastIndexOf(char)
609+
return pos < 0 ? input : input.slice(0, pos)
610+
}
611+
604612
export { ErrorOverlay }

packages/vite/src/client/overlay.ts

+2
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,9 @@ code {
127127
</div>
128128
`
129129

130+
// eslint-disable-next-line regexp/no-super-linear-move -- won't use to long strings
130131
const fileRE = /(?:[a-zA-Z]:\\|\/).*?:\d+:\d+/g
132+
// eslint-disable-next-line regexp/no-super-linear-move -- won't use to long strings
131133
const codeframeRE = /^(?:>?\s+\d+\s+\|.*|\s+\|\s*\^.*)\r?\n/gm
132134

133135
// Allow `ErrorOverlay` to extend `HTMLElement` even in environments where

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

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ const htmlTypesRE = /\.(html|vue|svelte|astro|imba)$/
4040
// since even missed imports can be caught at runtime, and false positives will
4141
// simply be ignored.
4242
export const importsRE =
43+
// eslint-disable-next-line regexp/no-super-linear-move -- TODO: FIXME backtracking
4344
/(?<!\/\/.*)(?<=^|;|\*\/)\s*import(?!\s+type)(?:[\w*{}\n\r\t, ]+from)?\s*("[^"]+"|'[^']+')\s*(?=$|;|\/\/|\/\*)/gm
4445

4546
export async function scanImports(config: ResolvedConfig): Promise<{

packages/vite/src/node/plugins/dynamicImportVars.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
normalizePath,
1313
parseRequest,
1414
removeComments,
15-
requestQuerySplitRE,
15+
splitRequestWithQuery,
1616
transformStableResult
1717
} from '../utils'
1818
import { toAbsoluteGlob } from './importMetaGlob'
@@ -59,8 +59,8 @@ function parseDynamicImportPattern(
5959
return null
6060
}
6161

62-
const [userPattern] = userPatternQuery.split(requestQuerySplitRE, 2)
63-
const [rawPattern] = filename.split(requestQuerySplitRE, 2)
62+
const [userPattern] = splitRequestWithQuery(userPatternQuery)
63+
const [rawPattern] = splitRequestWithQuery(filename)
6464

6565
if (rawQuery?.raw !== undefined) {
6666
globParams = { as: 'raw' }

packages/vite/src/node/plugins/ensureWatch.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Plugin } from '../plugin'
2-
import { cleanUrl, queryRE } from '../utils'
2+
import { cleanUrl } from '../utils'
33

44
/**
55
* plugin to ensure rollup can watch correctly.
@@ -8,7 +8,7 @@ export function ensureWatchPlugin(): Plugin {
88
return {
99
name: 'vite:ensure-watch',
1010
load(id) {
11-
if (queryRE.test(id)) {
11+
if (id.includes('?')) {
1212
this.addWatchFile(cleanUrl(id))
1313
}
1414
return null

packages/vite/src/node/plugins/html.ts

+48-34
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ import {
1919
isDataUrl,
2020
isExternalUrl,
2121
normalizePath,
22-
processSrcSet
22+
processSrcSet,
23+
removeComments
2324
} from '../utils'
2425
import type { ResolvedConfig } from '../config'
2526
import { toOutputFilePathInHtml } from '../build'
@@ -48,9 +49,9 @@ const inlineImportRE =
4849
const htmlLangRE = /\.(?:html|htm)$/
4950

5051
const importMapRE =
51-
/[ \t]*<script[^>]*type\s*=\s*(?:"importmap"|'importmap'|importmap)[^>]*>.*?<\/script>/is
52+
/<script[^>]*type\s*=\s*(?:"importmap"|'importmap'|importmap)[^>]*>.*?<\/script>/is
5253
const moduleScriptRE =
53-
/[ \t]*<script[^>]*type\s*=\s*(?:"module"|'module'|module)[^>]*>/i
54+
/<script[^>]*type\s*=\s*(?:"module"|'module'|module)[^>]*>/i
5455

5556
export const isHTMLProxy = (id: string): boolean => htmlProxyRE.test(id)
5657

@@ -986,11 +987,10 @@ export async function applyHtmlTransforms(
986987
}
987988

988989
const importRE = /\bimport\s*("[^"]*[^\\]"|'[^']*[^\\]');*/g
989-
const commentRE = /\/\*[\s\S]*?\*\/|\/\/.*$/gm
990990
function isEntirelyImport(code: string) {
991991
// only consider "side-effect" imports, which match <script type=module> semantics exactly
992992
// the regexes will remove too little in some exotic cases, but false-negatives are alright
993-
return !code.replace(importRE, '').replace(commentRE, '').trim().length
993+
return !removeComments(code.replace(importRE, '')).trim().length
994994
}
995995

996996
function getBaseInHTML(urlRelativePath: string, config: ResolvedConfig) {
@@ -1004,14 +1004,14 @@ function getBaseInHTML(urlRelativePath: string, config: ResolvedConfig) {
10041004
: config.base
10051005
}
10061006

1007-
const headInjectRE = /([ \t]*)<\/head>/i
1008-
const headPrependInjectRE = /([ \t]*)<head[^>]*>/i
1007+
const headInjectRE = /<\/head>/i
1008+
const headPrependInjectRE = /<head[^>]*>/i
10091009

10101010
const htmlInjectRE = /<\/html>/i
1011-
const htmlPrependInjectRE = /([ \t]*)<html[^>]*>/i
1011+
const htmlPrependInjectRE = /<html[^>]*>/i
10121012

1013-
const bodyInjectRE = /([ \t]*)<\/body>/i
1014-
const bodyPrependInjectRE = /([ \t]*)<body[^>]*>/i
1013+
const bodyInjectRE = /<\/body>/i
1014+
const bodyPrependInjectRE = /<body[^>]*>/i
10151015

10161016
const doctypePrependInjectRE = /<!doctype html>/i
10171017

@@ -1025,32 +1025,43 @@ function injectToHead(
10251025
if (prepend) {
10261026
// inject as the first element of head
10271027
if (headPrependInjectRE.test(html)) {
1028-
return html.replace(
1029-
headPrependInjectRE,
1030-
(match, p1) => `${match}\n${serializeTags(tags, incrementIndent(p1))}`
1031-
)
1028+
return html.replace(headPrependInjectRE, (match, offset) => {
1029+
const indent = getIndent(html, offset)
1030+
return `${match}\n${serializeTags(tags, incrementIndent(indent))}`
1031+
})
10321032
}
10331033
} else {
10341034
// inject before head close
10351035
if (headInjectRE.test(html)) {
10361036
// respect indentation of head tag
1037-
return html.replace(
1038-
headInjectRE,
1039-
(match, p1) => `${serializeTags(tags, incrementIndent(p1))}${match}`
1040-
)
1037+
return html.replace(headInjectRE, (match, offset) => {
1038+
const indent = getIndent(html, offset)
1039+
return `\n${serializeTags(
1040+
tags,
1041+
incrementIndent(indent)
1042+
)}${indent}${match}`
1043+
})
10411044
}
10421045
// try to inject before the body tag
10431046
if (bodyPrependInjectRE.test(html)) {
1044-
return html.replace(
1045-
bodyPrependInjectRE,
1046-
(match, p1) => `${serializeTags(tags, p1)}\n${match}`
1047-
)
1047+
return html.replace(bodyPrependInjectRE, (match, offset) => {
1048+
const indent = getIndent(html, offset)
1049+
return `${serializeTags(tags, indent)}\n${match}`
1050+
})
10481051
}
10491052
}
10501053
// if no head tag is present, we prepend the tag for both prepend and append
10511054
return prependInjectFallback(html, tags)
10521055
}
10531056

1057+
function getIndent(html: string, pos: number) {
1058+
let indent = ''
1059+
for (; pos >= 0 && (html[pos] === ' ' || html[pos] === '\t'); pos--) {
1060+
indent += html[pos]
1061+
}
1062+
return indent
1063+
}
1064+
10541065
function injectToBody(
10551066
html: string,
10561067
tags: HtmlTagDescriptor[],
@@ -1061,26 +1072,29 @@ function injectToBody(
10611072
if (prepend) {
10621073
// inject after body open
10631074
if (bodyPrependInjectRE.test(html)) {
1064-
return html.replace(
1065-
bodyPrependInjectRE,
1066-
(match, p1) => `${match}\n${serializeTags(tags, incrementIndent(p1))}`
1067-
)
1075+
return html.replace(bodyPrependInjectRE, (match, offset) => {
1076+
const indent = getIndent(html, offset)
1077+
return `${match}\n${serializeTags(tags, incrementIndent(indent))}`
1078+
})
10681079
}
10691080
// if no there is no body tag, inject after head or fallback to prepend in html
10701081
if (headInjectRE.test(html)) {
1071-
return html.replace(
1072-
headInjectRE,
1073-
(match, p1) => `${match}\n${serializeTags(tags, p1)}`
1074-
)
1082+
return html.replace(headInjectRE, (match, offset) => {
1083+
const indent = getIndent(html, offset)
1084+
return `${match}\n${serializeTags(tags, indent)}`
1085+
})
10751086
}
10761087
return prependInjectFallback(html, tags)
10771088
} else {
10781089
// inject before body close
10791090
if (bodyInjectRE.test(html)) {
1080-
return html.replace(
1081-
bodyInjectRE,
1082-
(match, p1) => `${serializeTags(tags, incrementIndent(p1))}${match}`
1083-
)
1091+
return html.replace(bodyInjectRE, (match, offset) => {
1092+
const indent = getIndent(match, offset)
1093+
return `\n${serializeTags(
1094+
tags,
1095+
incrementIndent(indent)
1096+
)}${indent}${match}`
1097+
})
10841098
}
10851099
// if no body tag is present, append to the html tag, or at the end of the file
10861100
if (htmlInjectRE.test(html)) {

packages/vite/src/node/plugins/importAnalysis.ts

+1
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
580580

581581
if (!ssr) {
582582
const url = rawUrl
583+
// eslint-disable-next-line regexp/no-super-linear-move -- `rawUrl` won't be so long
583584
.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '')
584585
.trim()
585586
if (

packages/vite/src/node/plugins/importMetaGlob.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ export async function parseImportGlob(
139139
// tailing comma in object or array will make the parser think it's a comma operation
140140
// we try to parse again removing the comma
141141
try {
142-
const statement = code.slice(start, lastTokenPos).replace(/[,\s]*$/, '')
142+
const statement = trimTrailingComma(code.slice(start, lastTokenPos))
143143
ast = parseExpressionAt(
144144
' '.repeat(start) + statement, // to keep the ast position
145145
start,
@@ -300,6 +300,14 @@ export async function parseImportGlob(
300300
return (await Promise.all(tasks)).filter(Boolean)
301301
}
302302

303+
const spaceRE = /^\s*$/
304+
305+
function trimTrailingComma(code: string): string {
306+
const pos = code.lastIndexOf(',')
307+
const remove = code.slice(pos + 1)
308+
return spaceRE.test(remove) ? code.slice(0, pos) : code
309+
}
310+
303311
const importPrefix = '__vite_glob_'
304312

305313
const { basename, dirname, relative, join } = posix

packages/vite/src/node/plugins/workerImportMetaUrl.ts

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ function getWorkerType(raw: string, clean: string, i: number): WorkerType {
4141
// need to find in comment code
4242
const workerOptString = raw
4343
.substring(commaIndex + 1, endIndex)
44+
// eslint-disable-next-line regexp/no-super-linear-move -- `raw` won't be so long
4445
.replace(/\}[\s\S]*,/g, '}') // strip trailing comma for parsing
4546

4647
const hasViteIgnore = ignoreFlagRE.test(workerOptString)

0 commit comments

Comments
 (0)