Skip to content

Commit 2fdda65

Browse files
committed
Merge branch 'main' into feat/onEffectCleanup-and-baseWatch
2 parents 39f07cd + 2701355 commit 2fdda65

File tree

10 files changed

+299
-112
lines changed

10 files changed

+299
-112
lines changed

package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,11 @@
7070
"@rollup/plugin-terser": "^0.4.4",
7171
"@types/hash-sum": "^1.0.2",
7272
"@types/minimist": "^1.2.5",
73-
"@types/node": "^20.10.6",
73+
"@types/node": "^20.10.7",
7474
"@types/semver": "^7.5.6",
7575
"@typescript-eslint/eslint-plugin": "^6.17.0",
7676
"@typescript-eslint/parser": "^6.17.0",
77-
"@vitest/coverage-istanbul": "^1.1.1",
77+
"@vitest/coverage-istanbul": "^1.1.3",
7878
"@vue/consolidate": "0.17.3",
7979
"conventional-changelog-cli": "^4.1.0",
8080
"enquirer": "^2.4.1",
@@ -86,7 +86,7 @@
8686
"eslint-plugin-jest": "^27.6.1",
8787
"estree-walker": "^2.0.2",
8888
"execa": "^8.0.1",
89-
"jsdom": "^23.0.1",
89+
"jsdom": "^23.2.0",
9090
"lint-staged": "^15.2.0",
9191
"lodash": "^4.17.21",
9292
"magic-string": "^0.30.5",
@@ -98,7 +98,7 @@
9898
"prettier": "^3.1.1",
9999
"pretty-bytes": "^6.1.1",
100100
"pug": "^3.0.2",
101-
"puppeteer": "~21.6.1",
101+
"puppeteer": "~21.7.0",
102102
"rimraf": "^5.0.5",
103103
"rollup": "^4.1.4",
104104
"rollup-plugin-dts": "^6.1.0",
@@ -113,6 +113,6 @@
113113
"tsx": "^4.7.0",
114114
"typescript": "^5.2.2",
115115
"vite": "^5.0.5",
116-
"vitest": "^1.1.1"
116+
"vitest": "^1.1.3"
117117
}
118118
}

packages/reactivity/src/reactiveEffect.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
// The main WeakMap that stores {target -> key -> dep} connections.
1414
// Conceptually, it's easier to think of a dependency as a Dep class
1515
// which maintains a Set of subscribers, but we simply store them as
16-
// raw Sets to reduce memory overhead.
16+
// raw Maps to reduce memory overhead.
1717
type KeyToDepMap = Map<any, Dep>
1818
const targetMap = new WeakMap<object, KeyToDepMap>()
1919

packages/runtime-core/__tests__/hydration.spec.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1431,11 +1431,35 @@ describe('SSR hydration', () => {
14311431
mountWithHydration(`<div style="color:red;"></div>`, () =>
14321432
h('div', { style: `color:red;` }),
14331433
)
1434+
mountWithHydration(
1435+
`<div style="color:red; font-size: 12px;"></div>`,
1436+
() => h('div', { style: `font-size: 12px; color:red;` }),
1437+
)
1438+
mountWithHydration(`<div style="color:red;display:none;"></div>`, () =>
1439+
withDirectives(createVNode('div', { style: 'color: red' }, ''), [
1440+
[vShow, false],
1441+
]),
1442+
)
14341443
expect(`Hydration style mismatch`).not.toHaveBeenWarned()
14351444
mountWithHydration(`<div style="color:red;"></div>`, () =>
14361445
h('div', { style: { color: 'green' } }),
14371446
)
1438-
expect(`Hydration style mismatch`).toHaveBeenWarned()
1447+
expect(`Hydration style mismatch`).toHaveBeenWarnedTimes(1)
1448+
})
1449+
1450+
test('style mismatch w/ v-show', () => {
1451+
mountWithHydration(`<div style="color:red;display:none"></div>`, () =>
1452+
withDirectives(createVNode('div', { style: 'color: red' }, ''), [
1453+
[vShow, false],
1454+
]),
1455+
)
1456+
expect(`Hydration style mismatch`).not.toHaveBeenWarned()
1457+
mountWithHydration(`<div style="color:red;"></div>`, () =>
1458+
withDirectives(createVNode('div', { style: 'color: red' }, ''), [
1459+
[vShow, false],
1460+
]),
1461+
)
1462+
expect(`Hydration style mismatch`).toHaveBeenWarnedTimes(1)
14391463
})
14401464

14411465
test('attr mismatch', () => {
@@ -1451,6 +1475,12 @@ describe('SSR hydration', () => {
14511475
mountWithHydration(`<select multiple></div>`, () =>
14521476
h('select', { multiple: 'multiple' }),
14531477
)
1478+
mountWithHydration(`<textarea>foo</textarea>`, () =>
1479+
h('textarea', { value: 'foo' }),
1480+
)
1481+
mountWithHydration(`<textarea></textarea>`, () =>
1482+
h('textarea', { value: '' }),
1483+
)
14541484
expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
14551485

14561486
mountWithHydration(`<div></div>`, () => h('div', { id: 'foo' }))

packages/runtime-core/src/components/Suspense.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,6 @@ export interface SuspenseBoundary {
400400
namespace: ElementNamespace
401401
container: RendererElement
402402
hiddenContainer: RendererElement
403-
anchor: RendererNode | null
404403
activeBranch: VNode | null
405404
pendingBranch: VNode | null
406405
deps: number
@@ -473,14 +472,14 @@ function createSuspenseBoundary(
473472
assertNumber(timeout, `Suspense timeout`)
474473
}
475474

475+
const initialAnchor = anchor
476476
const suspense: SuspenseBoundary = {
477477
vnode,
478478
parent: parentSuspense,
479479
parentComponent,
480480
namespace,
481481
container,
482482
hiddenContainer,
483-
anchor,
484483
deps: 0,
485484
pendingId: suspenseId++,
486485
timeout: typeof timeout === 'number' ? timeout : -1,
@@ -529,20 +528,28 @@ function createSuspenseBoundary(
529528
move(
530529
pendingBranch!,
531530
container,
532-
next(activeBranch!),
531+
anchor === initialAnchor ? next(activeBranch!) : anchor,
533532
MoveType.ENTER,
534533
)
535534
queuePostFlushCb(effects)
536535
}
537536
}
538537
}
539-
// this is initial anchor on mount
540-
let { anchor } = suspense
541538
// unmount current active tree
542539
if (activeBranch) {
543540
// if the fallback tree was mounted, it may have been moved
544541
// as part of a parent suspense. get the latest anchor for insertion
545-
anchor = next(activeBranch)
542+
// #8105 if `delayEnter` is true, it means that the mounting of
543+
// `activeBranch` will be delayed. if the branch switches before
544+
// transition completes, both `activeBranch` and `pendingBranch` may
545+
// coexist in the `hiddenContainer`. This could result in
546+
// `next(activeBranch!)` obtaining an incorrect anchor
547+
// (got `pendingBranch.el`).
548+
// Therefore, after the mounting of activeBranch is completed,
549+
// it is necessary to get the latest anchor.
550+
if (parentNode(activeBranch.el!) !== suspense.hiddenContainer) {
551+
anchor = next(activeBranch)
552+
}
546553
unmount(activeBranch, parentComponent, suspense, true)
547554
}
548555
if (!delayEnter) {

packages/runtime-core/src/hydration.ts

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ export function createHydrationFunctions(
448448
) {
449449
for (const key in props) {
450450
// check hydration mismatch
451-
if (__DEV__ && propHasMismatch(el, key, props[key])) {
451+
if (__DEV__ && propHasMismatch(el, key, props[key], vnode)) {
452452
hasMismatch = true
453453
}
454454
if (
@@ -712,7 +712,12 @@ export function createHydrationFunctions(
712712
/**
713713
* Dev only
714714
*/
715-
function propHasMismatch(el: Element, key: string, clientValue: any): boolean {
715+
function propHasMismatch(
716+
el: Element,
717+
key: string,
718+
clientValue: any,
719+
vnode: VNode,
720+
): boolean {
716721
let mismatchType: string | undefined
717722
let mismatchKey: string | undefined
718723
let actual: any
@@ -726,24 +731,41 @@ function propHasMismatch(el: Element, key: string, clientValue: any): boolean {
726731
mismatchType = mismatchKey = `class`
727732
}
728733
} else if (key === 'style') {
729-
actual = el.getAttribute('style')
730-
expected = isString(clientValue)
731-
? clientValue
732-
: stringifyStyle(normalizeStyle(clientValue))
733-
if (actual !== expected) {
734+
// style might be in different order, but that doesn't affect cascade
735+
actual = toStyleMap(el.getAttribute('style') || '')
736+
expected = toStyleMap(
737+
isString(clientValue)
738+
? clientValue
739+
: stringifyStyle(normalizeStyle(clientValue)),
740+
)
741+
// If `v-show=false`, `display: 'none'` should be added to expected
742+
if (vnode.dirs) {
743+
for (const { dir, value } of vnode.dirs) {
744+
// @ts-expect-error only vShow has this internal name
745+
if (dir.name === 'show' && !value) {
746+
expected.set('display', 'none')
747+
}
748+
}
749+
}
750+
if (!isMapEqual(actual, expected)) {
734751
mismatchType = mismatchKey = 'style'
735752
}
736753
} else if (
737754
(el instanceof SVGElement && isKnownSvgAttr(key)) ||
738755
(el instanceof HTMLElement && (isBooleanAttr(key) || isKnownHtmlAttr(key)))
739756
) {
740-
actual = el.hasAttribute(key) && el.getAttribute(key)
757+
// #10000 some attrs such as textarea.value can't be get by `hasAttribute`
758+
actual = el.hasAttribute(key)
759+
? el.getAttribute(key)
760+
: key in el
761+
? el[key as keyof typeof el]
762+
: ''
741763
expected = isBooleanAttr(key)
742764
? includeBooleanAttr(clientValue)
743765
? ''
744766
: false
745767
: clientValue == null
746-
? false
768+
? ''
747769
: String(clientValue)
748770
if (actual !== expected) {
749771
mismatchType = `attribute`
@@ -783,3 +805,28 @@ function isSetEqual(a: Set<string>, b: Set<string>): boolean {
783805
}
784806
return true
785807
}
808+
809+
function toStyleMap(str: string): Map<string, string> {
810+
const styleMap: Map<string, string> = new Map()
811+
for (const item of str.split(';')) {
812+
let [key, value] = item.split(':')
813+
key = key?.trim()
814+
value = value?.trim()
815+
if (key && value) {
816+
styleMap.set(key, value)
817+
}
818+
}
819+
return styleMap
820+
}
821+
822+
function isMapEqual(a: Map<string, string>, b: Map<string, string>): boolean {
823+
if (a.size !== b.size) {
824+
return false
825+
}
826+
for (const [key, value] of a) {
827+
if (value !== b.get(key)) {
828+
return false
829+
}
830+
}
831+
return true
832+
}

packages/runtime-core/src/scheduler.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,6 @@ function flushJobs(seen?: CountMap) {
243243
if (__DEV__ && check(job)) {
244244
continue
245245
}
246-
// console.log(`running:`, job.id)
247246
callWithErrorHandling(job, null, ErrorCodes.SCHEDULER)
248247
}
249248
}

packages/runtime-dom/__tests__/directives/vShow.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ describe('runtime-dom: v-show directive', () => {
3030
})
3131
render(h(component), root)
3232

33-
const $div = root.querySelector('div')
33+
const $div = root.children[0]
3434

3535
expect($div.style.display).toEqual('')
3636
})
@@ -46,7 +46,7 @@ describe('runtime-dom: v-show directive', () => {
4646
})
4747
render(h(component), root)
4848

49-
const $div = root.querySelector('div')
49+
const $div = root.children[0]
5050

5151
expect($div.style.display).toEqual('none')
5252
})
@@ -62,7 +62,7 @@ describe('runtime-dom: v-show directive', () => {
6262
})
6363
render(h(component), root)
6464

65-
const $div = root.querySelector('div')
65+
const $div = root.children[0]
6666
const data = root._vnode.component.data
6767

6868
expect($div.style.display).toEqual('')
@@ -113,7 +113,7 @@ describe('runtime-dom: v-show directive', () => {
113113
})
114114
render(h(component), root)
115115

116-
const $div = root.querySelector('div')
116+
const $div = root.children[0]
117117
const data = root._vnode.component.data
118118

119119
expect($div.style.display).toEqual('block')
@@ -138,7 +138,7 @@ describe('runtime-dom: v-show directive', () => {
138138
})
139139
render(h(component), root)
140140

141-
const $div = root.querySelector('div')
141+
const $div = root.children[0]
142142

143143
expect($div.style.display).toEqual('none')
144144

@@ -173,7 +173,7 @@ describe('runtime-dom: v-show directive', () => {
173173
})
174174
render(h(component), root)
175175

176-
const $div = root.querySelector('div')
176+
const $div = root.children[0]
177177

178178
expect($div.style.display).toEqual('none')
179179

packages/runtime-dom/src/directives/vShow.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ interface VShowElement extends HTMLElement {
77
[vShowOldKey]: string
88
}
99

10-
export const vShow: ObjectDirective<VShowElement> = {
10+
export const vShow: ObjectDirective<VShowElement> & { name?: 'show' } = {
1111
beforeMount(el, { value }, { transition }) {
1212
el[vShowOldKey] = el.style.display === 'none' ? '' : el.style.display
1313
if (transition && value) {
@@ -42,6 +42,10 @@ export const vShow: ObjectDirective<VShowElement> = {
4242
},
4343
}
4444

45+
if (__DEV__) {
46+
vShow.name = 'show'
47+
}
48+
4549
function setDisplay(el: VShowElement, value: unknown): void {
4650
el.style.display = value ? el[vShowOldKey] : 'none'
4751
}

0 commit comments

Comments
 (0)