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: hoist regex patterns #18438

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/vite/src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { ErrorPayload, HotPayload } from 'types/hmrPayload'
import type { ViteHotContext } from 'types/hot'
import type { InferCustomEventPayload } from 'types/customEvent'
import { HMRClient, HMRContext } from '../shared/hmr'
import { postfixRE } from '../shared/utils'
import { ErrorOverlay, overlayId } from './overlay'
import '@vite/env'

Expand Down Expand Up @@ -456,7 +457,7 @@ export function injectQuery(url: string, queryToInject: string): string {
}

// can't use pathname from URL since it may be relative like ../
const pathname = url.replace(/[?#].*$/, '')
const pathname = url.replace(postfixRE, '')
const { search, hash } = new URL(url, 'http://vite.dev')

return `${pathname}?${queryToInject}${search ? `&` + search.slice(1) : ''}${
Expand Down
13 changes: 8 additions & 5 deletions packages/vite/src/module-runner/evaluatedModules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,10 @@ export class EvaluatedModules {

// unique id that is not available as "$bare_import" like "test"
const prefixedBuiltins = new Set(['node:test', 'node:sqlite'])

const fsRE = /^\/@fs\//
const nodeRE = /^node:/
const leadingSlashRE = /^\/+/
const fileRE = /^file:\//
// transform file url to id
// virtual:custom -> virtual:custom
// \0custom -> \0custom
Expand All @@ -138,10 +141,10 @@ function normalizeModuleId(file: string): string {

// unix style, but Windows path still starts with the drive letter to check the root
const unixFile = slash(file)
.replace(/^\/@fs\//, isWindows ? '' : '/')
.replace(/^node:/, '')
.replace(/^\/+/, '/')
.replace(fsRE, isWindows ? '' : '/')
.replace(nodeRE, '')
.replace(leadingSlashRE, '/')

// if it's not in the root, keep it as a path, not a URL
return unixFile.replace(/^file:\//, '/')
return unixFile.replace(fileRE, '/')
}
21 changes: 13 additions & 8 deletions packages/vite/src/module-runner/sourcemap/interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,16 @@ interface CachedMapEntry {
vite?: boolean
}

const protocolRE = /^\w+:\/\/[^/]*/
const fileUrlRE = /^\/\w:/
// Support URLs relative to a directory, but be careful about a protocol prefix
function supportRelativeURL(file: string, url: string) {
if (!file) return url
const dir = posixDirname(slash(file))
const match = /^\w+:\/\/[^/]*/.exec(dir)
const match = protocolRE.exec(dir)
let protocol = match ? match[0] : ''
const startPath = dir.slice(protocol.length)
if (protocol && /^\/\w:/.test(startPath)) {
if (protocol && fileUrlRE.test(startPath)) {
// handle file:///C:/ paths
protocol += '/'
return protocol + slash(posixResolve(startPath, url))
Expand Down Expand Up @@ -125,17 +127,17 @@ function retrieveFile(path: string): string | null | undefined | false {
return null
}

const sourceMapUrlRE =
/\/\/[@#]\s*sourceMappingURL=([^\s'"]+)\s*$|\/\*[@#]\s*sourceMappingURL=[^\s*'"]+\s*\*\/\s*$/gm
function retrieveSourceMapURL(source: string) {
// Get the URL of the source map
const fileData = retrieveFile(source)
if (!fileData) return null
const re =
/\/\/[@#]\s*sourceMappingURL=([^\s'"]+)\s*$|\/\*[@#]\s*sourceMappingURL=[^\s*'"]+\s*\*\/\s*$/gm
// Keep executing the search to find the *last* sourceMappingURL to avoid
// picking up sourceMappingURLs from comments, strings, etc.
let lastMatch, match

while ((match = re.exec(fileData))) lastMatch = match
while ((match = sourceMapUrlRE.exec(fileData))) lastMatch = match
if (!lastMatch) return null
return lastMatch[1]
}
Expand Down Expand Up @@ -234,11 +236,13 @@ function mapSourcePosition(position: OriginalMapping) {
return position
}

const evalCallRE = /^eval at ([^(]+) \((.+):(\d+):(\d+)\)$/
const nestedEvalRE = /^eval at ([^(]+) \((.+)\)$/
// Parses code generated by FormatEvalOrigin(), a function inside V8:
// https://code.google.com/p/v8/source/browse/trunk/src/messages.js
function mapEvalOrigin(origin: string): string {
// Most eval() calls are in this format
let match = /^eval at ([^(]+) \((.+):(\d+):(\d+)\)$/.exec(origin)
let match = evalCallRE.exec(origin)
if (match) {
const position = mapSourcePosition({
name: null,
Expand All @@ -250,7 +254,7 @@ function mapEvalOrigin(origin: string): string {
}

// Parse nested eval() calls using recursion
match = /^eval at ([^(]+) \((.+)\)$/.exec(origin)
match = nestedEvalRE.exec(origin)
if (match) return `eval at ${match[1]} (${mapEvalOrigin(match[2])})`

// Make sure we still return useful information if we didn't find anything
Expand Down Expand Up @@ -329,12 +333,13 @@ function CallSiteToString(this: CallSite) {
return line
}

const isGetRE = /^(?:is|get)/
function cloneCallSite(frame: CallSite) {
const object = {} as CallSite
Object.getOwnPropertyNames(Object.getPrototypeOf(frame)).forEach((name) => {
const key = name as keyof CallSite
// @ts-expect-error difficult to type
object[key] = /^(?:is|get)/.test(name)
object[key] = isGetRE.test(name)
? function () {
return frame[key].call(frame)
}
Expand Down
19 changes: 11 additions & 8 deletions packages/vite/src/module-runner/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import * as pathe from 'pathe'
import { isWindows, slash } from '../shared/utils'
import {
backslashRE,
hashRE,
isWindows,
newlineRegEx,
slash,
} from '../shared/utils'

export function normalizeAbsoluteUrl(url: string, root: string): string {
url = slash(url)
Expand Down Expand Up @@ -31,19 +37,16 @@ const CHAR_FORWARD_SLASH = 47
const CHAR_BACKWARD_SLASH = 92

const percentRegEx = /%/g
const backslashRegEx = /\\/g
const newlineRegEx = /\n/g
const carriageReturnRegEx = /\r/g
const tabRegEx = /\t/g
const questionRegex = /\?/g
const hashRegex = /#/g

function encodePathChars(filepath: string) {
if (filepath.indexOf('%') !== -1)
filepath = filepath.replace(percentRegEx, '%25')
// In posix, backslash is a valid character in paths:
if (!isWindows && filepath.indexOf('\\') !== -1)
filepath = filepath.replace(backslashRegEx, '%5C')
filepath = filepath.replace(backslashRE, '%5C')
if (filepath.indexOf('\n') !== -1)
filepath = filepath.replace(newlineRegEx, '%0A')
if (filepath.indexOf('\r') !== -1)
Expand Down Expand Up @@ -76,11 +79,11 @@ export function posixPathToFileHref(posixPath: string): string {
// later triggering pathname setter, which impacts performance
if (resolved.indexOf('?') !== -1)
resolved = resolved.replace(questionRegex, '%3F')
if (resolved.indexOf('#') !== -1)
resolved = resolved.replace(hashRegex, '%23')
if (resolved.indexOf('#') !== -1) resolved = resolved.replace(hashRE, '%23')
return new URL(`file://${resolved}`).href
}

const forwardSlashRE = /\//g
export function toWindowsPath(path: string): string {
return path.replace(/\//g, '\\')
return path.replace(forwardSlashRE, '\\')
}
8 changes: 4 additions & 4 deletions packages/vite/src/node/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import commonjsPlugin from '@rollup/plugin-commonjs'
import type { RollupCommonJSOptions } from 'dep-types/commonjs'
import type { RollupDynamicImportVarsOptions } from 'dep-types/dynamicImportVars'
import type { TransformOptions } from 'esbuild'
import { withTrailingSlash } from '../shared/utils'
import { backslashRE, withTrailingSlash } from '../shared/utils'
import {
DEFAULT_ASSETS_INLINE_LIMIT,
ESBUILD_MODULES_TARGET,
Expand Down Expand Up @@ -617,12 +617,13 @@ async function buildEnvironment(
return stack
}

const esbuildNewlineRE = /^\n|\n$/g
/**
* Esbuild code frames have newlines at the start and end of the frame, rollup doesn't
* This function normalizes the frame to match the esbuild format which has more pleasing padding
*/
const normalizeCodeFrame = (frame: string) => {
const trimmedPadding = frame.replace(/^\n|\n$/g, '')
const trimmedPadding = frame.replace(esbuildNewlineRE, '')
return `\n${trimmedPadding}\n`
}

Expand Down Expand Up @@ -1248,11 +1249,10 @@ function injectSsrFlag<T extends Record<string, any>>(
*/
const needsEscapeRegEx = /[\n\r'\\\u2028\u2029]/
const quoteNewlineRegEx = /([\n\r'\u2028\u2029])/g
const backSlashRegEx = /\\/g

function escapeId(id: string): string {
if (!needsEscapeRegEx.test(id)) return id
return id.replace(backSlashRegEx, '\\\\').replace(quoteNewlineRegEx, '\\$1')
return id.replace(backslashRE, '\\\\').replace(quoteNewlineRegEx, '\\$1')
}

const getResolveUrl = (path: string, URL = 'URL') => `new ${URL}(${path}).href`
Expand Down
3 changes: 2 additions & 1 deletion packages/vite/src/node/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,13 +160,14 @@ export function createLogger(
return logger
}

const portRE = /:(\d+)\//
export function printServerUrls(
urls: ResolvedServerUrls,
optionsHost: string | boolean | undefined,
info: Logger['info'],
): void {
const colorUrl = (url: string) =>
colors.cyan(url.replace(/:(\d+)\//, (_, port) => `:${colors.bold(port)}/`))
colors.cyan(url.replace(portRE, (_, port) => `:${colors.bold(port)}/`))
for (const url of urls.local) {
info(` ${colors.green('➜')} ${colors.bold('Local')}: ${colorUrl(url)}`)
}
Expand Down
10 changes: 6 additions & 4 deletions packages/vite/src/node/optimizer/esbuildDepPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import type { ImportKind, Plugin } from 'esbuild'
import { JS_TYPES_RE, KNOWN_ASSET_TYPES } from '../constants'
import type { PackageCache } from '../packages'
import {
anyRE,
escapeRegex,
flattenId,
isBuiltin,
isExternalUrl,
moduleListContains,
normalizePath,
windowsVolumeRE,
} from '../utils'
import { browserExternalId, optionalPeerDepId } from '../plugins/resolve'
import { isCSSRequest, isModuleCSSRequest } from '../plugins/css'
Expand Down Expand Up @@ -207,7 +209,7 @@ export function esbuildDepPlugin(
}

build.onResolve(
{ filter: /^[\w@][^:]/ },
{ filter: windowsVolumeRE },
async ({ path: id, importer, kind }) => {
if (moduleListContains(external, id)) {
return {
Expand Down Expand Up @@ -237,7 +239,7 @@ export function esbuildDepPlugin(
)

build.onLoad(
{ filter: /.*/, namespace: 'browser-external' },
{ filter: anyRE, namespace: 'browser-external' },
({ path }) => {
if (isProduction) {
return {
Expand Down Expand Up @@ -280,7 +282,7 @@ module.exports = Object.create(new Proxy({}, {
)

build.onLoad(
{ filter: /.*/, namespace: 'optional-peer-dep' },
{ filter: anyRE, namespace: 'optional-peer-dep' },
({ path }) => {
if (isProduction) {
return {
Expand Down Expand Up @@ -334,7 +336,7 @@ export function esbuildCjsExternalPlugin(
})

build.onLoad(
{ filter: /.*/, namespace: cjsExternalFacadeNamespace },
{ filter: anyRE, namespace: cjsExternalFacadeNamespace },
(args) => ({
contents:
`import * as m from ${JSON.stringify(
Expand Down
3 changes: 2 additions & 1 deletion packages/vite/src/node/optimizer/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export function createOptimizeDepsIncludeResolver(
}
}

const asteriskRE = /\*/g
/**
* Expand the glob syntax in `optimizeDeps.include` to proper import paths
*/
Expand Down Expand Up @@ -82,7 +83,7 @@ export function expandGlobIds(id: string, config: ResolvedConfig): string[] {
// "./dist/glob/*-browser/*.js" => "./dist/glob/**/*-browser/**/*.js"
// NOTE: in some cases, this could expand to consecutive /**/*/**/* etc
// but it's fine since `tinyglobby` handles it the same.
const exportValuePattern = exportsValue.replace(/\*/g, '**/*')
const exportValuePattern = exportsValue.replace(asteriskRE, '**/*')
// "./dist/glob/*-browser/*.js" => /dist\/glob\/(.*)-browser\/(.*)\.js/
const exportsValueGlobRe = new RegExp(
exportsValue.split('*').map(escapeRegex).join('(.*)'),
Expand Down
10 changes: 6 additions & 4 deletions packages/vite/src/node/optimizer/scan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
SPECIAL_QUERY_RE,
} from '../constants'
import {
anyRE,
arraify,
createDebugger,
dataUrlRE,
Expand All @@ -33,6 +34,7 @@ import {
singlelineCommentsRE,
virtualModulePrefix,
virtualModuleRE,
windowsVolumeRE,
} from '../utils'
import { resolveEnvironmentPlugins } from '../plugin'
import type { EnvironmentPluginContainer } from '../server/pluginContainer'
Expand Down Expand Up @@ -473,7 +475,7 @@ function esbuildScanPlugin(
}
})

build.onLoad({ filter: /.*/, namespace: 'script' }, ({ path }) => {
build.onLoad({ filter: anyRE, namespace: 'script' }, ({ path }) => {
return scripts[path]
})

Expand Down Expand Up @@ -631,7 +633,7 @@ function esbuildScanPlugin(
build.onResolve(
{
// avoid matching windows volume
filter: /^[\w@][^:]/,
filter: windowsVolumeRE,
},
async ({ path: id, importer, pluginData }) => {
if (moduleListContains(exclude, id)) {
Expand Down Expand Up @@ -704,7 +706,7 @@ function esbuildScanPlugin(

build.onResolve(
{
filter: /.*/,
filter: anyRE,
},
async ({ path: id, importer, pluginData }) => {
// use vite resolver to support urls and omitted extensions
Expand Down Expand Up @@ -767,7 +769,7 @@ function esbuildScanPlugin(
// onResolve is not called for glob imports.
// we need to add that here as well until esbuild calls onResolve for glob imports.
// https://github.com/evanw/esbuild/issues/3317
build.onLoad({ filter: /.*/, namespace: 'file' }, () => {
build.onLoad({ filter: anyRE, namespace: 'file' }, () => {
return {
loader: 'js',
contents: 'export default {}',
Expand Down
7 changes: 4 additions & 3 deletions packages/vite/src/node/plugins/asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
rawRE,
removeLeadingSlash,
removeUrlQuery,
spaceRE,
urlRE,
} from '../utils'
import { DEFAULT_ASSETS_INLINE_LIMIT, FS_PREFIX } from '../constants'
Expand Down Expand Up @@ -443,7 +444,7 @@ const shouldInline = (
}

const nestedQuotesRE = /"[^"']*'[^"]*"|'[^'"]*"[^']*'/

const chevronRE = />\s+</g
// Inspired by https://github.com/iconify/iconify/blob/main/packages/utils/src/svg/url.ts
function svgToDataURL(content: Buffer): string {
const stringContent = content.toString()
Expand All @@ -460,7 +461,7 @@ function svgToDataURL(content: Buffer): string {
'data:image/svg+xml,' +
stringContent
.trim()
.replaceAll(/>\s+</g, '><')
.replaceAll(chevronRE, '><')
.replaceAll('"', "'")
.replaceAll('%', '%25')
.replaceAll('#', '%23')
Expand All @@ -469,7 +470,7 @@ function svgToDataURL(content: Buffer): string {
// Spaces are not valid in srcset it has some use cases
// it can make the uncompressed URI slightly higher than base64, but will compress way better
// https://github.com/vitejs/vite/pull/14643#issuecomment-1766288673
.replaceAll(/\s+/g, '%20')
.replaceAll(spaceRE, '%20')
)
}
}
4 changes: 2 additions & 2 deletions packages/vite/src/node/plugins/clientInjections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const normalizedEnvEntry = normalizePath(ENV_ENTRY)
*/
export function clientInjectionsPlugin(config: ResolvedConfig): Plugin {
let injectConfigValues: (code: string) => string

const baseRE = /__BASE__/g
return {
name: 'vite:client-inject',
async buildStart() {
Expand Down Expand Up @@ -76,7 +76,7 @@ export function clientInjectionsPlugin(config: ResolvedConfig): Plugin {
injectConfigValues = (code: string) => {
return code
.replace(`__MODE__`, modeReplacement)
.replace(/__BASE__/g, baseReplacement)
.replace(baseRE, baseReplacement)
.replace(`__DEFINES__`, definesReplacement)
.replace(`__SERVER_HOST__`, serverHostReplacement)
.replace(`__HMR_PROTOCOL__`, hmrProtocolReplacement)
Expand Down
Loading