From: daiwei Date: Tue, 20 Jan 2026 00:41:58 +0000 (+0800) Subject: fix(compiler-vapor): prevent end tag omission for scope boundary elements X-Git-Tag: v3.6.0-beta.4~8 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3d550db31ab40293179e099469e39eaa74b5071d;p=thirdparty%2Fvuejs%2Fcore.git fix(compiler-vapor): prevent end tag omission for scope boundary elements --- diff --git a/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap b/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap index 4ae24a007f..61f944aac4 100644 --- a/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap @@ -215,7 +215,7 @@ export function render(_ctx) { exports[`compile > execution order > setInsertionState > next, child and nthChild should be above the setInsertionState 1`] = ` "import { resolveComponent as _resolveComponent, child as _child, next as _next, setInsertionState as _setInsertionState, createComponentWithFallback as _createComponentWithFallback, nthChild as _nthChild, createIf as _createIf, setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue'; const t0 = _template("
") -const t1 = _template("
", true) export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") diff --git a/packages/compiler-vapor/__tests__/abbreviation.spec.ts b/packages/compiler-vapor/__tests__/abbreviation.spec.ts index b0a8cc920f..c125424861 100644 --- a/packages/compiler-vapor/__tests__/abbreviation.spec.ts +++ b/packages/compiler-vapor/__tests__/abbreviation.spec.ts @@ -129,3 +129,54 @@ test('deeply nested', () => { '
ab
', ) }) + +test('always close tags', () => { + // button always needs closing tag + checkAbbr( + '
', + '
', + '
', + ) + + // select always needs closing tag + checkAbbr( + '
', + '
', + '
', + ) + + // table always needs closing tag + checkAbbr( + '
', + '
', + '
', + ) + + // textarea always needs closing tag + checkAbbr( + '
', + '
', + '
', + ) + + // template always needs closing tag + checkAbbr( + '
', + '
', + '
', + ) + + // script always needs closing tag + checkAbbr( + '
', + '
', + '
', + ) + + // without always-close elements, normal abbreviation should work + checkAbbr( + '
', + '
', + '
', + ) +}) diff --git a/packages/compiler-vapor/src/transforms/transformElement.ts b/packages/compiler-vapor/src/transforms/transformElement.ts index 81ae8295a3..f7e22f2bb9 100644 --- a/packages/compiler-vapor/src/transforms/transformElement.ts +++ b/packages/compiler-vapor/src/transforms/transformElement.ts @@ -20,6 +20,7 @@ import { camelize, capitalize, extend, + isAlwaysCloseTag, isBuiltInDirective, isFormattingTag, isVoidTag, @@ -138,6 +139,12 @@ function canOmitEndTag( return true } + // Elements in the alwaysClose list cannot have their end tags omitted + // because the browser's HTML parser has special handling for them + if (isAlwaysCloseTag(node.tag)) { + return false + } + // Formatting tags and same-name nested tags require explicit closing // unless on the rightmost path of the tree: // - Formatting tags: https://html.spec.whatwg.org/multipage/parsing.html#reconstruct-the-active-formatting-elements diff --git a/packages/shared/src/domTagConfig.ts b/packages/shared/src/domTagConfig.ts index d36eae915a..b2a9e78e06 100644 --- a/packages/shared/src/domTagConfig.ts +++ b/packages/shared/src/domTagConfig.ts @@ -41,6 +41,15 @@ const VOID_TAGS = // https://html.spec.whatwg.org/multipage/parsing.html#formatting const FORMATTING_TAGS = 'a,b,big,code,em,font,i,nobr,s,small,strike,strong,tt,u' +// Elements that always require explicit closing tags due to HTML parsing rules. +// These include: +// - Formatting elements (a, b, i, etc.) - handled by FORMATTING_TAGS +// - Elements with special parsing rules +// - Scope boundary elements +const ALWAYS_CLOSE_TAGS = + 'title,style,script,noscript,template,' + // raw text / special parsing + 'object,table,button,textarea,select,iframe,fieldset' // scope boundary / form elements + /** * Compiler only. * Do NOT use in runtime code paths unless behind `__DEV__` flag. @@ -71,3 +80,9 @@ export const isVoidTag: (key: string) => boolean = */ export const isFormattingTag: (key: string) => boolean = /*@__PURE__*/ makeMap(FORMATTING_TAGS) +/** + * Compiler only. + * Do NOT use in runtime code paths unless behind `__DEV__` flag. + */ +export const isAlwaysCloseTag: (key: string) => boolean = + /*@__PURE__*/ makeMap(ALWAYS_CLOSE_TAGS)