From d7e406f3a5fa9c1fe7bcbc00fc3eadb0583ced36 Mon Sep 17 00:00:00 2001 From: ShuiRuTian <158983297@qq.com> Date: Sun, 1 Dec 2024 12:40:56 +0800 Subject: [PATCH 1/5] refactor(shared): use instanceof rather than toString and use Map rather than object to be faster and more accurate --- packages/shared/src/domAttrConfig.ts | 17 +++++++++-------- packages/shared/src/general.ts | 28 +++++++++++----------------- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/packages/shared/src/domAttrConfig.ts b/packages/shared/src/domAttrConfig.ts index b5f0166327f..02b426ba12e 100644 --- a/packages/shared/src/domAttrConfig.ts +++ b/packages/shared/src/domAttrConfig.ts @@ -34,17 +34,18 @@ export function includeBooleanAttr(value: unknown): boolean { } const unsafeAttrCharRE = /[>/="'\u0009\u000a\u000c\u0020]/ -const attrValidationCache: Record = {} +const attrValidationCache: Map = new Map() export function isSSRSafeAttrName(name: string): boolean { - if (attrValidationCache.hasOwnProperty(name)) { - return attrValidationCache[name] + if (!attrValidationCache.has(name)) { + const isUnsafe = unsafeAttrCharRE.test(name) + if (isUnsafe) { + console.error(`unsafe attribute name: ${name}`) + } + attrValidationCache.set(name, !isUnsafe) } - const isUnsafe = unsafeAttrCharRE.test(name) - if (isUnsafe) { - console.error(`unsafe attribute name: ${name}`) - } - return (attrValidationCache[name] = !isUnsafe) + + return attrValidationCache.get(name)! } export const propsToAttrMap: Record = { diff --git a/packages/shared/src/general.ts b/packages/shared/src/general.ts index 9c6a2313240..00c127b25d6 100644 --- a/packages/shared/src/general.ts +++ b/packages/shared/src/general.ts @@ -37,15 +37,11 @@ export const hasOwn = ( ): key is keyof typeof val => hasOwnProperty.call(val, key) export const isArray: typeof Array.isArray = Array.isArray -export const isMap = (val: unknown): val is Map => - toTypeString(val) === '[object Map]' -export const isSet = (val: unknown): val is Set => - toTypeString(val) === '[object 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]' +export const isMap = (val: unknown): val is Map => val instanceof Map +export const isSet = (val: unknown): val is Set => val instanceof Set + +export const isDate = (val: unknown): val is Date => val instanceof Date +export const isRegExp = (val: unknown): val is RegExp => val instanceof RegExp export const isFunction = (val: unknown): val is Function => typeof val === 'function' export const isString = (val: unknown): val is string => typeof val === 'string' @@ -54,11 +50,7 @@ export const isObject = (val: unknown): val is Record => val !== null && typeof val === 'object' export const isPromise = (val: unknown): val is Promise => { - return ( - (isObject(val) || isFunction(val)) && - isFunction((val as any).then) && - isFunction((val as any).catch) - ) + return val instanceof Promise } export const objectToString: typeof Object.prototype.toString = @@ -94,10 +86,12 @@ export const isBuiltInDirective: (key: string) => boolean = ) const cacheStringFunction = string>(fn: T): T => { - const cache: Record = Object.create(null) + const cache: Map = new Map() return ((str: string) => { - const hit = cache[str] - return hit || (cache[str] = fn(str)) + if (!cache.has(str)) { + cache.set(str, fn(str)) + } + return cache.get(str)! }) as T } From 1ecaac3fb7f3eef05506e2b2790aac214776f78f Mon Sep 17 00:00:00 2001 From: ShuiRuTian <158983297@qq.com> Date: Sun, 1 Dec 2024 18:09:01 +0800 Subject: [PATCH 2/5] refactor(shared): Use @@toStringTag rather than instance --- packages/shared/src/general.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/shared/src/general.ts b/packages/shared/src/general.ts index 00c127b25d6..8d9d6203baf 100644 --- a/packages/shared/src/general.ts +++ b/packages/shared/src/general.ts @@ -37,11 +37,16 @@ export const hasOwn = ( ): key is keyof typeof val => hasOwnProperty.call(val, key) export const isArray: typeof Array.isArray = Array.isArray -export const isMap = (val: unknown): val is Map => val instanceof Map -export const isSet = (val: unknown): val is Set => val instanceof Set - -export const isDate = (val: unknown): val is Date => val instanceof Date -export const isRegExp = (val: unknown): val is RegExp => val instanceof RegExp +export const isMap = (val: unknown): val is Map => + !!val && (val as any)[Symbol.toStringTag] === 'Map' +export const isSet = (val: unknown): val is 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 => + // refer https://262.ecma-international.org/15.0/index.html?_gl=1*tsu3al*_ga*ODYzNDU4MjA5LjE3MjQ3Nzc5NDY.*_ga_TDCK4DWEPP*MTczMTg0NTY5MS41LjAuMTczMTg0NTY5MS4wLjAuMA..#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' @@ -50,7 +55,7 @@ export const isObject = (val: unknown): val is Record => val !== null && typeof val === 'object' export const isPromise = (val: unknown): val is Promise => { - return val instanceof Promise + return !!val && (val as any)[Symbol.toStringTag] === 'Promise' } export const objectToString: typeof Object.prototype.toString = From ef9bd2d98b7c3ac75fdf32d2e8a4bfd1904b1d84 Mon Sep 17 00:00:00 2001 From: ShuiRuTian <158983297@qq.com> Date: Mon, 2 Dec 2024 00:14:56 +0800 Subject: [PATCH 3/5] refactor(shared): use Map and Set to replace object --- packages/runtime-dom/src/directives/vOn.ts | 109 ++++++++++-------- packages/runtime-dom/src/modules/style.ts | 10 +- .../server-renderer/src/helpers/ssrCompile.ts | 9 +- packages/shared/src/domAttrConfig.ts | 17 +-- packages/shared/src/general.ts | 9 +- packages/shared/src/makeMap.ts | 6 +- packages/vue-compat/src/index.ts | 7 +- packages/vue/src/index.ts | 7 +- 8 files changed, 98 insertions(+), 76 deletions(-) diff --git a/packages/runtime-dom/src/directives/vOn.ts b/packages/runtime-dom/src/directives/vOn.ts index c1a2e182f00..44b7958846d 100644 --- a/packages/runtime-dom/src/directives/vOn.ts +++ b/packages/runtime-dom/src/directives/vOn.ts @@ -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 }, 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. @@ -88,7 +91,7 @@ const keyNames: Record< * @private */ export const withKeys = any>( - fn: T & { _withKeys?: { [k: string]: T } }, + fn: T & { _withKeys?: Map }, modifiers: string[], ): T => { let globalKeyCodes: LegacyConfig['keyCodes'] @@ -110,54 +113,60 @@ export const withKeys = any>( } } - const cache: { [k: string]: T } = fn._withKeys || (fn._withKeys = {}) + const cache: Map = 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 diff --git a/packages/runtime-dom/src/modules/style.ts b/packages/runtime-dom/src/modules/style.ts index 383628a6ad0..380cb766905 100644 --- a/packages/runtime-dom/src/modules/style.ts +++ b/packages/runtime-dom/src/modules/style.ts @@ -103,22 +103,24 @@ function setStyle( } const prefixes = ['Webkit', 'Moz', 'ms'] -const prefixCache: Record = {} +const prefixCache: Map = 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 diff --git a/packages/server-renderer/src/helpers/ssrCompile.ts b/packages/server-renderer/src/helpers/ssrCompile.ts index 8412a65e843..b8223bfd532 100644 --- a/packages/server-renderer/src/helpers/ssrCompile.ts +++ b/packages/server-renderer/src/helpers/ssrCompile.ts @@ -17,7 +17,7 @@ type SSRRenderFunction = ( parentInstance: ComponentInternalInstance, ) => void -const compileCache: Record = Object.create(null) +const compileCache: Map = new Map() export function ssrCompile( template: string, @@ -62,7 +62,7 @@ export function ssrCompile( }, ) - const cached = compileCache[cacheKey] + const cached = compileCache.get(cacheKey) if (cached) { return cached } @@ -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 } diff --git a/packages/shared/src/domAttrConfig.ts b/packages/shared/src/domAttrConfig.ts index 02b426ba12e..249198ae3a0 100644 --- a/packages/shared/src/domAttrConfig.ts +++ b/packages/shared/src/domAttrConfig.ts @@ -37,15 +37,18 @@ const unsafeAttrCharRE = /[>/="'\u0009\u000a\u000c\u0020]/ const attrValidationCache: Map = new Map() export function isSSRSafeAttrName(name: string): boolean { - if (!attrValidationCache.has(name)) { - const isUnsafe = unsafeAttrCharRE.test(name) - if (isUnsafe) { - console.error(`unsafe attribute name: ${name}`) - } - attrValidationCache.set(name, !isUnsafe) + const cached = attrValidationCache.get(name) + if (cached) { + return cached } - return attrValidationCache.get(name)! + const isUnsafe = unsafeAttrCharRE.test(name) + if (isUnsafe) { + console.error(`unsafe attribute name: ${name}`) + } + attrValidationCache.set(name, !isUnsafe) + + return !isUnsafe } export const propsToAttrMap: Record = { diff --git a/packages/shared/src/general.ts b/packages/shared/src/general.ts index 8d9d6203baf..603b27f0c70 100644 --- a/packages/shared/src/general.ts +++ b/packages/shared/src/general.ts @@ -93,10 +93,13 @@ export const isBuiltInDirective: (key: string) => boolean = const cacheStringFunction = string>(fn: T): T => { const cache: Map = new Map() return ((str: string) => { - if (!cache.has(str)) { - cache.set(str, fn(str)) + const cached = cache.get(str) + if (cached) { + return cached } - return cache.get(str)! + const res = fn(str) + cache.set(str, res) + return res }) as T } diff --git a/packages/shared/src/makeMap.ts b/packages/shared/src/makeMap.ts index e85efe21e5b..c91c28c088e 100644 --- a/packages/shared/src/makeMap.ts +++ b/packages/shared/src/makeMap.ts @@ -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) + return val => map.has(val) } diff --git a/packages/vue-compat/src/index.ts b/packages/vue-compat/src/index.ts index d7e9695d824..879b0796ab6 100644 --- a/packages/vue-compat/src/index.ts +++ b/packages/vue-compat/src/index.ts @@ -26,7 +26,7 @@ import { warnDeprecation, } from '../../runtime-core/src/compat/compatConfig' -const compileCache: Record = Object.create(null) +const compileCache: Map = new Map() function compileToFunction( template: string | HTMLElement, @@ -42,7 +42,7 @@ function compileToFunction( } const key = genCacheKey(template, options) - const cached = compileCache[key] + const cached = compileCache.get(key) if (cached) { return cached } @@ -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) diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index 785f3fd4bb4..79cc0788b9e 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -25,7 +25,7 @@ if (__DEV__) { initDev() } -const compileCache: Record = Object.create(null) +const compileCache: Map = new Map() function compileToFunction( template: string | HTMLElement, @@ -41,7 +41,7 @@ function compileToFunction( } const key = genCacheKey(template, options) - const cached = compileCache[key] + const cached = compileCache.get(key) if (cached) { return cached } @@ -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) From c46cd24858698472e7516b4994fd44e1152631dc Mon Sep 17 00:00:00 2001 From: edison Date: Fri, 6 Dec 2024 15:16:13 +0800 Subject: [PATCH 4/5] Apply suggestions from code review --- packages/shared/src/general.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared/src/general.ts b/packages/shared/src/general.ts index 603b27f0c70..c58677c878e 100644 --- a/packages/shared/src/general.ts +++ b/packages/shared/src/general.ts @@ -45,7 +45,7 @@ export const isSet = (val: unknown): val is Set => export const isDate = (val: unknown): val is Date => toTypeString(val) === '[object Date]' export const isRegExp = (val: unknown): val is RegExp => - // refer https://262.ecma-international.org/15.0/index.html?_gl=1*tsu3al*_ga*ODYzNDU4MjA5LjE3MjQ3Nzc5NDY.*_ga_TDCK4DWEPP*MTczMTg0NTY5MS41LjAuMTczMTg0NTY5MS4wLjAuMA..#sec-isregexp + // 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' From b27c5ee772827cc3934d0f836a284ec286d54377 Mon Sep 17 00:00:00 2001 From: ShuiRuTian <158983297@qq.com> Date: Sat, 7 Dec 2024 23:11:52 +0800 Subject: [PATCH 5/5] chore: simplify `makeMap` --- packages/shared/src/makeMap.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/shared/src/makeMap.ts b/packages/shared/src/makeMap.ts index c91c28c088e..b1003b3b90f 100644 --- a/packages/shared/src/makeMap.ts +++ b/packages/shared/src/makeMap.ts @@ -8,7 +8,6 @@ /*! #__NO_SIDE_EFFECTS__ */ export function makeMap(str: string): (key: string) => boolean { - const map = new Set() - for (const key of str.split(',')) map.add(key) + const map = new Set(str.split(',')) return val => map.has(val) }