Skip to content

feat(runtime-vapor): support svg and MathML #13703

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: minor
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/compiler-dom/src/parserOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,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 (
Expand Down Expand Up @@ -57,10 +57,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
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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("<math><mrow><mi>x</mi></mrow></math>", 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';

Expand Down Expand Up @@ -407,6 +417,16 @@ export function render(_ctx) {
}"
`;

exports[`compiler: element transform > svg 1`] = `
"import { template as _template } from 'vue';
const t0 = _template("<svg><circle r=\\"40\\"></circle></svg>", 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("<div></div>", true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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("<svg></svg>", 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("<div></div>", true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,7 @@ describe('compiler: element transform', () => {
const template = '<div id="foo" class="bar"></div>'
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)
})

Expand All @@ -591,7 +591,7 @@ describe('compiler: element transform', () => {
const template = '<div id="foo"><span></span></div>'
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)
})

Expand Down Expand Up @@ -937,7 +937,11 @@ describe('compiler: element transform', () => {
<form><form/></form>`,
)
expect(code).toMatchSnapshot()
expect(ir.template).toEqual(['<div>123</div>', '<p></p>', '<form></form>'])
expect([...ir.template.keys()]).toEqual([
'<div>123</div>',
'<p></p>',
'<form></form>',
])
expect(ir.block.dynamic).toMatchObject({
children: [
{ id: 1, template: 1, children: [{ id: 0, template: 0 }] },
Expand All @@ -956,4 +960,26 @@ describe('compiler: element transform', () => {
expect(code).toMatchSnapshot()
expect(code).contain('return null')
})

test('svg', () => {
const t = `<svg><circle r="40"></circle></svg>`
const { code, ir } = compileWithElementTransform(t)
expect(code).toMatchSnapshot()
expect(code).contains(
'_template("<svg><circle r=\\"40\\"></circle></svg>", true, 1)',
)
expect([...ir.template.keys()]).toMatchObject([t])
expect(ir.template.get(t)).toBe(1)
})

test('MathML', () => {
const t = `<math><mrow><mi>x</mi></mrow></math>`
const { code, ir } = compileWithElementTransform(t)
expect(code).toMatchSnapshot()
expect(code).contains(
'_template("<math><mrow><mi>x</mi></mrow></math>", true, 2)',
)
expect([...ir.template.keys()]).toMatchObject([t])
expect(ir.template.get(t)).toBe(2)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ describe('compiler: transform <slot> outlets', () => {
test('default slot outlet with fallback', () => {
const { ir, code } = compileWithSlotsOutlet(`<slot><div/></slot>`)
expect(code).toMatchSnapshot()
expect(ir.template[0]).toBe('<div></div>')
expect([...ir.template.keys()][0]).toBe('<div></div>')
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
Expand All @@ -175,7 +175,7 @@ describe('compiler: transform <slot> outlets', () => {
`<slot name="foo"><div/></slot>`,
)
expect(code).toMatchSnapshot()
expect(ir.template[0]).toBe('<div></div>')
expect([...ir.template.keys()][0]).toBe('<div></div>')
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
Expand All @@ -195,7 +195,7 @@ describe('compiler: transform <slot> outlets', () => {
`<slot :foo="bar"><div/></slot>`,
)
expect(code).toMatchSnapshot()
expect(ir.template[0]).toBe('<div></div>')
expect([...ir.template.keys()][0]).toBe('<div></div>')
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
Expand All @@ -216,7 +216,7 @@ describe('compiler: transform <slot> outlets', () => {
`<slot name="foo" :foo="bar"><div/></slot>`,
)
expect(code).toMatchSnapshot()
expect(ir.template[0]).toBe('<div></div>')
expect([...ir.template.keys()][0]).toBe('<div></div>')
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.SLOT_OUTLET_NODE,
id: 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe('compiler: template ref transform', () => {
id: 0,
flags: DynamicFlag.REFERENCED,
})
expect(ir.template).toEqual(['<div></div>'])
expect([...ir.template.keys()]).toEqual(['<div></div>'])
expect(ir.block.operation).lengthOf(1)
expect(ir.block.operation[0]).toMatchObject({
type: IRNodeTypes.SET_TEMPLATE_REF,
Expand Down Expand Up @@ -66,7 +66,7 @@ describe('compiler: template ref transform', () => {
id: 0,
flags: DynamicFlag.REFERENCED,
})
expect(ir.template).toEqual(['<div></div>'])
expect([...ir.template.keys()]).toEqual(['<div></div>'])
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.DECLARE_OLD_REF,
Expand Down Expand Up @@ -104,7 +104,7 @@ describe('compiler: template ref transform', () => {
id: 0,
flags: DynamicFlag.REFERENCED,
})
expect(ir.template).toEqual(['<div></div>'])
expect([...ir.template.keys()]).toEqual(['<div></div>'])
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.DECLARE_OLD_REF,
Expand Down
12 changes: 10 additions & 2 deletions packages/compiler-vapor/__tests__/transforms/vBind.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ describe('compiler v-bind', () => {
id: 0,
flags: DynamicFlag.REFERENCED,
})
expect(ir.template).toEqual(['<div></div>'])
expect([...ir.template.keys()]).toEqual(['<div></div>'])
expect(ir.block.effect).lengthOf(1)
expect(ir.block.effect[0].expressions).lengthOf(1)
expect(ir.block.effect[0].operations).lengthOf(1)
Expand Down Expand Up @@ -241,7 +241,7 @@ describe('compiler v-bind', () => {
end: { line: 1, column: 19 },
},
})
expect(ir.template).toEqual(['<div arg></div>'])
expect([...ir.template.keys()]).toEqual(['<div arg></div>'])

expect(code).matchSnapshot()
expect(code).contains(JSON.stringify('<div arg></div>'))
Expand Down Expand Up @@ -656,6 +656,14 @@ describe('compiler v-bind', () => {
expect(code).contains('_setProp(n0, "value", _ctx.foo)')
})

test(':class w/ svg elements', () => {
const { code } = compileWithVBind(`
<svg :class="cls"/>
`)
expect(code).matchSnapshot()
expect(code).contains('_setAttr(n0, "class", _ctx.cls))')
})

test('number value', () => {
const { code } = compileWithVBind(`<Comp :depth="0" />`)
expect(code).matchSnapshot()
Expand Down
4 changes: 2 additions & 2 deletions packages/compiler-vapor/__tests__/transforms/vFor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ describe('compiler: v-for', () => {

expect(code).matchSnapshot()
expect(helpers).contains('createFor')
expect(ir.template).toEqual(['<div> </div>'])
expect([...ir.template.keys()]).toEqual(['<div> </div>'])
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.FOR,
id: 0,
Expand Down Expand Up @@ -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(['<span> </span>', '<div></div>'])
expect([...ir.template.keys()]).toEqual(['<span> </span>', '<div></div>'])
const parentOp = ir.block.dynamic.children[0].operation
expect(parentOp).toMatchObject({
type: IRNodeTypes.FOR,
Expand Down
2 changes: 1 addition & 1 deletion packages/compiler-vapor/__tests__/transforms/vHtml.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ describe('v-html', () => {
expect(helpers).contains('setHtml')

// children should have been removed
expect(ir.template).toEqual(['<div></div>'])
expect([...ir.template.keys()]).toEqual(['<div></div>'])

expect(ir.block.operation).toMatchObject([])
expect(ir.block.effect).toMatchObject([
Expand Down
18 changes: 11 additions & 7 deletions packages/compiler-vapor/__tests__/transforms/vIf.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ describe('compiler: v-if', () => {

expect(helpers).contains('createIf')

expect(ir.template).toEqual(['<div> </div>'])
expect([...ir.template.keys()]).toEqual(['<div> </div>'])

const op = ir.block.dynamic.children[0].operation
expect(op).toMatchObject({
Expand Down Expand Up @@ -68,7 +68,11 @@ describe('compiler: v-if', () => {
)
expect(code).matchSnapshot()

expect(ir.template).toEqual(['<div></div>', 'hello', '<p> </p>'])
expect([...ir.template.keys()]).toEqual([
'<div></div>',
'hello',
'<p> </p>',
])
expect(ir.block.effect).toEqual([])
const op = ir.block.dynamic.children[0].operation as IfIRNode
expect(op.positive.effect).toMatchObject([
Expand Down Expand Up @@ -103,7 +107,7 @@ describe('compiler: v-if', () => {
`<div v-if="ok">hello</div><div v-if="ok">hello</div>`,
)
expect(code).matchSnapshot()
expect(ir.template).toEqual(['<div>hello</div>'])
expect([...ir.template.keys()]).toEqual(['<div>hello</div>'])
expect(ir.block.returns).toEqual([0, 3])
})

Expand All @@ -113,7 +117,7 @@ describe('compiler: v-if', () => {
test('v-if + v-else', () => {
const { code, ir, helpers } = compileWithVIf(`<div v-if="ok"/><p v-else/>`)
expect(code).matchSnapshot()
expect(ir.template).toEqual(['<div></div>', '<p></p>'])
expect([...ir.template.keys()]).toEqual(['<div></div>', '<p></p>'])

expect(helpers).contains('createIf')
expect(ir.block.effect).lengthOf(0)
Expand Down Expand Up @@ -146,7 +150,7 @@ describe('compiler: v-if', () => {
`<div v-if="ok"/><p v-else-if="orNot"/>`,
)
expect(code).matchSnapshot()
expect(ir.template).toEqual(['<div></div>', '<p></p>'])
expect([...ir.template.keys()]).toEqual(['<div></div>', '<p></p>'])

expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.IF,
Expand Down Expand Up @@ -185,7 +189,7 @@ describe('compiler: v-if', () => {
`<div v-if="ok"/><p v-else-if="orNot"/><template v-else>fine</template>`,
)
expect(code).matchSnapshot()
expect(ir.template).toEqual(['<div></div>', '<p></p>', 'fine'])
expect([...ir.template.keys()]).toEqual(['<div></div>', '<p></p>', 'fine'])

expect(ir.block.returns).toEqual([0])
expect(ir.block.dynamic.children[0].operation).toMatchObject({
Expand Down Expand Up @@ -236,7 +240,7 @@ describe('compiler: v-if', () => {
<div v-text="text" />
`)
expect(code).matchSnapshot()
expect(ir.template).toEqual([
expect([...ir.template.keys()]).toEqual([
'<div></div>',
'<!--foo-->',
'<p></p>',
Expand Down
4 changes: 2 additions & 2 deletions packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ describe('compiler: transform slot', () => {
const { ir, code } = compileWithSlots(`<Comp><div/></Comp>`)
expect(code).toMatchSnapshot()

expect(ir.template).toEqual(['<div></div>'])
expect([...ir.template.keys()]).toEqual(['<div></div>'])
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
id: 1,
Expand Down Expand Up @@ -163,7 +163,7 @@ describe('compiler: transform slot', () => {
)
expect(code).toMatchSnapshot()

expect(ir.template).toEqual(['foo', 'bar', '<span></span>'])
expect([...ir.template.keys()]).toEqual(['foo', 'bar', '<span></span>'])
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
id: 4,
Expand Down
2 changes: 1 addition & 1 deletion packages/compiler-vapor/__tests__/transforms/vText.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ describe('v-text', () => {
])

// children should have been removed
expect(ir.template).toEqual(['<div> </div>'])
expect([...ir.template.keys()]).toEqual(['<div> </div>'])

expect(ir.block.effect).toMatchObject([
{
Expand Down
18 changes: 10 additions & 8 deletions packages/compiler-vapor/src/generators/prop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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('-')) {
Expand Down
Loading