Skip to content

Commit d42b6ba

Browse files
authored
feat: MathML support (#7836)
close #7820
1 parent bc7698d commit d42b6ba

19 files changed

+372
-157
lines changed

packages/compiler-dom/src/parserOptions.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { ParserOptions, NodeTypes, Namespaces } from '@vue/compiler-core'
2-
import { isVoidTag, isHTMLTag, isSVGTag } from '@vue/shared'
2+
import { isVoidTag, isHTMLTag, isSVGTag, isMathMLTag } from '@vue/shared'
33
import { TRANSITION, TRANSITION_GROUP } from './runtimeHelpers'
44
import { decodeHtmlBrowser } from './decodeHtmlBrowser'
55

66
export const parserOptions: ParserOptions = {
77
parseMode: 'html',
88
isVoidTag,
9-
isNativeTag: tag => isHTMLTag(tag) || isSVGTag(tag),
9+
isNativeTag: tag => isHTMLTag(tag) || isSVGTag(tag) || isMathMLTag(tag),
1010
isPreTag: tag => tag === 'pre',
1111
decodeEntities: __BROWSER__ ? decodeHtmlBrowser : undefined,
1212

packages/runtime-core/src/apiCreateApp.ts

+17-5
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
ComponentPublicInstance
1717
} from './componentPublicInstance'
1818
import { Directive, validateDirectiveName } from './directives'
19-
import { RootRenderFunction } from './renderer'
19+
import { ElementNamespace, RootRenderFunction } from './renderer'
2020
import { InjectionKey } from './apiInject'
2121
import { warn } from './warning'
2222
import { createVNode, cloneVNode, VNode } from './vnode'
@@ -47,7 +47,7 @@ export interface App<HostElement = any> {
4747
mount(
4848
rootContainer: HostElement | string,
4949
isHydrate?: boolean,
50-
isSVG?: boolean
50+
namespace?: boolean | ElementNamespace
5151
): ComponentPublicInstance
5252
unmount(): void
5353
provide<T>(key: InjectionKey<T> | string, value: T): this
@@ -297,7 +297,7 @@ export function createAppAPI<HostElement>(
297297
mount(
298298
rootContainer: HostElement,
299299
isHydrate?: boolean,
300-
isSVG?: boolean
300+
namespace?: boolean | ElementNamespace
301301
): any {
302302
if (!isMounted) {
303303
// #5571
@@ -313,17 +313,29 @@ export function createAppAPI<HostElement>(
313313
// this will be set on the root instance on initial mount.
314314
vnode.appContext = context
315315

316+
if (namespace === true) {
317+
namespace = 'svg'
318+
} else if (namespace === false) {
319+
namespace = undefined
320+
}
321+
316322
// HMR root reload
317323
if (__DEV__) {
318324
context.reload = () => {
319-
render(cloneVNode(vnode), rootContainer, isSVG)
325+
// casting to ElementNamespace because TS doesn't guarantee type narrowing
326+
// over function boundaries
327+
render(
328+
cloneVNode(vnode),
329+
rootContainer,
330+
namespace as ElementNamespace
331+
)
320332
}
321333
}
322334

323335
if (isHydrate && hydrate) {
324336
hydrate(vnode as VNode<Node, Element>, rootContainer as any)
325337
} else {
326-
render(vnode, rootContainer, isSVG)
338+
render(vnode, rootContainer, namespace)
327339
}
328340
isMounted = true
329341
app._container = rootContainer

packages/runtime-core/src/compat/global.ts

+10-4
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
} from '@vue/shared'
1818
import { warn } from '../warning'
1919
import { cloneVNode, createVNode } from '../vnode'
20-
import { RootRenderFunction } from '../renderer'
20+
import { ElementNamespace, RootRenderFunction } from '../renderer'
2121
import {
2222
App,
2323
AppConfig,
@@ -503,15 +503,21 @@ function installCompatMount(
503503
container = selectorOrEl || document.createElement('div')
504504
}
505505

506-
const isSVG = container instanceof SVGElement
506+
let namespace: ElementNamespace
507+
if (container instanceof SVGElement) namespace = 'svg'
508+
else if (
509+
typeof MathMLElement === 'function' &&
510+
container instanceof MathMLElement
511+
)
512+
namespace = 'mathml'
507513

508514
// HMR root reload
509515
if (__DEV__) {
510516
context.reload = () => {
511517
const cloned = cloneVNode(vnode)
512518
// compat mode will use instance if not reset to null
513519
cloned.component = null
514-
render(cloned, container, isSVG)
520+
render(cloned, container, namespace)
515521
}
516522
}
517523

@@ -538,7 +544,7 @@ function installCompatMount(
538544
container.innerHTML = ''
539545

540546
// TODO hydration
541-
render(vnode, container, isSVG)
547+
render(vnode, container, namespace)
542548

543549
if (container instanceof Element) {
544550
container.removeAttribute('v-cloak')

packages/runtime-core/src/components/KeepAlive.ts

+11-4
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ import {
3737
queuePostRenderEffect,
3838
MoveType,
3939
RendererElement,
40-
RendererNode
40+
RendererNode,
41+
ElementNamespace
4142
} from '../renderer'
4243
import { setTransitionHooks } from './BaseTransition'
4344
import { ComponentRenderContext } from '../componentPublicInstance'
@@ -64,7 +65,7 @@ export interface KeepAliveContext extends ComponentRenderContext {
6465
vnode: VNode,
6566
container: RendererElement,
6667
anchor: RendererNode | null,
67-
isSVG: boolean,
68+
namespace: ElementNamespace,
6869
optimized: boolean
6970
) => void
7071
deactivate: (vnode: VNode) => void
@@ -125,7 +126,13 @@ const KeepAliveImpl: ComponentOptions = {
125126
} = sharedContext
126127
const storageContainer = createElement('div')
127128

128-
sharedContext.activate = (vnode, container, anchor, isSVG, optimized) => {
129+
sharedContext.activate = (
130+
vnode,
131+
container,
132+
anchor,
133+
namespace,
134+
optimized
135+
) => {
129136
const instance = vnode.component!
130137
move(vnode, container, anchor, MoveType.ENTER, parentSuspense)
131138
// in case props have changed
@@ -136,7 +143,7 @@ const KeepAliveImpl: ComponentOptions = {
136143
anchor,
137144
instance,
138145
parentSuspense,
139-
isSVG,
146+
namespace,
140147
vnode.slotScopeIds,
141148
optimized
142149
)

packages/runtime-core/src/components/Suspense.ts

+26-25
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ import {
1818
MoveType,
1919
SetupRenderEffectFn,
2020
RendererNode,
21-
RendererElement
21+
RendererElement,
22+
ElementNamespace
2223
} from '../renderer'
2324
import { queuePostFlushCb } from '../scheduler'
2425
import { filterSingleRoot, updateHOCHostEl } from '../componentRenderUtils'
@@ -63,7 +64,7 @@ export const SuspenseImpl = {
6364
anchor: RendererNode | null,
6465
parentComponent: ComponentInternalInstance | null,
6566
parentSuspense: SuspenseBoundary | null,
66-
isSVG: boolean,
67+
namespace: ElementNamespace,
6768
slotScopeIds: string[] | null,
6869
optimized: boolean,
6970
// platform-specific impl passed from renderer
@@ -76,7 +77,7 @@ export const SuspenseImpl = {
7677
anchor,
7778
parentComponent,
7879
parentSuspense,
79-
isSVG,
80+
namespace,
8081
slotScopeIds,
8182
optimized,
8283
rendererInternals
@@ -88,7 +89,7 @@ export const SuspenseImpl = {
8889
container,
8990
anchor,
9091
parentComponent,
91-
isSVG,
92+
namespace,
9293
slotScopeIds,
9394
optimized,
9495
rendererInternals
@@ -130,7 +131,7 @@ function mountSuspense(
130131
anchor: RendererNode | null,
131132
parentComponent: ComponentInternalInstance | null,
132133
parentSuspense: SuspenseBoundary | null,
133-
isSVG: boolean,
134+
namespace: ElementNamespace,
134135
slotScopeIds: string[] | null,
135136
optimized: boolean,
136137
rendererInternals: RendererInternals
@@ -147,7 +148,7 @@ function mountSuspense(
147148
container,
148149
hiddenContainer,
149150
anchor,
150-
isSVG,
151+
namespace,
151152
slotScopeIds,
152153
optimized,
153154
rendererInternals
@@ -161,7 +162,7 @@ function mountSuspense(
161162
null,
162163
parentComponent,
163164
suspense,
164-
isSVG,
165+
namespace,
165166
slotScopeIds
166167
)
167168
// now check if we have encountered any async deps
@@ -179,7 +180,7 @@ function mountSuspense(
179180
anchor,
180181
parentComponent,
181182
null, // fallback tree will not have suspense context
182-
isSVG,
183+
namespace,
183184
slotScopeIds
184185
)
185186
setActiveBranch(suspense, vnode.ssFallback!)
@@ -195,7 +196,7 @@ function patchSuspense(
195196
container: RendererElement,
196197
anchor: RendererNode | null,
197198
parentComponent: ComponentInternalInstance | null,
198-
isSVG: boolean,
199+
namespace: ElementNamespace,
199200
slotScopeIds: string[] | null,
200201
optimized: boolean,
201202
{ p: patch, um: unmount, o: { createElement } }: RendererInternals
@@ -218,7 +219,7 @@ function patchSuspense(
218219
null,
219220
parentComponent,
220221
suspense,
221-
isSVG,
222+
namespace,
222223
slotScopeIds,
223224
optimized
224225
)
@@ -232,7 +233,7 @@ function patchSuspense(
232233
anchor,
233234
parentComponent,
234235
null, // fallback tree will not have suspense context
235-
isSVG,
236+
namespace,
236237
slotScopeIds,
237238
optimized
238239
)
@@ -267,7 +268,7 @@ function patchSuspense(
267268
null,
268269
parentComponent,
269270
suspense,
270-
isSVG,
271+
namespace,
271272
slotScopeIds,
272273
optimized
273274
)
@@ -281,7 +282,7 @@ function patchSuspense(
281282
anchor,
282283
parentComponent,
283284
null, // fallback tree will not have suspense context
284-
isSVG,
285+
namespace,
285286
slotScopeIds,
286287
optimized
287288
)
@@ -296,7 +297,7 @@ function patchSuspense(
296297
anchor,
297298
parentComponent,
298299
suspense,
299-
isSVG,
300+
namespace,
300301
slotScopeIds,
301302
optimized
302303
)
@@ -311,7 +312,7 @@ function patchSuspense(
311312
null,
312313
parentComponent,
313314
suspense,
314-
isSVG,
315+
namespace,
315316
slotScopeIds,
316317
optimized
317318
)
@@ -330,7 +331,7 @@ function patchSuspense(
330331
anchor,
331332
parentComponent,
332333
suspense,
333-
isSVG,
334+
namespace,
334335
slotScopeIds,
335336
optimized
336337
)
@@ -349,7 +350,7 @@ function patchSuspense(
349350
null,
350351
parentComponent,
351352
suspense,
352-
isSVG,
353+
namespace,
353354
slotScopeIds,
354355
optimized
355356
)
@@ -376,7 +377,7 @@ export interface SuspenseBoundary {
376377
vnode: VNode<RendererNode, RendererElement, SuspenseProps>
377378
parent: SuspenseBoundary | null
378379
parentComponent: ComponentInternalInstance | null
379-
isSVG: boolean
380+
namespace: ElementNamespace
380381
container: RendererElement
381382
hiddenContainer: RendererElement
382383
anchor: RendererNode | null
@@ -413,7 +414,7 @@ function createSuspenseBoundary(
413414
container: RendererElement,
414415
hiddenContainer: RendererElement,
415416
anchor: RendererNode | null,
416-
isSVG: boolean,
417+
namespace: ElementNamespace,
417418
slotScopeIds: string[] | null,
418419
optimized: boolean,
419420
rendererInternals: RendererInternals,
@@ -455,7 +456,7 @@ function createSuspenseBoundary(
455456
vnode,
456457
parent: parentSuspense,
457458
parentComponent,
458-
isSVG,
459+
namespace,
459460
container,
460461
hiddenContainer,
461462
anchor,
@@ -576,7 +577,7 @@ function createSuspenseBoundary(
576577
return
577578
}
578579

579-
const { vnode, activeBranch, parentComponent, container, isSVG } =
580+
const { vnode, activeBranch, parentComponent, container, namespace } =
580581
suspense
581582

582583
// invoke @fallback event
@@ -594,7 +595,7 @@ function createSuspenseBoundary(
594595
next(activeBranch!),
595596
parentComponent,
596597
null, // fallback tree will not have suspense context
597-
isSVG,
598+
namespace,
598599
slotScopeIds,
599600
optimized
600601
)
@@ -675,7 +676,7 @@ function createSuspenseBoundary(
675676
// consider the comment placeholder case.
676677
hydratedEl ? null : next(instance.subTree),
677678
suspense,
678-
isSVG,
679+
namespace,
679680
optimized
680681
)
681682
if (placeholder) {
@@ -721,7 +722,7 @@ function hydrateSuspense(
721722
vnode: VNode,
722723
parentComponent: ComponentInternalInstance | null,
723724
parentSuspense: SuspenseBoundary | null,
724-
isSVG: boolean,
725+
namespace: ElementNamespace,
725726
slotScopeIds: string[] | null,
726727
optimized: boolean,
727728
rendererInternals: RendererInternals,
@@ -742,7 +743,7 @@ function hydrateSuspense(
742743
node.parentNode!,
743744
document.createElement('div'),
744745
null,
745-
isSVG,
746+
namespace,
746747
slotScopeIds,
747748
optimized,
748749
rendererInternals,

0 commit comments

Comments
 (0)