Skip to content

Commit

Permalink
perf: regexp perf issues, refactor regexp stylistic issues (#10905)
Browse files Browse the repository at this point in the history
fix #10900
  • Loading branch information
sapphi-red committed Nov 14, 2022
1 parent 92a206b commit fc007df
Show file tree
Hide file tree
Showing 38 changed files with 156 additions and 89 deletions.
9 changes: 6 additions & 3 deletions .eslintrc.cjs
Expand Up @@ -7,9 +7,10 @@ module.exports = defineConfig({
extends: [
'eslint:recommended',
'plugin:node/recommended',
'plugin:@typescript-eslint/recommended'
'plugin:@typescript-eslint/recommended',
'plugin:regexp/recommended'
],
plugins: ['import'],
plugins: ['import', 'regexp'],
parser: '@typescript-eslint/parser',
parserOptions: {
sourceType: 'module',
Expand Down Expand Up @@ -97,7 +98,9 @@ module.exports = defineConfig({
memberSyntaxSortOrder: ['none', 'all', 'multiple', 'single'],
allowSeparatedGroups: false
}
]
],

'regexp/no-contradiction-with-assertion': 'error'
},
overrides: [
{
Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -66,6 +66,7 @@
"eslint-define-config": "^1.11.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-regexp": "^1.10.0",
"execa": "^6.1.0",
"fast-glob": "^3.2.12",
"fs-extra": "^10.1.0",
Expand Down
4 changes: 2 additions & 2 deletions packages/create-vite/src/index.ts
Expand Up @@ -388,7 +388,7 @@ function copy(src: string, dest: string) {
}

function isValidPackageName(projectName: string) {
return /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(
return /^(?:@[a-z\d\-*~][a-z\d\-*._~]*\/)?[a-z\d\-~][a-z\d\-._~]*$/.test(
projectName
)
}
Expand All @@ -399,7 +399,7 @@ function toValidPackageName(projectName: string) {
.toLowerCase()
.replace(/\s+/g, '-')
.replace(/^[._]/, '')
.replace(/[^a-z0-9-~]+/g, '-')
.replace(/[^a-z\d\-~]+/g, '-')
}

function copyDir(srcDir: string, destDir: string) {
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-react/src/fast-refresh.ts
Expand Up @@ -56,7 +56,7 @@ if (import.meta.hot) {
RefreshRuntime.register(type, __SOURCE__ + " " + id)
};
window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform;
}`.replace(/[\n]+/gm, '')
}`.replace(/\n+/g, '')

const timeout = `
if (!window.__vite_plugin_react_timeout) {
Expand Down
6 changes: 3 additions & 3 deletions packages/plugin-react/src/index.ts
Expand Up @@ -110,10 +110,10 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
// - import * as React from 'react';
// - import React from 'react';
// - import React, {useEffect} from 'react';
const importReactRE = /(^|\n)import\s+(\*\s+as\s+)?React(,|\s+)/
const importReactRE = /(?:^|\n)import\s+(?:\*\s+as\s+)?React(?:,|\s+)/

// Any extension, including compound ones like '.bs.js'
const fileExtensionRE = /\.[^\/\s\?]+$/
const fileExtensionRE = /\.[^/\s?]+$/

const viteBabel: Plugin = {
name: 'vite:react-babel',
Expand Down Expand Up @@ -202,7 +202,7 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
filepath.match(fileExtensionRE) ||
[]

if (/\.(mjs|[tj]sx?)$/.test(extension)) {
if (/\.(?:mjs|[tj]sx?)$/.test(extension)) {
const isJSX = extension.endsWith('x')
const isNodeModules = id.includes('/node_modules/')
const isProjectFile =
Expand Down
4 changes: 2 additions & 2 deletions packages/plugin-vue/src/handleHotUpdate.ts
Expand Up @@ -11,7 +11,7 @@ import type { ResolvedOptions } from '.'

const debug = _debug('vite:hmr')

const directRequestRE = /(\?|&)direct\b/
const directRequestRE = /(?:\?|&)direct\b/

/**
* Vite-specific HMR handling
Expand Down Expand Up @@ -148,7 +148,7 @@ export async function handleHotUpdate(
affectedModules.add(mainModule)
} else if (mainModule && !affectedModules.has(mainModule)) {
const styleImporters = [...mainModule.importers].filter((m) =>
/\.css($|\?)/.test(m.url)
/\.css(?:$|\?)/.test(m.url)
)
styleImporters.forEach((m) => affectedModules.add(m))
}
Expand Down
4 changes: 2 additions & 2 deletions packages/vite/src/node/config.ts
Expand Up @@ -465,8 +465,8 @@ export async function resolveConfig(
)

const clientAlias = [
{ find: /^[\/]?@vite\/env/, replacement: () => ENV_ENTRY },
{ find: /^[\/]?@vite\/client/, replacement: () => CLIENT_ENTRY }
{ find: /^\/?@vite\/env/, replacement: () => ENV_ENTRY },
{ find: /^\/?@vite\/client/, replacement: () => CLIENT_ENTRY }
]

// resolve alias with internal client alias
Expand Down
6 changes: 3 additions & 3 deletions packages/vite/src/node/constants.ts
Expand Up @@ -46,9 +46,9 @@ export const DEFAULT_CONFIG_FILES = [

export const JS_TYPES_RE = /\.(?:j|t)sx?$|\.mjs$/

export const OPTIMIZABLE_ENTRY_RE = /\.(?:[cm]?[jt]s)$/
export const OPTIMIZABLE_ENTRY_RE = /\.[cm]?[jt]s$/

export const SPECIAL_QUERY_RE = /[\?&](?:worker|sharedworker|raw|url)\b/
export const SPECIAL_QUERY_RE = /[?&](?:worker|sharedworker|raw|url)\b/

/**
* Prefix for resolved fs paths, since windows paths may not be valid as URLs.
Expand Down Expand Up @@ -129,7 +129,7 @@ export const DEFAULT_ASSETS_RE = new RegExp(
`\\.(` + KNOWN_ASSET_TYPES.join('|') + `)(\\?.*)?$`
)

export const DEP_VERSION_RE = /[\?&](v=[\w\.-]+)\b/
export const DEP_VERSION_RE = /[?&](v=[\w.-]+)\b/

export const loopbackHosts = new Set([
'localhost',
Expand Down
2 changes: 1 addition & 1 deletion packages/vite/src/node/optimizer/esbuildDepPlugin.ts
Expand Up @@ -265,7 +265,7 @@ export function esbuildCjsExternalPlugin(externals: string[]): Plugin {
name: 'cjs-external',
setup(build) {
const escape = (text: string) =>
`^${text.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')}$`
`^${text.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')}$`
const filter = new RegExp(externals.map(escape).join('|'))

build.onResolve({ filter: /.*/, namespace: 'external' }, (args) => ({
Expand Down
14 changes: 7 additions & 7 deletions packages/vite/src/node/optimizer/scan.ts
Expand Up @@ -40,7 +40,7 @@ const htmlTypesRE = /\.(html|vue|svelte|astro|imba)$/
// since even missed imports can be caught at runtime, and false positives will
// simply be ignored.
export const importsRE =
/(?<!\/\/.*)(?<=^|;|\*\/)\s*import(?!\s+type)(?:[\w*{}\n\r\t, ]+from\s*)?\s*("[^"]+"|'[^']+')\s*(?=$|;|\/\/|\/\*)/gm
/(?<!\/\/.*)(?<=^|;|\*\/)\s*import(?!\s+type)(?:[\w*{}\n\r\t, ]+from)?\s*("[^"]+"|'[^']+')\s*(?=$|;|\/\/|\/\*)/gm

export async function scanImports(config: ResolvedConfig): Promise<{
deps: Record<string, string>
Expand Down Expand Up @@ -149,13 +149,13 @@ function globEntries(pattern: string | string[], config: ResolvedConfig) {
}

const scriptModuleRE =
/(<script\b[^>]*type\s*=\s*(?:"module"|'module')[^>]*>)(.*?)<\/script>/gims
export const scriptRE = /(<script\b(?:\s[^>]*>|>))(.*?)<\/script>/gims
/(<script\b[^>]+type\s*=\s*(?:"module"|'module')[^>]*>)(.*?)<\/script>/gis
export const scriptRE = /(<script(?:\s[^>]*>|>))(.*?)<\/script>/gis
export const commentRE = /<!--.*?-->/gs
const srcRE = /\bsrc\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s'">]+))/im
const typeRE = /\btype\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s'">]+))/im
const langRE = /\blang\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s'">]+))/im
const contextRE = /\bcontext\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s'">]+))/im
const srcRE = /\bsrc\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s'">]+))/i
const typeRE = /\btype\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s'">]+))/i
const langRE = /\blang\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s'">]+))/i
const contextRE = /\bcontext\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s'">]+))/i

function esbuildScanPlugin(
config: ResolvedConfig,
Expand Down
4 changes: 2 additions & 2 deletions packages/vite/src/node/plugins/asset.ts
Expand Up @@ -21,7 +21,7 @@ import { FS_PREFIX } from '../constants'

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

const rawRE = /(\?|&)raw(?:&|$)/
const rawRE = /(?:\?|&)raw(?:&|$)/
const urlRE = /(\?|&)url(?:&|$)/

const assetCache = new WeakMap<ResolvedConfig, Map<string, string>>()
Expand Down Expand Up @@ -164,7 +164,7 @@ export function assetPlugin(config: ResolvedConfig): Plugin {
return
}

id = id.replace(urlRE, '$1').replace(/[\?&]$/, '')
id = id.replace(urlRE, '$1').replace(/[?&]$/, '')
const url = await fileToUrl(id, config, this)
return `export default ${JSON.stringify(url)}`
},
Expand Down
2 changes: 1 addition & 1 deletion packages/vite/src/node/plugins/assetImportMetaUrl.ts
Expand Up @@ -38,7 +38,7 @@ export function assetImportMetaUrlPlugin(config: ResolvedConfig): Plugin {
) {
let s: MagicString | undefined
const assetImportMetaUrlRE =
/\bnew\s+URL\s*\(\s*('[^']+'|"[^"]+"|`[^`]+`)\s*,\s*import\.meta\.url\s*,?\s*\)/g
/\bnew\s+URL\s*\(\s*('[^']+'|"[^"]+"|`[^`]+`)\s*,\s*import\.meta\.url\s*(?:,\s*)?\)/g
const cleanString = stripLiteral(code)

let match: RegExpExecArray | null
Expand Down
29 changes: 16 additions & 13 deletions packages/vite/src/node/plugins/css.ts
Expand Up @@ -104,13 +104,14 @@ export interface CSSModulesOptions {

const cssLangs = `\\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)($|\\?)`
const cssLangRE = new RegExp(cssLangs)
// eslint-disable-next-line regexp/no-unused-capturing-group
const cssModuleRE = new RegExp(`\\.module${cssLangs}`)
const directRequestRE = /(\?|&)direct\b/
const htmlProxyRE = /(\?|&)html-proxy\b/
const directRequestRE = /(?:\?|&)direct\b/
const htmlProxyRE = /(?:\?|&)html-proxy\b/
const commonjsProxyRE = /\?commonjs-proxy/
const inlineRE = /(\?|&)inline\b/
const inlineCSSRE = /(\?|&)inline-css\b/
const usedRE = /(\?|&)used\b/
const inlineRE = /(?:\?|&)inline\b/
const inlineCSSRE = /(?:\?|&)inline-css\b/
const usedRE = /(?:\?|&)used\b/
const varRE = /^var\(/i

const cssBundleName = 'style.css'
Expand Down Expand Up @@ -1163,11 +1164,13 @@ type CssUrlReplacer = (
) => string | Promise<string>
// https://drafts.csswg.org/css-syntax-3/#identifier-code-point
export const cssUrlRE =
/(?<=^|[^\w\-\u0080-\uffff])url\(\s*('[^']+'|"[^"]+"|[^'")]+)\s*\)/
/(?<=^|[^\w\-\u0080-\uffff])url\((\s*('[^']+'|"[^"]+")\s*|[^'")]+)\)/
export const cssDataUriRE =
/(?<=^|[^\w\-\u0080-\uffff])data-uri\(\s*('[^']+'|"[^"]+"|[^'")]+)\s*\)/
/(?<=^|[^\w\-\u0080-\uffff])data-uri\((\s*('[^']+'|"[^"]+")\s*|[^'")]+)\)/
export const importCssRE = /@import ('[^']+\.css'|"[^"]+\.css"|[^'")]+\.css)/
const cssImageSetRE = /(?<=image-set\()((?:[\w\-]+\([^\)]*\)|[^)])*)(?=\))/
// Assuming a function name won't be longer than 256 chars
// eslint-disable-next-line regexp/no-unused-capturing-group -- doesn't detect asyncReplace usage
const cssImageSetRE = /(?<=image-set\()((?:[\w\-]{1,256}\([^)]*\)|[^)])*)(?=\))/

const UrlRewritePostcssPlugin: PostCSS.PluginCreator<{
replacer: CssUrlReplacer
Expand Down Expand Up @@ -1223,7 +1226,7 @@ function rewriteCssUrls(
): Promise<string> {
return asyncReplace(css, cssUrlRE, async (match) => {
const [matched, rawUrl] = match
return await doUrlReplace(rawUrl, matched, replacer)
return await doUrlReplace(rawUrl.trim(), matched, replacer)
})
}

Expand All @@ -1233,7 +1236,7 @@ function rewriteCssDataUris(
): Promise<string> {
return asyncReplace(css, cssDataUriRE, async (match) => {
const [matched, rawUrl] = match
return await doUrlReplace(rawUrl, matched, replacer, 'data-uri')
return await doUrlReplace(rawUrl.trim(), matched, replacer, 'data-uri')
})
}

Expand All @@ -1250,7 +1253,7 @@ function rewriteImportCss(
// TODO: image and cross-fade could contain a "url" that needs to be processed
// https://drafts.csswg.org/css-images-4/#image-notation
// https://drafts.csswg.org/css-images-4/#cross-fade-function
const cssNotProcessedRE = /(gradient|element|cross-fade|image)\(/
const cssNotProcessedRE = /(?:gradient|element|cross-fade|image)\(/

async function rewriteCssImageSet(
css: string,
Expand Down Expand Up @@ -1379,7 +1382,7 @@ export async function hoistAtRules(css: string): Promise<string> {
// to top when multiple files are concatenated.
// match until semicolon that's not in quotes
const atImportRE =
/@import\s*(?:url\([^\)]*\)|"([^"]|(?<=\\)")*"|'([^']|(?<=\\)')*'|[^;]*).*?;/gm
/@import(?:\s*(?:url\([^)]*\)|"(?:[^"]|(?<=\\)")*"|'(?:[^']|(?<=\\)')*').*?|[^;]*);/g
while ((match = atImportRE.exec(cleanCss))) {
s.remove(match.index, match.index + match[0].length)
// Use `appendLeft` instead of `prepend` to preserve original @import order
Expand All @@ -1389,7 +1392,7 @@ export async function hoistAtRules(css: string): Promise<string> {
// #6333
// CSS @charset must be the top-first in the file, hoist the first to top
const atCharsetRE =
/@charset\s*(?:"([^"]|(?<=\\)")*"|'([^']|(?<=\\)')*'|[^;]*).*?;/gm
/@charset(?:\s*(?:"(?:[^"]|(?<=\\)")*"|'(?:[^']|(?<=\\)')*').*?|[^;]*);/g
let foundCharset = false
while ((match = atCharsetRE.exec(cleanCss))) {
s.remove(match.index, match.index + match[0].length)
Expand Down
2 changes: 1 addition & 1 deletion packages/vite/src/node/plugins/define.ts
Expand Up @@ -5,7 +5,7 @@ import { transformStableResult } from '../utils'
import { isCSSRequest } from './css'
import { isHTMLRequest } from './html'

const nonJsRe = /\.(json)($|\?)/
const nonJsRe = /\.json(?:$|\?)/
const isNonJsRequest = (request: string): boolean => nonJsRe.test(request)

export function definePlugin(config: ResolvedConfig): Plugin {
Expand Down
4 changes: 2 additions & 2 deletions packages/vite/src/node/plugins/esbuild.ts
Expand Up @@ -27,9 +27,9 @@ import { searchForWorkspaceRoot } from '..'
const debug = createDebugger('vite:esbuild')

const INJECT_HELPERS_IIFE_RE =
/^(.*)((?:const|var) [^\s]+=function\([^)]*?\){"use strict";)/s
/^(.*?)((?:const|var) \S+=function\([^)]*\)\{"use strict";)/s
const INJECT_HELPERS_UMD_RE =
/^(.*)(\(function\([^)]*?\){.+amd.+function\([^)]*?\){"use strict";)/s
/^(.*?)(\(function\([^)]*\)\{.+amd.+function\([^)]*\)\{"use strict";)/s

let server: ViteDevServer

Expand Down
11 changes: 6 additions & 5 deletions packages/vite/src/node/plugins/html.ts
Expand Up @@ -43,12 +43,13 @@ const htmlProxyRE = /\?html-proxy=?(?:&inline-css)?&index=(\d+)\.(js|css)$/
const inlineCSSRE = /__VITE_INLINE_CSS__([a-z\d]{8}_\d+)__/g
// Do not allow preceding '.', but do allow preceding '...' for spread operations
const inlineImportRE =
/(?<!(?<!\.\.)\.)\bimport\s*\(("([^"]|(?<=\\)")*"|'([^']|(?<=\\)')*')\)/g
const htmlLangRE = /\.(html|htm)$/
/(?<!(?<!\.\.)\.)\bimport\s*\(("(?:[^"]|(?<=\\)")*"|'(?:[^']|(?<=\\)')*')\)/g
const htmlLangRE = /\.(?:html|htm)$/

const importMapRE =
/[ \t]*<script[^>]*type\s*=\s*["']?importmap["']?[^>]*>.*?<\/script>/is
const moduleScriptRE = /[ \t]*<script[^>]*type\s*=\s*["']?module["']?[^>]*>/is
/[ \t]*<script[^>]*type\s*=\s*(?:"importmap"|'importmap'|importmap)[^>]*>.*?<\/script>/is
const moduleScriptRE =
/[ \t]*<script[^>]*type\s*=\s*(?:"module"|'module'|module)[^>]*>/i

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

Expand Down Expand Up @@ -196,7 +197,7 @@ export function getScriptInfo(node: DefaultTreeAdapterMap['element']): {
return { src, sourceCodeLocation, isModule, isAsync }
}

const attrValueStartRE = /=[\s\t\n\r]*(.)/
const attrValueStartRE = /=\s*(.)/

export function overwriteAttrValue(
s: MagicString,
Expand Down
10 changes: 5 additions & 5 deletions packages/vite/src/node/plugins/importAnalysis.ts
Expand Up @@ -71,12 +71,12 @@ const debug = createDebugger('vite:import-analysis')

const clientDir = normalizePath(CLIENT_DIR)

const skipRE = /\.(map|json)($|\?)/
const skipRE = /\.(?:map|json)(?:$|\?)/
export const canSkipImportAnalysis = (id: string): boolean =>
skipRE.test(id) || isDirectCSSRequest(id)

const optimizedDepChunkRE = /\/chunk-[A-Z0-9]{8}\.js/
const optimizedDepDynamicRE = /-[A-Z0-9]{8}\.js/
const optimizedDepChunkRE = /\/chunk-[A-Z\d]{8}\.js/
const optimizedDepDynamicRE = /-[A-Z\d]{8}\.js/

export function isExplicitImportRequired(url: string): boolean {
return !isJSRequest(cleanUrl(url)) && !isCSSRequest(url)
Expand Down Expand Up @@ -347,7 +347,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
// query can break 3rd party plugin's extension checks.
if (
(isRelative || isSelfImport) &&
!/[\?&]import=?\b/.test(url) &&
!/[?&]import=?\b/.test(url) &&
!url.match(DEP_VERSION_RE)
) {
const versionMatch = importer.match(DEP_VERSION_RE)
Expand Down Expand Up @@ -583,7 +583,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '')
.trim()
if (
!/^('.*'|".*"|`.*`)$/.test(url) ||
!/^(?:'.*'|".*"|`.*`)$/.test(url) ||
isExplicitImportRequired(url.slice(1, -1))
) {
needQueryInjectHelper = true
Expand Down
4 changes: 2 additions & 2 deletions packages/vite/src/node/plugins/importAnalysisBuild.ts
Expand Up @@ -38,8 +38,8 @@ const preloadMarkerWithQuote = `"${preloadMarker}"` as const
const dynamicImportPrefixRE = /import\s*\(/

// TODO: abstract
const optimizedDepChunkRE = /\/chunk-[A-Z0-9]{8}\.js/
const optimizedDepDynamicRE = /-[A-Z0-9]{8}\.js/
const optimizedDepChunkRE = /\/chunk-[A-Z\d]{8}\.js/
const optimizedDepDynamicRE = /-[A-Z\d]{8}\.js/

function toRelativePath(filename: string, importer: string) {
const relPath = path.relative(path.dirname(importer), filename)
Expand Down
6 changes: 3 additions & 3 deletions packages/vite/src/node/plugins/json.ts
Expand Up @@ -26,9 +26,9 @@ export interface JsonOptions {
}

// Custom json filter for vite
const jsonExtRE = /\.json($|\?)(?!commonjs-(proxy|external))/
const jsonExtRE = /\.json(?:$|\?)(?!commonjs-(?:proxy|external))/

const jsonLangs = `\\.(json|json5)($|\\?)`
const jsonLangs = `\\.(?:json|json5)(?:$|\\?)`
const jsonLangRE = new RegExp(jsonLangs)
export const isJSONRequest = (request: string): boolean =>
jsonLangRE.test(request)
Expand Down Expand Up @@ -71,7 +71,7 @@ export function jsonPlugin(
map: { mappings: '' }
}
} catch (e) {
const errorMessageList = /[\d]+/.exec(e.message)
const errorMessageList = /\d+/.exec(e.message)
const position = errorMessageList && parseInt(errorMessageList[0], 10)
const msg = position
? `, invalid JSON syntax found at line ${position}`
Expand Down

0 comments on commit fc007df

Please sign in to comment.