From a60f2bd3718f8f84aee2b11e6285d9933c3965bc Mon Sep 17 00:00:00 2001 From: daiwei Date: Thu, 29 May 2025 09:04:53 +0800 Subject: [PATCH 01/17] chore: update changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d9a2f75038..51e718af1e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,8 @@ ### Reverts -* Revert "fix(compiler-sfc): add scoping tag to trailing universal selector (#1…" (#13406) ([19f23b1](https://github.com/vuejs/core/commit/19f23b180bb679e38db95d6a10a420abeedc8e1c)), closes [#1](https://github.com/vuejs/core/issues/1) [#13406](https://github.com/vuejs/core/issues/13406) -* Revert "fix(compiler-sfc): add error handling for defineModel() without varia…" (#13390) ([42f879f](https://github.com/vuejs/core/commit/42f879fcab48e0e1011967a771b4ad9e8838d760)), closes [#13390](https://github.com/vuejs/core/issues/13390) +* Revert "fix(compiler-sfc): add scoping tag to trailing universal selector" (#13406) ([19f23b1](https://github.com/vuejs/core/commit/19f23b180bb679e38db95d6a10a420abeedc8e1c)), closes [#13406](https://github.com/vuejs/core/issues/13406) +* Revert "fix(compiler-sfc): add error handling for defineModel() without variable" (#13390) ([42f879f](https://github.com/vuejs/core/commit/42f879fcab48e0e1011967a771b4ad9e8838d760)), closes [#13390](https://github.com/vuejs/core/issues/13390) From 62f2aa11a2032db43eb820063fb781bcf3b0fad0 Mon Sep 17 00:00:00 2001 From: btea <2356281422@qq.com> Date: Fri, 30 May 2025 10:35:27 +0800 Subject: [PATCH 02/17] test(compiler-sfc): direct descendant wildcard rule selector (#13411) add test case for #13387 --- .../__tests__/compileStyle.spec.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/compiler-sfc/__tests__/compileStyle.spec.ts b/packages/compiler-sfc/__tests__/compileStyle.spec.ts index 78fd52425e8..9b7e7c53710 100644 --- a/packages/compiler-sfc/__tests__/compileStyle.spec.ts +++ b/packages/compiler-sfc/__tests__/compileStyle.spec.ts @@ -39,6 +39,24 @@ describe('SFC scoped CSS', () => { expect(compileScoped(`h1 .foo { color: red; }`)).toMatch( `h1 .foo[data-v-test] { color: red;`, ) + + // #13387 + expect( + compileScoped(`main { + width: 100%; + > * { + max-width: 200px; + } +}`), + ).toMatchInlineSnapshot(` + "main { +&[data-v-test] { + width: 100%; +} +> *[data-v-test] { + max-width: 200px; +} +}"`) }) test('nesting selector', () => { From e53a4ffbe0264b06971090fcaf0d8b2201478e29 Mon Sep 17 00:00:00 2001 From: skirtle <65301168+skirtles-code@users.noreply.github.com> Date: Fri, 30 May 2025 07:43:23 +0100 Subject: [PATCH 03/17] chore(hydration): reuse existing variable (#13412) --- packages/runtime-core/src/hydration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/runtime-core/src/hydration.ts b/packages/runtime-core/src/hydration.ts index 6ffdbc68de4..12813b598b5 100644 --- a/packages/runtime-core/src/hydration.ts +++ b/packages/runtime-core/src/hydration.ts @@ -997,6 +997,6 @@ function isMismatchAllowed( if (allowedType === MismatchTypes.TEXT && list.includes('children')) { return true } - return allowedAttr.split(',').includes(MismatchTypeString[allowedType]) + return list.includes(MismatchTypeString[allowedType]) } } From 762fae4b57ad60602e5c84465a3bff562785b314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=B1=E5=90=B9=E8=89=B2=E5=BE=A1=E5=AE=88?= <85992002+KazariEX@users.noreply.github.com> Date: Wed, 4 Jun 2025 18:36:47 -0700 Subject: [PATCH 04/17] fix(types): typo of `vOnce` and `vSlot` (#13343) --- packages/runtime-dom/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/runtime-dom/src/index.ts b/packages/runtime-dom/src/index.ts index ca9a307dd98..c69375983d8 100644 --- a/packages/runtime-dom/src/index.ts +++ b/packages/runtime-dom/src/index.ts @@ -58,8 +58,8 @@ declare module '@vue/runtime-core' { vOn: VOnDirective vBind: VModelDirective vIf: Directive - VOnce: Directive - VSlot: Directive + vOnce: Directive + vSlot: Directive } } From f05a8d613bd873b811cfdb9979ccac8382dba322 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=B1=E5=90=B9=E8=89=B2=E5=BE=A1=E5=AE=88?= <85992002+KazariEX@users.noreply.github.com> Date: Wed, 4 Jun 2025 18:39:05 -0700 Subject: [PATCH 05/17] fix(compiler-core): do not increase newlines in `InEntity` state (#13362) --- packages/compiler-core/__tests__/parse.spec.ts | 5 +++++ packages/compiler-core/src/tokenizer.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/compiler-core/__tests__/parse.spec.ts b/packages/compiler-core/__tests__/parse.spec.ts index 4e5a9616511..cdc2b09fd48 100644 --- a/packages/compiler-core/__tests__/parse.spec.ts +++ b/packages/compiler-core/__tests__/parse.spec.ts @@ -2271,6 +2271,11 @@ describe('compiler: parse', () => { expect(span.loc.start.offset).toBe(0) expect(span.loc.end.offset).toBe(27) }) + + test('correct loc when a line in attribute value ends with &', () => { + const [span] = baseParse(``).children + expect(span.loc.end.line).toBe(2) + }) }) describe('decodeEntities option', () => { diff --git a/packages/compiler-core/src/tokenizer.ts b/packages/compiler-core/src/tokenizer.ts index 329e8b48181..b8a74790259 100644 --- a/packages/compiler-core/src/tokenizer.ts +++ b/packages/compiler-core/src/tokenizer.ts @@ -929,7 +929,7 @@ export default class Tokenizer { this.buffer = input while (this.index < this.buffer.length) { const c = this.buffer.charCodeAt(this.index) - if (c === CharCodes.NewLine) { + if (c === CharCodes.NewLine && this.state !== State.InEntity) { this.newlines.push(this.index) } switch (this.state) { From 10ebcef8c870dbc042b0ea49b1424b2e8f692145 Mon Sep 17 00:00:00 2001 From: edison Date: Thu, 5 Jun 2025 09:44:25 +0800 Subject: [PATCH 06/17] fix(compiler-core): ignore whitespace when matching adjacent v-if (#12321) close #9173 --- .../__snapshots__/vSlot.spec.ts.snap | 22 +++++++++++++++++++ .../__tests__/transforms/vSlot.spec.ts | 14 ++++++++++++ .../compiler-core/src/transforms/vSlot.ts | 2 +- 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap index 0d9c0e743de..2cd13bab036 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap @@ -246,6 +246,28 @@ return function render(_ctx, _cache) { }" `; +exports[`compiler: transform component slots > with whitespace: 'preserve' > named slot with v-if + v-else 1`] = ` +"const { resolveComponent: _resolveComponent, withCtx: _withCtx, createSlots: _createSlots, openBlock: _openBlock, createBlock: _createBlock } = Vue + +return function render(_ctx, _cache) { + const _component_Comp = _resolveComponent("Comp") + + return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 2 /* DYNAMIC */ }, [ + ok + ? { + name: "one", + fn: _withCtx(() => ["foo"]), + key: "0" + } + : { + name: "two", + fn: _withCtx(() => ["baz"]), + key: "1" + } + ]), 1024 /* DYNAMIC_SLOTS */)) +}" +`; + exports[`compiler: transform component slots > with whitespace: 'preserve' > should not generate whitespace only default slot 1`] = ` "const { resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue diff --git a/packages/compiler-core/__tests__/transforms/vSlot.spec.ts b/packages/compiler-core/__tests__/transforms/vSlot.spec.ts index 4766c2ca9d8..e0f44a064fb 100644 --- a/packages/compiler-core/__tests__/transforms/vSlot.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vSlot.spec.ts @@ -988,5 +988,19 @@ describe('compiler: transform component slots', () => { expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot() }) + + test('named slot with v-if + v-else', () => { + const source = ` + + + + + ` + const { root } = parseWithSlots(source, { + whitespace: 'preserve', + }) + + expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot() + }) }) }) diff --git a/packages/compiler-core/src/transforms/vSlot.ts b/packages/compiler-core/src/transforms/vSlot.ts index 28625439a47..43296dcc9b6 100644 --- a/packages/compiler-core/src/transforms/vSlot.ts +++ b/packages/compiler-core/src/transforms/vSlot.ts @@ -222,7 +222,7 @@ export function buildSlots( let prev while (j--) { prev = children[j] - if (prev.type !== NodeTypes.COMMENT) { + if (prev.type !== NodeTypes.COMMENT && isNonWhitespaceContent(prev)) { break } } From 4eb46e443f1878199755cb73d481d318a9714392 Mon Sep 17 00:00:00 2001 From: edison Date: Thu, 5 Jun 2025 10:01:22 +0800 Subject: [PATCH 07/17] fix(compile-sfc): handle mapped types work with omit and pick (#12648) close #12647 --- .../compileScript/resolveType.spec.ts | 17 +++++++++++ .../compiler-sfc/src/script/resolveType.ts | 29 +++++++++++++++---- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts index 4ced9b8879a..68fc5cc3159 100644 --- a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts @@ -278,6 +278,23 @@ describe('resolveType', () => { }) }) + test('utility type: mapped type with Omit and Pick', () => { + expect( + resolve(` + type Optional = Omit & Partial> + interface Test { + foo: string; + bar?: string; + } + type OptionalTest = Optional + defineProps() + `).props, + ).toStrictEqual({ + foo: ['String'], + bar: ['String'], + }) + }) + test('utility type: ReadonlyArray', () => { expect( resolve(` diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index 3507a783c76..85832dfc39a 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -546,26 +546,43 @@ function resolveStringType( ctx: TypeResolveContext, node: Node, scope: TypeScope, + typeParameters?: Record, ): string[] { switch (node.type) { case 'StringLiteral': return [node.value] case 'TSLiteralType': - return resolveStringType(ctx, node.literal, scope) + return resolveStringType(ctx, node.literal, scope, typeParameters) case 'TSUnionType': - return node.types.map(t => resolveStringType(ctx, t, scope)).flat() + return node.types + .map(t => resolveStringType(ctx, t, scope, typeParameters)) + .flat() case 'TemplateLiteral': { return resolveTemplateKeys(ctx, node, scope) } case 'TSTypeReference': { const resolved = resolveTypeReference(ctx, node, scope) if (resolved) { - return resolveStringType(ctx, resolved, scope) + return resolveStringType(ctx, resolved, scope, typeParameters) } if (node.typeName.type === 'Identifier') { + const name = node.typeName.name + if (typeParameters && typeParameters[name]) { + return resolveStringType( + ctx, + typeParameters[name], + scope, + typeParameters, + ) + } const getParam = (index = 0) => - resolveStringType(ctx, node.typeParameters!.params[index], scope) - switch (node.typeName.name) { + resolveStringType( + ctx, + node.typeParameters!.params[index], + scope, + typeParameters, + ) + switch (name) { case 'Extract': return getParam(1) case 'Exclude': { @@ -671,6 +688,7 @@ function resolveBuiltin( ctx, node.typeParameters!.params[1], scope, + typeParameters, ) const res: ResolvedElements = { props: {}, calls: t.calls } for (const key of picked) { @@ -683,6 +701,7 @@ function resolveBuiltin( ctx, node.typeParameters!.params[1], scope, + typeParameters, ) const res: ResolvedElements = { props: {}, calls: t.calls } for (const key in t.props) { From 4aa7a4aba30d261b37c680e550333353a3709bf1 Mon Sep 17 00:00:00 2001 From: Arman Tang Date: Thu, 5 Jun 2025 10:01:51 +0800 Subject: [PATCH 08/17] chore(shared): update patch flag name (#12831) --- .../__snapshots__/cacheStatic.spec.ts.snap | 26 +++---- .../stringifyStatic.spec.ts.snap | 8 +- .../__snapshots__/compileScript.spec.ts.snap | 2 +- .../templateTransformAssetUrl.spec.ts.snap | 4 +- .../templateTransformSrcset.spec.ts.snap | 76 +++++++++---------- packages/shared/src/patchFlags.ts | 2 +- 6 files changed, 59 insertions(+), 59 deletions(-) diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/cacheStatic.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/cacheStatic.spec.ts.snap index 375a0c8674a..884727cfb40 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/cacheStatic.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/cacheStatic.spec.ts.snap @@ -8,7 +8,7 @@ return function render(_ctx, _cache) { const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [ - _createElementVNode("div", { key: "foo" }, null, -1 /* HOISTED */) + _createElementVNode("div", { key: "foo" }, null, -1 /* CACHED */) ]))) } }" @@ -25,11 +25,11 @@ return function render(_ctx, _cache) { _createElementVNode("p", null, [ _createElementVNode("span"), _createElementVNode("span") - ], -1 /* HOISTED */), + ], -1 /* CACHED */), _createElementVNode("p", null, [ _createElementVNode("span"), _createElementVNode("span") - ], -1 /* HOISTED */) + ], -1 /* CACHED */) ]))) } }" @@ -45,7 +45,7 @@ return function render(_ctx, _cache) { return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [ _createElementVNode("div", null, [ _createCommentVNode("comment") - ], -1 /* HOISTED */) + ], -1 /* CACHED */) ]))) } }" @@ -59,9 +59,9 @@ return function render(_ctx, _cache) { const { createElementVNode: _createElementVNode, createTextVNode: _createTextVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [ - _createElementVNode("span", null, null, -1 /* HOISTED */), + _createElementVNode("span", null, null, -1 /* CACHED */), _createTextVNode("foo"), - _createElementVNode("div", null, null, -1 /* HOISTED */) + _createElementVNode("div", null, null, -1 /* CACHED */) ]))) } }" @@ -75,7 +75,7 @@ return function render(_ctx, _cache) { const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [ - _createElementVNode("span", { class: "inline" }, "hello", -1 /* HOISTED */) + _createElementVNode("span", { class: "inline" }, "hello", -1 /* CACHED */) ]))) } }" @@ -148,7 +148,7 @@ return function render(_ctx, _cache) { const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [ - _createElementVNode("span", null, "foo " + _toDisplayString(1) + " " + _toDisplayString(true), -1 /* HOISTED */) + _createElementVNode("span", null, "foo " + _toDisplayString(1) + " " + _toDisplayString(true), -1 /* CACHED */) ]))) } }" @@ -162,7 +162,7 @@ return function render(_ctx, _cache) { const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [ - _createElementVNode("span", { foo: 0 }, _toDisplayString(1), -1 /* HOISTED */) + _createElementVNode("span", { foo: 0 }, _toDisplayString(1), -1 /* CACHED */) ]))) } }" @@ -178,7 +178,7 @@ return function render(_ctx, _cache) { return (_openBlock(), _createElementBlock("div", null, [ (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(1, (i) => { return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [ - _createElementVNode("span", { class: "hi" }, null, -1 /* HOISTED */) + _createElementVNode("span", { class: "hi" }, null, -1 /* CACHED */) ]))])) }), 256 /* UNKEYED_FRAGMENT */)) ])) @@ -216,7 +216,7 @@ return function render(_ctx, _cache) { return (_openBlock(), _createElementBlock("div", null, [ _withDirectives((_openBlock(), _createElementBlock("svg", null, _cache[0] || (_cache[0] = [ - _createElementVNode("path", { d: "M2,3H5.5L12" }, null, -1 /* HOISTED */) + _createElementVNode("path", { d: "M2,3H5.5L12" }, null, -1 /* CACHED */) ]))), [ [_directive_foo] ]) @@ -402,7 +402,7 @@ return function render(_ctx, _cache) { return (_openBlock(), _createElementBlock("div", null, [ ok ? (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [ - _createElementVNode("span", null, null, -1 /* HOISTED */) + _createElementVNode("span", null, null, -1 /* CACHED */) ]))) : _createCommentVNode("v-if", true) ])) @@ -423,7 +423,7 @@ return function render(_ctx, _cache) { return (_openBlock(), _createElementBlock("div", null, [ (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => { return (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [ - _createElementVNode("span", null, null, -1 /* HOISTED */) + _createElementVNode("span", null, null, -1 /* CACHED */) ]))) }), 256 /* UNKEYED_FRAGMENT */)) ])) diff --git a/packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap b/packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap index 2ed15ef5e62..6d77797aaca 100644 --- a/packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap +++ b/packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap @@ -6,7 +6,7 @@ exports[`stringify static html > eligible content (elements > 20) + non-eligible return function render(_ctx, _cache) { return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [ _createStaticVNode("", 20), - _createElementVNode("div", { key: "1" }, "1", -1 /* HOISTED */), + _createElementVNode("div", { key: "1" }, "1", -1 /* CACHED */), _createStaticVNode("", 20) ]))) }" @@ -54,7 +54,7 @@ return function render(_ctx, _cache) { _createElementVNode("option", { value: "1" }), _createElementVNode("option", { value: "1" }), _createElementVNode("option", { value: "1" }) - ], -1 /* HOISTED */) + ], -1 /* CACHED */) ]))) }" `; @@ -70,7 +70,7 @@ return function render(_ctx, _cache) { _createElementVNode("option", { value: 1 }), _createElementVNode("option", { value: 1 }), _createElementVNode("option", { value: 1 }) - ], -1 /* HOISTED */) + ], -1 /* CACHED */) ]))) }" `; @@ -87,7 +87,7 @@ return function render(_ctx, _cache) { _createElementVNode("span", { class: "foo" }, "foo"), _createElementVNode("span", { class: "foo" }, "foo"), _createElementVNode("img", { src: _imports_0_ }) - ], -1 /* HOISTED */) + ], -1 /* CACHED */) ]))) }" `; diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap index 7fcc1d39506..d9511e829df 100644 --- a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap @@ -861,7 +861,7 @@ export default { return (_ctx, _cache) => { return (_openBlock(), _createElementBlock(_Fragment, null, [ _createElementVNode("div", null, _toDisplayString(count.value), 1 /* TEXT */), - _cache[0] || (_cache[0] = _createElementVNode("div", null, "static", -1 /* HOISTED */)) + _cache[0] || (_cache[0] = _createElementVNode("div", null, "static", -1 /* CACHED */)) ], 64 /* STABLE_FRAGMENT */)) } } diff --git a/packages/compiler-sfc/__tests__/__snapshots__/templateTransformAssetUrl.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/templateTransformAssetUrl.spec.ts.snap index 85ae6194a70..18ec05da2b5 100644 --- a/packages/compiler-sfc/__tests__/__snapshots__/templateTransformAssetUrl.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/__snapshots__/templateTransformAssetUrl.spec.ts.snap @@ -41,8 +41,8 @@ const _hoisted_1 = _imports_0 + '#fragment' export function render(_ctx, _cache) { return (_openBlock(), _createElementBlock(_Fragment, null, [ - _cache[0] || (_cache[0] = _createElementVNode("use", { href: _hoisted_1 }, null, -1 /* HOISTED */)), - _cache[1] || (_cache[1] = _createElementVNode("use", { href: _hoisted_1 }, null, -1 /* HOISTED */)) + _cache[0] || (_cache[0] = _createElementVNode("use", { href: _hoisted_1 }, null, -1 /* CACHED */)), + _cache[1] || (_cache[1] = _createElementVNode("use", { href: _hoisted_1 }, null, -1 /* CACHED */)) ], 64 /* STABLE_FRAGMENT */)) }" `; diff --git a/packages/compiler-sfc/__tests__/__snapshots__/templateTransformSrcset.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/templateTransformSrcset.spec.ts.snap index 81cdb51b9ca..0469ffaba88 100644 --- a/packages/compiler-sfc/__tests__/__snapshots__/templateTransformSrcset.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/__snapshots__/templateTransformSrcset.spec.ts.snap @@ -10,8 +10,8 @@ const _hoisted_2 = _imports_0 + ' 1x, ' + "/foo/logo.png" + ' 2x' export function render(_ctx, _cache) { return (_openBlock(), _createElementBlock(_Fragment, null, [ - _cache[0] || (_cache[0] = _createElementVNode("img", { srcset: _hoisted_1 }, null, -1 /* HOISTED */)), - _cache[1] || (_cache[1] = _createElementVNode("img", { srcset: _hoisted_2 }, null, -1 /* HOISTED */)) + _cache[0] || (_cache[0] = _createElementVNode("img", { srcset: _hoisted_1 }, null, -1 /* CACHED */)), + _cache[1] || (_cache[1] = _createElementVNode("img", { srcset: _hoisted_2 }, null, -1 /* CACHED */)) ], 64 /* STABLE_FRAGMENT */)) }" `; @@ -35,51 +35,51 @@ export function render(_ctx, _cache) { _cache[0] || (_cache[0] = _createElementVNode("img", { src: "./logo.png", srcset: "" - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[1] || (_cache[1] = _createElementVNode("img", { src: "./logo.png", srcset: _hoisted_1 - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[2] || (_cache[2] = _createElementVNode("img", { src: "./logo.png", srcset: _hoisted_2 - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[3] || (_cache[3] = _createElementVNode("img", { src: "./logo.png", srcset: _hoisted_3 - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[4] || (_cache[4] = _createElementVNode("img", { src: "./logo.png", srcset: _hoisted_4 - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[5] || (_cache[5] = _createElementVNode("img", { src: "./logo.png", srcset: _hoisted_5 - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[6] || (_cache[6] = _createElementVNode("img", { src: "./logo.png", srcset: _hoisted_6 - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[7] || (_cache[7] = _createElementVNode("img", { src: "./logo.png", srcset: _hoisted_7 - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[8] || (_cache[8] = _createElementVNode("img", { src: "/logo.png", srcset: "/logo.png, /logo.png 2x" - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[9] || (_cache[9] = _createElementVNode("img", { src: "https://example.com/logo.png", srcset: "https://example.com/logo.png, https://example.com/logo.png 2x" - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[10] || (_cache[10] = _createElementVNode("img", { src: "/logo.png", srcset: _hoisted_8 - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[11] || (_cache[11] = _createElementVNode("img", { src: "data:image/png;base64,i", srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x" - }, null, -1 /* HOISTED */)) + }, null, -1 /* CACHED */)) ], 64 /* STABLE_FRAGMENT */)) }" `; @@ -92,51 +92,51 @@ export function render(_ctx, _cache) { _cache[0] || (_cache[0] = _createElementVNode("img", { src: "./logo.png", srcset: "" - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[1] || (_cache[1] = _createElementVNode("img", { src: "./logo.png", srcset: "/foo/logo.png" - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[2] || (_cache[2] = _createElementVNode("img", { src: "./logo.png", srcset: "/foo/logo.png 2x" - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[3] || (_cache[3] = _createElementVNode("img", { src: "./logo.png", srcset: "/foo/logo.png 2x" - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[4] || (_cache[4] = _createElementVNode("img", { src: "./logo.png", srcset: "/foo/logo.png, /foo/logo.png 2x" - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[5] || (_cache[5] = _createElementVNode("img", { src: "./logo.png", srcset: "/foo/logo.png 2x, /foo/logo.png" - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[6] || (_cache[6] = _createElementVNode("img", { src: "./logo.png", srcset: "/foo/logo.png 2x, /foo/logo.png 3x" - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[7] || (_cache[7] = _createElementVNode("img", { src: "./logo.png", srcset: "/foo/logo.png, /foo/logo.png 2x, /foo/logo.png 3x" - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[8] || (_cache[8] = _createElementVNode("img", { src: "/logo.png", srcset: "/logo.png, /logo.png 2x" - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[9] || (_cache[9] = _createElementVNode("img", { src: "https://example.com/logo.png", srcset: "https://example.com/logo.png, https://example.com/logo.png 2x" - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[10] || (_cache[10] = _createElementVNode("img", { src: "/logo.png", srcset: "/logo.png, /foo/logo.png 2x" - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[11] || (_cache[11] = _createElementVNode("img", { src: "data:image/png;base64,i", srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x" - }, null, -1 /* HOISTED */)) + }, null, -1 /* CACHED */)) ], 64 /* STABLE_FRAGMENT */)) }" `; @@ -162,51 +162,51 @@ export function render(_ctx, _cache) { _cache[0] || (_cache[0] = _createElementVNode("img", { src: "./logo.png", srcset: "" - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[1] || (_cache[1] = _createElementVNode("img", { src: "./logo.png", srcset: _hoisted_1 - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[2] || (_cache[2] = _createElementVNode("img", { src: "./logo.png", srcset: _hoisted_2 - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[3] || (_cache[3] = _createElementVNode("img", { src: "./logo.png", srcset: _hoisted_3 - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[4] || (_cache[4] = _createElementVNode("img", { src: "./logo.png", srcset: _hoisted_4 - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[5] || (_cache[5] = _createElementVNode("img", { src: "./logo.png", srcset: _hoisted_5 - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[6] || (_cache[6] = _createElementVNode("img", { src: "./logo.png", srcset: _hoisted_6 - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[7] || (_cache[7] = _createElementVNode("img", { src: "./logo.png", srcset: _hoisted_7 - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[8] || (_cache[8] = _createElementVNode("img", { src: "/logo.png", srcset: _hoisted_8 - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[9] || (_cache[9] = _createElementVNode("img", { src: "https://example.com/logo.png", srcset: "https://example.com/logo.png, https://example.com/logo.png 2x" - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[10] || (_cache[10] = _createElementVNode("img", { src: "/logo.png", srcset: _hoisted_9 - }, null, -1 /* HOISTED */)), + }, null, -1 /* CACHED */)), _cache[11] || (_cache[11] = _createElementVNode("img", { src: "data:image/png;base64,i", srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x" - }, null, -1 /* HOISTED */)) + }, null, -1 /* CACHED */)) ], 64 /* STABLE_FRAGMENT */)) }" `; diff --git a/packages/shared/src/patchFlags.ts b/packages/shared/src/patchFlags.ts index fc22f10de40..289da19029d 100644 --- a/packages/shared/src/patchFlags.ts +++ b/packages/shared/src/patchFlags.ts @@ -139,6 +139,6 @@ export const PatchFlagNames: Record = { [PatchFlags.NEED_PATCH]: `NEED_PATCH`, [PatchFlags.DYNAMIC_SLOTS]: `DYNAMIC_SLOTS`, [PatchFlags.DEV_ROOT_FRAGMENT]: `DEV_ROOT_FRAGMENT`, - [PatchFlags.CACHED]: `HOISTED`, + [PatchFlags.CACHED]: `CACHED`, [PatchFlags.BAIL]: `BAIL`, } From 73055d8d9578d485e3fe846726b50666e1aa56f5 Mon Sep 17 00:00:00 2001 From: edison Date: Thu, 5 Jun 2025 10:02:26 +0800 Subject: [PATCH 09/17] fix(custom-element): prevent injecting child styles if shadowRoot is false (#12769) close #12630 --- packages/runtime-core/src/renderer.ts | 7 +++++- .../__tests__/customElement.spec.ts | 24 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 7b39aa917a2..56f9759bc55 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -86,6 +86,7 @@ import { isAsyncWrapper } from './apiAsyncComponent' import { isCompatEnabled } from './compat/compatConfig' import { DeprecationTypes } from './compat/compatConfig' import type { TransitionHooks } from './components/BaseTransition' +import type { VueElement } from '@vue/runtime-dom' export interface Renderer { render: RootRenderFunction @@ -1348,7 +1349,11 @@ function baseCreateRenderer( } } else { // custom element style injection - if (root.ce) { + if ( + root.ce && + // @ts-expect-error _def is private + (root.ce as VueElement)._def.shadowRoot !== false + ) { root.ce._injectChildStyle(type) } diff --git a/packages/runtime-dom/__tests__/customElement.spec.ts b/packages/runtime-dom/__tests__/customElement.spec.ts index eee2151716e..a2b4c263699 100644 --- a/packages/runtime-dom/__tests__/customElement.spec.ts +++ b/packages/runtime-dom/__tests__/customElement.spec.ts @@ -916,6 +916,30 @@ describe('defineCustomElement', () => { assertStyles(el, [`div { color: blue; }`, `div { color: red; }`]) }) + test("child components should not inject styles to root element's shadow root w/ shadowRoot false", async () => { + const Bar = defineComponent({ + styles: [`div { color: green; }`], + render() { + return 'bar' + }, + }) + const Baz = () => h(Bar) + const Foo = defineCustomElement( + { + render() { + return [h(Baz)] + }, + }, + { shadowRoot: false }, + ) + + customElements.define('my-foo-with-shadowroot-false', Foo) + container.innerHTML = `` + const el = container.childNodes[0] as VueElement + const style = el.shadowRoot?.querySelector('style') + expect(style).toBeUndefined() + }) + test('with nonce', () => { const Foo = defineCustomElement( { From 5ba1afba09c3ea56c1c17484f5d8aeae210ce52a Mon Sep 17 00:00:00 2001 From: edison Date: Thu, 5 Jun 2025 10:10:52 +0800 Subject: [PATCH 10/17] fix(custom-element): ensure configureApp is applied to async component (#12607) close #12448 --- .../__tests__/customElement.spec.ts | 23 +++++++++++++++++++ packages/runtime-dom/src/apiCustomElement.ts | 7 +++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/packages/runtime-dom/__tests__/customElement.spec.ts b/packages/runtime-dom/__tests__/customElement.spec.ts index a2b4c263699..c44840df5e3 100644 --- a/packages/runtime-dom/__tests__/customElement.spec.ts +++ b/packages/runtime-dom/__tests__/customElement.spec.ts @@ -1566,6 +1566,29 @@ describe('defineCustomElement', () => { expect(e.shadowRoot?.innerHTML).toBe('
app-injected
') }) + // #12448 + test('work with async component', async () => { + const AsyncComp = defineAsyncComponent(() => { + return Promise.resolve({ + render() { + const msg: string | undefined = inject('msg') + return h('div', {}, msg) + }, + } as any) + }) + const E = defineCustomElement(AsyncComp, { + configureApp(app) { + app.provide('msg', 'app-injected') + }, + }) + customElements.define('my-async-element-with-app', E) + + container.innerHTML = `` + const e = container.childNodes[0] as VueElement + await new Promise(r => setTimeout(r)) + expect(e.shadowRoot?.innerHTML).toBe('
app-injected
') + }) + test('with hmr reload', async () => { const __hmrId = '__hmrWithApp' const def = defineComponent({ diff --git a/packages/runtime-dom/src/apiCustomElement.ts b/packages/runtime-dom/src/apiCustomElement.ts index 56b86a5fd9e..edf7c431353 100644 --- a/packages/runtime-dom/src/apiCustomElement.ts +++ b/packages/runtime-dom/src/apiCustomElement.ts @@ -404,9 +404,10 @@ export class VueElement const asyncDef = (this._def as ComponentOptions).__asyncLoader if (asyncDef) { - this._pendingResolve = asyncDef().then(def => - resolve((this._def = def), true), - ) + this._pendingResolve = asyncDef().then((def: InnerComponentDef) => { + def.configureApp = this._def.configureApp + resolve((this._def = def), true) + }) } else { resolve(this._def) } From e8d8f5f604e821acc46b4200d5b06979c05af1c2 Mon Sep 17 00:00:00 2001 From: SerKo <44749100+serkodev@users.noreply.github.com> Date: Thu, 5 Jun 2025 10:19:16 +0800 Subject: [PATCH 11/17] fix(reactivity): add `__v_skip` flag to `Dep` to prevent reactive conversion (#12804) close #12803 --- packages/reactivity/__tests__/readonly.spec.ts | 15 +++++++++++++++ packages/reactivity/src/dep.ts | 6 ++++++ 2 files changed, 21 insertions(+) diff --git a/packages/reactivity/__tests__/readonly.spec.ts b/packages/reactivity/__tests__/readonly.spec.ts index 9acd5c6491a..b035779f85a 100644 --- a/packages/reactivity/__tests__/readonly.spec.ts +++ b/packages/reactivity/__tests__/readonly.spec.ts @@ -8,7 +8,9 @@ import { reactive, readonly, ref, + shallowRef, toRaw, + triggerRef, } from '../src' /** @@ -520,3 +522,16 @@ describe('reactivity/readonly', () => { expect(r.value).toBe(ro) }) }) + +test('should be able to trigger with triggerRef', () => { + const r = shallowRef({ a: 1 }) + const ror = readonly(r) + let dummy + effect(() => { + dummy = ror.value.a + }) + r.value.a = 2 + expect(dummy).toBe(1) + triggerRef(ror) + expect(dummy).toBe(2) +}) diff --git a/packages/reactivity/src/dep.ts b/packages/reactivity/src/dep.ts index 196c2aaf98e..8ad8a389baf 100644 --- a/packages/reactivity/src/dep.ts +++ b/packages/reactivity/src/dep.ts @@ -93,6 +93,12 @@ export class Dep { */ sc: number = 0 + /** + * @internal + */ + readonly __v_skip = true + // TODO isolatedDeclarations ReactiveFlags.SKIP + constructor(public computed?: ComputedRefImpl | undefined) { if (__DEV__) { this.subsHead = undefined From 47ddf986021dff8de68b0da72787e53a6c19de4c Mon Sep 17 00:00:00 2001 From: inottn Date: Thu, 5 Jun 2025 10:19:48 +0800 Subject: [PATCH 12/17] fix(runtime-core): unset old ref during patching when new ref is absent (#12900) fix #12898 --- .../__tests__/rendererTemplateRef.spec.ts | 31 +++++++++++++++++++ packages/runtime-core/src/renderer.ts | 2 ++ 2 files changed, 33 insertions(+) diff --git a/packages/runtime-core/__tests__/rendererTemplateRef.spec.ts b/packages/runtime-core/__tests__/rendererTemplateRef.spec.ts index a7ae7a06bfd..7803826e3df 100644 --- a/packages/runtime-core/__tests__/rendererTemplateRef.spec.ts +++ b/packages/runtime-core/__tests__/rendererTemplateRef.spec.ts @@ -179,6 +179,37 @@ describe('api: template refs', () => { expect(el.value).toBe(null) }) + it('unset old ref when new ref is absent', async () => { + const root1 = nodeOps.createElement('div') + const root2 = nodeOps.createElement('div') + const el1 = ref(null) + const el2 = ref(null) + const toggle = ref(true) + + const Comp1 = { + setup() { + return () => (toggle.value ? h('div', { ref: el1 }) : h('div')) + }, + } + + const Comp2 = { + setup() { + return () => h('div', { ref: toggle.value ? el2 : undefined }) + }, + } + + render(h(Comp1), root1) + render(h(Comp2), root2) + + expect(el1.value).toBe(root1.children[0]) + expect(el2.value).toBe(root2.children[0]) + + toggle.value = false + await nextTick() + expect(el1.value).toBe(null) + expect(el2.value).toBe(null) + }) + test('string ref inside slots', async () => { const root = nodeOps.createElement('div') const spy = vi.fn() diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 56f9759bc55..a57be791a44 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -485,6 +485,8 @@ function baseCreateRenderer( // set ref if (ref != null && parentComponent) { setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2) + } else if (ref == null && n1 && n1.ref != null) { + setRef(n1.ref, null, parentSuspense, n1, true) } } From 55dad625acd9e9ddd5a933d5e323ecfdec1a612f Mon Sep 17 00:00:00 2001 From: Alex Snezhko Date: Wed, 4 Jun 2025 22:23:00 -0400 Subject: [PATCH 13/17] fix(compiler-core): prevent comments from blocking static node hoisting (#13345) close #13344 --- .../__snapshots__/cacheStatic.spec.ts.snap | 26 +++++++++++++++++++ .../__tests__/transforms/cacheStatic.spec.ts | 26 +++++++++++++++++++ packages/compiler-core/src/transform.ts | 10 +++---- .../src/transforms/cacheStatic.ts | 19 +++++++------- .../stringifyStatic.spec.ts.snap | 16 ++++++++++++ .../transforms/stringifyStatic.spec.ts | 10 +++++++ 6 files changed, 92 insertions(+), 15 deletions(-) diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/cacheStatic.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/cacheStatic.spec.ts.snap index 884727cfb40..daa0b42245d 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/cacheStatic.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/cacheStatic.spec.ts.snap @@ -410,6 +410,32 @@ return function render(_ctx, _cache) { }" `; +exports[`compiler: cacheStatic transform > should hoist props for root with single element excluding comments 1`] = ` +"const _Vue = Vue +const { createElementVNode: _createElementVNode, createCommentVNode: _createCommentVNode } = _Vue + +const _hoisted_1 = { id: "a" } + +return function render(_ctx, _cache) { + with (_ctx) { + const { createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue + + return (_openBlock(), _createElementBlock(_Fragment, null, [ + _createCommentVNode("comment"), + _createElementVNode("div", _hoisted_1, _cache[0] || (_cache[0] = [ + _createElementVNode("div", { id: "b" }, [ + _createElementVNode("div", { id: "c" }, [ + _createElementVNode("div", { id: "d" }, [ + _createElementVNode("div", { id: "e" }, "hello") + ]) + ]) + ], -1 /* HOISTED */) + ])) + ], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */)) + } +}" +`; + exports[`compiler: cacheStatic transform > should hoist v-for children if static 1`] = ` "const _Vue = Vue const { createElementVNode: _createElementVNode } = _Vue diff --git a/packages/compiler-core/__tests__/transforms/cacheStatic.spec.ts b/packages/compiler-core/__tests__/transforms/cacheStatic.spec.ts index 358c0e31c3d..74f6caca328 100644 --- a/packages/compiler-core/__tests__/transforms/cacheStatic.spec.ts +++ b/packages/compiler-core/__tests__/transforms/cacheStatic.spec.ts @@ -543,6 +543,32 @@ describe('compiler: cacheStatic transform', () => { expect(generate(root).code).toMatchSnapshot() }) + test('should hoist props for root with single element excluding comments', () => { + // deeply nested div to trigger stringification condition + const root = transformWithCache( + `
hello
`, + ) + expect(root.cached.length).toBe(1) + expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'a' })]) + + expect((root.codegenNode as VNodeCall).children).toMatchObject([ + { + type: NodeTypes.COMMENT, + content: 'comment', + }, + { + type: NodeTypes.ELEMENT, + codegenNode: { + type: NodeTypes.VNODE_CALL, + tag: `"div"`, + props: { content: `_hoisted_1` }, + children: { type: NodeTypes.JS_CACHE_EXPRESSION }, + }, + }, + ]) + expect(generate(root).code).toMatchSnapshot() + }) + describe('prefixIdentifiers', () => { test('cache nested static tree with static interpolation', () => { const root = transformWithCache( diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts index aeb96cc2b4a..9d8fd842935 100644 --- a/packages/compiler-core/src/transform.ts +++ b/packages/compiler-core/src/transform.ts @@ -37,7 +37,7 @@ import { helperNameMap, } from './runtimeHelpers' import { isVSlot } from './utils' -import { cacheStatic, isSingleElementRoot } from './transforms/cacheStatic' +import { cacheStatic, getSingleElementRoot } from './transforms/cacheStatic' import type { CompilerCompatOptions } from './compat/compatConfig' // There are two types of transforms: @@ -356,12 +356,12 @@ function createRootCodegen(root: RootNode, context: TransformContext) { const { helper } = context const { children } = root if (children.length === 1) { - const child = children[0] + const singleElementRootChild = getSingleElementRoot(root) // if the single child is an element, turn it into a block. - if (isSingleElementRoot(root, child) && child.codegenNode) { + if (singleElementRootChild && singleElementRootChild.codegenNode) { // single element root is never hoisted so codegenNode will never be // SimpleExpressionNode - const codegenNode = child.codegenNode + const codegenNode = singleElementRootChild.codegenNode if (codegenNode.type === NodeTypes.VNODE_CALL) { convertToBlock(codegenNode, context) } @@ -370,7 +370,7 @@ function createRootCodegen(root: RootNode, context: TransformContext) { // - single , IfNode, ForNode: already blocks. // - single text node: always patched. // root codegen falls through via genNode() - root.codegenNode = child + root.codegenNode = children[0] } } else if (children.length > 1) { // root has multiple nodes - return a fragment block. diff --git a/packages/compiler-core/src/transforms/cacheStatic.ts b/packages/compiler-core/src/transforms/cacheStatic.ts index e5d67380640..239ee689a9f 100644 --- a/packages/compiler-core/src/transforms/cacheStatic.ts +++ b/packages/compiler-core/src/transforms/cacheStatic.ts @@ -41,20 +41,19 @@ export function cacheStatic(root: RootNode, context: TransformContext): void { context, // Root node is unfortunately non-hoistable due to potential parent // fallthrough attributes. - isSingleElementRoot(root, root.children[0]), + !!getSingleElementRoot(root), ) } -export function isSingleElementRoot( +export function getSingleElementRoot( root: RootNode, - child: TemplateChildNode, -): child is PlainElementNode | ComponentNode | TemplateNode { - const { children } = root - return ( - children.length === 1 && - child.type === NodeTypes.ELEMENT && - !isSlotOutlet(child) - ) +): PlainElementNode | ComponentNode | TemplateNode | null { + const children = root.children.filter(x => x.type !== NodeTypes.COMMENT) + return children.length === 1 && + children[0].type === NodeTypes.ELEMENT && + !isSlotOutlet(children[0]) + ? children[0] + : null } function walk( diff --git a/packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap b/packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap index 6d77797aaca..5bc40d3fab5 100644 --- a/packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap +++ b/packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap @@ -75,6 +75,22 @@ return function render(_ctx, _cache) { }" `; +exports[`stringify static html > should bail for comments 1`] = ` +"const { createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue + +const _hoisted_1 = { class: "a" } + +return function render(_ctx, _cache) { + return (_openBlock(), _createElementBlock(_Fragment, null, [ + _createCommentVNode(" Comment 1 "), + _createElementVNode("div", _hoisted_1, [ + _createCommentVNode(" Comment 2 "), + _cache[0] || (_cache[0] = _createStaticVNode("", 5)) + ]) + ], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */)) +}" +`; + exports[`stringify static html > should bail on bindings that are cached but not stringifiable 1`] = ` "const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue diff --git a/packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts b/packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts index 79e6fc9c6e8..f58e207d6cf 100644 --- a/packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts +++ b/packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts @@ -491,6 +491,16 @@ describe('stringify static html', () => { expect(code).toMatchSnapshot() }) + test('should bail for comments', () => { + const { code } = compileWithStringify( + `
${repeat( + ``, + StringifyThresholds.ELEMENT_WITH_BINDING_COUNT, + )}
`, + ) + expect(code).toMatchSnapshot() + }) + test('should bail for