diff --git a/packages/compiler-core/__tests__/parse.spec.ts b/packages/compiler-core/__tests__/parse.spec.ts
index cdc2b09fd48..306d8089ff2 100644
--- a/packages/compiler-core/__tests__/parse.spec.ts
+++ b/packages/compiler-core/__tests__/parse.spec.ts
@@ -7,7 +7,6 @@ import {
type ElementNode,
ElementTypes,
type InterpolationNode,
- Namespaces,
NodeTypes,
type Position,
type TextNode,
@@ -15,6 +14,7 @@ import {
import { baseParse } from '../src/parser'
import type { Program } from '@babel/types'
+import { Namespaces } from '@vue/shared'
describe('compiler: parse', () => {
describe('Text', () => {
diff --git a/packages/compiler-core/__tests__/testUtils.ts b/packages/compiler-core/__tests__/testUtils.ts
index a2525e0cab9..6ef41b48b07 100644
--- a/packages/compiler-core/__tests__/testUtils.ts
+++ b/packages/compiler-core/__tests__/testUtils.ts
@@ -1,7 +1,6 @@
import {
type ElementNode,
ElementTypes,
- Namespaces,
NodeTypes,
type Property,
type SimpleExpressionNode,
@@ -9,6 +8,7 @@ import {
locStub,
} from '../src'
import {
+ Namespaces,
PatchFlagNames,
type PatchFlags,
type ShapeFlags,
diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts
index bae13372a98..929a86e4bf3 100644
--- a/packages/compiler-core/src/ast.ts
+++ b/packages/compiler-core/src/ast.ts
@@ -1,4 +1,4 @@
-import { type PatchFlags, isString } from '@vue/shared'
+import { type Namespace, type PatchFlags, isString } from '@vue/shared'
import {
CREATE_BLOCK,
CREATE_ELEMENT_BLOCK,
@@ -16,16 +16,6 @@ import type { PropsExpression } from './transforms/transformElement'
import type { ImportItem, TransformContext } from './transform'
import type { Node as BabelNode } from '@babel/types'
-// Vue template is a platform-agnostic superset of HTML (syntax only).
-// More namespaces can be declared by platform specific compilers.
-export type Namespace = number
-
-export enum Namespaces {
- HTML,
- SVG,
- MATH_ML,
-}
-
export enum NodeTypes {
ROOT,
ELEMENT,
diff --git a/packages/compiler-core/src/options.ts b/packages/compiler-core/src/options.ts
index 9983071609e..4c2b2f77070 100644
--- a/packages/compiler-core/src/options.ts
+++ b/packages/compiler-core/src/options.ts
@@ -1,10 +1,5 @@
-import type {
- ElementNode,
- Namespace,
- Namespaces,
- ParentNode,
- TemplateChildNode,
-} from './ast'
+import type { ElementNode, ParentNode, TemplateChildNode } from './ast'
+import type { Namespace, Namespaces } from '@vue/shared'
import type { CompilerError } from './errors'
import type {
DirectiveTransform,
diff --git a/packages/compiler-core/src/parser.ts b/packages/compiler-core/src/parser.ts
index 3eb3a976f4e..2bea73986d6 100644
--- a/packages/compiler-core/src/parser.ts
+++ b/packages/compiler-core/src/parser.ts
@@ -5,7 +5,6 @@ import {
type ElementNode,
ElementTypes,
type ForParseResult,
- Namespaces,
NodeTypes,
type RootNode,
type SimpleExpressionNode,
@@ -14,6 +13,7 @@ import {
createRoot,
createSimpleExpression,
} from './ast'
+import { Namespaces } from '@vue/shared'
import type { ParserOptions } from './options'
import Tokenizer, {
CharCodes,
diff --git a/packages/compiler-dom/__tests__/parse.spec.ts b/packages/compiler-dom/__tests__/parse.spec.ts
index 7418b8e33fb..e02b8c7b87a 100644
--- a/packages/compiler-dom/__tests__/parse.spec.ts
+++ b/packages/compiler-dom/__tests__/parse.spec.ts
@@ -4,12 +4,12 @@ import {
type ElementNode,
ElementTypes,
type InterpolationNode,
- Namespaces,
NodeTypes,
type TextNode,
baseParse as parse,
} from '@vue/compiler-core'
import { parserOptions } from '../src/parserOptions'
+import { Namespaces } from '@vue/shared'
describe('DOM parser', () => {
describe('Text', () => {
@@ -491,6 +491,17 @@ describe('DOM parser', () => {
expect(element.ns).toBe(Namespaces.SVG)
})
+ test('SVG tags without explicit root', () => {
+ const ast = parse('', parserOptions)
+ const textNode = ast.children[0] as ElementNode
+ const viewNode = ast.children[1] as ElementNode
+ const tspanNode = ast.children[2] as ElementNode
+
+ expect(textNode.ns).toBe(Namespaces.SVG)
+ expect(viewNode.ns).toBe(Namespaces.SVG)
+ expect(tspanNode.ns).toBe(Namespaces.SVG)
+ })
+
test('MATH in HTML namespace', () => {
const ast = parse('', parserOptions)
const elementHtml = ast.children[0] as ElementNode
@@ -500,6 +511,17 @@ describe('DOM parser', () => {
expect(element.ns).toBe(Namespaces.MATH_ML)
})
+ test('MATH tags without explicit root', () => {
+ const ast = parse('', parserOptions)
+ const miNode = ast.children[0] as ElementNode
+ const mnNode = ast.children[1] as ElementNode
+ const moNode = ast.children[2] as ElementNode
+
+ expect(miNode.ns).toBe(Namespaces.MATH_ML)
+ expect(mnNode.ns).toBe(Namespaces.MATH_ML)
+ expect(moNode.ns).toBe(Namespaces.MATH_ML)
+ })
+
test('root ns', () => {
const ast = parse('', {
...parserOptions,
diff --git a/packages/compiler-dom/src/parserOptions.ts b/packages/compiler-dom/src/parserOptions.ts
index 7da13bf534d..4106bd5af31 100644
--- a/packages/compiler-dom/src/parserOptions.ts
+++ b/packages/compiler-dom/src/parserOptions.ts
@@ -1,5 +1,11 @@
-import { Namespaces, NodeTypes, type ParserOptions } from '@vue/compiler-core'
-import { isHTMLTag, isMathMLTag, isSVGTag, isVoidTag } from '@vue/shared'
+import { NodeTypes, type ParserOptions } from '@vue/compiler-core'
+import {
+ Namespaces,
+ isHTMLTag,
+ isMathMLTag,
+ isSVGTag,
+ isVoidTag,
+} from '@vue/shared'
import { TRANSITION, TRANSITION_GROUP } from './runtimeHelpers'
import { decodeHtmlBrowser } from './decodeHtmlBrowser'
@@ -24,7 +30,7 @@ export const parserOptions: ParserOptions = {
let ns = parent ? parent.ns : rootNamespace
if (parent && ns === Namespaces.MATH_ML) {
if (parent.tag === 'annotation-xml') {
- if (tag === 'svg') {
+ if (isSVGTag(tag)) {
return Namespaces.SVG
}
if (
@@ -57,10 +63,10 @@ export const parserOptions: ParserOptions = {
}
if (ns === Namespaces.HTML) {
- if (tag === 'svg') {
+ if (isSVGTag(tag)) {
return Namespaces.SVG
}
- if (tag === 'math') {
+ if (isMathMLTag(tag)) {
return Namespaces.MATH_ML
}
}
diff --git a/packages/compiler-dom/src/transforms/stringifyStatic.ts b/packages/compiler-dom/src/transforms/stringifyStatic.ts
index cd8f1a9d184..d35211f5d6b 100644
--- a/packages/compiler-dom/src/transforms/stringifyStatic.ts
+++ b/packages/compiler-dom/src/transforms/stringifyStatic.ts
@@ -9,7 +9,6 @@ import {
ElementTypes,
type ExpressionNode,
type HoistTransform,
- Namespaces,
NodeTypes,
type PlainElementNode,
type SimpleExpressionNode,
@@ -20,6 +19,7 @@ import {
isStaticArgOf,
} from '@vue/compiler-core'
import {
+ Namespaces,
escapeHtml,
isArray,
isBooleanAttr,
diff --git a/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
index cad1ee81028..5cf9c736a84 100644
--- a/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
+++ b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
@@ -10,7 +10,6 @@ import {
type ExpressionNode,
type FunctionExpression,
type JSChildNode,
- Namespaces,
type NodeTransform,
NodeTypes,
RESOLVE_DYNAMIC_COMPONENT,
@@ -55,7 +54,14 @@ import {
ssrProcessTransitionGroup,
ssrTransformTransitionGroup,
} from './ssrTransformTransitionGroup'
-import { extend, isArray, isObject, isPlainObject, isSymbol } from '@vue/shared'
+import {
+ Namespaces,
+ extend,
+ isArray,
+ isObject,
+ isPlainObject,
+ isSymbol,
+} from '@vue/shared'
import { buildSSRProps } from './ssrTransformElement'
import {
ssrProcessTransition,
diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap
index 7aa56aa9c2f..5a3c13946bb 100644
--- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap
+++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap
@@ -1,5 +1,15 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+exports[`compiler: element transform > MathML 1`] = `
+"import { template as _template } from 'vue';
+const t0 = _template("", true, 2)
+
+export function render(_ctx) {
+ const n0 = t0()
+ return n0
+}"
+`;
+
exports[`compiler: element transform > component > cache v-on expression with unique handler name 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
@@ -407,6 +417,16 @@ export function render(_ctx) {
}"
`;
+exports[`compiler: element transform > svg 1`] = `
+"import { template as _template } from 'vue';
+const t0 = _template("", true, 1)
+
+export function render(_ctx) {
+ const n0 = t0()
+ return n0
+}"
+`;
+
exports[`compiler: element transform > v-bind="obj" 1`] = `
"import { setDynamicProps as _setDynamicProps, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("
", true)
diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap
index 4ea0db55fe5..edac6aef96a 100644
--- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap
+++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap
@@ -465,6 +465,17 @@ export function render(_ctx) {
}"
`;
+exports[`compiler v-bind > :class w/ svg elements 1`] = `
+"import { setAttr as _setAttr, renderEffect as _renderEffect, template as _template } from 'vue';
+const t0 = _template("", true, 1)
+
+export function render(_ctx) {
+ const n0 = t0()
+ _renderEffect(() => _setAttr(n0, "class", _ctx.cls))
+ return n0
+}"
+`;
+
exports[`compiler v-bind > :innerHTML 1`] = `
"import { setHtml as _setHtml, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("", true)
diff --git a/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts b/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts
index a693db4ad39..43280f6061a 100644
--- a/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts
+++ b/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts
@@ -579,7 +579,7 @@ describe('compiler: element transform', () => {
const template = ''
expect(code).toMatchSnapshot()
expect(code).contains(JSON.stringify(template))
- expect(ir.template).toMatchObject([template])
+ expect([...ir.template.keys()]).toMatchObject([template])
expect(ir.block.effect).lengthOf(0)
})
@@ -591,7 +591,7 @@ describe('compiler: element transform', () => {
const template = '
'
expect(code).toMatchSnapshot()
expect(code).contains(JSON.stringify(template))
- expect(ir.template).toMatchObject([template])
+ expect([...ir.template.keys()]).toMatchObject([template])
expect(ir.block.effect).lengthOf(0)
})
@@ -937,7 +937,11 @@ describe('compiler: element transform', () => {
`,
)
expect(code).toMatchSnapshot()
- expect(ir.template).toEqual(['123
', '', ''])
+ expect([...ir.template.keys()]).toEqual([
+ '123
',
+ '',
+ '',
+ ])
expect(ir.block.dynamic).toMatchObject({
children: [
{ id: 1, template: 1, children: [{ id: 0, template: 0 }] },
@@ -956,4 +960,26 @@ describe('compiler: element transform', () => {
expect(code).toMatchSnapshot()
expect(code).contain('return null')
})
+
+ test('svg', () => {
+ const t = ``
+ const { code, ir } = compileWithElementTransform(t)
+ expect(code).toMatchSnapshot()
+ expect(code).contains(
+ '_template("", true, 1)',
+ )
+ expect([...ir.template.keys()]).toMatchObject([t])
+ expect(ir.template.get(t)).toBe(1)
+ })
+
+ test('MathML', () => {
+ const t = ``
+ const { code, ir } = compileWithElementTransform(t)
+ expect(code).toMatchSnapshot()
+ expect(code).contains(
+ '_template("", true, 2)',
+ )
+ expect([...ir.template.keys()]).toMatchObject([t])
+ expect(ir.template.get(t)).toBe(2)
+ })
})
diff --git a/packages/compiler-vapor/__tests__/transforms/transformSlotOutlet.spec.ts b/packages/compiler-vapor/__tests__/transforms/transformSlotOutlet.spec.ts
index 389c665a12f..99daa571f60 100644
--- a/packages/compiler-vapor/__tests__/transforms/transformSlotOutlet.spec.ts
+++ b/packages/compiler-vapor/__tests__/transforms/transformSlotOutlet.spec.ts
@@ -155,7 +155,7 @@ describe('compiler: transform outlets', () => {
test('default slot outlet with fallback', () => {
const { ir, code } = compileWithSlotsOutlet(``)
expect(code).toMatchSnapshot()
- expect(ir.template[0]).toBe('')
+ expect([...ir.template.keys()][0]).toBe('')
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
@@ -175,7 +175,7 @@ describe('compiler: transform outlets', () => {
``,
)
expect(code).toMatchSnapshot()
- expect(ir.template[0]).toBe('')
+ expect([...ir.template.keys()][0]).toBe('')
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
@@ -195,7 +195,7 @@ describe('compiler: transform outlets', () => {
``,
)
expect(code).toMatchSnapshot()
- expect(ir.template[0]).toBe('')
+ expect([...ir.template.keys()][0]).toBe('')
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
@@ -216,7 +216,7 @@ describe('compiler: transform outlets', () => {
``,
)
expect(code).toMatchSnapshot()
- expect(ir.template[0]).toBe('')
+ expect([...ir.template.keys()][0]).toBe('')
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
diff --git a/packages/compiler-vapor/__tests__/transforms/transformTemplateRef.spec.ts b/packages/compiler-vapor/__tests__/transforms/transformTemplateRef.spec.ts
index 2c883d10cc6..82d07ce0ab6 100644
--- a/packages/compiler-vapor/__tests__/transforms/transformTemplateRef.spec.ts
+++ b/packages/compiler-vapor/__tests__/transforms/transformTemplateRef.spec.ts
@@ -30,7 +30,7 @@ describe('compiler: template ref transform', () => {
id: 0,
flags: DynamicFlag.REFERENCED,
})
- expect(ir.template).toEqual([''])
+ expect([...ir.template.keys()]).toEqual([''])
expect(ir.block.operation).lengthOf(1)
expect(ir.block.operation[0]).toMatchObject({
type: IRNodeTypes.SET_TEMPLATE_REF,
@@ -66,7 +66,7 @@ describe('compiler: template ref transform', () => {
id: 0,
flags: DynamicFlag.REFERENCED,
})
- expect(ir.template).toEqual([''])
+ expect([...ir.template.keys()]).toEqual([''])
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.DECLARE_OLD_REF,
@@ -104,7 +104,7 @@ describe('compiler: template ref transform', () => {
id: 0,
flags: DynamicFlag.REFERENCED,
})
- expect(ir.template).toEqual([''])
+ expect([...ir.template.keys()]).toEqual([''])
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.DECLARE_OLD_REF,
diff --git a/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts b/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts
index e96186c275c..60cd9d986ed 100644
--- a/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts
+++ b/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts
@@ -23,7 +23,7 @@ describe('compiler v-bind', () => {
id: 0,
flags: DynamicFlag.REFERENCED,
})
- expect(ir.template).toEqual([''])
+ expect([...ir.template.keys()]).toEqual([''])
expect(ir.block.effect).lengthOf(1)
expect(ir.block.effect[0].expressions).lengthOf(1)
expect(ir.block.effect[0].operations).lengthOf(1)
@@ -241,7 +241,7 @@ describe('compiler v-bind', () => {
end: { line: 1, column: 19 },
},
})
- expect(ir.template).toEqual([''])
+ expect([...ir.template.keys()]).toEqual([''])
expect(code).matchSnapshot()
expect(code).contains(JSON.stringify(''))
@@ -656,6 +656,14 @@ describe('compiler v-bind', () => {
expect(code).contains('_setProp(n0, "value", _ctx.foo)')
})
+ test(':class w/ svg elements', () => {
+ const { code } = compileWithVBind(`
+
+ `)
+ expect(code).matchSnapshot()
+ expect(code).contains('_setAttr(n0, "class", _ctx.cls))')
+ })
+
test('number value', () => {
const { code } = compileWithVBind(``)
expect(code).matchSnapshot()
diff --git a/packages/compiler-vapor/__tests__/transforms/vFor.spec.ts b/packages/compiler-vapor/__tests__/transforms/vFor.spec.ts
index 7357ad84fef..011eb0ca92c 100644
--- a/packages/compiler-vapor/__tests__/transforms/vFor.spec.ts
+++ b/packages/compiler-vapor/__tests__/transforms/vFor.spec.ts
@@ -32,7 +32,7 @@ describe('compiler: v-for', () => {
expect(code).matchSnapshot()
expect(helpers).contains('createFor')
- expect(ir.template).toEqual(['
'])
+ expect([...ir.template.keys()]).toEqual(['
'])
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.FOR,
id: 0,
@@ -156,7 +156,7 @@ describe('compiler: v-for', () => {
`_createFor(() => (_for_item0.value), (_for_item1) => {`,
)
expect(code).contains(`_for_item1.value+_for_item0.value`)
- expect(ir.template).toEqual([' ', ''])
+ expect([...ir.template.keys()]).toEqual([' ', ''])
const parentOp = ir.block.dynamic.children[0].operation
expect(parentOp).toMatchObject({
type: IRNodeTypes.FOR,
diff --git a/packages/compiler-vapor/__tests__/transforms/vHtml.spec.ts b/packages/compiler-vapor/__tests__/transforms/vHtml.spec.ts
index 0de0b6abca6..f297362b2cb 100644
--- a/packages/compiler-vapor/__tests__/transforms/vHtml.spec.ts
+++ b/packages/compiler-vapor/__tests__/transforms/vHtml.spec.ts
@@ -66,7 +66,7 @@ describe('v-html', () => {
expect(helpers).contains('setHtml')
// children should have been removed
- expect(ir.template).toEqual([''])
+ expect([...ir.template.keys()]).toEqual([''])
expect(ir.block.operation).toMatchObject([])
expect(ir.block.effect).toMatchObject([
diff --git a/packages/compiler-vapor/__tests__/transforms/vIf.spec.ts b/packages/compiler-vapor/__tests__/transforms/vIf.spec.ts
index e5fd61add2e..76a1bde6123 100644
--- a/packages/compiler-vapor/__tests__/transforms/vIf.spec.ts
+++ b/packages/compiler-vapor/__tests__/transforms/vIf.spec.ts
@@ -32,7 +32,7 @@ describe('compiler: v-if', () => {
expect(helpers).contains('createIf')
- expect(ir.template).toEqual(['
'])
+ expect([...ir.template.keys()]).toEqual(['
'])
const op = ir.block.dynamic.children[0].operation
expect(op).toMatchObject({
@@ -68,7 +68,11 @@ describe('compiler: v-if', () => {
)
expect(code).matchSnapshot()
- expect(ir.template).toEqual(['', 'hello', '
'])
+ expect([...ir.template.keys()]).toEqual([
+ '',
+ 'hello',
+ '
',
+ ])
expect(ir.block.effect).toEqual([])
const op = ir.block.dynamic.children[0].operation as IfIRNode
expect(op.positive.effect).toMatchObject([
@@ -103,7 +107,7 @@ describe('compiler: v-if', () => {
`hello
hello
`,
)
expect(code).matchSnapshot()
- expect(ir.template).toEqual(['hello
'])
+ expect([...ir.template.keys()]).toEqual(['hello
'])
expect(ir.block.returns).toEqual([0, 3])
})
@@ -113,7 +117,7 @@ describe('compiler: v-if', () => {
test('v-if + v-else', () => {
const { code, ir, helpers } = compileWithVIf(``)
expect(code).matchSnapshot()
- expect(ir.template).toEqual(['', ''])
+ expect([...ir.template.keys()]).toEqual(['', ''])
expect(helpers).contains('createIf')
expect(ir.block.effect).lengthOf(0)
@@ -146,7 +150,7 @@ describe('compiler: v-if', () => {
``,
)
expect(code).matchSnapshot()
- expect(ir.template).toEqual(['', ''])
+ expect([...ir.template.keys()]).toEqual(['', ''])
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.IF,
@@ -185,7 +189,7 @@ describe('compiler: v-if', () => {
`fine`,
)
expect(code).matchSnapshot()
- expect(ir.template).toEqual(['', '', 'fine'])
+ expect([...ir.template.keys()]).toEqual(['', '', 'fine'])
expect(ir.block.returns).toEqual([0])
expect(ir.block.dynamic.children[0].operation).toMatchObject({
@@ -236,7 +240,7 @@ describe('compiler: v-if', () => {
`)
expect(code).matchSnapshot()
- expect(ir.template).toEqual([
+ expect([...ir.template.keys()]).toEqual([
'',
'',
'',
diff --git a/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts b/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts
index 909162fe3ca..2a462479c3e 100644
--- a/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts
+++ b/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts
@@ -35,7 +35,7 @@ describe('compiler: transform slot', () => {
const { ir, code } = compileWithSlots(``)
expect(code).toMatchSnapshot()
- expect(ir.template).toEqual([''])
+ expect([...ir.template.keys()]).toEqual([''])
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
id: 1,
@@ -163,7 +163,7 @@ describe('compiler: transform slot', () => {
)
expect(code).toMatchSnapshot()
- expect(ir.template).toEqual(['foo', 'bar', ''])
+ expect([...ir.template.keys()]).toEqual(['foo', 'bar', ''])
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
id: 4,
diff --git a/packages/compiler-vapor/__tests__/transforms/vText.spec.ts b/packages/compiler-vapor/__tests__/transforms/vText.spec.ts
index 4f074fee87e..7ebac8554a8 100644
--- a/packages/compiler-vapor/__tests__/transforms/vText.spec.ts
+++ b/packages/compiler-vapor/__tests__/transforms/vText.spec.ts
@@ -68,7 +68,7 @@ describe('v-text', () => {
])
// children should have been removed
- expect(ir.template).toEqual(['
'])
+ expect([...ir.template.keys()]).toEqual(['
'])
expect(ir.block.effect).toMatchObject([
{
diff --git a/packages/compiler-vapor/src/generators/prop.ts b/packages/compiler-vapor/src/generators/prop.ts
index 42f063331fc..392420613f5 100644
--- a/packages/compiler-vapor/src/generators/prop.ts
+++ b/packages/compiler-vapor/src/generators/prop.ts
@@ -169,6 +169,14 @@ function getRuntimeHelper(
modifier: '.' | '^' | undefined,
): HelperConfig {
const tagName = tag.toUpperCase()
+ const isSVG = isSVGTag(tag)
+
+ // 1. SVG: always attribute
+ if (isSVG) {
+ // TODO pass svg flag
+ return helpers.setAttr
+ }
+
if (modifier) {
if (modifier === '.') {
return getSpecialHelper(key, tagName) || helpers.setDOMProp
@@ -177,24 +185,18 @@ function getRuntimeHelper(
}
}
- // 1. special handling for value / style / class / textContent / innerHTML
+ // 2. special handling for value / style / class / textContent / innerHTML
const helper = getSpecialHelper(key, tagName)
if (helper) {
return helper
}
- // 2. Aria DOM properties shared between all Elements in
+ // 3. Aria DOM properties shared between all Elements in
// https://developer.mozilla.org/en-US/docs/Web/API/Element
if (/aria[A-Z]/.test(key)) {
return helpers.setDOMProp
}
- // 3. SVG: always attribute
- if (isSVGTag(tag)) {
- // TODO pass svg flag
- return helpers.setAttr
- }
-
// 4. respect shouldSetAsAttr used in vdom and setDynamicProp for consistency
// also fast path for presence of hyphen (covers data-* and aria-*)
if (shouldSetAsAttr(tagName, key) || key.includes('-')) {
diff --git a/packages/compiler-vapor/src/generators/template.ts b/packages/compiler-vapor/src/generators/template.ts
index 5a066b09e9a..e3cca1b48b0 100644
--- a/packages/compiler-vapor/src/generators/template.ts
+++ b/packages/compiler-vapor/src/generators/template.ts
@@ -5,18 +5,21 @@ import { genOperationWithInsertionState } from './operation'
import { type CodeFragment, NEWLINE, buildCodeFragment, genCall } from './utils'
export function genTemplates(
- templates: string[],
+ templates: Map,
rootIndex: number | undefined,
{ helper }: CodegenContext,
): string {
- return templates
- .map(
- (template, i) =>
- `const t${i} = ${helper('template')}(${JSON.stringify(
- template,
- )}${i === rootIndex ? ', true' : ''})\n`,
+ const result: string[] = []
+ let i = 0
+ templates.forEach((ns, template) => {
+ result.push(
+ `const t${i} = ${helper('template')}(${JSON.stringify(
+ template,
+ )}${i === rootIndex ? ', true' : ns ? ', false' : ''}${ns ? `, ${ns}` : ''})\n`,
)
- .join('')
+ i++
+ })
+ return result.join('')
}
export function genSelf(
diff --git a/packages/compiler-vapor/src/ir/index.ts b/packages/compiler-vapor/src/ir/index.ts
index 18f0139ab56..fd4eefd559b 100644
--- a/packages/compiler-vapor/src/ir/index.ts
+++ b/packages/compiler-vapor/src/ir/index.ts
@@ -5,7 +5,7 @@ import type {
SimpleExpressionNode,
TemplateChildNode,
} from '@vue/compiler-dom'
-import type { Prettify } from '@vue/shared'
+import type { Namespace, Prettify } from '@vue/shared'
import type { DirectiveTransform, NodeTransform } from '../transform'
import type { IRProp, IRProps, IRSlots } from './component'
@@ -59,7 +59,8 @@ export interface RootIRNode {
type: IRNodeTypes.ROOT
node: RootNode
source: string
- template: string[]
+ template: Map
+ templateIndexMap: Map
rootTemplateIndex?: number
component: Set
directive: Set
diff --git a/packages/compiler-vapor/src/transform.ts b/packages/compiler-vapor/src/transform.ts
index 946c89b734a..e993daf4b0c 100644
--- a/packages/compiler-vapor/src/transform.ts
+++ b/packages/compiler-vapor/src/transform.ts
@@ -6,6 +6,7 @@ import {
type ElementNode,
ElementTypes,
NodeTypes,
+ type PlainElementNode,
type RootNode,
type SimpleExpressionNode,
type TemplateChildNode,
@@ -125,12 +126,15 @@ export class TransformContext {
}
pushTemplate(content: string): number {
- const existing = this.ir.template.findIndex(
- template => template === content,
- )
- if (existing !== -1) return existing
- this.ir.template.push(content)
- return this.ir.template.length - 1
+ const existingIndex = this.ir.templateIndexMap.get(content)
+ if (existingIndex !== undefined) {
+ return existingIndex
+ }
+
+ const newIndex = this.ir.template.size
+ this.ir.template.set(content, (this.node as PlainElementNode).ns)
+ this.ir.templateIndexMap.set(content, newIndex)
+ return newIndex
}
registerTemplate(): number {
if (!this.template) return -1
@@ -214,7 +218,8 @@ export function transform(
type: IRNodeTypes.ROOT,
node,
source: node.source,
- template: [],
+ template: new Map(),
+ templateIndexMap: new Map(),
component: new Set(),
directive: new Set(),
block: newBlock(node),
diff --git a/packages/compiler-vapor/src/transforms/transformElement.ts b/packages/compiler-vapor/src/transforms/transformElement.ts
index 05153e729af..1919ccd2a2b 100644
--- a/packages/compiler-vapor/src/transforms/transformElement.ts
+++ b/packages/compiler-vapor/src/transforms/transformElement.ts
@@ -250,7 +250,7 @@ function transformNativeElement(
}
if (singleRoot) {
- context.ir.rootTemplateIndex = context.ir.template.length
+ context.ir.rootTemplateIndex = context.ir.template.size
}
if (
diff --git a/packages/runtime-dom/src/index.ts b/packages/runtime-dom/src/index.ts
index b241458dba7..4c43efd4e7f 100644
--- a/packages/runtime-dom/src/index.ts
+++ b/packages/runtime-dom/src/index.ts
@@ -348,3 +348,7 @@ export {
vModelSelectInit,
vModelSetSelected,
} from './directives/vModel'
+/**
+ * @internal
+ */
+export { svgNS, mathmlNS } from './nodeOps'
diff --git a/packages/runtime-vapor/__tests__/dom/mathML.spec.ts b/packages/runtime-vapor/__tests__/dom/mathML.spec.ts
new file mode 100644
index 00000000000..eed0b8218a4
--- /dev/null
+++ b/packages/runtime-vapor/__tests__/dom/mathML.spec.ts
@@ -0,0 +1,84 @@
+import { makeRender } from '../_utils'
+import { template } from '../../src/dom/template'
+import { child } from '../../src/dom/node'
+import { setClass } from '../../src/dom/prop'
+import { renderEffect } from '../../src'
+import { nextTick, ref } from '@vue/runtime-dom'
+
+const define = makeRender()
+
+describe('MathML support', () => {
+ afterEach(() => {
+ document.body.innerHTML = ''
+ })
+
+ test('should mount elements with correct html namespace', () => {
+ define({
+ setup() {
+ const t0 = template(
+ ``,
+ true,
+ 2,
+ )
+ const n0 = t0()
+ return n0
+ },
+ }).render()
+
+ const e0 = document.getElementById('e0')!
+ expect(e0.namespaceURI).toMatch('Math')
+ expect(e0.querySelector('#e1')!.namespaceURI).toMatch('Math')
+ expect(e0.querySelector('#e2')!.namespaceURI).toMatch('Math')
+ expect(e0.querySelector('#e3')!.namespaceURI).toMatch('Math')
+ expect(e0.querySelector('#e4')!.namespaceURI).toMatch('xhtml')
+ expect(e0.querySelector('#e5')!.namespaceURI).toMatch('svg')
+ })
+
+ test('should patch elements with correct namespaces', async () => {
+ const cls = ref('foo')
+ define({
+ setup() {
+ const t0 = template(
+ '',
+ true,
+ )
+
+ const n2 = t0() as HTMLElement
+ const n1 = child(n2) as HTMLElement
+ const p0 = child(n1) as HTMLElement
+ const n0 = child(p0) as HTMLElement
+ renderEffect(() => {
+ const _cls = cls.value
+ setClass(n1, _cls)
+ setClass(n0, _cls)
+ })
+ return n2
+ },
+ }).render()
+
+ const f1 = document.querySelector('#f1')!
+ const f2 = document.querySelector('#f2')!
+ expect(f1.getAttribute('class')).toBe('foo')
+ expect(f2.className).toBe('foo')
+
+ cls.value = 'bar'
+ await nextTick()
+ expect(f1.getAttribute('class')).toBe('bar')
+ expect(f2.className).toBe('bar')
+ })
+})
diff --git a/packages/runtime-vapor/__tests__/dom/svg.spec.ts b/packages/runtime-vapor/__tests__/dom/svg.spec.ts
new file mode 100644
index 00000000000..850a7a9c928
--- /dev/null
+++ b/packages/runtime-vapor/__tests__/dom/svg.spec.ts
@@ -0,0 +1,73 @@
+import { makeRender } from '../_utils'
+import { template } from '../../src/dom/template'
+import { child } from '../../src/dom/node'
+import { setAttr, setClass } from '../../src/dom/prop'
+import { renderEffect } from '../../src'
+import { nextTick, ref } from '@vue/runtime-dom'
+
+const define = makeRender()
+
+describe('SVG support', () => {
+ afterEach(() => {
+ document.body.innerHTML = ''
+ })
+
+ test('should mount elements with correct html namespace', () => {
+ define({
+ setup() {
+ const t0 = template(
+ ``,
+ true,
+ )
+ return t0()
+ },
+ }).render()
+
+ const e0 = document.getElementById('e0')!
+ expect(e0.namespaceURI).toMatch('xhtml')
+ expect(e0.querySelector('#e1')!.namespaceURI).toMatch('svg')
+ expect(e0.querySelector('#e2')!.namespaceURI).toMatch('svg')
+ expect(e0.querySelector('#e3')!.namespaceURI).toMatch('xhtml')
+ expect(e0.querySelector('#e4')!.namespaceURI).toMatch('svg')
+ expect(e0.querySelector('#e5')!.namespaceURI).toMatch('Math')
+ })
+
+ test('should patch elements with correct namespaces', async () => {
+ const cls = ref('foo')
+ define({
+ setup() {
+ const t0 = template(
+ '',
+ true,
+ )
+ const n2 = t0() as HTMLElement
+ const n1 = child(n2) as HTMLElement
+ const p0 = child(n1) as HTMLElement
+ const n0 = child(p0) as HTMLElement
+ renderEffect(() => {
+ const _cls = cls.value
+ setAttr(n1, 'class', _cls)
+ setClass(n0, _cls)
+ })
+ return n2
+ },
+ }).render()
+ const f1 = document.querySelector('#f1')!
+ const f2 = document.querySelector('#f2')!
+ expect(f1.getAttribute('class')).toBe('foo')
+ expect(f2.className).toBe('foo')
+
+ cls.value = 'bar'
+ await nextTick()
+ expect(f1.getAttribute('class')).toBe('bar')
+ expect(f2.className).toBe('bar')
+ })
+})
diff --git a/packages/runtime-vapor/src/dom/template.ts b/packages/runtime-vapor/src/dom/template.ts
index b78ca4e52cf..66b9ea7d2de 100644
--- a/packages/runtime-vapor/src/dom/template.ts
+++ b/packages/runtime-vapor/src/dom/template.ts
@@ -1,10 +1,14 @@
+import { mathmlNS, svgNS } from '@vue/runtime-dom'
import { adoptTemplate, currentHydrationNode, isHydrating } from './hydration'
import { child, createTextNode } from './node'
+import { type Namespace, Namespaces } from '@vue/shared'
let t: HTMLTemplateElement
+let st: HTMLTemplateElement
+let mt: HTMLTemplateElement
/*! #__NO_SIDE_EFFECTS__ */
-export function template(html: string, root?: boolean) {
+export function template(html: string, root?: boolean, ns?: Namespace) {
let node: Node
return (): Node & { $root?: true } => {
if (isHydrating) {
@@ -19,9 +23,19 @@ export function template(html: string, root?: boolean) {
return createTextNode(html)
}
if (!node) {
- t = t || document.createElement('template')
- t.innerHTML = html
- node = child(t.content)
+ if (!ns) {
+ t = t || document.createElement('template')
+ t.innerHTML = html
+ node = child(t.content)
+ } else if (ns === Namespaces.SVG) {
+ st = st || document.createElementNS(svgNS, 'template')
+ st.innerHTML = html
+ node = child(st)
+ } else {
+ mt = mt || document.createElementNS(mathmlNS, 'template')
+ mt.innerHTML = html
+ node = child(mt)
+ }
}
const ret = node.cloneNode(true)
if (root) (ret as any).$root = true
diff --git a/packages/shared/src/domNamespace.ts b/packages/shared/src/domNamespace.ts
new file mode 100644
index 00000000000..42ef2380872
--- /dev/null
+++ b/packages/shared/src/domNamespace.ts
@@ -0,0 +1,10 @@
+// Vue template is a platform-agnostic superset of HTML (syntax only).
+// More namespaces can be declared by platform specific compilers.
+
+export type Namespace = number
+
+export enum Namespaces {
+ HTML,
+ SVG,
+ MATH_ML,
+}
diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts
index 0c38d640ba0..519546a4685 100644
--- a/packages/shared/src/index.ts
+++ b/packages/shared/src/index.ts
@@ -8,6 +8,7 @@ export * from './codeframe'
export * from './normalizeProp'
export * from './domTagConfig'
export * from './domAttrConfig'
+export * from './domNamespace'
export * from './escapeHtml'
export * from './looseEqual'
export * from './toDisplayString'