Skip to content

Commit f1c3867

Browse files
committed
use simple normalization for components, fix functional component multi-root node (fix #4472)
1 parent 6116bbf commit f1c3867

File tree

6 files changed

+79
-19
lines changed

6 files changed

+79
-19
lines changed

src/compiler/codegen/index.js

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33
import { genHandlers } from './events'
44
import { baseWarn, pluckModuleFunction } from '../helpers'
55
import baseDirectives from '../directives/index'
6-
import { camelize } from 'shared/util'
6+
import { camelize, no } from 'shared/util'
77

88
// configurable state
99
let warn
1010
let transforms
1111
let dataGenFns
1212
let platformDirectives
13+
let isPlatformReservedTag
1314
let staticRenderFns
1415
let onceCount
1516
let currentOptions
@@ -31,6 +32,7 @@ export function generate (
3132
transforms = pluckModuleFunction(options.modules, 'transformCode')
3233
dataGenFns = pluckModuleFunction(options.modules, 'genData')
3334
platformDirectives = options.directives || {}
35+
isPlatformReservedTag = options.isReservedTag || no
3436
const code = ast ? genElement(ast) : '_c("div")'
3537
staticRenderFns = prevStaticRenderFns
3638
onceCount = prevOnceCount
@@ -287,29 +289,42 @@ function genChildren (el: ASTElement, checkSkip?: boolean): string | void {
287289
el.tag !== 'slot') {
288290
return genElement(el)
289291
}
292+
const normalizationType = getNormalizationType(children)
290293
return `[${children.map(genNode).join(',')}]${
291294
checkSkip
292-
? canSkipNormalization(children) ? '' : ',true'
295+
? normalizationType ? `,${normalizationType}` : ''
293296
: ''
294297
}`
295298
}
296299
}
297300

298-
function canSkipNormalization (children): boolean {
301+
// determine the normalzation needed for the children array.
302+
// 0: no normalization needed
303+
// 1: simple normalization needed (possible 1-level deep nested array)
304+
// 2: full nomralization needed
305+
function getNormalizationType (children): number {
299306
for (let i = 0; i < children.length; i++) {
300307
const el: any = children[i]
301308
if (needsNormalization(el) ||
302309
(el.if && el.ifConditions.some(c => needsNormalization(c.block)))) {
303-
return false
310+
return 2
311+
}
312+
if (maybeComponent(el) ||
313+
(el.if && el.ifConditions.some(c => maybeComponent(c.block)))) {
314+
return 1
304315
}
305316
}
306-
return true
317+
return 0
307318
}
308319

309320
function needsNormalization (el) {
310321
return el.for || el.tag === 'template' || el.tag === 'slot'
311322
}
312323

324+
function maybeComponent (el) {
325+
return el.type === 1 && !isPlatformReservedTag(el.tag)
326+
}
327+
313328
function genNode (node: ASTNode) {
314329
if (node.type === 1) {
315330
return genElement(node)

src/core/instance/render.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export function initRender (vm: Component) {
3333
vm.$scopedSlots = {}
3434
// bind the createElement fn to this instance
3535
// so that we get proper render context inside it.
36-
// args order: tag, data, children, needNormalization, alwaysNormalize
36+
// args order: tag, data, children, normalizationType, alwaysNormalize
3737
// internal version is used by render functions compiled from templates
3838
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
3939
// normalization is always applied for the public version, used in

src/core/vdom/create-element.js

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,37 @@
33
import VNode, { createEmptyVNode } from './vnode'
44
import config from '../config'
55
import { createComponent } from './create-component'
6-
import { normalizeChildren } from './helpers/index'
6+
import { normalizeChildren, simpleNormalizeChildren } from './helpers/index'
77
import { warn, resolveAsset, isPrimitive } from '../util/index'
88

9+
const SIMPLE_NORMALIZE = 1
10+
const ALWAYS_NORMALIZE = 2
11+
912
// wrapper function for providing a more flexible interface
1013
// without getting yelled at by flow
1114
export function createElement (
1215
context: Component,
1316
tag: any,
1417
data: any,
1518
children: any,
16-
needNormalization: any,
19+
normalizationType: any,
1720
alwaysNormalize: boolean
1821
): VNode {
1922
if (Array.isArray(data) || isPrimitive(data)) {
20-
needNormalization = children
23+
normalizationType = children
2124
children = data
2225
data = undefined
2326
}
24-
if (alwaysNormalize) needNormalization = true
25-
return _createElement(context, tag, data, children, needNormalization)
27+
if (alwaysNormalize) normalizationType = ALWAYS_NORMALIZE
28+
return _createElement(context, tag, data, children, normalizationType)
2629
}
2730

2831
export function _createElement (
2932
context: Component,
3033
tag?: string | Class<Component> | Function | Object,
3134
data?: VNodeData,
3235
children?: any,
33-
needNormalization?: boolean
36+
normalizationType?: number
3437
): VNode {
3538
if (data && data.__ob__) {
3639
process.env.NODE_ENV !== 'production' && warn(
@@ -51,8 +54,10 @@ export function _createElement (
5154
data.scopedSlots = { default: children[0] }
5255
children.length = 0
5356
}
54-
if (needNormalization) {
57+
if (normalizationType === ALWAYS_NORMALIZE) {
5558
children = normalizeChildren(children)
59+
} else if (normalizationType === SIMPLE_NORMALIZE) {
60+
children = simpleNormalizeChildren(children)
5661
}
5762
let vnode, ns
5863
if (typeof tag === 'string') {

src/core/vdom/helpers/normalize-children.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,31 @@
33
import { isPrimitive } from 'core/util/index'
44
import VNode, { createTextVNode } from 'core/vdom/vnode'
55

6+
// The template compiler attempts to minimize the need for normalization by
7+
// statically analyzing the template at compile time.
8+
//
9+
// For plain HTML markup, normalization can be completely skipped because the
10+
// generated render function is guaranteed to return Array<VNode>. There are
11+
// two cases where extra normalization is needed:
12+
13+
// 1. When the children contains components - because a functional component
14+
// may return an Array instead of a single root. In this case, just a simple
15+
// nomralization is needed - if any child is an Array, we flatten the whole
16+
// thing with Array.prototype.concat. It is guaranteed to be only 1-level deep
17+
// because functional components already normalize their own children.
18+
export function simpleNormalizeChildren (children: any) {
19+
for (let i = 0; i < children.length; i++) {
20+
if (Array.isArray(children[i])) {
21+
return Array.prototype.concat.apply([], children)
22+
}
23+
}
24+
return children
25+
}
26+
27+
// 2. When the children contains constrcuts that always generated nested Arrays,
28+
// e.g. <template>, <slot>, v-for, or when the children is provided by user
29+
// with hand-written render functions / JSX. In such cases a full normalization
30+
// is needed to cater to all possible types of children values.
631
export function normalizeChildren (children: any): ?Array<VNode> {
732
return isPrimitive(children)
833
? [createTextVNode(children)]

test/unit/features/options/functional.spec.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,21 @@ describe('Options functional', () => {
2222
}).then(done)
2323
})
2424

25+
it('should support returning more than one root node', () => {
26+
const vm = new Vue({
27+
template: `<div><test></test></div>`,
28+
components: {
29+
test: {
30+
functional: true,
31+
render (h) {
32+
return [h('span', 'foo'), h('span', 'bar')]
33+
}
34+
}
35+
}
36+
}).$mount()
37+
expect(vm.$el.innerHTML).toBe('<span>foo</span><span>bar</span>')
38+
})
39+
2540
it('should support slots', () => {
2641
const vm = new Vue({
2742
data: { test: 'foo' },

test/unit/modules/compiler/codegen.spec.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ describe('codegen', () => {
7070
// v-for with extra element
7171
assertCodegen(
7272
'<div><p></p><li v-for="item in items"></li></div>',
73-
`with(this){return _c('div',[_c('p'),_l((items),function(item){return _c('li')})],true)}`
73+
`with(this){return _c('div',[_c('p'),_l((items),function(item){return _c('li')})],2)}`
7474
)
7575
})
7676

@@ -133,28 +133,28 @@ describe('codegen', () => {
133133
it('generate template tag', () => {
134134
assertCodegen(
135135
'<div><template><p>{{hello}}</p></template></div>',
136-
`with(this){return _c('div',[[_c('p',[_v(_s(hello))])]],true)}`
136+
`with(this){return _c('div',[[_c('p',[_v(_s(hello))])]],2)}`
137137
)
138138
})
139139

140140
it('generate single slot', () => {
141141
assertCodegen(
142142
'<div><slot></slot></div>',
143-
`with(this){return _c('div',[_t("default")],true)}`
143+
`with(this){return _c('div',[_t("default")],2)}`
144144
)
145145
})
146146

147147
it('generate named slot', () => {
148148
assertCodegen(
149149
'<div><slot name="one"></slot></div>',
150-
`with(this){return _c('div',[_t("one")],true)}`
150+
`with(this){return _c('div',[_t("one")],2)}`
151151
)
152152
})
153153

154154
it('generate slot fallback content', () => {
155155
assertCodegen(
156156
'<div><slot><div>hi</div></slot></div>',
157-
`with(this){return _c('div',[_t("default",[_c('div',[_v("hi")])])],true)}`
157+
`with(this){return _c('div',[_t("default",[_c('div',[_v("hi")])])],2)}`
158158
)
159159
})
160160

@@ -397,7 +397,7 @@ describe('codegen', () => {
397397
it('generate svg component with children', () => {
398398
assertCodegen(
399399
'<svg><my-comp><circle :r="10"></circle></my-comp></svg>',
400-
`with(this){return _c('svg',[_c('my-comp',[_c('circle',{attrs:{"r":10}})])])}`
400+
`with(this){return _c('svg',[_c('my-comp',[_c('circle',{attrs:{"r":10}})])],1)}`
401401
)
402402
})
403403

0 commit comments

Comments
 (0)