, node: ExpressionNode) {
+ if (node.ast) {
+ walkIdentifiers(node.ast, n => ids.add(n.name), true)
+ } else if (node.ast === null) {
+ ids.add((node as SimpleExpressionNode).content)
+ }
+}
diff --git a/packages/runtime-vapor/__tests__/apiSetupContext.spec.ts b/packages/runtime-vapor/__tests__/apiSetupContext.spec.ts
index 3444c3407..e94cd2196 100644
--- a/packages/runtime-vapor/__tests__/apiSetupContext.spec.ts
+++ b/packages/runtime-vapor/__tests__/apiSetupContext.spec.ts
@@ -78,7 +78,8 @@ describe('api: setup context', () => {
inheritAttrs: false,
setup(props, { attrs }) {
const el = document.createElement('div')
- renderEffect(() => setDynamicProps(el, [attrs]))
+ let prev: any
+ renderEffect(() => (prev = setDynamicProps(el, prev, [attrs])))
return el
},
})
@@ -115,7 +116,10 @@ describe('api: setup context', () => {
const n0 = createComponent(Wrapper, null, {
default: () => {
const n0 = template('')() as HTMLDivElement
- renderEffect(() => setDynamicProps(n0, [attrs], true))
+ let prev: any
+ renderEffect(
+ () => (prev = setDynamicProps(n0, prev, [attrs], true)),
+ )
return n0
},
})
diff --git a/packages/runtime-vapor/__tests__/dom/prop.spec.ts b/packages/runtime-vapor/__tests__/dom/prop.spec.ts
index 50a39cea9..6f6666e4c 100644
--- a/packages/runtime-vapor/__tests__/dom/prop.spec.ts
+++ b/packages/runtime-vapor/__tests__/dom/prop.spec.ts
@@ -14,7 +14,6 @@ import {
ComponentInternalInstance,
setCurrentInstance,
} from '../../src/component'
-import { getMetadata, recordPropMetadata } from '../../src/componentMetadata'
import { getCurrentScope } from '@vue/reactivity'
let removeComponentInstance = NOOP
@@ -35,34 +34,6 @@ afterEach(() => {
})
describe('patchProp', () => {
- describe('recordPropMetadata', () => {
- test('should record prop metadata', () => {
- const node = {} as Node // the node is just a key
- let prev = recordPropMetadata(node, 'class', 'foo')
- expect(prev).toBeUndefined()
- prev = recordPropMetadata(node, 'class', 'bar')
- expect(prev).toBe('foo')
- prev = recordPropMetadata(node, 'style', 'color: red')
- expect(prev).toBeUndefined()
- prev = recordPropMetadata(node, 'style', 'color: blue')
- expect(prev).toBe('color: red')
-
- expect(getMetadata(node)).toEqual([
- { class: 'bar', style: 'color: blue' },
- {},
- ])
- })
-
- test('should have different metadata for different nodes', () => {
- const node1 = {} as Node
- const node2 = {} as Node
- recordPropMetadata(node1, 'class', 'foo')
- recordPropMetadata(node2, 'class', 'bar')
- expect(getMetadata(node1)).toEqual([{ class: 'foo' }, {}])
- expect(getMetadata(node2)).toEqual([{ class: 'bar' }, {}])
- })
- })
-
describe('setClass', () => {
test('should set class', () => {
const el = document.createElement('div')
@@ -78,83 +49,87 @@ describe('patchProp', () => {
describe('setStyle', () => {
test('should set style', () => {
const el = document.createElement('div')
- setStyle(el, 'color: red')
+ setStyle(el, '', 'color: red')
expect(el.style.cssText).toBe('color: red;')
})
test('should work with camelCase', () => {
const el = document.createElement('div')
- setStyle(el, { fontSize: '12px' })
+ setStyle(el, null, { fontSize: '12px' })
expect(el.style.cssText).toBe('font-size: 12px;')
})
test('shoud set style with object and array property', () => {
const el = document.createElement('div')
- setStyle(el, { color: 'red' })
+ let prev: any
+ prev = setStyle(el, prev, { color: 'red' })
expect(el.style.cssText).toBe('color: red;')
- setStyle(el, [{ color: 'blue' }, { fontSize: '12px' }])
+ setStyle(el, prev, [{ color: 'blue' }, { fontSize: '12px' }])
expect(el.style.cssText).toBe('color: blue; font-size: 12px;')
})
test('should remove if falsy value', () => {
const el = document.createElement('div')
- setStyle(el, { color: undefined, borderRadius: null })
+ let prev
+ prev = setStyle(el, prev, { color: undefined, borderRadius: null })
expect(el.style.cssText).toBe('')
- setStyle(el, { color: 'red' })
+ prev = setStyle(el, prev, { color: 'red' })
expect(el.style.cssText).toBe('color: red;')
- setStyle(el, { color: undefined, borderRadius: null })
+ setStyle(el, prev, { color: undefined, borderRadius: null })
expect(el.style.cssText).toBe('')
})
test('should work with !important', () => {
const el = document.createElement('div')
- setStyle(el, { color: 'red !important' })
+ setStyle(el, null, { color: 'red !important' })
expect(el.style.cssText).toBe('color: red !important;')
})
test('should work with camelCase and !important', () => {
const el = document.createElement('div')
- setStyle(el, { fontSize: '12px !important' })
+ setStyle(el, null, { fontSize: '12px !important' })
expect(el.style.cssText).toBe('font-size: 12px !important;')
})
test('should work with multiple entries', () => {
const el = document.createElement('div')
- setStyle(el, { color: 'red', marginRight: '10px' })
+ setStyle(el, null, { color: 'red', marginRight: '10px' })
expect(el.style.getPropertyValue('color')).toBe('red')
expect(el.style.getPropertyValue('margin-right')).toBe('10px')
})
test('should patch with falsy style value', () => {
const el = document.createElement('div')
- setStyle(el, { width: '100px' })
+ let prev: any
+ prev = setStyle(el, prev, { width: '100px' })
expect(el.style.cssText).toBe('width: 100px;')
- setStyle(el, { width: 0 })
+ prev = setStyle(el, prev, { width: 0 })
expect(el.style.cssText).toBe('width: 0px;')
})
test('should remove style attribute on falsy value', () => {
const el = document.createElement('div')
- setStyle(el, { width: '100px' })
+ let prev: any
+ prev = setStyle(el, prev, { width: '100px' })
expect(el.style.cssText).toBe('width: 100px;')
- setStyle(el, { width: undefined })
+ prev = setStyle(el, prev, { width: undefined })
expect(el.style.cssText).toBe('')
- setStyle(el, { width: '100px' })
+ prev = setStyle(el, prev, { width: '100px' })
expect(el.style.cssText).toBe('width: 100px;')
- setStyle(el, null)
+ setStyle(el, prev, null)
expect(el.hasAttribute('style')).toBe(false)
expect(el.style.cssText).toBe('')
})
test('should warn for trailing semicolons', () => {
const el = document.createElement('div')
- setStyle(el, { color: 'red;' })
+ setStyle(el, null, { color: 'red;' })
expect(
`Unexpected semicolon at the end of 'color' style value: 'red;'`,
).toHaveBeenWarned()
- setStyle(el, { '--custom': '100; ' })
+ setStyle(el, null, { '--custom': '100; ' })
expect(
`Unexpected semicolon at the end of '--custom' style value: '100; '`,
).toHaveBeenWarned()
@@ -162,13 +137,16 @@ describe('patchProp', () => {
test('should not warn for trailing semicolons', () => {
const el = document.createElement('div')
- setStyle(el, { '--custom': '100\\;' })
+ setStyle(el, null, { '--custom': '100\\;' })
expect(el.style.getPropertyValue('--custom')).toBe('100\\;')
})
test('should work with shorthand properties', () => {
const el = document.createElement('div')
- setStyle(el, { borderBottom: '1px solid red', border: '1px solid green' })
+ setStyle(el, null, {
+ borderBottom: '1px solid red',
+ border: '1px solid green',
+ })
expect(el.style.border).toBe('1px solid green')
expect(el.style.borderBottom).toBe('1px solid green')
})
@@ -193,19 +171,21 @@ describe('patchProp', () => {
test('should work with css custom properties', () => {
const el = mockElementWithStyle()
- setStyle(el as any, { '--theme': 'red' })
+ setStyle(el as any, null, { '--theme': 'red' })
expect(el.style.getPropertyValue('--theme')).toBe('red')
})
test('should auto vendor prefixing', () => {
const el = mockElementWithStyle()
- setStyle(el as any, { transition: 'all 1s' })
+ setStyle(el as any, null, { transition: 'all 1s' })
expect(el.style.WebkitTransition).toBe('all 1s')
})
test('should work with multiple values', () => {
const el = mockElementWithStyle()
- setStyle(el as any, { display: ['-webkit-box', '-ms-flexbox', 'flex'] })
+ setStyle(el as any, null, {
+ display: ['-webkit-box', '-ms-flexbox', 'flex'],
+ })
expect(el.style.display).toBe('flex')
})
})
@@ -335,12 +315,13 @@ describe('patchProp', () => {
describe('setDynamicProp', () => {
const element = document.createElement('div')
+ let prev: any
function setDynamicProp(
key: string,
value: any,
el = element.cloneNode(true) as HTMLElement,
) {
- _setDynamicProp(el, key, value)
+ prev = _setDynamicProp(el, key, prev, value)
return el
}
@@ -397,25 +378,25 @@ describe('patchProp', () => {
describe('setDynamicProps', () => {
test('basic set dynamic props', () => {
const el = document.createElement('div')
- setDynamicProps(el, [{ foo: 'val' }, { bar: 'val' }])
+ setDynamicProps(el, null, [{ foo: 'val' }, { bar: 'val' }])
expect(el.getAttribute('foo')).toBe('val')
expect(el.getAttribute('bar')).toBe('val')
})
test('should merge props', () => {
const el = document.createElement('div')
- setDynamicProps(el, [{ foo: 'val' }, { foo: 'newVal' }])
+ setDynamicProps(el, null, [{ foo: 'val' }, { foo: 'newVal' }])
expect(el.getAttribute('foo')).toBe('newVal')
})
test('should reset old props', () => {
const el = document.createElement('div')
-
- setDynamicProps(el, [{ foo: 'val' }])
+ let prev: any
+ prev = setDynamicProps(el, prev, [{ foo: 'val' }])
expect(el.attributes.length).toBe(1)
expect(el.getAttribute('foo')).toBe('val')
- setDynamicProps(el, [{ bar: 'val' }])
+ prev = setDynamicProps(el, prev, [{ bar: 'val' }])
expect(el.attributes.length).toBe(1)
expect(el.getAttribute('bar')).toBe('val')
expect(el.getAttribute('foo')).toBeNull()
@@ -424,18 +405,19 @@ describe('patchProp', () => {
test('should reset old modifier props', () => {
const el = document.createElement('div')
- setDynamicProps(el, [{ ['.foo']: 'val' }])
+ let prev: any
+ prev = setDynamicProps(el, prev, [{ ['.foo']: 'val' }])
expect((el as any).foo).toBe('val')
- setDynamicProps(el, [{ ['.bar']: 'val' }])
+ prev = setDynamicProps(el, prev, [{ ['.bar']: 'val' }])
expect((el as any).bar).toBe('val')
expect((el as any).foo).toBe('')
- setDynamicProps(el, [{ ['^foo']: 'val' }])
+ prev = setDynamicProps(el, prev, [{ ['^foo']: 'val' }])
expect(el.attributes.length).toBe(1)
expect(el.getAttribute('foo')).toBe('val')
- setDynamicProps(el, [{ ['^bar']: 'val' }])
+ prev = setDynamicProps(el, prev, [{ ['^bar']: 'val' }])
expect(el.attributes.length).toBe(1)
expect(el.getAttribute('bar')).toBe('val')
expect(el.getAttribute('foo')).toBeNull()
diff --git a/packages/runtime-vapor/src/componentAttrs.ts b/packages/runtime-vapor/src/componentAttrs.ts
index 635b45a7a..683597783 100644
--- a/packages/runtime-vapor/src/componentAttrs.ts
+++ b/packages/runtime-vapor/src/componentAttrs.ts
@@ -119,6 +119,7 @@ export function fallThroughAttrs(
}
}
+ let prevAttrs = instance.attrs
renderEffect(() => {
for (const key in instance.attrs) {
if (dynamicAttrs && dynamicAttrs.includes(key)) continue
@@ -130,7 +131,7 @@ export function fallThroughAttrs(
value = instance.attrs[key]
}
- setDynamicProp(element, key, value)
+ setDynamicProp(element, key, prevAttrs[key], value)
}
})
}
diff --git a/packages/runtime-vapor/src/componentFallback.ts b/packages/runtime-vapor/src/componentFallback.ts
index adceb5446..ce0e82e5d 100644
--- a/packages/runtime-vapor/src/componentFallback.ts
+++ b/packages/runtime-vapor/src/componentFallback.ts
@@ -24,6 +24,7 @@ export function fallbackComponent(
if (rawProps || Object.keys(instance.attrs).length) {
rawProps = [() => instance.attrs, ...normalizeRawProps(rawProps)]
+ let prevValue: any, prevStyle: any
renderEffect(() => {
let classes: unknown[] | undefined
let styles: unknown[] | undefined
@@ -34,12 +35,16 @@ export function fallbackComponent(
const value = getter ? valueOrGetter() : valueOrGetter
if (key === 'class') (classes ||= []).push(value)
else if (key === 'style') (styles ||= []).push(value)
- else setDynamicProp(el, key, value)
+ else {
+ prevValue = setDynamicProp(el, key, prevValue, value)
+ }
},
)
if (classes) setClass(el, classes)
- if (styles) setStyle(el, styles)
+ if (styles) {
+ prevStyle = setStyle(el, prevStyle, styles)
+ }
})
}
diff --git a/packages/runtime-vapor/src/componentMetadata.ts b/packages/runtime-vapor/src/componentMetadata.ts
index ab2ad0bc6..4160d083c 100644
--- a/packages/runtime-vapor/src/componentMetadata.ts
+++ b/packages/runtime-vapor/src/componentMetadata.ts
@@ -18,13 +18,6 @@ export function getMetadata(
return el.$$metadata || (el.$$metadata = [{}, {}])
}
-export function recordPropMetadata(el: Node, key: string, value: any): any {
- const metadata = getMetadata(el)[MetadataKind.prop]
- const prev = metadata[key]
- if (prev !== value) metadata[key] = value
- return prev
-}
-
export function recordEventMetadata(el: Node, key: string, value: any) {
const metadata = getMetadata(el)[MetadataKind.event]
const handlers = (metadata[key] ||= [])
diff --git a/packages/runtime-vapor/src/dom/prop.ts b/packages/runtime-vapor/src/dom/prop.ts
index bd6ce1f12..e43cff1d9 100644
--- a/packages/runtime-vapor/src/dom/prop.ts
+++ b/packages/runtime-vapor/src/dom/prop.ts
@@ -14,11 +14,6 @@ import {
} from '@vue/shared'
import { warn } from '../warning'
import { setStyle } from './style'
-import {
- MetadataKind,
- getMetadata,
- recordPropMetadata,
-} from '../componentMetadata'
import { on } from './event'
import type { Data } from '@vue/runtime-shared'
import { currentInstance } from '../component'
@@ -29,32 +24,18 @@ export function mergeInheritAttr(key: string, value: any): unknown {
}
export function setClass(el: Element, value: any, root?: boolean): void {
- const prev = recordPropMetadata(
- el,
- 'class',
- (value = normalizeClass(root ? mergeInheritAttr('class', value) : value)),
- )
-
- if (value !== prev && (value || prev)) {
- el.className = value
- }
+ el.className = normalizeClass(root ? mergeInheritAttr('class', value) : value)
}
export function setAttr(el: Element, key: string, value: any): void {
- const oldVal = recordPropMetadata(el, key, value)
- if (value !== oldVal) {
- if (value != null) {
- el.setAttribute(key, value)
- } else {
- el.removeAttribute(key)
- }
+ if (value != null) {
+ el.setAttribute(key, value)
+ } else {
+ el.removeAttribute(key)
}
}
export function setValue(el: any, value: any): void {
- const oldVal = recordPropMetadata(el, 'value', value)
- if (value === oldVal) return
-
// store value as _value as well since
// non-string values will be stringified.
el._value = value
@@ -71,9 +52,6 @@ export function setValue(el: any, value: any): void {
}
export function setDOMProp(el: any, key: string, value: any): void {
- const oldVal = recordPropMetadata(el, key, value)
- if (value === oldVal) return
-
let needRemove = false
if (value === '' || value == null) {
const type = typeof el[key]
@@ -109,13 +87,18 @@ export function setDOMProp(el: any, key: string, value: any): void {
needRemove && el.removeAttribute(key)
}
-export function setDynamicProp(el: Element, key: string, value: any): void {
+export function setDynamicProp(
+ el: Element,
+ key: string,
+ prev: any,
+ value: any,
+): any {
// TODO
const isSVG = false
if (key === 'class') {
setClass(el, value)
} else if (key === 'style') {
- setStyle(el as HTMLElement, value)
+ return setStyle(el as HTMLElement, prev, value)
} else if (isOn(key)) {
on(el, key[2].toLowerCase() + key.slice(3), () => value, { effect: true })
} else if (
@@ -150,30 +133,42 @@ export function setDynamicProp(el: Element, key: string, value: any): void {
export function setDynamicProps(
el: Element,
+ oldProps: any,
args: any[],
root?: boolean,
): void {
- const oldProps = getMetadata(el)[MetadataKind.prop]
+ // const oldProps = getMetadata(el)[MetadataKind.prop]
if (root) {
args.unshift(currentInstance!.attrs)
}
const props = args.length > 1 ? mergeProps(...args) : args[0]
- for (const key in oldProps) {
- // TODO should these keys be allowed as dynamic keys? The current logic of the runtime-core will throw an error
- if (key === 'textContent' || key === 'innerHTML') {
- continue
- }
+ if (oldProps) {
+ for (const key in oldProps) {
+ // TODO should these keys be allowed as dynamic keys? The current logic of the runtime-core will throw an error
+ if (key === 'textContent' || key === 'innerHTML') {
+ continue
+ }
- const hasNewValue = props[key] || props['.' + key] || props['^' + key]
- if (oldProps[key] && !hasNewValue) {
- setDynamicProp(el, key, null)
+ const oldValue = oldProps[key]
+ const hasNewValue = props[key] || props['.' + key] || props['^' + key]
+ if (oldValue && !hasNewValue) {
+ setDynamicProp(el, key, oldValue, null)
+ }
}
}
+ const prev = Object.create(null)
for (const key in props) {
- setDynamicProp(el, key, props[key])
+ setDynamicProp(
+ el,
+ key,
+ oldProps ? oldProps[key] : undefined,
+ (prev[key] = props[key]),
+ )
}
+
+ return prev
}
export function mergeProp(
@@ -213,18 +208,11 @@ export function mergeProps(...args: Data[]): Data {
}
export function setText(el: Node, ...values: any[]): void {
- const text = values.map(v => toDisplayString(v)).join('')
- const oldVal = recordPropMetadata(el, 'textContent', text)
- if (text !== oldVal) {
- el.textContent = text
- }
+ el.textContent = values.map(v => toDisplayString(v)).join('')
}
export function setHtml(el: Element, value: any): void {
- const oldVal = recordPropMetadata(el, 'innerHTML', value)
- if (value !== oldVal) {
- el.innerHTML = value == null ? '' : value
- }
+ el.innerHTML = value == null ? '' : value
}
// TODO copied from runtime-dom
diff --git a/packages/runtime-vapor/src/dom/style.ts b/packages/runtime-vapor/src/dom/style.ts
index 5ee233a0c..213dfec4b 100644
--- a/packages/runtime-vapor/src/dom/style.ts
+++ b/packages/runtime-vapor/src/dom/style.ts
@@ -7,16 +7,17 @@ import {
normalizeStyle,
} from '@vue/shared'
import { warn } from '../warning'
-import { recordPropMetadata } from '../componentMetadata'
import { mergeInheritAttr } from './prop'
-export function setStyle(el: HTMLElement, value: any, root?: boolean): void {
- const prev = recordPropMetadata(
- el,
- 'style',
- (value = normalizeStyle(root ? mergeInheritAttr('style', value) : value)),
- )
+export function setStyle(
+ el: HTMLElement,
+ prev: any,
+ value: any,
+ root?: boolean,
+): any {
+ value = normalizeStyle(root ? mergeInheritAttr('style', value) : value)
patchStyle(el, prev, value)
+ return value
}
// TODO copied from packages/runtime-dom/src/modules/style.ts