diff --git a/packages/compiler-core/src/babelUtils.ts b/packages/compiler-core/src/babelUtils.ts index 52fabeea896..102122fbdd8 100644 --- a/packages/compiler-core/src/babelUtils.ts +++ b/packages/compiler-core/src/babelUtils.ts @@ -11,6 +11,7 @@ import type { ObjectProperty, Program, } from '@babel/types' +import { Empty } from '@vue/shared' import { walk } from 'estree-walker' /** @@ -27,7 +28,7 @@ export function walkIdentifiers( ) => void, includeAll = false, parentStack: Node[] = [], - knownIds: Record = Object.create(null), + knownIds: Record = new Empty(), ): void { if (__BROWSER__) { return diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts index b47b6b8d408..4bebbdfb681 100644 --- a/packages/compiler-core/src/transform.ts +++ b/packages/compiler-core/src/transform.ts @@ -22,6 +22,7 @@ import { } from './ast' import { EMPTY_OBJ, + Empty, NOOP, PatchFlags, camelize, @@ -187,7 +188,7 @@ export function createTransformContext( cached: [], constantCache: new WeakMap(), temps: 0, - identifiers: Object.create(null), + identifiers: new Empty(), scopes: { vFor: 0, vSlot: 0, diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index fee05beed96..f859cb1360b 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -11,7 +11,7 @@ import { type SFCScriptBlock, } from './parse' import type { ParserPlugin } from '@babel/parser' -import { generateCodeFrame } from '@vue/shared' +import { Empty, generateCodeFrame } from '@vue/shared' import type { ArrayPattern, CallExpression, @@ -194,8 +194,8 @@ export function compileScript( // metadata that needs to be returned // const ctx.bindingMetadata: BindingMetadata = {} - const scriptBindings: Record = Object.create(null) - const setupBindings: Record = Object.create(null) + const scriptBindings: Record = new Empty() + const setupBindings: Record = new Empty() let defaultExport: Node | undefined let hasAwait = false diff --git a/packages/compiler-sfc/src/script/context.ts b/packages/compiler-sfc/src/script/context.ts index 47b6b442a49..0df0149a182 100644 --- a/packages/compiler-sfc/src/script/context.ts +++ b/packages/compiler-sfc/src/script/context.ts @@ -1,6 +1,6 @@ import type { CallExpression, Node, ObjectPattern, Program } from '@babel/types' import type { SFCDescriptor } from '../parse' -import { generateCodeFrame, isArray } from '@vue/shared' +import { Empty, generateCodeFrame, isArray } from '@vue/shared' import { type ParserPlugin, parse as babelParse } from '@babel/parser' import type { ImportBinding, SFCScriptCompileOptions } from '../compileScript' import type { PropsDestructureBindings } from './defineProps' @@ -28,7 +28,7 @@ export class ScriptCompileContext { // import / type analysis scope?: TypeScope globalScopes?: TypeScope[] - userImports: Record = Object.create(null) + userImports: Record = new Empty() // macros presence check hasDefinePropsCall = false @@ -46,7 +46,7 @@ export class ScriptCompileContext { propsRuntimeDecl: Node | undefined propsTypeDecl: Node | undefined propsDestructureDecl: ObjectPattern | undefined - propsDestructuredBindings: PropsDestructureBindings = Object.create(null) + propsDestructuredBindings: PropsDestructureBindings = new Empty() propsDestructureRestId: string | undefined propsRuntimeDefaults: Node | undefined @@ -56,7 +56,7 @@ export class ScriptCompileContext { emitDecl: Node | undefined // defineModel - modelDecls: Record = Object.create(null) + modelDecls: Record = new Empty() // defineOptions optionsRuntimeDecl: Node | undefined diff --git a/packages/compiler-sfc/src/script/definePropsDestructure.ts b/packages/compiler-sfc/src/script/definePropsDestructure.ts index 27b4d445bbe..e52d9899897 100644 --- a/packages/compiler-sfc/src/script/definePropsDestructure.ts +++ b/packages/compiler-sfc/src/script/definePropsDestructure.ts @@ -19,7 +19,7 @@ import { unwrapTSNode, walkFunctionParams, } from '@vue/compiler-dom' -import { genPropsAccessExp } from '@vue/shared' +import { Empty, genPropsAccessExp } from '@vue/shared' import { isCallOf, resolveObjectKey } from './utils' import type { ScriptCompileContext } from './context' import { DEFINE_PROPS } from './defineProps' @@ -103,12 +103,12 @@ export function transformDestructuredProps( return } - const rootScope: Scope = Object.create(null) + const rootScope: Scope = new Empty() const scopeStack: Scope[] = [rootScope] let currentScope: Scope = rootScope const excludedIds = new WeakSet() const parentStack: Node[] = [] - const propsLocalToPublicMap: Record = Object.create(null) + const propsLocalToPublicMap: Record = new Empty() for (const key in ctx.propsDestructuredBindings) { const { local } = ctx.propsDestructuredBindings[key] diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index 6bb647f11ff..9937f46b39b 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -34,7 +34,7 @@ import { } from './utils' import { type ScriptCompileContext, resolveParserPlugins } from './context' import type { ImportBinding, SFCScriptCompileOptions } from '../compileScript' -import { capitalize, hasOwn } from '@vue/shared' +import { Empty, capitalize, hasOwn } from '@vue/shared' import { parse as babelParse } from '@babel/parser' import { parse } from '../parse' import { createCache } from '../cache' @@ -111,14 +111,14 @@ export class TypeScope { public filename: string, public source: string, public offset: number = 0, - public imports: Record = Object.create(null), - public types: Record = Object.create(null), - public declares: Record = Object.create(null), + public imports: Record = new Empty(), + public types: Record = new Empty(), + public declares: Record = new Empty(), ) {} isGenericScope = false - resolvedImportSources: Record = Object.create(null) - exportedTypes: Record = Object.create(null) - exportedDeclares: Record = Object.create(null) + resolvedImportSources: Record = new Empty() + exportedTypes: Record = new Empty() + exportedDeclares: Record = new Empty() } export interface MaybeWithScope { @@ -231,7 +231,7 @@ function innerResolveTypeElements( resolved.typeParameters && node.typeParameters ) { - typeParams = Object.create(null) + typeParams = new Empty() resolved.typeParameters.params.forEach((p, i) => { let param = typeParameters && typeParameters[p.name] if (!param) param = node.typeParameters!.params[i] @@ -1442,7 +1442,7 @@ function attachNamespace( } export function recordImports(body: Statement[]): Record { - const imports: TypeScope['imports'] = Object.create(null) + const imports: TypeScope['imports'] = new Empty() for (const s of body) { recordImport(s, imports) } diff --git a/packages/compiler-sfc/src/style/pluginScoped.ts b/packages/compiler-sfc/src/style/pluginScoped.ts index b0224cf20d8..bcd595bab68 100644 --- a/packages/compiler-sfc/src/style/pluginScoped.ts +++ b/packages/compiler-sfc/src/style/pluginScoped.ts @@ -7,12 +7,13 @@ import { } from 'postcss' import selectorParser from 'postcss-selector-parser' import { warn } from '../warn' +import { Empty } from '@vue/shared' const animationNameRE = /^(-\w+-)?animation-name$/ const animationRE = /^(-\w+-)?animation$/ const scopedPlugin: PluginCreator = (id = '') => { - const keyframes = Object.create(null) + const keyframes = new Empty() const shortId = id.replace(/^data-v-/, '') return { diff --git a/packages/runtime-core/src/apiCreateApp.ts b/packages/runtime-core/src/apiCreateApp.ts index 3d53716de08..e060e3c3b53 100644 --- a/packages/runtime-core/src/apiCreateApp.ts +++ b/packages/runtime-core/src/apiCreateApp.ts @@ -22,7 +22,7 @@ import { warn } from './warning' import { type VNode, cloneVNode, createVNode } from './vnode' import type { RootHydrateFunction } from './hydration' import { devtoolsInitApp, devtoolsUnmountApp } from './devtools' -import { NO, extend, isFunction, isObject } from '@vue/shared' +import { Empty, NO, extend, isFunction, isObject } from '@vue/shared' import { version } from '.' import { installAppCompatProperties } from './compat/global' import type { NormalizedPropsOptions } from './componentProps' @@ -234,7 +234,7 @@ export function createAppContext(): AppContext { mixins: [], components: {}, directives: {}, - provides: Object.create(null), + provides: new Empty(), optionsCache: new WeakMap(), propsCache: new WeakMap(), emitsCache: new WeakMap(), diff --git a/packages/runtime-core/src/compat/compatConfig.ts b/packages/runtime-core/src/compat/compatConfig.ts index de62873ff02..b9ee388c294 100644 --- a/packages/runtime-core/src/compat/compatConfig.ts +++ b/packages/runtime-core/src/compat/compatConfig.ts @@ -1,4 +1,4 @@ -import { extend, hasOwn, isArray, isFunction } from '@vue/shared' +import { Empty, extend, hasOwn, isArray, isFunction } from '@vue/shared' import { type Component, type ComponentInternalInstance, @@ -425,8 +425,8 @@ export const deprecationData: Record = { }, } -const instanceWarned: Record = Object.create(null) -const warnCount: Record = Object.create(null) +const instanceWarned: Record = new Empty() +const warnCount: Record = new Empty() // test only let warningEnabled = true diff --git a/packages/runtime-core/src/compat/global.ts b/packages/runtime-core/src/compat/global.ts index edc57436a56..6c17a497866 100644 --- a/packages/runtime-core/src/compat/global.ts +++ b/packages/runtime-core/src/compat/global.ts @@ -7,6 +7,7 @@ import { trigger, } from '@vue/reactivity' import { + Empty, NOOP, extend, invokeArrayFns, @@ -259,7 +260,7 @@ export function createCompatVue( mergeBase[key] = isArray(superValue) ? superValue.slice() : isObject(superValue) - ? extend(Object.create(null), superValue) + ? extend(new Empty(), superValue) : superValue } @@ -640,7 +641,7 @@ function defineReactive(obj: any, key: string, val: any) { if (i && obj === i.proxy) { // target is a Vue instance - define on instance.ctx defineReactiveSimple(i.ctx, key, val) - i.accessCache = Object.create(null) + i.accessCache = new Empty() } else if (isReactive(obj)) { obj[key] = val } else { diff --git a/packages/runtime-core/src/compat/instanceEventEmitter.ts b/packages/runtime-core/src/compat/instanceEventEmitter.ts index 2c99e6c53b4..391dd817f9c 100644 --- a/packages/runtime-core/src/compat/instanceEventEmitter.ts +++ b/packages/runtime-core/src/compat/instanceEventEmitter.ts @@ -1,4 +1,4 @@ -import { isArray } from '@vue/shared' +import { Empty, isArray } from '@vue/shared' import type { ComponentInternalInstance } from '../component' import { ErrorCodes, callWithAsyncErrorHandling } from '../errorHandling' import { DeprecationTypes, assertCompatEnabled } from './compatConfig' @@ -18,7 +18,7 @@ export function getRegistry( ): EventRegistry { let events = eventRegistryMap.get(instance) if (!events) { - eventRegistryMap.set(instance, (events = Object.create(null))) + eventRegistryMap.set(instance, (events = new Empty())) } return events! } @@ -69,7 +69,7 @@ export function off( const vm = instance.proxy // all if (!event) { - eventRegistryMap.set(instance, Object.create(null)) + eventRegistryMap.set(instance, new Empty()) return vm } // array of events diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 3ed42ed0b55..fbcaadc020e 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -61,6 +61,7 @@ import { } from './componentEmits' import { EMPTY_OBJ, + Empty, type IfAny, NOOP, ShapeFlags, @@ -847,7 +848,7 @@ function setupStatefulComponent( } } // 0. create render proxy property access cache - instance.accessCache = Object.create(null) + instance.accessCache = new Empty() // 1. create public instance / render proxy instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers) if (__DEV__) { diff --git a/packages/runtime-core/src/componentOptions.ts b/packages/runtime-core/src/componentOptions.ts index f864f39e419..713ec7e1cfe 100644 --- a/packages/runtime-core/src/componentOptions.ts +++ b/packages/runtime-core/src/componentOptions.ts @@ -9,6 +9,7 @@ import { currentInstance, } from './component' import { + Empty, type LooseRequired, NOOP, type Prettify, @@ -503,7 +504,7 @@ enum OptionTypes { } function createDuplicateChecker() { - const cache = Object.create(null) + const cache = new Empty() return (type: OptionTypes, key: string) => { if (cache[key]) { warn(`${type} property "${key}" is already defined in ${cache[key]}.`) @@ -1079,7 +1080,7 @@ function mergeAsArray(to: T[] | T | undefined, from: T | T[]) { } function mergeObjectOptions(to: Object | undefined, from: Object | undefined) { - return to ? extend(Object.create(null), to, from) : from + return to ? extend(new Empty(), to, from) : from } function mergeEmitsOrPropsOptions( @@ -1099,7 +1100,7 @@ function mergeEmitsOrPropsOptions( return [...new Set([...to, ...from])] } return extend( - Object.create(null), + new Empty(), normalizePropsOrEmits(to), normalizePropsOrEmits(from ?? {}), ) @@ -1114,7 +1115,7 @@ function mergeWatchOptions( ) { if (!to) return from if (!from) return to - const merged = extend(Object.create(null), to) + const merged = extend(new Empty(), to) for (const key in from) { merged[key] = mergeAsArray(to[key], from[key]) } diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index 8baa7808665..4170c65ad19 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -8,6 +8,7 @@ import { import { EMPTY_ARR, EMPTY_OBJ, + Empty, type IfAny, PatchFlags, camelize, @@ -197,7 +198,7 @@ export function initProps( const props: Data = {} const attrs: Data = createInternalObject() - instance.propsDefaults = Object.create(null) + instance.propsDefaults = new Empty() setFullProps(instance, rawProps, props, attrs) diff --git a/packages/runtime-core/src/componentPublicInstance.ts b/packages/runtime-core/src/componentPublicInstance.ts index e9e7770ebd9..1cc74d133be 100644 --- a/packages/runtime-core/src/componentPublicInstance.ts +++ b/packages/runtime-core/src/componentPublicInstance.ts @@ -14,6 +14,7 @@ import { } from './apiWatch' import { EMPTY_OBJ, + Empty, type IfAny, NOOP, type Prettify, @@ -365,7 +366,7 @@ const getPublicInstance = ( export const publicPropertiesMap: PublicPropertiesMap = // Move PURE marker to new line to workaround compiler discarding it // due to type annotation - /*@__PURE__*/ extend(Object.create(null), { + /*@__PURE__*/ extend(new Empty(), { $: i => i, $el: i => i.vnode.el, $data: i => i.data, diff --git a/packages/runtime-core/src/components/BaseTransition.ts b/packages/runtime-core/src/components/BaseTransition.ts index 6ce06d28239..81f29787ef9 100644 --- a/packages/runtime-core/src/components/BaseTransition.ts +++ b/packages/runtime-core/src/components/BaseTransition.ts @@ -16,7 +16,7 @@ import { warn } from '../warning' import { isKeepAlive } from './KeepAlive' import { toRaw } from '@vue/reactivity' import { ErrorCodes, callWithAsyncErrorHandling } from '../errorHandling' -import { PatchFlags, ShapeFlags, isArray, isFunction } from '@vue/shared' +import { Empty, PatchFlags, ShapeFlags, isArray, isFunction } from '@vue/shared' import { onBeforeUnmount, onMounted } from '../apiLifecycle' import { isTeleport } from './Teleport' import type { RendererElement } from '../renderer' @@ -303,7 +303,7 @@ function getLeavingNodesForType( const { leavingVNodes } = state let leavingVNodesCache = leavingVNodes.get(vnode.type)! if (!leavingVNodesCache) { - leavingVNodesCache = Object.create(null) + leavingVNodesCache = new Empty() leavingVNodes.set(vnode.type, leavingVNodesCache) } return leavingVNodesCache diff --git a/packages/runtime-dom/src/apiCustomElement.ts b/packages/runtime-dom/src/apiCustomElement.ts index 6ddaf897130..6c7717a1424 100644 --- a/packages/runtime-dom/src/apiCustomElement.ts +++ b/packages/runtime-dom/src/apiCustomElement.ts @@ -33,6 +33,7 @@ import { warn, } from '@vue/runtime-core' import { + Empty, camelize, extend, hasOwn, @@ -373,9 +374,7 @@ export class VueElement if (key in this._props) { this._props[key] = toNumber(this._props[key]) } - ;(numberProps || (numberProps = Object.create(null)))[ - camelize(key) - ] = true + ;(numberProps || (numberProps = new Empty()))[camelize(key)] = true } } } diff --git a/packages/server-renderer/src/helpers/ssrCompile.ts b/packages/server-renderer/src/helpers/ssrCompile.ts index 8412a65e843..6674f62398a 100644 --- a/packages/server-renderer/src/helpers/ssrCompile.ts +++ b/packages/server-renderer/src/helpers/ssrCompile.ts @@ -4,7 +4,7 @@ import { warn, } from 'vue' import { compile } from '@vue/compiler-ssr' -import { NO, extend, generateCodeFrame, isFunction } from '@vue/shared' +import { Empty, NO, extend, generateCodeFrame, isFunction } from '@vue/shared' import type { CompilerError, CompilerOptions } from '@vue/compiler-core' import type { PushFn } from '../render' @@ -17,7 +17,7 @@ type SSRRenderFunction = ( parentInstance: ComponentInternalInstance, ) => void -const compileCache: Record = Object.create(null) +const compileCache: Record = new Empty() export function ssrCompile( template: string, diff --git a/packages/shared/__tests__/toDisplayString.spec.ts b/packages/shared/__tests__/toDisplayString.spec.ts index cd8db0b4726..457d0dddbac 100644 --- a/packages/shared/__tests__/toDisplayString.spec.ts +++ b/packages/shared/__tests__/toDisplayString.spec.ts @@ -2,7 +2,7 @@ * @vitest-environment jsdom */ import { computed, ref } from '@vue/reactivity' -import { toDisplayString } from '../src' +import { Empty, toDisplayString } from '../src' describe('toDisplayString', () => { test('nullish values', () => { @@ -59,7 +59,7 @@ describe('toDisplayString', () => { ) // object created from null does not have .toString in its prototype - const nullObjectWithoutToString = Object.create(null) + const nullObjectWithoutToString = new Empty() nullObjectWithoutToString.bar = 1 expect(toDisplayString(nullObjectWithoutToString)).toBe( `{ diff --git a/packages/shared/src/general.ts b/packages/shared/src/general.ts index 9c6a2313240..e041014a977 100644 --- a/packages/shared/src/general.ts +++ b/packages/shared/src/general.ts @@ -92,9 +92,11 @@ export const isBuiltInDirective: (key: string) => boolean = /*@__PURE__*/ makeMap( 'bind,cloak,else-if,else,for,html,if,model,on,once,pre,show,slot,text,memo', ) +export const Empty: any = function () {} +Empty.prototype = Object.create(null) const cacheStringFunction = string>(fn: T): T => { - const cache: Record = Object.create(null) + const cache: Record = new Empty() return ((str: string) => { const hit = cache[str] return hit || (cache[str] = fn(str)) diff --git a/packages/shared/src/makeMap.ts b/packages/shared/src/makeMap.ts index e85efe21e5b..d64313a4a8c 100644 --- a/packages/shared/src/makeMap.ts +++ b/packages/shared/src/makeMap.ts @@ -7,8 +7,11 @@ */ /*! #__NO_SIDE_EFFECTS__ */ + +import { Empty } from './general' + export function makeMap(str: string): (key: string) => boolean { - const map = Object.create(null) + const map = new Empty() for (const key of str.split(',')) map[key] = 1 return val => val in map } diff --git a/packages/vue-compat/src/index.ts b/packages/vue-compat/src/index.ts index d7e9695d824..41ca4b24a80 100644 --- a/packages/vue-compat/src/index.ts +++ b/packages/vue-compat/src/index.ts @@ -13,6 +13,7 @@ import { warn, } from '@vue/runtime-dom' import { + Empty, NOOP, extend, genCacheKey, @@ -26,7 +27,7 @@ import { warnDeprecation, } from '../../runtime-core/src/compat/compatConfig' -const compileCache: Record = Object.create(null) +const compileCache: Record = new Empty() function compileToFunction( template: string | HTMLElement, diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index 785f3fd4bb4..79344beef8f 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -13,6 +13,7 @@ import { } from '@vue/runtime-dom' import * as runtimeDom from '@vue/runtime-dom' import { + Empty, NOOP, extend, genCacheKey, @@ -25,7 +26,7 @@ if (__DEV__) { initDev() } -const compileCache: Record = Object.create(null) +const compileCache: Record = new Empty() function compileToFunction( template: string | HTMLElement,