Browse Source

annotation for vdom (wip)

dev
Evan You 9 years ago
parent
commit
45d96c7dbc
  1. 1
      .eslintignore
  2. 33
      flow/component.js
  3. 22
      flow/global-api.js
  4. 3
      flow/options.js
  5. 43
      flow/vnode.js
  6. 6
      src/core/instance/lifecycle.js
  7. 13
      src/core/instance/render.js
  8. 72
      src/core/vdom/create-component.js
  9. 30
      src/core/vdom/create-element.js
  10. 20
      src/core/vdom/helpers.js
  11. 23
      src/core/vdom/vnode.js
  12. 20
      src/server/render.js
  13. 4
      test/unit/features/directives/once.spec.js

1
.eslintignore

@ -1 +1,2 @@
flow
dist

33
flow/declarations.js → flow/component.js

@ -38,7 +38,7 @@ declare interface Component {
$createElement: (
tag?: string | Component,
data?: Object,
children?: Array<?VNode> | string,
children?: VNodeChildren,
namespace?: string
) => VNode;
@ -61,10 +61,10 @@ declare interface Component {
_mount: () => Component;
_update: (vnode: VNode) => void;
_updateFromParent: (
propsData?: Object,
listeners?: { [key: string]: Function | Array<Function> },
propsData: ?Object,
listeners: ?{ [key: string]: Function | Array<Function> },
parentVnode: VNode,
renderChildren: Array<VNode> | () => Array<VNode>
renderChildren: ?VNodeChildren
) => void;
// rendering
_render: () => VNode;
@ -72,7 +72,7 @@ declare interface Component {
__h__: (
tag?: string | Component | Object,
data?: Object,
children?: Array<?VNode> | string,
children?: VNodeChildren,
namespace?: string
) => VNode;
__toString__: (value: any) => string;
@ -91,26 +91,3 @@ declare interface Component {
// allow dynamic method registration
[key: string]: any
}
declare interface GlobalAPI {
cid: number;
options: Object;
config: Config;
util: Object;
extend: (options: Object) => Function;
set: (obj: Object, key: string, value: any) => void;
delete: (obj: Object, key: string) => void;
nextTick: (fn: Function, context?: Object) => void;
use: (plugin: Function | Object) => void;
mixin: (mixin: Object) => void;
compile: (template: string) => { render: Function, staticRenderFns: Array<Function> };
directive: (id: string, def?: Function | Object) => Function | Object | void;
component: (id: string, def?: Class<Component> | Object) => Class<Component>;
transition: (id: string, def?: Object) => Object | void;
filter: (id: string, def?: Function) => Function | void;
// allow dynamic method registration
[key: string]: any
}

22
flow/global-api.js

@ -0,0 +1,22 @@
declare interface GlobalAPI {
cid: number;
options: Object;
config: Config;
util: Object;
extend: (options: Object) => Function;
set: (obj: Object, key: string, value: any) => void;
delete: (obj: Object, key: string) => void;
nextTick: (fn: Function, context?: Object) => void;
use: (plugin: Function | Object) => void;
mixin: (mixin: Object) => void;
compile: (template: string) => { render: Function, staticRenderFns: Array<Function> };
directive: (id: string, def?: Function | Object) => Function | Object | void;
component: (id: string, def?: Class<Component> | Object) => Class<Component>;
transition: (id: string, def?: Object) => Object | void;
filter: (id: string, def?: Function) => Function | void;
// allow dynamic method registration
[key: string]: any
}

3
flow/options.js

@ -0,0 +1,3 @@
declare type ComponentOptions = {
}

43
flow/vnode.js

@ -0,0 +1,43 @@
declare type VNodeChildren = Array<any> | () => Array<any> | string
declare type VNodeComponentOptions = {
Ctor: Class<Component>,
propsData: ?Object,
listeners: ?Object,
parent: Component,
children: ?VNodeChildren
}
declare interface MountedComponentVNode {
componentOptions: VNodeComponentOptions;
child: Component;
}
declare interface VNodeData {
pre?: true;
key?: string | number;
slot?: string;
staticClass?: string;
class?: any;
style?: Array<Object> | Object;
show?: true;
props?: { [key: string]: any };
attrs?: { [key: string]: string };
staticAttrs?: { [key: string]: string };
hook?: { [key: string]: Function };
on?: { [key: string]: Function | Array<Function> };
transition?: {
definition: String | Object,
appear: boolean
};
inlineTemplate?: {
render: Function,
staticRenderFns: Array<Function>
};
directives?: Array<{
name: string,
value?: any,
arg?: string,
modifiers?: { [key: string]: boolean }
}>;
}

6
src/core/instance/lifecycle.js

@ -79,10 +79,10 @@ export function lifecycleMixin (Vue: Class<Component>) {
}
Vue.prototype._updateFromParent = function (
propsData?: Object,
listeners?: Object,
propsData: ?Object,
listeners: ?Object,
parentVnode: VNode,
renderChildren: Array<VNode> | () => Array<VNode>
renderChildren: ?VNodeChildren
) {
const vm: Component = this
vm.$options._parentVnode = parentVnode

13
src/core/instance/render.js

@ -3,7 +3,7 @@
import type VNode from '../vdom/vnode'
import createElement from '../vdom/create-element'
import { emptyVNode } from '../vdom/vnode'
import { flatten } from '../vdom/helpers'
import { normalizeChildren } from '../vdom/helpers'
import { bind, remove, isObject, renderString } from 'shared/util'
import { resolveAsset, nextTick } from '../util/index'
@ -32,12 +32,12 @@ export function renderMixin (Vue: Class<Component>) {
Vue.prototype._render = function (): VNode {
const vm: Component = this
const prev = renderState.activeInstance
renderState.activeInstance = vm
if (!vm._isMounted) {
// render static sub-trees for once on initial render
renderStaticTrees(vm)
}
const prev = renderState.activeInstance
renderState.activeInstance = vm
const { render, _renderChildren, _parentVnode } = vm.$options
// resolve slots. becaues slots are rendered in parent scope,
// we set the activeInstance to parent.
@ -131,9 +131,12 @@ function renderStaticTrees (vm: Component) {
}
}
function resolveSlots (vm: Component, renderChildren: () => Array<?VNode> | void) {
function resolveSlots (
vm: Component,
renderChildren: Array<any> | () => Array<any> | string
) {
if (renderChildren) {
const children = flatten(renderChildren())
const children = normalizeChildren(renderChildren)
const slots = {}
const defaultSlot = []
let i = children.length

72
src/core/vdom/create-component.js

@ -1,3 +1,5 @@
/* @flow */
import Vue from '../instance/index'
import VNode from './vnode'
import { callHook } from '../instance/lifecycle'
@ -6,7 +8,13 @@ import { warn, isObject, hasOwn, hyphenate } from '../util/index'
const hooks = { init, prepatch, insert, destroy }
const hooksToMerge = Object.keys(hooks)
export function createComponent (Ctor, data, parent, children, context) {
export function createComponent (
Ctor: Class<Component> | Function | Object | void,
data: VNodeData | void,
parent: Component,
context: Component,
children: ?VNodeChildren
): VNode | void {
if (process.env.NODE_ENV !== 'production' &&
children && typeof children !== 'function') {
warn(
@ -21,8 +29,10 @@ export function createComponent (Ctor, data, parent, children, context) {
if (isObject(Ctor)) {
Ctor = Vue.extend(Ctor)
}
if (process.env.NODE_ENV !== 'production' && typeof Ctor !== 'function') {
warn(`Invalid Component definition: ${Ctor}`, parent)
if (typeof Ctor !== 'function') {
if (process.env.NODE_ENV !== 'production') {
warn(`Invalid Component definition: ${Ctor}`, parent)
}
return
}
@ -57,22 +67,30 @@ export function createComponent (Ctor, data, parent, children, context) {
// child component listeners instead of DOM listeners
const listeners = data.on
if (listeners) {
data.on = null
delete data.on
}
// return a placeholder vnode
const name = Ctor.options.name ? ('-' + Ctor.options.name) : ''
const vnode = new VNode(
`vue-component-${Ctor.cid}${name}`, data,
undefined, undefined, undefined, undefined, context
`vue-component-${Ctor.cid}${name}`,
data, undefined, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, parent, children }
)
vnode.componentOptions = { Ctor, propsData, listeners, parent, children }
return vnode
}
export function createComponentInstanceForVnode (vnode) {
const { Ctor, propsData, listeners, parent, children } = vnode.componentOptions
const options = {
export function createComponentInstanceForVnode (vnode: VNode): Component {
const { Ctor, propsData, listeners, parent, children } = vnode.componentOptions || {}
const options: {
parent: Component,
propsData: ?Object,
_parentVnode: VNode,
_parentListeners: ?Object,
_renderChildren: ?VNodeChildren,
render?: Function,
staticRenderFns?: Array<Function>
} = {
parent,
propsData,
_parentVnode: vnode,
@ -80,7 +98,7 @@ export function createComponentInstanceForVnode (vnode) {
_renderChildren: children
}
// check inline-template render functions
const inlineTemplate = vnode.data.inlineTemplate
const inlineTemplate = vnode.data && vnode.data.inlineTemplate
if (inlineTemplate) {
options.render = inlineTemplate.render
options.staticRenderFns = inlineTemplate.staticRenderFns
@ -88,12 +106,15 @@ export function createComponentInstanceForVnode (vnode) {
return new Ctor(options)
}
function init (vnode) {
function init (vnode: VNode) {
const child = vnode.child = createComponentInstanceForVnode(vnode)
child.$mount()
}
function prepatch (oldVnode, vnode) {
function prepatch (
oldVnode: MountedComponentVNode,
vnode: MountedComponentVNode
) {
const { listeners, propsData, children } = vnode.componentOptions
vnode.child = oldVnode.child
vnode.child._updateFromParent(
@ -104,15 +125,18 @@ function prepatch (oldVnode, vnode) {
)
}
function insert (vnode) {
function insert (vnode: MountedComponentVNode) {
callHook(vnode.child, 'mounted')
}
function destroy (vnode) {
function destroy (vnode: MountedComponentVNode) {
vnode.child.$destroy()
}
function resolveAsyncComponent (factory, cb) {
function resolveAsyncComponent (
factory: Function,
cb: Function
): Class<Component> | void {
if (factory.resolved) {
return factory.resolved
} else if (factory.requested) {
@ -124,7 +148,7 @@ function resolveAsyncComponent (factory, cb) {
let sync = true
factory(
// resolve
res => {
(res: Object | Class<Component>) => {
if (isObject(res)) {
res = Vue.extend(res)
}
@ -152,7 +176,7 @@ function resolveAsyncComponent (factory, cb) {
}
}
function extractProps (data, Ctor) {
function extractProps (data: VNodeData, Ctor: Class<Component>): ?Object {
// we are only extrating raw values here.
// validation and default values are handled in the child
// component itself.
@ -176,7 +200,12 @@ function extractProps (data, Ctor) {
return res
}
function checkProp (res, hash, key, altKey) {
function checkProp (
res: Object,
hash: ?Object,
key: string,
altKey: string
): boolean {
if (hash) {
if (hasOwn(hash, key)) {
res[key] = hash[key]
@ -188,9 +217,10 @@ function checkProp (res, hash, key, altKey) {
return true
}
}
return false
}
function mergeHooks (data) {
function mergeHooks (data: VNodeData) {
if (data.hook) {
for (let i = 0; i < hooksToMerge.length; i++) {
const key = hooksToMerge[i]
@ -203,7 +233,7 @@ function mergeHooks (data) {
}
}
function mergeHook (a, b) {
function mergeHook (a: Function, b: Function): Function {
// since all hooks have at most two args, use fixed args
// to avoid having to use fn.apply().
return (_, __) => {

30
src/core/vdom/create-element.js

@ -1,13 +1,27 @@
/* @flow */
import VNode, { emptyVNode } from './vnode'
import config from '../config'
import { createComponent } from './create-component'
import { flatten } from './helpers'
import { normalizeChildren } from './helpers'
import { renderState } from '../instance/render'
import { warn, resolveAsset } from '../util/index'
export default function createElement (tag, data, children, namespace) {
const context = this
const parent = renderState.activeInstance
export default function createElement (
tag?: string | Class<Component> | Function | Object,
data?: VNodeData,
children?: VNodeChildren,
namespace?: string
): VNode | void {
const context: Component = this
const parent: Component | null = renderState.activeInstance
if (!parent) {
process.env.NODE_ENV !== 'production' && warn(
'createElement cannot be called outside of component ' +
'render functions.'
)
return
}
if (!tag) {
// in case of component :is set to falsy value
return emptyVNode
@ -16,11 +30,11 @@ export default function createElement (tag, data, children, namespace) {
let Ctor
if (config.isReservedTag(tag)) {
return new VNode(
tag, data, flatten(children),
tag, data, normalizeChildren(children),
undefined, undefined, namespace, context
)
} else if ((Ctor = resolveAsset(context.$options, 'components', tag))) {
return createComponent(Ctor, data, parent, children, context)
return createComponent(Ctor, data, parent, context, children)
} else {
if (process.env.NODE_ENV !== 'production') {
if (!namespace && config.isUnknownElement(tag)) {
@ -32,11 +46,11 @@ export default function createElement (tag, data, children, namespace) {
}
}
return new VNode(
tag, data, flatten(children && children()),
tag, data, normalizeChildren(children),
undefined, undefined, namespace, context
)
}
} else {
return createComponent(tag, data, parent, children, context)
return createComponent(tag, data, parent, context, children)
}
}

20
src/core/vdom/helpers.js

@ -1,19 +1,24 @@
/* @flow */
import { isPrimitive } from '../util/index'
import VNode from './vnode'
const whitespace = new VNode(undefined, undefined, undefined, ' ')
export function flatten (children) {
export function normalizeChildren (children: any): Array<VNode> {
if (typeof children === 'string') {
return [new VNode(undefined, undefined, undefined, children)]
}
if (typeof children === 'function') {
children = children()
}
if (Array.isArray(children)) {
const res = []
for (let i = 0, l = children.length; i < l; i++) {
const c = children[i]
// flatten nested
// nested
if (Array.isArray(c)) {
res.push.apply(res, flatten(c))
res.push.apply(res, normalizeChildren(c))
} else if (isPrimitive(c)) {
// optimize whitespace
if (c === ' ') {
@ -22,15 +27,16 @@ export function flatten (children) {
// convert primitive to vnode
res.push(new VNode(undefined, undefined, undefined, c))
}
} else if (c) {
} else if (c instanceof VNode) {
res.push(c)
}
}
return res
}
return []
}
export function updateListeners (on, oldOn, add) {
export function updateListeners (on: Object, oldOn: Object, add: Function) {
let name, cur, old, event, capture
for (name in on) {
cur = on[name]
@ -56,7 +62,7 @@ export function updateListeners (on, oldOn, add) {
}
}
function arrInvoker (arr) {
function arrInvoker (arr: Array<Function>): Function {
return function (ev) {
const single = arguments.length === 1
for (let i = 0; i < arr.length; i++) {
@ -65,7 +71,7 @@ function arrInvoker (arr) {
}
}
function fnInvoker (o) {
function fnInvoker (o: { fn: Function }): Function {
return function (ev) {
const single = arguments.length === 1
single ? o.fn(ev) : o.fn.apply(null, arguments)

23
src/core/vdom/vnode.js

@ -1,16 +1,27 @@
import type Vue from 'core/instance/index'
/* @flow */
export default class VNode {
tag: string | void;
data: Object | void;
data: VNodeData | void;
children: Array<VNode> | void;
text: string | void;
elm: Element | void;
elm: Node | void;
ns: string | void;
context: Vue | void;
context: Component | void;
key: string | number | void;
componentOptions: VNodeComponentOptions | void;
child: Component | void;
constructor (tag, data, children, text, elm, ns, context) {
constructor (
tag?: string,
data?: VNodeData,
children?: Array<VNode>,
text?: string,
elm?: Node,
ns?: string,
context?: Component,
componentOptions?: VNodeComponentOptions
) {
this.tag = tag
this.data = data
this.children = children
@ -19,6 +30,8 @@ export default class VNode {
this.ns = ns
this.context = context
this.key = data && data.key
this.componentOptions = componentOptions
this.child = undefined
}
}

20
src/server/render.js

@ -8,7 +8,12 @@ export function createRenderFunction (
directives: Object,
isUnaryTag: Function
) {
function renderNode (node: VNode, write: Function, next: Function, isRoot: boolean) {
function renderNode (
node: VNode,
write: Function,
next: Function,
isRoot: boolean
) {
if (node.componentOptions) {
const child = createComponentInstanceForVnode(node)
renderNode(child._render(), write, next, isRoot)
@ -21,7 +26,12 @@ export function createRenderFunction (
}
}
function renderElement (el: VNode, write: Function, next: Function, isRoot: boolean) {
function renderElement (
el: VNode,
write: Function,
next: Function,
isRoot: boolean
) {
if (isRoot) {
if (!el.data) el.data = {}
if (!el.data.attrs) el.data.attrs = {}
@ -81,7 +91,11 @@ export function createRenderFunction (
return markup + '>'
}
return function render (component: Component, write: Function, done: Function) {
return function render (
component: Component,
write: (text: string, next: Function) => void,
done: Function
) {
renderNode(component._render(), write, done, true)
}
}

4
test/unit/features/directives/once.spec.js

@ -28,7 +28,7 @@ describe('Directive v-once', () => {
}
}
}).$mount()
expect(vm.$children.length).toBe(0)
expect(vm.$children.length).toBe(1)
expect(vm.$el.innerHTML)
.toBe('<span>hello</span><div>hello</div>')
vm.a = 'world'
@ -53,7 +53,7 @@ describe('Directive v-once', () => {
}
}
}).$mount()
expect(vm.$children.length).toBe(0)
expect(vm.$children.length).toBe(1)
expect(vm.$el.innerHTML)
.toBe('<span>hello</span><div>hello</div>')
vm.a = 'world'

Loading…
Cancel
Save