Skip to content

Commit abf536f

Browse files
authoredMar 22, 2023
perf: more regex improvements (#12520)
1 parent 45549e4 commit abf536f

File tree

11 files changed

+73
-36
lines changed

11 files changed

+73
-36
lines changed
 

‎packages/vite/scripts/util.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ export function rewriteImports(
1717
})
1818
}
1919

20+
const windowsSlashRE = /\\/g
2021
export function slash(p: string): string {
21-
return p.replace(/\\/g, '/')
22+
return p.replace(windowsSlashRE, '/')
2223
}
2324

2425
export function walkDir(dir: string, handleFile: (file: string) => void): void {

‎packages/vite/src/node/optimizer/esbuildDepPlugin.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { CSS_LANGS_RE, KNOWN_ASSET_TYPES } from '../constants'
44
import { getDepOptimizationConfig } from '..'
55
import type { PackageCache, ResolvedConfig } from '..'
66
import {
7+
escapeRegex,
78
flattenId,
89
isBuiltin,
910
isExternalUrl,
@@ -281,6 +282,8 @@ module.exports = Object.create(new Proxy({}, {
281282
}
282283
}
283284

285+
const matchesEntireLine = (text: string) => `^${escapeRegex(text)}$`
286+
284287
// esbuild doesn't transpile `require('foo')` into `import` statements if 'foo' is externalized
285288
// https://github.com/evanw/esbuild/issues/566#issuecomment-735551834
286289
export function esbuildCjsExternalPlugin(
@@ -290,9 +293,7 @@ export function esbuildCjsExternalPlugin(
290293
return {
291294
name: 'cjs-external',
292295
setup(build) {
293-
const escape = (text: string) =>
294-
`^${text.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')}$`
295-
const filter = new RegExp(externals.map(escape).join('|'))
296+
const filter = new RegExp(externals.map(matchesEntireLine).join('|'))
296297

297298
build.onResolve({ filter: new RegExp(`^${nonFacadePrefix}`) }, (args) => {
298299
return {

‎packages/vite/src/node/optimizer/index.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
normalizeId,
2222
normalizePath,
2323
removeDir,
24+
removeLeadingSlash,
2425
renameDir,
2526
writeFile,
2627
} from '../utils'
@@ -41,6 +42,7 @@ const isDebugEnabled = _debug('vite:deps').enabled
4142

4243
const jsExtensionRE = /\.js$/i
4344
const jsMapExtensionRE = /\.js\.map$/i
45+
const reExportRE = /export\s+\*\s+from/
4446

4547
export type ExportsData = {
4648
hasImports: boolean
@@ -956,7 +958,7 @@ export function createIsOptimizedDepUrl(
956958
const depsCacheDirPrefix = depsCacheDirRelative.startsWith('../')
957959
? // if the cache directory is outside root, the url prefix would be something
958960
// like '/@fs/absolute/path/to/node_modules/.vite'
959-
`/@fs/${normalizePath(depsCacheDir).replace(/^\//, '')}`
961+
`/@fs/${removeLeadingSlash(normalizePath(depsCacheDir))}`
960962
: // if the cache directory is inside root, the url prefix would be something
961963
// like '/node_modules/.vite'
962964
`/${depsCacheDirRelative}`
@@ -1140,7 +1142,7 @@ export async function extractExportsData(
11401142
facade,
11411143
hasReExports: imports.some(({ ss, se }) => {
11421144
const exp = entryContent.slice(ss, se)
1143-
return /export\s+\*\s+from/.test(exp)
1145+
return reExportRE.test(exp)
11441146
}),
11451147
jsxLoader: usedJsxLoader,
11461148
}

‎packages/vite/src/node/plugins/asset.ts

+10-3
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,21 @@ import {
1616
} from '../build'
1717
import type { Plugin } from '../plugin'
1818
import type { ResolvedConfig } from '../config'
19-
import { cleanUrl, getHash, joinUrlSegments, normalizePath } from '../utils'
19+
import {
20+
cleanUrl,
21+
getHash,
22+
joinUrlSegments,
23+
normalizePath,
24+
removeLeadingSlash,
25+
} from '../utils'
2026
import { FS_PREFIX } from '../constants'
2127

2228
export const assetUrlRE = /__VITE_ASSET__([a-z\d]+)__(?:\$_(.*?)__)?/g
2329

2430
const rawRE = /(?:\?|&)raw(?:&|$)/
2531
const urlRE = /(\?|&)url(?:&|$)/
2632
const jsSourceMapRE = /\.[cm]?js\.map$/
33+
const unnededFinalQueryCharRE = /[?&]$/
2734

2835
const assetCache = new WeakMap<ResolvedConfig, Map<string, string>>()
2936

@@ -169,7 +176,7 @@ export function assetPlugin(config: ResolvedConfig): Plugin {
169176
return
170177
}
171178

172-
id = id.replace(urlRE, '$1').replace(/[?&]$/, '')
179+
id = id.replace(urlRE, '$1').replace(unnededFinalQueryCharRE, '')
173180
const url = await fileToUrl(id, config, this)
174181
return `export default ${JSON.stringify(url)}`
175182
},
@@ -255,7 +262,7 @@ function fileToDevUrl(id: string, config: ResolvedConfig) {
255262
rtn = path.posix.join(FS_PREFIX, id)
256263
}
257264
const base = joinUrlSegments(config.server?.origin ?? '', config.base)
258-
return joinUrlSegments(base, rtn.replace(/^\//, ''))
265+
return joinUrlSegments(base, removeLeadingSlash(rtn))
259266
}
260267

261268
export function getPublicAssetFilename(

‎packages/vite/src/node/plugins/clientInjections.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import type { ResolvedConfig } from '../config'
44
import { CLIENT_ENTRY, ENV_ENTRY } from '../constants'
55
import { isObject, normalizePath, resolveHostname } from '../utils'
66

7+
const process_env_NODE_ENV_RE =
8+
/(\bglobal(This)?\.)?\bprocess\.env\.NODE_ENV\b/g
9+
710
// ids in transform are normalized to unix style
811
const normalizedClientEntry = normalizePath(CLIENT_ENTRY)
912
const normalizedEnvEntry = normalizePath(ENV_ENTRY)
@@ -86,7 +89,7 @@ export function clientInjectionsPlugin(config: ResolvedConfig): Plugin {
8689
// for it to avoid shimming a `process` object during dev,
8790
// avoiding inconsistencies between dev and build
8891
return code.replace(
89-
/(\bglobal(This)?\.)?\bprocess\.env\.NODE_ENV\b/g,
92+
process_env_NODE_ENV_RE,
9093
config.define?.['process.env.NODE_ENV'] ||
9194
JSON.stringify(process.env.NODE_ENV || config.mode),
9295
)

‎packages/vite/src/node/plugins/define.ts

+2-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import MagicString from 'magic-string'
22
import type { ResolvedConfig } from '../config'
33
import type { Plugin } from '../plugin'
4-
import { transformStableResult } from '../utils'
4+
import { escapeRegex, transformStableResult } from '../utils'
55
import { isCSSRequest } from './css'
66
import { isHTMLRequest } from './html'
77

@@ -113,11 +113,7 @@ export function definePlugin(config: ResolvedConfig): Plugin {
113113
// Mustn't be preceded by a char that can be part of an identifier
114114
// or a '.' that isn't part of a spread operator
115115
'(?<![\\p{L}\\p{N}_$]|(?<!\\.\\.)\\.)(' +
116-
replacementsKeys
117-
.map((str) => {
118-
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&')
119-
})
120-
.join('|') +
116+
replacementsKeys.map(escapeRegex).join('|') +
121117
// Mustn't be followed by a char that can be part of an identifier
122118
// or an assignment (but allow equality operators)
123119
')(?:(?<=\\.)|(?![\\p{L}\\p{N}_$]|\\s*?=[^=]))',

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
isExternalUrl,
2121
normalizePath,
2222
processSrcSet,
23+
removeLeadingSlash,
2324
} from '../utils'
2425
import type { ResolvedConfig } from '../config'
2526
import { toOutputFilePathInHtml } from '../build'
@@ -537,7 +538,7 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
537538
if (
538539
content !== '' && // Empty attribute
539540
!namedOutput.includes(content) && // Direct reference to named output
540-
!namedOutput.includes(content.replace(/^\//, '')) // Allow for absolute references as named output can't be an absolute path
541+
!namedOutput.includes(removeLeadingSlash(content)) // Allow for absolute references as named output can't be an absolute path
541542
) {
542543
try {
543544
const url =

‎packages/vite/src/node/server/__tests__/pluginContainer.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ async function getPluginContainer(
196196
)
197197

198198
// @ts-expect-error This plugin requires a ViteDevServer instance.
199-
config.plugins = config.plugins.filter((p) => !/pre-alias/.test(p.name))
199+
config.plugins = config.plugins.filter((p) => !p.name.includes('pre-alias'))
200200

201201
resolveId = (id) => container.resolveId(id)
202202
const container = await createPluginContainer(config, moduleGraph)

‎packages/vite/src/node/server/hmr.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import type { ModuleNode } from './moduleGraph'
1414

1515
export const debugHmr = createDebugger('vite:hmr')
1616

17+
const whitespaceRE = /\s/
18+
1719
const normalizedClientDir = normalizePath(CLIENT_DIR)
1820

1921
export interface HmrOptions {
@@ -388,7 +390,7 @@ export function lexAcceptedHmrDeps(
388390
} else if (char === '`') {
389391
prevState = state
390392
state = LexerState.inTemplateString
391-
} else if (/\s/.test(char)) {
393+
} else if (whitespaceRE.test(char)) {
392394
continue
393395
} else {
394396
if (state === LexerState.inCall) {

‎packages/vite/src/node/server/middlewares/static.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@ import {
1414
isInternalRequest,
1515
isParentDirectory,
1616
isWindows,
17+
removeLeadingSlash,
1718
shouldServeFile,
1819
slash,
1920
} from '../../utils'
2021

22+
const knownJavascriptExtensionRE = /\.[tj]sx?$/
23+
2124
const sirvOptions = ({
2225
headers,
2326
shouldServe,
@@ -35,7 +38,7 @@ const sirvOptions = ({
3538
// for the MIME type video/mp2t. In almost all cases, we can expect
3639
// these files to be TypeScript files, and for Vite to serve them with
3740
// this Content-Type.
38-
if (/\.[tj]sx?$/.test(pathname)) {
41+
if (knownJavascriptExtensionRE.test(pathname)) {
3942
res.setHeader('Content-Type', 'application/javascript')
4043
}
4144
if (headers) {
@@ -119,7 +122,7 @@ export function serveStaticMiddleware(
119122
}
120123

121124
const resolvedPathname = redirectedPathname || pathname
122-
let fileUrl = path.resolve(dir, resolvedPathname.replace(/^\//, ''))
125+
let fileUrl = path.resolve(dir, removeLeadingSlash(resolvedPathname))
123126
if (resolvedPathname.endsWith('/') && !fileUrl.endsWith('/')) {
124127
fileUrl = fileUrl + '/'
125128
}

‎packages/vite/src/node/utils.ts

+36-15
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,9 @@ export const createFilter = _createFilter as (
5050
options?: { resolve?: string | false | null },
5151
) => (id: string | unknown) => boolean
5252

53+
const windowsSlashRE = /\\/g
5354
export function slash(p: string): string {
54-
return p.replace(/\\/g, '/')
55+
return p.replace(windowsSlashRE, '/')
5556
}
5657

5758
/**
@@ -74,15 +75,19 @@ export function unwrapId(id: string): string {
7475
: id
7576
}
7677

78+
const replaceSlashOrColonRE = /[/:]/g
79+
const replaceDotRE = /\./g
80+
const replaceNestedIdRE = /(\s*>\s*)/g
81+
const replaceHashRE = /#/g
7782
export const flattenId = (id: string): string =>
7883
id
79-
.replace(/[/:]/g, '_')
80-
.replace(/\./g, '__')
81-
.replace(/(\s*>\s*)/g, '___')
82-
.replace(/#/g, '____')
84+
.replace(replaceSlashOrColonRE, '_')
85+
.replace(replaceDotRE, '__')
86+
.replace(replaceNestedIdRE, '___')
87+
.replace(replaceHashRE, '____')
8388

8489
export const normalizeId = (id: string): string =>
85-
id.replace(/(\s*>\s*)/g, ' > ')
90+
id.replace(replaceNestedIdRE, ' > ')
8691

8792
//TODO: revisit later to see if the edge case that "compiling using node v12 code to be run in node v16 in the server" is what we intend to support.
8893
const builtins = new Set([
@@ -300,10 +305,14 @@ export function removeDirectQuery(url: string): string {
300305
return url.replace(directRequestRE, '$1').replace(trailingSeparatorRE, '')
301306
}
302307

308+
const replacePercentageRE = /%/g
303309
export function injectQuery(url: string, queryToInject: string): string {
304310
// encode percents for consistent behavior with pathToFileURL
305311
// see #2614 for details
306-
const resolvedUrl = new URL(url.replace(/%/g, '%25'), 'relative:///')
312+
const resolvedUrl = new URL(
313+
url.replace(replacePercentageRE, '%25'),
314+
'relative:///',
315+
)
307316
const { search, hash } = resolvedUrl
308317
let pathname = cleanUrl(url)
309318
pathname = isWindows ? slash(pathname) : pathname
@@ -659,13 +668,12 @@ export function processSrcSetSync(
659668
)
660669
}
661670

671+
const cleanSrcSetRE =
672+
/(?:url|image|gradient|cross-fade)\([^)]*\)|"([^"]|(?<=\\)")*"|'([^']|(?<=\\)')*'/g
662673
function splitSrcSet(srcs: string) {
663674
const parts: string[] = []
664675
// There could be a ',' inside of url(data:...), linear-gradient(...) or "data:..."
665-
const cleanedSrcs = srcs.replace(
666-
/(?:url|image|gradient|cross-fade)\([^)]*\)|"([^"]|(?<=\\)")*"|'([^']|(?<=\\)')*'/g,
667-
blankReplacer,
668-
)
676+
const cleanedSrcs = srcs.replace(cleanSrcSetRE, blankReplacer)
669677
let startIndex = 0
670678
let splitIndex: number
671679
do {
@@ -678,22 +686,26 @@ function splitSrcSet(srcs: string) {
678686
return parts
679687
}
680688

689+
const windowsDriveRE = /^[A-Z]:/
690+
const replaceWindowsDriveRE = /^([A-Z]):\//
691+
const linuxAbsolutePathRE = /^\/[^/]/
681692
function escapeToLinuxLikePath(path: string) {
682-
if (/^[A-Z]:/.test(path)) {
683-
return path.replace(/^([A-Z]):\//, '/windows/$1/')
693+
if (windowsDriveRE.test(path)) {
694+
return path.replace(replaceWindowsDriveRE, '/windows/$1/')
684695
}
685-
if (/^\/[^/]/.test(path)) {
696+
if (linuxAbsolutePathRE.test(path)) {
686697
return `/linux${path}`
687698
}
688699
return path
689700
}
690701

702+
const revertWindowsDriveRE = /^\/windows\/([A-Z])\//
691703
function unescapeToLinuxLikePath(path: string) {
692704
if (path.startsWith('/linux/')) {
693705
return path.slice('/linux'.length)
694706
}
695707
if (path.startsWith('/windows/')) {
696-
return path.replace(/^\/windows\/([A-Z])\//, '$1:/')
708+
return path.replace(revertWindowsDriveRE, '$1:/')
697709
}
698710
return path
699711
}
@@ -1222,6 +1234,10 @@ export function joinUrlSegments(a: string, b: string): string {
12221234
return a + b
12231235
}
12241236

1237+
export function removeLeadingSlash(str: string): string {
1238+
return str[0] === '/' ? str.slice(1) : str
1239+
}
1240+
12251241
export function stripBase(path: string, base: string): string {
12261242
if (path === base) {
12271243
return '/'
@@ -1246,3 +1262,8 @@ export function evalValue<T = any>(rawValue: string): T {
12461262
`)
12471263
return fn()
12481264
}
1265+
1266+
const escapeRegexRE = /[-/\\^$*+?.()|[\]{}]/g
1267+
export function escapeRegex(str: string): string {
1268+
return str.replace(escapeRegexRE, '\\$&')
1269+
}

0 commit comments

Comments
 (0)
Please sign in to comment.