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: Use @@toStringTag and Map for better perf #12491

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 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
109 changes: 59 additions & 50 deletions packages/runtime-dom/src/directives/vOn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,21 +52,24 @@ const modifierGuards: Record<
export const withModifiers = <
T extends (event: Event, ...args: unknown[]) => any,
>(
fn: T & { _withMods?: { [key: string]: T } },
fn: T & { _withMods?: Map<string, T> },
modifiers: VOnModifiers[],
): T => {
const cache = fn._withMods || (fn._withMods = {})
const cache = fn._withMods || (fn._withMods = new Map())
const cacheKey = modifiers.join('.')
return (
cache[cacheKey] ||
(cache[cacheKey] = ((event, ...args) => {
for (let i = 0; i < modifiers.length; i++) {
const guard = modifierGuards[modifiers[i] as ModifierGuards]
if (guard && guard(event, modifiers)) return
}
return fn(event, ...args)
}) as T)
)
const cached = cache.get(cacheKey)
if (cached) {
return cached
}
const modifier = ((event, ...args) => {
for (let i = 0; i < modifiers.length; i++) {
const guard = modifierGuards[modifiers[i] as ModifierGuards]
if (guard && guard(event, modifiers)) return
}
return fn(event, ...args)
}) as T
cache.set(cacheKey, modifier)
return modifier
}

// Kept for 2.x compat.
Expand All @@ -88,7 +91,7 @@ const keyNames: Record<
* @private
*/
export const withKeys = <T extends (event: KeyboardEvent) => any>(
fn: T & { _withKeys?: { [k: string]: T } },
fn: T & { _withKeys?: Map<string, T> },
modifiers: string[],
): T => {
let globalKeyCodes: LegacyConfig['keyCodes']
Expand All @@ -110,54 +113,60 @@ export const withKeys = <T extends (event: KeyboardEvent) => any>(
}
}

const cache: { [k: string]: T } = fn._withKeys || (fn._withKeys = {})
const cache: Map<string, T> = fn._withKeys || (fn._withKeys = new Map())
const cacheKey = modifiers.join('.')

return (
cache[cacheKey] ||
(cache[cacheKey] = (event => {
if (!('key' in event)) {
return
}
const cached = cache.get(cacheKey)
if (cached) {
return cached
}

const eventKey = hyphenate(event.key)
const withKey = (event => {
if (!('key' in event)) {
return
}

const eventKey = hyphenate(event.key)
if (
modifiers.some(
k =>
k === eventKey ||
keyNames[k as unknown as CompatModifiers] === eventKey,
)
) {
return fn(event)
}

if (__COMPAT__) {
const keyCode = String(event.keyCode)
if (
modifiers.some(
k =>
k === eventKey ||
keyNames[k as unknown as CompatModifiers] === eventKey,
)
compatUtils.isCompatEnabled(
DeprecationTypes.V_ON_KEYCODE_MODIFIER,
instance,
) &&
modifiers.some(mod => mod == keyCode)
) {
return fn(event)
}

if (__COMPAT__) {
const keyCode = String(event.keyCode)
if (
compatUtils.isCompatEnabled(
DeprecationTypes.V_ON_KEYCODE_MODIFIER,
instance,
) &&
modifiers.some(mod => mod == keyCode)
) {
return fn(event)
}
if (globalKeyCodes) {
for (const mod of modifiers) {
const codes = globalKeyCodes[mod]
if (codes) {
const matches = isArray(codes)
? codes.some(code => String(code) === keyCode)
: String(codes) === keyCode
if (matches) {
return fn(event)
}
if (globalKeyCodes) {
for (const mod of modifiers) {
const codes = globalKeyCodes[mod]
if (codes) {
const matches = isArray(codes)
? codes.some(code => String(code) === keyCode)
: String(codes) === keyCode
if (matches) {
return fn(event)
}
}
}
}
}) as T)
)
}
}) as T

cache.set(cacheKey, withKey)

return withKey
}

export type VOnDirective = Directive<any, any, VOnModifiers>
10 changes: 6 additions & 4 deletions packages/runtime-dom/src/modules/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,22 +103,24 @@ function setStyle(
}

const prefixes = ['Webkit', 'Moz', 'ms']
const prefixCache: Record<string, string> = {}
const prefixCache: Map<string, string> = new Map()

function autoPrefix(style: CSSStyleDeclaration, rawName: string): string {
const cached = prefixCache[rawName]
const cached = prefixCache.get(rawName)
if (cached) {
return cached
}
let name = camelize(rawName)
if (name !== 'filter' && name in style) {
return (prefixCache[rawName] = name)
prefixCache.set(rawName, name)
return name
}
name = capitalize(name)
for (let i = 0; i < prefixes.length; i++) {
const prefixed = prefixes[i] + name
if (prefixed in style) {
return (prefixCache[rawName] = prefixed)
prefixCache.set(rawName, prefixed)
return prefixed
}
}
return rawName
Expand Down
9 changes: 6 additions & 3 deletions packages/server-renderer/src/helpers/ssrCompile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type SSRRenderFunction = (
parentInstance: ComponentInternalInstance,
) => void

const compileCache: Record<string, SSRRenderFunction> = Object.create(null)
const compileCache: Map<string, SSRRenderFunction> = new Map()

export function ssrCompile(
template: string,
Expand Down Expand Up @@ -62,7 +62,7 @@ export function ssrCompile(
},
)

const cached = compileCache[cacheKey]
const cached = compileCache.get(cacheKey)
if (cached) {
return cached
}
Expand All @@ -89,5 +89,8 @@ export function ssrCompile(
'vue/server-renderer': helpers,
}
const fakeRequire = (id: 'vue' | 'vue/server-renderer') => requireMap[id]
return (compileCache[cacheKey] = Function('require', code)(fakeRequire))

const ssrRenderFunction = Function('require', code)(fakeRequire)
compileCache.set(cacheKey, ssrRenderFunction)
return ssrRenderFunction
}
12 changes: 8 additions & 4 deletions packages/shared/src/domAttrConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,21 @@ export function includeBooleanAttr(value: unknown): boolean {
}

const unsafeAttrCharRE = /[>/="'\u0009\u000a\u000c\u0020]/
const attrValidationCache: Record<string, boolean> = {}
const attrValidationCache: Map<string, boolean> = new Map()

export function isSSRSafeAttrName(name: string): boolean {
if (attrValidationCache.hasOwnProperty(name)) {
return attrValidationCache[name]
const cached = attrValidationCache.get(name)
if (cached) {
return cached
}

const isUnsafe = unsafeAttrCharRE.test(name)
if (isUnsafe) {
console.error(`unsafe attribute name: ${name}`)
}
return (attrValidationCache[name] = !isUnsafe)
attrValidationCache.set(name, !isUnsafe)

return !isUnsafe
}

export const propsToAttrMap: Record<string, string | undefined> = {
Expand Down
24 changes: 13 additions & 11 deletions packages/shared/src/general.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,15 @@ export const hasOwn = (

export const isArray: typeof Array.isArray = Array.isArray
export const isMap = (val: unknown): val is Map<any, any> =>
toTypeString(val) === '[object Map]'
!!val && (val as any)[Symbol.toStringTag] === 'Map'
export const isSet = (val: unknown): val is Set<any> =>
toTypeString(val) === '[object Set]'
!!val && (val as any)[Symbol.toStringTag] === 'Set'

export const isDate = (val: unknown): val is Date =>
toTypeString(val) === '[object Date]'
export const isRegExp = (val: unknown): val is RegExp =>
toTypeString(val) === '[object RegExp]'
// refer https://262.ecma-international.org/15.0/index.html#sec-isregexp
!!val && !!(val as any)[Symbol.match]
export const isFunction = (val: unknown): val is Function =>
typeof val === 'function'
export const isString = (val: unknown): val is string => typeof val === 'string'
Expand All @@ -54,11 +55,7 @@ export const isObject = (val: unknown): val is Record<any, any> =>
val !== null && typeof val === 'object'

export const isPromise = <T = any>(val: unknown): val is Promise<T> => {
return (
(isObject(val) || isFunction(val)) &&
isFunction((val as any).then) &&
isFunction((val as any).catch)
)
return !!val && (val as any)[Symbol.toStringTag] === 'Promise'
}

export const objectToString: typeof Object.prototype.toString =
Expand Down Expand Up @@ -94,10 +91,15 @@ export const isBuiltInDirective: (key: string) => boolean =
)

const cacheStringFunction = <T extends (str: string) => string>(fn: T): T => {
const cache: Record<string, string> = Object.create(null)
const cache: Map<string, string> = new Map()
return ((str: string) => {
const hit = cache[str]
return hit || (cache[str] = fn(str))
const cached = cache.get(str)
if (cached) {
return cached
}
const res = fn(str)
cache.set(str, res)
return res
}) as T
}

Expand Down
6 changes: 3 additions & 3 deletions packages/shared/src/makeMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

/*! #__NO_SIDE_EFFECTS__ */
export function makeMap(str: string): (key: string) => boolean {
const map = Object.create(null)
for (const key of str.split(',')) map[key] = 1
return val => val in map
const map = new Set()
for (const key of str.split(',')) map.add(key)
ShuiRuTian marked this conversation as resolved.
Show resolved Hide resolved
return val => map.has(val)
}
7 changes: 4 additions & 3 deletions packages/vue-compat/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
warnDeprecation,
} from '../../runtime-core/src/compat/compatConfig'

const compileCache: Record<string, RenderFunction> = Object.create(null)
const compileCache: Map<string, RenderFunction> = new Map()

function compileToFunction(
template: string | HTMLElement,
Expand All @@ -42,7 +42,7 @@ function compileToFunction(
}

const key = genCacheKey(template, options)
const cached = compileCache[key]
const cached = compileCache.get(key)
if (cached) {
return cached
}
Expand Down Expand Up @@ -101,7 +101,8 @@ function compileToFunction(
// mark the function as runtime compiled
;(render as InternalRenderFunction)._rc = true

return (compileCache[key] = render)
compileCache.set(key, render)
return render
}

registerRuntimeCompiler(compileToFunction)
Expand Down
7 changes: 4 additions & 3 deletions packages/vue/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ if (__DEV__) {
initDev()
}

const compileCache: Record<string, RenderFunction> = Object.create(null)
const compileCache: Map<string, RenderFunction> = new Map()

function compileToFunction(
template: string | HTMLElement,
Expand All @@ -41,7 +41,7 @@ function compileToFunction(
}

const key = genCacheKey(template, options)
const cached = compileCache[key]
const cached = compileCache.get(key)
if (cached) {
return cached
}
Expand Down Expand Up @@ -98,7 +98,8 @@ function compileToFunction(
// mark the function as runtime compiled
;(render as InternalRenderFunction)._rc = true

return (compileCache[key] = render)
compileCache.set(key, render)
return render
}

registerRuntimeCompiler(compileToFunction)
Expand Down
Loading