]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compiler-vapor): prevent end tag omission for scope boundary elements
authordaiwei <daiwei521@126.com>
Tue, 20 Jan 2026 00:41:58 +0000 (08:41 +0800)
committeredison <daiwei521@126.com>
Tue, 20 Jan 2026 02:36:55 +0000 (10:36 +0800)
packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap
packages/compiler-vapor/__tests__/abbreviation.spec.ts
packages/compiler-vapor/src/transforms/transformElement.ts
packages/shared/src/domTagConfig.ts

index 4ae24a007f1e4a16de9777066069a8c959d766bb..61f944aac4683df1101bb405012345519089076e 100644 (file)
@@ -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("<div>")
-const t1 = _template("<div><div></div><!><div></div><!><div><button>", true)
+const t1 = _template("<div><div></div><!><div></div><!><div><button></button>", true)
 
 export function render(_ctx) {
   const _component_Comp = _resolveComponent("Comp")
index b0a8cc920fec4ab936356ef4d5a9b5635541dbd6..c125424861e3b4279e1137cf9d39e0203a697f26 100644 (file)
@@ -129,3 +129,54 @@ test('deeply nested', () => {
     '<div><div><span>a</span><span>b</span></div></div>',
   )
 })
+
+test('always close tags', () => {
+  // button always needs closing tag
+  checkAbbr(
+    '<div><button>click</button></div>',
+    '<div><button>click</button>',
+    '<div><button>click</button></div>',
+  )
+
+  // select always needs closing tag
+  checkAbbr(
+    '<div><select></select></div>',
+    '<div><select></select>',
+    '<div><select></select></div>',
+  )
+
+  // table always needs closing tag
+  checkAbbr(
+    '<div><table></table></div>',
+    '<div><table></table>',
+    '<div><table></table></div>',
+  )
+
+  // textarea always needs closing tag
+  checkAbbr(
+    '<div><textarea></textarea></div>',
+    '<div><textarea></textarea>',
+    '<div><textarea></textarea></div>',
+  )
+
+  // template always needs closing tag
+  checkAbbr(
+    '<div><template></template></div>',
+    '<div><template></template>',
+    '<div><template></template></div>',
+  )
+
+  // script always needs closing tag
+  checkAbbr(
+    '<div><script></script></div>',
+    '<div><script></script>',
+    '<div><script></script></div>',
+  )
+
+  // without always-close elements, normal abbreviation should work
+  checkAbbr(
+    '<div><form><input></form></div>',
+    '<div><form><input>',
+    '<div><form><input></form></div>',
+  )
+})
index 81ae8295a3057ae9767e4d8184cfa19c318591b5..f7e22f2bb95be8301cef6cdaf6c5e11a58ad8557 100644 (file)
@@ -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
index d36eae915aac5d7087443aed9055d936b421a673..b2a9e78e06e49c14c480cdff35dc0659c3039b41 100644 (file)
@@ -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)