diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6acaa893f..28447dfe3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,14 @@
+## [3.4.21](https://github.com/vuejs/core/compare/v3.4.20...v3.4.21) (2024-02-28)
+
+
+### Bug Fixes
+
+* **runtime-dom:** avoid unset option's value ([#10416](https://github.com/vuejs/core/issues/10416)) ([b3f8b5a](https://github.com/vuejs/core/commit/b3f8b5a4e700d4c47a146b6040882287d180f6cb)), closes [#10412](https://github.com/vuejs/core/issues/10412) [#10396](https://github.com/vuejs/core/issues/10396)
+* **suspense:** ensure nested suspense patching if in fallback state ([#10417](https://github.com/vuejs/core/issues/10417)) ([7c97778](https://github.com/vuejs/core/commit/7c97778aec1e3513035e5df265e1b8a7801f6106)), closes [#10415](https://github.com/vuejs/core/issues/10415)
+* **warning:** stringify args in warn handler ([#10414](https://github.com/vuejs/core/issues/10414)) ([bc37258](https://github.com/vuejs/core/commit/bc37258caa2f6f67f4554ab8587aca3798d92124)), closes [#10409](https://github.com/vuejs/core/issues/10409)
+
+
+
## [3.4.20](https://github.com/vuejs/core/compare/v3.4.19...v3.4.20) (2024-02-26)
diff --git a/packages/compiler-core/package.json b/packages/compiler-core/package.json
index 899e72b37..98bb107ec 100644
--- a/packages/compiler-core/package.json
+++ b/packages/compiler-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/compiler-core",
- "version": "3.4.20",
+ "version": "3.4.21",
"description": "@vue/compiler-core",
"main": "index.js",
"module": "dist/compiler-core.esm-bundler.js",
diff --git a/packages/compiler-dom/package.json b/packages/compiler-dom/package.json
index 1337dbc6c..a92d1315c 100644
--- a/packages/compiler-dom/package.json
+++ b/packages/compiler-dom/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/compiler-dom",
- "version": "3.4.20",
+ "version": "3.4.21",
"description": "@vue/compiler-dom",
"main": "index.js",
"module": "dist/compiler-dom.esm-bundler.js",
diff --git a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/importUsageCheck.spec.ts.snap b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/importUsageCheck.spec.ts.snap
index cfdbf5f45..764d120a7 100644
--- a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/importUsageCheck.spec.ts.snap
+++ b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/importUsageCheck.spec.ts.snap
@@ -79,21 +79,6 @@ return { get FooBar() { return FooBar }, get foo() { return foo }, get bar() { r
})"
`;
-exports[`import namespace 1`] = `
-"import { defineComponent as _defineComponent } from 'vue'
-import * as Foo from './foo'
-
-export default /*#__PURE__*/_defineComponent({
- setup(__props, { expose: __expose }) {
- __expose();
-
-
-return { get Foo() { return Foo } }
-}
-
-})"
-`;
-
exports[`js template string interpolations 1`] = `
"import { defineComponent as _defineComponent } from 'vue'
import { VAR, VAR2, VAR3 } from './x'
diff --git a/packages/compiler-sfc/package.json b/packages/compiler-sfc/package.json
index bf30add51..b624cdacc 100644
--- a/packages/compiler-sfc/package.json
+++ b/packages/compiler-sfc/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/compiler-sfc",
- "version": "3.4.20",
+ "version": "3.4.21",
"description": "@vue/compiler-sfc",
"main": "dist/compiler-sfc.cjs.js",
"module": "dist/compiler-sfc.esm-browser.js",
diff --git a/packages/compiler-ssr/package.json b/packages/compiler-ssr/package.json
index 59861946c..fede83c2c 100644
--- a/packages/compiler-ssr/package.json
+++ b/packages/compiler-ssr/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/compiler-ssr",
- "version": "3.4.20",
+ "version": "3.4.21",
"description": "@vue/compiler-ssr",
"main": "dist/compiler-ssr.cjs.js",
"types": "dist/compiler-ssr.d.ts",
diff --git a/packages/reactivity/package.json b/packages/reactivity/package.json
index 5b404c0e8..60997b87b 100644
--- a/packages/reactivity/package.json
+++ b/packages/reactivity/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/reactivity",
- "version": "3.4.20",
+ "version": "3.4.21",
"description": "@vue/reactivity",
"main": "index.js",
"module": "dist/reactivity.esm-bundler.js",
diff --git a/packages/runtime-core/__tests__/components/Suspense.spec.ts b/packages/runtime-core/__tests__/components/Suspense.spec.ts
index fd1913b2c..a448972e1 100644
--- a/packages/runtime-core/__tests__/components/Suspense.spec.ts
+++ b/packages/runtime-core/__tests__/components/Suspense.spec.ts
@@ -54,6 +54,18 @@ describe('Suspense', () => {
}
}
+ const RouterView = {
+ setup(_: any, { slots }: any) {
+ const route = inject('route') as any
+ const depth = inject('depth', 0)
+ provide('depth', depth + 1)
+ return () => {
+ const current = route.value[depth]
+ return slots.default({ Component: current })[0]
+ }
+ },
+ }
+
test('fallback content', async () => {
const Async = defineAsyncComponent({
render() {
@@ -1041,18 +1053,6 @@ describe('Suspense', () => {
// #10098
test('switching branches w/ nested suspense', async () => {
- const RouterView = {
- setup(_: any, { slots }: any) {
- const route = inject('route') as any
- const depth = inject('depth', 0)
- provide('depth', depth + 1)
- return () => {
- const current = route.value[depth]
- return slots.default({ Component: current })[0]
- }
- },
- }
-
const OuterB = defineAsyncComponent({
setup: () => {
return () =>
@@ -1132,6 +1132,121 @@ describe('Suspense', () => {
expect(serializeInner(root)).toBe(`
innerA
`)
})
+ // #10415
+ test('nested suspense (w/ suspensible) switch several times before parent suspense resolve', async () => {
+ const OuterA = defineAsyncComponent({
+ setup: () => {
+ return () =>
+ h(RouterView, null, {
+ default: ({ Component }: any) => [
+ h(Suspense, null, {
+ default: () => h(Component),
+ }),
+ ],
+ })
+ },
+ })
+
+ const InnerA = defineAsyncComponent({
+ setup: () => {
+ return () => h('div', 'innerA')
+ },
+ })
+
+ const route = shallowRef([OuterA, InnerA])
+ const InnerB = defineAsyncComponent(
+ {
+ setup: () => {
+ return () => h('div', 'innerB')
+ },
+ },
+ 5,
+ )
+
+ const InnerB1 = defineAsyncComponent(
+ {
+ setup: () => {
+ return () => h('div', 'innerB1')
+ },
+ },
+ 5,
+ )
+
+ const InnerB2 = defineAsyncComponent(
+ {
+ setup: () => {
+ return () => h('div', 'innerB2')
+ },
+ },
+ 5,
+ )
+
+ const OuterB = defineAsyncComponent(
+ {
+ setup() {
+ nextTick(async () => {
+ await new Promise(resolve => setTimeout(resolve, 1))
+ route.value = [OuterB, InnerB1]
+ })
+
+ nextTick(async () => {
+ await new Promise(resolve => setTimeout(resolve, 1))
+ route.value = [OuterB, InnerB2]
+ })
+
+ return () =>
+ h(RouterView, null, {
+ default: ({ Component }: any) => [
+ h(
+ Suspense,
+ { suspensible: true },
+ {
+ default: () => h(Component),
+ },
+ ),
+ ],
+ })
+ },
+ },
+ 5,
+ )
+
+ const Comp = {
+ setup() {
+ provide('route', route)
+ return () =>
+ h(RouterView, null, {
+ default: ({ Component }: any) => [
+ h(Suspense, null, {
+ default: () => h(Component),
+ }),
+ ],
+ })
+ },
+ }
+
+ const root = nodeOps.createElement('div')
+ render(h(Comp), root)
+ await Promise.all(deps)
+ await nextTick()
+ expect(serializeInner(root)).toBe(``)
+
+ await Promise.all(deps)
+ await nextTick()
+ expect(serializeInner(root)).toBe(`innerA
`)
+
+ deps.length = 0
+
+ route.value = [OuterB, InnerB]
+ await nextTick()
+
+ await Promise.all(deps)
+ await Promise.all(deps)
+ await Promise.all(deps)
+ await nextTick()
+ expect(serializeInner(root)).toBe(`innerB2
`)
+ })
+
test('branch switch to 3rd branch before resolve', async () => {
const calls: string[] = []
diff --git a/packages/runtime-core/package.json b/packages/runtime-core/package.json
index 611f9cef0..d38b8fa21 100644
--- a/packages/runtime-core/package.json
+++ b/packages/runtime-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/runtime-core",
- "version": "3.4.20",
+ "version": "3.4.21",
"description": "@vue/runtime-core",
"main": "index.js",
"module": "dist/runtime-core.esm-bundler.js",
diff --git a/packages/runtime-core/src/components/Suspense.ts b/packages/runtime-core/src/components/Suspense.ts
index 65b05c3dd..9461d7fec 100644
--- a/packages/runtime-core/src/components/Suspense.ts
+++ b/packages/runtime-core/src/components/Suspense.ts
@@ -99,7 +99,11 @@ export const SuspenseImpl = {
// 2. mounting along with the pendingBranch of parentSuspense
// it is necessary to skip the current patch to avoid multiple mounts
// of inner components.
- if (parentSuspense && parentSuspense.deps > 0) {
+ if (
+ parentSuspense &&
+ parentSuspense.deps > 0 &&
+ !n1.suspense!.isInFallback
+ ) {
n2.suspense = n1.suspense!
n2.suspense.vnode = n2
n2.el = n1.el
diff --git a/packages/runtime-core/src/warning.ts b/packages/runtime-core/src/warning.ts
index 14f1b020a..eea810f57 100644
--- a/packages/runtime-core/src/warning.ts
+++ b/packages/runtime-core/src/warning.ts
@@ -44,7 +44,7 @@ export function warn(msg: string, ...args: any[]) {
instance,
ErrorCodes.APP_WARN_HANDLER,
[
- msg + args.join(''),
+ msg + args.map(a => a.toString?.() ?? JSON.stringify(a)).join(''),
instance && instance.proxy,
trace
.map(
diff --git a/packages/runtime-dom/__tests__/patchProps.spec.ts b/packages/runtime-dom/__tests__/patchProps.spec.ts
index 7c356f1a6..61dd98513 100644
--- a/packages/runtime-dom/__tests__/patchProps.spec.ts
+++ b/packages/runtime-dom/__tests__/patchProps.spec.ts
@@ -291,6 +291,18 @@ describe('runtime-dom: props patching', () => {
expect(el.value).toBe('baz')
})
+ test('init empty value for option', () => {
+ const root = document.createElement('div')
+ render(
+ h('select', { value: 'foo' }, [h('option', { value: '' }, 'foo')]),
+ root,
+ )
+ const select = root.children[0] as HTMLSelectElement
+ const option = select.children[0] as HTMLOptionElement
+ expect(select.value).toBe('')
+ expect(option.value).toBe('')
+ })
+
// #8780
test('embedded tag with width and height', () => {
// Width and height of some embedded element such as img、video、source、canvas
diff --git a/packages/runtime-dom/package.json b/packages/runtime-dom/package.json
index bd645877f..591cae2be 100644
--- a/packages/runtime-dom/package.json
+++ b/packages/runtime-dom/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue/runtime-dom",
- "version": "3.4.20",
+ "version": "3.4.21",
"description": "@vue/runtime-dom",
"main": "index.js",
"module": "dist/runtime-dom.esm-bundler.js",
diff --git a/packages/runtime-dom/src/modules/props.ts b/packages/runtime-dom/src/modules/props.ts
index e9b3bca35..c4bb4987b 100644
--- a/packages/runtime-dom/src/modules/props.ts
+++ b/packages/runtime-dom/src/modules/props.ts
@@ -34,20 +34,20 @@ export function patchDOMProp(
// custom elements may use _value internally
!tag.includes('-')
) {
- // store value as _value as well since
- // non-string values will be stringified.
- el._value = value
// #4956: