Skip to content

Commit

Permalink
perf: regex perf (super-linear-move)
Browse files Browse the repository at this point in the history
  • Loading branch information
sapphi-red committed Nov 13, 2022
1 parent 9963f71 commit 68be9c7
Show file tree
Hide file tree
Showing 15 changed files with 127 additions and 53 deletions.
8 changes: 7 additions & 1 deletion .eslintrc.cjs
Expand Up @@ -107,7 +107,13 @@ module.exports = defineConfig({
files: ['packages/**'],
excludedFiles: '**/__tests__/**',
rules: {
'no-restricted-globals': ['error', 'require', '__dirname', '__filename']
'no-restricted-globals': [
'error',
'require',
'__dirname',
'__filename'
],
'regexp/no-super-linear-move': 'error'
}
},
{
Expand Down
1 change: 1 addition & 0 deletions packages/create-vite/src/index.ts
Expand Up @@ -375,6 +375,7 @@ async function init() {
}

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

Expand Down
10 changes: 9 additions & 1 deletion packages/plugin-legacy/src/index.ts
Expand Up @@ -313,6 +313,7 @@ function viteLegacyPlugin(options: Options = {}): Plugin[] {
fileName = fileName.replace('[name]', '[name]-legacy')
} else {
// entry.js -> entry-legacy.js
// eslint-disable-next-line regexp/no-super-linear-move -- fileName won't be so long
fileName = fileName.replace(/(.+)\.(.+)/, '$1-legacy.$2')
}

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

const tags: HtmlTagDescriptor[] = []
const htmlFilename = chunk.facadeModuleId?.replace(/\?.*$/, '')
const htmlFilename = chunk.facadeModuleId
? trimQuery(chunk.facadeModuleId)
: undefined

// 1. inject modern polyfills
const modernPolyfillFilename = facadeToModernPolyfillMap.get(
Expand Down Expand Up @@ -793,6 +796,11 @@ function wrapIIFEBabelPlugin(): BabelPlugin {
}
}

function trimQuery(id: string) {
const pos = id.lastIndexOf('?')
return pos < 0 ? id : id.slice(0, pos)
}

export const cspHashes = [
createHash('sha256').update(safari10NoModuleFix).digest('base64'),
createHash('sha256').update(systemJSInlineCode).digest('base64'),
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-react/src/index.ts
Expand Up @@ -113,6 +113,7 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
const importReactRE = /(?:^|\n)import\s+(?:\*\s+as\s+)?React(?:,|\s+)/

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

const viteBabel: Plugin = {
Expand Down
7 changes: 6 additions & 1 deletion packages/plugin-vue/src/main.ts
Expand Up @@ -473,7 +473,12 @@ async function linkSrcToDescriptor(
(await pluginContext.resolve(src, descriptor.filename))?.id || src
// #1812 if the src points to a dep file, the resolved id may contain a
// version query.
setSrcDescriptor(srcFile.replace(/\?.*$/, ''), descriptor, scoped)
setSrcDescriptor(trimQuery(srcFile), descriptor, scoped)
}

function trimQuery(id: string) {
const pos = id.lastIndexOf('?')
return pos < 0 ? id : id.slice(0, pos)
}

// these are built-in query parameters so should be ignored
Expand Down
10 changes: 9 additions & 1 deletion packages/vite/src/client/client.ts
Expand Up @@ -593,12 +593,20 @@ export function injectQuery(url: string, queryToInject: string): string {
}

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

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

function trimEndFromLastOfThatChar(input: string, char: string) {
const pos = input.lastIndexOf(char)
return pos < 0 ? input : input.slice(0, pos)
}

export { ErrorOverlay }
2 changes: 2 additions & 0 deletions packages/vite/src/client/overlay.ts
Expand Up @@ -127,7 +127,9 @@ code {
</div>
`

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

// Allow `ErrorOverlay` to extend `HTMLElement` even in environments where
Expand Down
1 change: 1 addition & 0 deletions packages/vite/src/node/optimizer/scan.ts
Expand Up @@ -40,6 +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 =
// eslint-disable-next-line regexp/no-super-linear-move -- TODO: FIXME backtracking
/(?<!\/\/.*)(?<=^|;|\*\/)\s*import(?!\s+type)(?:[\w*{}\n\r\t, ]+from)?\s*("[^"]+"|'[^']+')\s*(?=$|;|\/\/|\/\*)/gm

export async function scanImports(config: ResolvedConfig): Promise<{
Expand Down
6 changes: 3 additions & 3 deletions packages/vite/src/node/plugins/dynamicImportVars.ts
Expand Up @@ -12,7 +12,7 @@ import {
normalizePath,
parseRequest,
removeComments,
requestQuerySplitRE,
splitRequestWithQuery,
transformStableResult
} from '../utils'
import { toAbsoluteGlob } from './importMetaGlob'
Expand Down Expand Up @@ -59,8 +59,8 @@ function parseDynamicImportPattern(
return null
}

const [userPattern] = userPatternQuery.split(requestQuerySplitRE, 2)
const [rawPattern] = filename.split(requestQuerySplitRE, 2)
const [userPattern] = splitRequestWithQuery(userPatternQuery)
const [rawPattern] = splitRequestWithQuery(filename)

if (rawQuery?.raw !== undefined) {
globParams = { as: 'raw' }
Expand Down
4 changes: 2 additions & 2 deletions packages/vite/src/node/plugins/ensureWatch.ts
@@ -1,5 +1,5 @@
import type { Plugin } from '../plugin'
import { cleanUrl, queryRE } from '../utils'
import { cleanUrl } from '../utils'

/**
* plugin to ensure rollup can watch correctly.
Expand All @@ -8,7 +8,7 @@ export function ensureWatchPlugin(): Plugin {
return {
name: 'vite:ensure-watch',
load(id) {
if (queryRE.test(id)) {
if (id.includes('?')) {
this.addWatchFile(cleanUrl(id))
}
return null
Expand Down
82 changes: 48 additions & 34 deletions packages/vite/src/node/plugins/html.ts
Expand Up @@ -19,7 +19,8 @@ import {
isDataUrl,
isExternalUrl,
normalizePath,
processSrcSet
processSrcSet,
removeComments
} from '../utils'
import type { ResolvedConfig } from '../config'
import { toOutputFilePathInHtml } from '../build'
Expand Down Expand Up @@ -48,9 +49,9 @@ const inlineImportRE =
const htmlLangRE = /\.(?:html|htm)$/

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

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

Expand Down Expand Up @@ -986,11 +987,10 @@ export async function applyHtmlTransforms(
}

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

function getBaseInHTML(urlRelativePath: string, config: ResolvedConfig) {
Expand All @@ -1004,14 +1004,14 @@ function getBaseInHTML(urlRelativePath: string, config: ResolvedConfig) {
: config.base
}

const headInjectRE = /([ \t]*)<\/head>/i
const headPrependInjectRE = /([ \t]*)<head[^>]*>/i
const headInjectRE = /<\/head>/i
const headPrependInjectRE = /<head[^>]*>/i

const htmlInjectRE = /<\/html>/i
const htmlPrependInjectRE = /([ \t]*)<html[^>]*>/i
const htmlPrependInjectRE = /<html[^>]*>/i

const bodyInjectRE = /([ \t]*)<\/body>/i
const bodyPrependInjectRE = /([ \t]*)<body[^>]*>/i
const bodyInjectRE = /<\/body>/i
const bodyPrependInjectRE = /<body[^>]*>/i

const doctypePrependInjectRE = /<!doctype html>/i

Expand All @@ -1025,32 +1025,43 @@ function injectToHead(
if (prepend) {
// inject as the first element of head
if (headPrependInjectRE.test(html)) {
return html.replace(
headPrependInjectRE,
(match, p1) => `${match}\n${serializeTags(tags, incrementIndent(p1))}`
)
return html.replace(headPrependInjectRE, (match, offset) => {
const indent = getIndent(html, offset)
return `${match}\n${serializeTags(tags, incrementIndent(indent))}`
})
}
} else {
// inject before head close
if (headInjectRE.test(html)) {
// respect indentation of head tag
return html.replace(
headInjectRE,
(match, p1) => `${serializeTags(tags, incrementIndent(p1))}${match}`
)
return html.replace(headInjectRE, (match, offset) => {
const indent = getIndent(html, offset)
return `\n${serializeTags(
tags,
incrementIndent(indent)
)}${indent}${match}`
})
}
// try to inject before the body tag
if (bodyPrependInjectRE.test(html)) {
return html.replace(
bodyPrependInjectRE,
(match, p1) => `${serializeTags(tags, p1)}\n${match}`
)
return html.replace(bodyPrependInjectRE, (match, offset) => {
const indent = getIndent(html, offset)
return `${serializeTags(tags, indent)}\n${match}`
})
}
}
// if no head tag is present, we prepend the tag for both prepend and append
return prependInjectFallback(html, tags)
}

function getIndent(html: string, pos: number) {
let indent = ''
for (; pos >= 0 && (html[pos] === ' ' || html[pos] === '\t'); pos--) {
indent += html[pos]
}
return indent
}

function injectToBody(
html: string,
tags: HtmlTagDescriptor[],
Expand All @@ -1061,26 +1072,29 @@ function injectToBody(
if (prepend) {
// inject after body open
if (bodyPrependInjectRE.test(html)) {
return html.replace(
bodyPrependInjectRE,
(match, p1) => `${match}\n${serializeTags(tags, incrementIndent(p1))}`
)
return html.replace(bodyPrependInjectRE, (match, offset) => {
const indent = getIndent(html, offset)
return `${match}\n${serializeTags(tags, incrementIndent(indent))}`
})
}
// if no there is no body tag, inject after head or fallback to prepend in html
if (headInjectRE.test(html)) {
return html.replace(
headInjectRE,
(match, p1) => `${match}\n${serializeTags(tags, p1)}`
)
return html.replace(headInjectRE, (match, offset) => {
const indent = getIndent(html, offset)
return `${match}\n${serializeTags(tags, indent)}`
})
}
return prependInjectFallback(html, tags)
} else {
// inject before body close
if (bodyInjectRE.test(html)) {
return html.replace(
bodyInjectRE,
(match, p1) => `${serializeTags(tags, incrementIndent(p1))}${match}`
)
return html.replace(bodyInjectRE, (match, offset) => {
const indent = getIndent(match, offset)
return `\n${serializeTags(
tags,
incrementIndent(indent)
)}${indent}${match}`
})
}
// if no body tag is present, append to the html tag, or at the end of the file
if (htmlInjectRE.test(html)) {
Expand Down
1 change: 1 addition & 0 deletions packages/vite/src/node/plugins/importAnalysis.ts
Expand Up @@ -580,6 +580,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {

if (!ssr) {
const url = rawUrl
// eslint-disable-next-line regexp/no-super-linear-move -- `rawUrl` won't be so long
.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '')
.trim()
if (
Expand Down
10 changes: 9 additions & 1 deletion packages/vite/src/node/plugins/importMetaGlob.ts
Expand Up @@ -139,7 +139,7 @@ export async function parseImportGlob(
// tailing comma in object or array will make the parser think it's a comma operation
// we try to parse again removing the comma
try {
const statement = code.slice(start, lastTokenPos).replace(/[,\s]*$/, '')
const statement = trimTrailingComma(code.slice(start, lastTokenPos))
ast = parseExpressionAt(
' '.repeat(start) + statement, // to keep the ast position
start,
Expand Down Expand Up @@ -300,6 +300,14 @@ export async function parseImportGlob(
return (await Promise.all(tasks)).filter(Boolean)
}

const spaceRE = /^\s*$/

function trimTrailingComma(code: string): string {
const pos = code.lastIndexOf(',')
const remove = code.slice(pos + 1)
return spaceRE.test(remove) ? code.slice(0, pos) : code
}

const importPrefix = '__vite_glob_'

const { basename, dirname, relative, join } = posix
Expand Down
1 change: 1 addition & 0 deletions packages/vite/src/node/plugins/workerImportMetaUrl.ts
Expand Up @@ -41,6 +41,7 @@ function getWorkerType(raw: string, clean: string, i: number): WorkerType {
// need to find in comment code
const workerOptString = raw
.substring(commaIndex + 1, endIndex)
// eslint-disable-next-line regexp/no-super-linear-move -- `raw` won't be so long
.replace(/\}[\s\S]*,/g, '}') // strip trailing comma for parsing

const hasViteIgnore = ignoreFlagRE.test(workerOptString)
Expand Down

0 comments on commit 68be9c7

Please sign in to comment.