Explorar el Código

fix(compiler-vapor): prevent end tag omission for scope boundary elements

daiwei hace 3 meses
padre
commit
3d550db31a

+ 1 - 1
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("<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")

+ 51 - 0
packages/compiler-vapor/__tests__/abbreviation.spec.ts

@@ -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>',
+  )
+})

+ 7 - 0
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

+ 15 - 0
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)