Sfoglia il codice sorgente

refactor(runtime-vapor): move metadata from instance to node

三咲智子 Kevin Deng 2 anni fa
parent
commit
26f29b2529

+ 1 - 2
packages/compiler-vapor/src/generate.ts

@@ -128,8 +128,7 @@ export function generate(
   }
 
   // TODO source map?
-  const templates = ir.template.length ? genTemplates(ir.template, context) : ''
-
+  const templates = genTemplates(ir.template, context)
   const imports = genHelperImports(context)
   const preamble = imports + templates
 

+ 4 - 16
packages/runtime-vapor/__tests__/dom/patchProp.spec.ts

@@ -1,7 +1,6 @@
 import { NOOP } from '@vue/shared'
 import {
   setDynamicProp as _setDynamicProp,
-  recordPropMetadata,
   setAttr,
   setClass,
   setDOMProp,
@@ -12,10 +11,9 @@ import {
 } from '../../src'
 import {
   createComponentInstance,
-  currentInstance,
-  getCurrentInstance,
   setCurrentInstance,
 } from '../../src/component'
+import { getMetadata, recordPropMetadata } from '../../src/metadata'
 
 let removeComponentInstance = NOOP
 beforeEach(() => {
@@ -44,7 +42,7 @@ describe('patchProp', () => {
       prev = recordPropMetadata(node, 'style', 'color: blue')
       expect(prev).toBe('color: red')
 
-      expect(getCurrentInstance()?.metadata.get(node)).toEqual({
+      expect(getMetadata(node)).toEqual({
         props: { class: 'bar', style: 'color: blue' },
       })
     })
@@ -54,23 +52,13 @@ describe('patchProp', () => {
       const node2 = {} as Node
       recordPropMetadata(node1, 'class', 'foo')
       recordPropMetadata(node2, 'class', 'bar')
-      expect(getCurrentInstance()?.metadata.get(node1)).toEqual({
+      expect(getMetadata(node1)).toEqual({
         props: { class: 'foo' },
       })
-      expect(getCurrentInstance()?.metadata.get(node2)).toEqual({
+      expect(getMetadata(node2)).toEqual({
         props: { class: 'bar' },
       })
     })
-
-    test('should not record prop metadata outside of component', () => {
-      removeComponentInstance()
-      expect(currentInstance).toBeNull()
-
-      // FIXME
-      expect(() => recordPropMetadata({} as Node, 'class', 'foo')).toThrowError(
-        'cannot be used out of component',
-      )
-    })
   })
 
   describe('setClass', () => {

+ 0 - 6
packages/runtime-vapor/src/component.ts

@@ -34,10 +34,6 @@ export interface ObjectComponent {
 
 type LifecycleHook<TFn = Function> = TFn[] | null
 
-export interface ElementMetadata {
-  props: Data
-}
-
 export interface ComponentInternalInstance {
   uid: number
   container: ParentNode
@@ -61,7 +57,6 @@ export interface ComponentInternalInstance {
   emit: EmitFn
   emitted: Record<string, boolean> | null
   refs: Data
-  metadata: WeakMap<Node, ElementMetadata>
 
   vapor: true
 
@@ -181,7 +176,6 @@ export const createComponentInstance = (
     attrs: EMPTY_OBJ,
     setupState: EMPTY_OBJ,
     refs: EMPTY_OBJ,
-    metadata: new WeakMap(),
     vapor: true,
 
     dirs: new Map(),

+ 37 - 59
packages/runtime-vapor/src/directives/vModel.ts

@@ -6,7 +6,6 @@ import {
   looseIndexOf,
   looseToNumber,
 } from '@vue/shared'
-import type { ComponentInternalInstance } from '../component'
 import type {
   DirectiveBinding,
   DirectiveHook,
@@ -16,13 +15,11 @@ import type {
 import { addEventListener } from '../dom/event'
 import { nextTick } from '../scheduler'
 import { warn } from '../warning'
+import { getMetadata } from '../metadata'
 
 type AssignerFn = (value: any) => void
-function getModelAssigner(
-  el: Element,
-  instance: ComponentInternalInstance,
-): AssignerFn {
-  const metadata = instance.metadata.get(el)!
+function getModelAssigner(el: Element): AssignerFn {
+  const metadata = getMetadata(el)
   const fn: any = metadata.props['onUpdate:modelValue']
   return isArray(fn) ? value => invokeArrayFns(fn, value) : fn
 }
@@ -49,8 +46,8 @@ export const vModelText: ObjectDirective<
   any,
   'lazy' | 'trim' | 'number'
 > = {
-  beforeMount(el, { instance, modifiers: { lazy, trim, number } = {} }) {
-    const assigner = getModelAssigner(el, instance)
+  beforeMount(el, { modifiers: { lazy, trim, number } = {} }) {
+    const assigner = getModelAssigner(el)
     assignFnMap.set(el, assigner)
 
     const castToNumber = number // || (vnode.props && vnode.props.type === 'number')
@@ -84,11 +81,8 @@ export const vModelText: ObjectDirective<
   mounted(el, { value }) {
     el.value = value == null ? '' : value
   },
-  beforeUpdate(
-    el,
-    { instance, value, modifiers: { lazy, trim, number } = {} },
-  ) {
-    assignFnMap.set(el, getModelAssigner(el, instance))
+  beforeUpdate(el, { value, modifiers: { lazy, trim, number } = {} }) {
+    assignFnMap.set(el, getModelAssigner(el))
 
     // avoid clearing unresolved text. #2302
     if ((el as any).composing) return
@@ -116,17 +110,17 @@ export const vModelText: ObjectDirective<
 }
 
 export const vModelRadio: ObjectDirective<HTMLInputElement> = {
-  beforeMount(el, { value, instance }) {
-    el.checked = looseEqual(value, getValue(el, instance))
-    assignFnMap.set(el, getModelAssigner(el, instance))
+  beforeMount(el, { value }) {
+    el.checked = looseEqual(value, getValue(el))
+    assignFnMap.set(el, getModelAssigner(el))
     addEventListener(el, 'change', () => {
-      assignFnMap.get(el)!(getValue(el, instance))
+      assignFnMap.get(el)!(getValue(el))
     })
   },
-  beforeUpdate(el, { value, oldValue, instance }) {
-    assignFnMap.set(el, getModelAssigner(el, instance))
+  beforeUpdate(el, { value, oldValue }) {
+    assignFnMap.set(el, getModelAssigner(el))
     if (value !== oldValue) {
-      el.checked = looseEqual(value, getValue(el, instance))
+      el.checked = looseEqual(value, getValue(el))
     }
   },
 }
@@ -134,13 +128,13 @@ export const vModelRadio: ObjectDirective<HTMLInputElement> = {
 export const vModelSelect: ObjectDirective<HTMLSelectElement, any, 'number'> = {
   // <select multiple> value need to be deep traversed
   deep: true,
-  beforeMount(el, { value, instance, modifiers: { number = false } = {} }) {
+  beforeMount(el, { value, modifiers: { number = false } = {} }) {
     const isSetModel = isSet(value)
     addEventListener(el, 'change', () => {
       const selectedVal = Array.prototype.filter
         .call(el.options, (o: HTMLOptionElement) => o.selected)
         .map((o: HTMLOptionElement) =>
-          number ? looseToNumber(getValue(o, instance)) : getValue(o, instance),
+          number ? looseToNumber(getValue(o)) : getValue(o),
         )
       assignFnMap.get(el)!(
         el.multiple
@@ -155,28 +149,20 @@ export const vModelSelect: ObjectDirective<HTMLSelectElement, any, 'number'> = {
         assigningMap.set(el, false)
       })
     })
-    assignFnMap.set(el, getModelAssigner(el, instance))
-    setSelected(el, instance, value, number)
+    assignFnMap.set(el, getModelAssigner(el))
+    setSelected(el, value, number)
   },
-  beforeUpdate(el, { instance }) {
-    assignFnMap.set(el, getModelAssigner(el, instance))
+  beforeUpdate(el) {
+    assignFnMap.set(el, getModelAssigner(el))
   },
-  updated(
-    el,
-    { value, oldValue, instance, modifiers: { number = false } = {} },
-  ) {
+  updated(el, { value, modifiers: { number = false } = {} }) {
     if (!assigningMap.get(el)) {
-      setSelected(el, instance, value, number)
+      setSelected(el, value, number)
     }
   },
 }
 
-function setSelected(
-  el: HTMLSelectElement,
-  instance: ComponentInternalInstance,
-  value: any,
-  number: boolean,
-) {
+function setSelected(el: HTMLSelectElement, value: any, number: boolean) {
   const isMultiple = el.multiple
   const isArrayValue = isArray(value)
   if (isMultiple && !isArrayValue && !isSet(value)) {
@@ -190,7 +176,7 @@ function setSelected(
 
   for (let i = 0, l = el.options.length; i < l; i++) {
     const option = el.options[i]
-    const optionValue = getValue(option, instance)
+    const optionValue = getValue(option)
     if (isMultiple) {
       if (isArrayValue) {
         const optionType = typeof optionValue
@@ -206,7 +192,7 @@ function setSelected(
         option.selected = value.has(optionValue)
       }
     } else {
-      if (looseEqual(getValue(option, instance), value)) {
+      if (looseEqual(getValue(option), value)) {
         if (el.selectedIndex !== i) el.selectedIndex = i
         return
       }
@@ -218,21 +204,14 @@ function setSelected(
 }
 
 // retrieve raw value set via :value bindings
-function getValue(
-  el: HTMLOptionElement | HTMLInputElement,
-  instance: ComponentInternalInstance,
-) {
-  const metadata = instance.metadata.get(el)
+function getValue(el: HTMLOptionElement | HTMLInputElement) {
+  const metadata = getMetadata(el)
   return (metadata && metadata.props.value) || el.value
 }
 
 // retrieve raw value for true-value and false-value set via :true-value or :false-value bindings
-function getCheckboxValue(
-  el: HTMLInputElement,
-  instance: ComponentInternalInstance,
-  checked: boolean,
-) {
-  const metadata = instance.metadata.get(el)
+function getCheckboxValue(el: HTMLInputElement, checked: boolean) {
+  const metadata = getMetadata(el)
   const props = metadata && metadata.props
   const key = checked ? 'true-value' : 'false-value'
   if (props && key in props) {
@@ -246,14 +225,14 @@ function getCheckboxValue(
 
 const setChecked: DirectiveHook<HTMLInputElement> = (
   el,
-  { value, oldValue, instance },
+  { value, oldValue },
 ) => {
   if (isArray(value)) {
-    el.checked = looseIndexOf(value, getValue(el, instance)) > -1
+    el.checked = looseIndexOf(value, getValue(el)) > -1
   } else if (isSet(value)) {
-    el.checked = value.has(getValue(el, instance))
+    el.checked = value.has(getValue(el))
   } else if (value !== oldValue) {
-    el.checked = looseEqual(value, getCheckboxValue(el, instance, true))
+    el.checked = looseEqual(value, getCheckboxValue(el, true))
   }
 }
 
@@ -261,12 +240,11 @@ export const vModelCheckbox: ObjectDirective<HTMLInputElement> = {
   // #4096 array checkboxes need to be deep traversed
   deep: true,
   beforeMount(el, binding) {
-    const { instance } = binding
-    assignFnMap.set(el, getModelAssigner(el, binding.instance))
+    assignFnMap.set(el, getModelAssigner(el))
 
     addEventListener(el, 'change', () => {
       const modelValue = binding.value
-      const elementValue = getValue(el, instance)
+      const elementValue = getValue(el)
       const checked = el.checked
       const assigner = assignFnMap.get(el)!
       if (isArray(modelValue)) {
@@ -288,14 +266,14 @@ export const vModelCheckbox: ObjectDirective<HTMLInputElement> = {
         }
         assigner(cloned)
       } else {
-        assigner(getCheckboxValue(el, instance, checked))
+        assigner(getCheckboxValue(el, checked))
       }
     })
   },
   // set initial checked on mount to wait for true-value/false-value
   mounted: setChecked,
   beforeUpdate(el, binding) {
-    assignFnMap.set(el, getModelAssigner(el, binding.instance))
+    assignFnMap.set(el, getModelAssigner(el))
     setChecked(el, binding)
   },
 }

+ 1 - 1
packages/runtime-vapor/src/dom/event.ts

@@ -4,7 +4,7 @@ import {
   onEffectCleanup,
   onScopeDispose,
 } from '@vue/reactivity'
-import { recordPropMetadata } from './prop'
+import { recordPropMetadata } from '../metadata'
 import { toHandlerKey } from '@vue/shared'
 import { withKeys, withModifiers } from '@vue/runtime-dom'
 

+ 1 - 24
packages/runtime-vapor/src/dom/prop.ts

@@ -9,32 +9,9 @@ import {
   normalizeStyle,
   toDisplayString,
 } from '@vue/shared'
-import { type ElementMetadata, currentInstance } from '../component'
 import { warn } from '../warning'
 import { setStyle } from './style'
-
-function getMetadata(el: Node): ElementMetadata {
-  const EMPTY_METADATA = { props: {} }
-
-  if (!currentInstance) {
-    // TODO implement error handling
-    if (__DEV__) throw new Error('cannot be used out of component')
-    return EMPTY_METADATA
-  }
-
-  let metadata = currentInstance.metadata.get(el)
-  if (!metadata) {
-    currentInstance.metadata.set(el, (metadata = EMPTY_METADATA))
-  }
-  return metadata
-}
-
-export function recordPropMetadata(el: Node, key: string, value: any): any {
-  const metadata = getMetadata(el)
-  const prev = metadata.props[key]
-  metadata.props[key] = value
-  return prev
-}
+import { getMetadata, recordPropMetadata } from '../metadata'
 
 export function setClass(el: Element, value: any) {
   const prev = recordPropMetadata(el, 'class', (value = normalizeClass(value)))

+ 1 - 1
packages/runtime-vapor/src/dom/style.ts

@@ -7,7 +7,7 @@ import {
   normalizeStyle,
 } from '@vue/shared'
 import { warn } from '../warning'
-import { recordPropMetadata } from './prop'
+import { recordPropMetadata } from '../metadata'
 
 export function setStyle(el: HTMLElement, value: any) {
   const prev = recordPropMetadata(el, 'style', (value = normalizeStyle(value)))

+ 18 - 0
packages/runtime-vapor/src/metadata.ts

@@ -0,0 +1,18 @@
+import type { Data } from '@vue/shared'
+
+export interface ElementMetadata {
+  props: Data
+}
+
+export function getMetadata(
+  el: Node & { $$metadata?: ElementMetadata },
+): ElementMetadata {
+  return el.$$metadata || (el.$$metadata = { props: {} })
+}
+
+export function recordPropMetadata(el: Node, key: string, value: any): any {
+  const metadata = getMetadata(el)
+  const prev = metadata.props[key]
+  metadata.props[key] = value
+  return prev
+}

+ 1 - 1
playground/src/bench/profiling.ts

@@ -27,7 +27,6 @@ export const wrap = (
     fn(...args)
     await defer()
     const time = performance.now() - start
-    doProfile && console.profileEnd(id)
     const prevTimes = times[id] || (times[id] = [])
     prevTimes.push(time)
     const median = prevTimes.slice().sort((a, b) => a - b)[
@@ -42,6 +41,7 @@ export const wrap = (
       `time: ${time.toFixed(2)}ms / ` +
       `std: ${getStandardDeviation(prevTimes).toFixed(2)} ` +
       `over ${prevTimes.length} runs`
+    doProfile && console.profileEnd(id)
     console.log(msg)
     document.getElementById('time')!.textContent = msg