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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy