]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compiler-vapor): do not escape quotes in root-level text nodes (#14310)
authoredison <daiwei521@126.com>
Wed, 14 Jan 2026 00:57:29 +0000 (08:57 +0800)
committerGitHub <noreply@github.com>
Wed, 14 Jan 2026 00:57:29 +0000 (08:57 +0800)
close #14309

packages/compiler-vapor/__tests__/transforms/transformText.spec.ts
packages/compiler-vapor/src/transforms/transformText.ts

index 8af9415e36f35c8992085e12eb3faebf1e211207..8d2ca1fa7f71f0912893fe2597eb9752d6ffe124 100644 (file)
@@ -5,13 +5,19 @@ import {
   transformElement,
   transformText,
   transformVBind,
+  transformVIf,
   transformVOn,
 } from '../../src'
 
 import { makeCompile } from './_utils'
 
 const compileWithTextTransform = makeCompile({
-  nodeTransforms: [transformElement, transformChildren, transformText],
+  nodeTransforms: [
+    transformVIf,
+    transformElement,
+    transformChildren,
+    transformText,
+  ],
   directiveTransforms: {
     bind: transformVBind,
     on: transformVOn,
@@ -54,6 +60,35 @@ describe('compiler: text transform', () => {
     expect([...ir.template.keys()]).not.toContain('<code><script>')
   })
 
+  it('should not escape quotes in root-level text nodes', () => {
+    // Root-level text goes through createTextNode() which doesn't need escaping
+    const { ir } = compileWithTextTransform(`Howdy y'all`)
+    expect([...ir.template.keys()]).toContain(`Howdy y'all`)
+    expect([...ir.template.keys()]).not.toContain(`Howdy y&#39;all`)
+  })
+
+  it('should not escape double quotes in root-level text nodes', () => {
+    const { ir } = compileWithTextTransform(`Say "hello"`)
+    expect([...ir.template.keys()]).toContain(`Say "hello"`)
+    expect([...ir.template.keys()]).not.toContain(`Say &quot;hello&quot;`)
+  })
+
+  it('should not escape quotes in template v-if text', () => {
+    // Text inside <template> tag also goes through createTextNode()
+    const { code } = compileWithTextTransform(
+      `<template v-if="ok">Howdy y'all</template>`,
+    )
+    expect(code).toContain(`Howdy y'all`)
+    expect(code).not.toContain(`Howdy y&#39;all`)
+  })
+
+  it('should not escape quotes in component slot text', () => {
+    // Text inside component (slot content) also goes through createTextNode()
+    const { ir } = compileWithTextTransform(`<Comp>Howdy y'all</Comp>`)
+    expect([...ir.template.keys()]).toContain(`Howdy y'all`)
+    expect([...ir.template.keys()]).not.toContain(`Howdy y&#39;all`)
+  })
+
   test('constant text', () => {
     const { code } = compileWithTextTransform(
       `
index b22956305428580c9be1e7dbf6237323d5097c07..b71abfdc1e478d96471b06ee4e113ebda1b52452 100644 (file)
@@ -79,7 +79,18 @@ export const transformText: NodeTransform = (node, context) => {
   } else if (node.type === NodeTypes.INTERPOLATION) {
     processInterpolation(context as TransformContext<InterpolationNode>)
   } else if (node.type === NodeTypes.TEXT) {
-    context.template += escapeHtml(node.content)
+    // Check if this is a root-level text node (parent is ROOT or fragment)
+    // Root-level text nodes go through createTextNode() which doesn't need escaping
+    // Element children go through innerHTML which needs escaping
+    const parent = context.parent?.node
+    const isRootText =
+      !parent ||
+      parent.type === NodeTypes.ROOT ||
+      (parent.type === NodeTypes.ELEMENT &&
+        (parent.tagType === ElementTypes.TEMPLATE ||
+          parent.tagType === ElementTypes.COMPONENT))
+
+    context.template += isRootText ? node.content : escapeHtml(node.content)
   }
 }