From 4f06eebc1c2a29d0e4165c6e87f849732ec2cd0f Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 21 Jan 2020 11:32:17 -0500 Subject: [PATCH] fix(dom): fix and mount and updates --- .../src/transforms/transformElement.ts | 18 +++--- packages/runtime-core/src/renderer.ts | 17 +++-- packages/vue/__tests__/svg.spec.ts | 63 +++++++++++++++++++ 3 files changed, 85 insertions(+), 13 deletions(-) create mode 100644 packages/vue/__tests__/svg.spec.ts diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts index 09a865112dd..0d907498b67 100644 --- a/packages/compiler-core/src/transforms/transformElement.ts +++ b/packages/compiler-core/src/transforms/transformElement.ts @@ -70,9 +70,7 @@ export const transformElement: NodeTransform = (node, context) => { let runtimeDirectives: DirectiveNode[] | undefined let dynamicPropNames: string[] | undefined let dynamicComponent: string | CallExpression | undefined - // technically this is web specific but we are keeping it in core to avoid - // extra complexity - let isSVG = false + let shouldUseBlock = false // handle dynamic component const isProp = findProp(node, 'is') @@ -110,8 +108,12 @@ export const transformElement: NodeTransform = (node, context) => { nodeType = toValidAssetId(tag, `component`) } else { // plain element - nodeType = `"${node.tag}"` - isSVG = node.tag === 'svg' + nodeType = `"${tag}"` + // and must be forced into blocks so that block + // updates inside get proper isSVG flag at runtime. (#639, #643) + // This is technically web-specific, but splitting the logic out of core + // leads to too much unnecessary complexity. + shouldUseBlock = tag === 'svg' || tag === 'foreignObject' } const args: CallExpression['arguments'] = [nodeType] @@ -197,10 +199,8 @@ export const transformElement: NodeTransform = (node, context) => { } const { loc } = node - const vnode = isSVG - ? // must be forced into blocks so that block updates inside retain - // isSVG flag at runtime. (#639, #643) - createSequenceExpression([ + const vnode = shouldUseBlock + ? createSequenceExpression([ createCallExpression(context.helper(OPEN_BLOCK)), createCallExpression(context.helper(CREATE_BLOCK), args, loc) ]) diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index e136caa4d5e..d26f73dae9a 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -370,7 +370,7 @@ export function createRenderer< optimized: boolean ) { const el = (vnode.el = hostCreateElement(vnode.type as string, isSVG)) - const { props, shapeFlag, transition, scopeId } = vnode + const { type, props, shapeFlag, transition, scopeId } = vnode // props if (props != null) { @@ -406,7 +406,7 @@ export function createRenderer< null, parentComponent, parentSuspense, - isSVG, + isSVG && type !== 'foreignObject', optimized || vnode.dynamicChildren !== null ) } @@ -562,6 +562,7 @@ export function createRenderer< ) } + const areChildrenSVG = isSVG && n2.type !== 'foreignObject' if (dynamicChildren != null) { patchBlockChildren( n1.dynamicChildren!, @@ -569,11 +570,19 @@ export function createRenderer< el, parentComponent, parentSuspense, - isSVG + areChildrenSVG ) } else if (!optimized) { // full diff - patchChildren(n1, n2, el, null, parentComponent, parentSuspense, isSVG) + patchChildren( + n1, + n2, + el, + null, + parentComponent, + parentSuspense, + areChildrenSVG + ) } if (newProps.onVnodeUpdated != null) { diff --git a/packages/vue/__tests__/svg.spec.ts b/packages/vue/__tests__/svg.spec.ts new file mode 100644 index 00000000000..8644d399c91 --- /dev/null +++ b/packages/vue/__tests__/svg.spec.ts @@ -0,0 +1,63 @@ +// SVG logic is technically dom-specific, but the logic is placed in core +// because splitting it out of core would lead to unnecessary complexity in both +// the renderer and compiler implementations. +// Related files: +// - runtime-core/src/renderer.ts +// - compiler-core/src/transoforms/transformElement.ts + +import { render, h, ref, nextTick } from '../src' + +describe('SVG support', () => { + test('should mount elements with correct namespaces', () => { + const root = document.createElement('div') + document.body.appendChild(root) + const App = { + template: ` +
+ + +
+ + +
+ ` + } + render(h(App), root) + 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') + }) + + test('should patch elements with correct namespaces', async () => { + const root = document.createElement('div') + document.body.appendChild(root) + const cls = ref('foo') + const App = { + setup: () => ({ cls }), + template: ` +
+ + +
+ + +
+ ` + } + render(h(App), root) + const f1 = document.querySelector('#f1')! + const f2 = document.querySelector('#f2')! + expect(f1.getAttribute('class')).toBe('foo') + expect(f2.className).toBe('foo') + + // set a transition class on the
- which is only respected on non-svg + // patches + ;(f2 as any)._vtc = ['baz'] + cls.value = 'bar' + await nextTick() + expect(f1.getAttribute('class')).toBe('bar') + expect(f2.className).toBe('bar baz') + }) +})