Skip to content

Commit 79e1058

Browse files
committed
wip: refactor compiler to skip normalization when possible
wip fix wip fix wip fix
1 parent 1def2d1 commit 79e1058

File tree

9 files changed

+103
-61
lines changed

9 files changed

+103
-61
lines changed

flow/component.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ declare interface Component {
8888
_o: (vnode: VNode | Array<VNode>, index: number, key: string) => VNode | VNodeChildren;
8989
// toString
9090
_s: (value: any) => string;
91+
// text to VNode
92+
_v: (value: string | number) => VNode;
9193
// toNumber
9294
_n: (value: string) => number | string;
9395
// empty vnode

src/compiler/codegen/index.js

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ function genElement (el: ASTElement): string {
6161
} else {
6262
const data = el.plain ? undefined : genData(el)
6363

64-
const children = el.inlineTemplate ? null : genChildren(el)
64+
const children = el.inlineTemplate ? null : genChildren(el, true)
6565
code = `_h('${el.tag}'${
6666
data ? `,${data}` : '' // data
6767
}${
@@ -276,12 +276,36 @@ function genScopedSlot (key: string, el: ASTElement) {
276276
}}`
277277
}
278278

279-
function genChildren (el: ASTElement): string | void {
280-
if (el.children.length) {
281-
return '[' + el.children.map(genNode).join(',') + ']'
279+
function genChildren (el: ASTElement, checkSkip?: boolean): string | void {
280+
const children = el.children
281+
if (children.length) {
282+
const el: any = children[0]
283+
// optimize single v-for
284+
if (children.length === 1 &&
285+
el.for &&
286+
el.tag !== 'template' &&
287+
el.tag !== 'slot') {
288+
return genElement(el)
289+
}
290+
return `[${children.map(genNode).join(',')}]${
291+
checkSkip
292+
? canSkipNormalization(children) ? '' : ',true'
293+
: ''
294+
}`
282295
}
283296
}
284297

298+
function canSkipNormalization (children): boolean {
299+
for (let i = 0; i < children.length; i++) {
300+
const el: any = children[i]
301+
if (el.for || el.tag === 'template' || el.tag === 'slot' ||
302+
(el.if && el.ifConditions.some(c => c.tag === 'template'))) {
303+
return false
304+
}
305+
}
306+
return true
307+
}
308+
285309
function genNode (node: ASTNode) {
286310
if (node.type === 1) {
287311
return genElement(node)
@@ -291,9 +315,10 @@ function genNode (node: ASTNode) {
291315
}
292316

293317
function genText (text: ASTText | ASTExpression): string {
294-
return text.type === 2
318+
return `_v(${text.type === 2
295319
? text.expression // no need for () because already wrapped in _s()
296320
: transformSpecialNewlines(JSON.stringify(text.text))
321+
})`
297322
}
298323
299324
function genSlot (el: ASTElement): string {
@@ -310,7 +335,7 @@ function genSlot (el: ASTElement): string {
310335
311336
// componentName is el.component, take it as argument to shun flow's pessimistic refinement
312337
function genComponent (componentName, el): string {
313-
const children = el.inlineTemplate ? null : genChildren(el)
338+
const children = el.inlineTemplate ? null : genChildren(el, true)
314339
return `_h(${componentName},${genData(el)}${
315340
children ? `,${children}` : ''
316341
})`

src/core/instance/render.js

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
11
/* @flow */
22

33
import config from '../config'
4-
import VNode, { createEmptyVNode, cloneVNode, cloneVNodes } from '../vdom/vnode'
54
import { normalizeChildren } from '../vdom/helpers/index'
5+
import VNode, {
6+
cloneVNode,
7+
cloneVNodes,
8+
createTextVNode,
9+
createEmptyVNode
10+
} from '../vdom/vnode'
611
import {
7-
warn, formatComponentName, bind, isObject, toObject,
8-
nextTick, resolveAsset, _toString, toNumber, looseEqual, looseIndexOf
12+
warn,
13+
isObject,
14+
toObject,
15+
nextTick,
16+
toNumber,
17+
_toString,
18+
looseEqual,
19+
looseIndexOf,
20+
resolveAsset,
21+
formatComponentName
922
} from '../util/index'
1023

1124
import { createElement } from '../vdom/create-element'
@@ -18,9 +31,12 @@ export function initRender (vm: Component) {
1831
const renderContext = parentVnode && parentVnode.context
1932
vm.$slots = resolveSlots(vm.$options._renderChildren, renderContext)
2033
vm.$scopedSlots = {}
21-
// bind the public createElement fn to this instance
34+
// bind the createElement fn to this instance
2235
// so that we get proper render context inside it.
23-
vm.$createElement = bind(createElement, vm)
36+
// args order: tag, data, children, needNormalization
37+
// the needNormalization flag is flipped and defaults to true for the public version.
38+
vm._h = (a, b, c, d) => createElement(vm, a, b, c, d, false)
39+
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
2440
if (vm.$options.el) {
2541
vm.$mount(vm.$options.el)
2642
}
@@ -89,10 +105,10 @@ export function renderMixin (Vue: Class<Component>) {
89105
return vnode
90106
}
91107

92-
// shorthands used in render functions
93-
Vue.prototype._h = createElement
94108
// toString for mustaches
95109
Vue.prototype._s = _toString
110+
// convert text to vnode
111+
Vue.prototype._v = createTextVNode
96112
// number conversion
97113
Vue.prototype._n = toNumber
98114
// empty vnode

src/core/vdom/create-component.js

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { resolveConstructorOptions } from '../instance/init'
66
import { activeInstance, callHook } from '../instance/lifecycle'
77
import { resolveSlots } from '../instance/render'
88
import { createElement } from './create-element'
9-
import { warn, isObject, hasOwn, hyphenate, validateProp, bind } from '../util/index'
9+
import { warn, isObject, hasOwn, hyphenate, validateProp } from '../util/index'
1010

1111
const hooks = { init, prepatch, insert, destroy }
1212
const hooksToMerge = Object.keys(hooks)
@@ -105,19 +105,17 @@ function createFunctionalComponent (
105105
props[key] = validateProp(key, propOptions, propsData)
106106
}
107107
}
108-
const vnode = Ctor.options.render.call(
109-
null,
110-
// ensure the createElement function in functional components
111-
// gets a unique context - this is necessary for correct named slot check
112-
bind(createElement, { _self: Object.create(context) }),
113-
{
114-
props,
115-
data,
116-
parent: context,
117-
children: normalizeChildren(children),
118-
slots: () => resolveSlots(children, context)
119-
}
120-
)
108+
// ensure the createElement function in functional components
109+
// gets a unique context - this is necessary for correct named slot check
110+
const _context = Object.create(context)
111+
const h = (a, b, c, d) => createElement(_context, a, b, c, !d)
112+
const vnode = Ctor.options.render.call(null, h, {
113+
props,
114+
data,
115+
parent: context,
116+
children: normalizeChildren(children),
117+
slots: () => resolveSlots(children, context)
118+
})
121119
if (vnode instanceof VNode) {
122120
vnode.functionalContext = context
123121
if (data.slot) {

src/core/vdom/create-element.js

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,41 @@ import VNode, { createEmptyVNode } from './vnode'
44
import config from '../config'
55
import { createComponent } from './create-component'
66
import { normalizeChildren } from './helpers/index'
7-
import { warn, resolveAsset } from '../util/index'
7+
import { warn, resolveAsset, isPrimitive } from '../util/index'
88

99
// wrapper function for providing a more flexible interface
1010
// without getting yelled at by flow
1111
export function createElement (
12+
context: Component,
1213
tag: any,
1314
data: any,
14-
children: any
15-
): VNode | void {
16-
if (data && (Array.isArray(data) || typeof data !== 'object')) {
15+
children: any,
16+
needNormalization: any,
17+
flipNormalization: boolean
18+
): VNode {
19+
if (Array.isArray(data) || isPrimitive(data)) {
20+
needNormalization = children
1721
children = data
1822
data = undefined
1923
}
20-
// make sure to use real instance instead of proxy as context
21-
return _createElement(this._self, tag, data, children)
24+
if (flipNormalization) needNormalization = !needNormalization
25+
return _createElement(context, tag, data, children, needNormalization)
2226
}
2327

2428
export function _createElement (
2529
context: Component,
2630
tag?: string | Class<Component> | Function | Object,
2731
data?: VNodeData,
28-
children?: VNodeChildren | void
29-
): VNode | void {
32+
children?: any,
33+
needNormalization?: boolean
34+
): VNode {
3035
if (data && data.__ob__) {
3136
process.env.NODE_ENV !== 'production' && warn(
3237
`Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
3338
'Always create fresh vnode data objects in each render!',
3439
context
3540
)
36-
return
41+
return createEmptyVNode()
3742
}
3843
if (!tag) {
3944
// in case of component :is set to falsy value
@@ -52,24 +57,24 @@ export function _createElement (
5257
if (config.isReservedTag(tag)) {
5358
// platform built-in elements
5459
return new VNode(
55-
tag, data, normalizeChildren(children, ns),
60+
tag, data, needNormalization ? normalizeChildren(children, ns) : children,
5661
undefined, undefined, ns, context
5762
)
5863
} else if ((Ctor = resolveAsset(context.$options, 'components', tag))) {
5964
// component
60-
return createComponent(Ctor, data, context, children, tag)
65+
return createComponent(Ctor, data, context, children, tag) || createEmptyVNode()
6166
} else {
6267
// unknown or unlisted namespaced elements
6368
// check at runtime because it may get assigned a namespace when its
6469
// parent normalizes children
6570
const childNs = tag === 'foreignObject' ? 'xhtml' : ns
6671
return new VNode(
67-
tag, data, normalizeChildren(children, childNs),
72+
tag, data, needNormalization ? normalizeChildren(children, childNs) : children,
6873
undefined, undefined, ns, context
6974
)
7075
}
7176
} else {
7277
// direct component options / constructor
73-
return createComponent(tag, data, context, children)
78+
return createComponent(tag, data, context, children) || createEmptyVNode()
7479
}
7580
}

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

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* @flow */
22

33
import { isPrimitive } from 'core/util/index'
4-
import VNode from 'core/vdom/vnode'
4+
import VNode, { createTextVNode } from 'core/vdom/vnode'
55

66
export function normalizeChildren (children: any, ns: ?string): Array<VNode> | void {
77
return isPrimitive(children)
@@ -30,9 +30,7 @@ function normalizeArrayChildren (children: any, ns: ?string, nestedIndex?: strin
3030
}
3131
} else {
3232
if (c.text && last && last.text) {
33-
if (!last.isCloned) {
34-
last.text += c.text
35-
}
33+
res[res.length - 1] = createTextVNode(last.text + c.text)
3634
} else {
3735
// inherit parent namespace
3836
if (ns) {
@@ -49,10 +47,6 @@ function normalizeArrayChildren (children: any, ns: ?string, nestedIndex?: strin
4947
return res
5048
}
5149

52-
function createTextVNode (val) {
53-
return new VNode(undefined, undefined, undefined, String(val))
54-
}
55-
5650
function applyNS (vnode, ns) {
5751
if (vnode.tag && !vnode.ns) {
5852
vnode.ns = ns

src/core/vdom/vnode.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ export const createEmptyVNode = () => {
5858
return node
5959
}
6060

61+
export function createTextVNode (val: string | number) {
62+
return new VNode(undefined, undefined, undefined, String(val))
63+
}
64+
6165
// optimized shallow clone
6266
// used for static nodes and slot nodes because they may be reused across
6367
// multiple renders, cloning them avoids errors when DOM manipulations rely

test/unit/features/component/component-async.spec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ describe('Component async', () => {
1717
}
1818
}
1919
}).$mount()
20-
expect(vm.$el.innerHTML).toBe('')
20+
expect(vm.$el.innerHTML).toBe('<!---->')
2121
expect(vm.$children.length).toBe(0)
2222
function next () {
2323
expect(vm.$el.innerHTML).toBe('<div>hi</div>')
@@ -151,7 +151,7 @@ describe('Component async', () => {
151151
}
152152
}
153153
}).$mount()
154-
expect(vm.$el.innerHTML).toBe('')
154+
expect(vm.$el.innerHTML).toBe('<!---->')
155155
expect(vm.$children.length).toBe(0)
156156
function next () {
157157
expect(vm.$el.innerHTML).toBe('<div>hi</div>')

0 commit comments

Comments
 (0)