wip: switch to new implementation

This commit is contained in:
Evan You 2019-05-25 23:51:20 +08:00
parent 35effdee5a
commit 3cded86b98
24 changed files with 311 additions and 4412 deletions

View File

@ -1,214 +0,0 @@
import { EMPTY_OBJ, NOOP } from '@vue/shared'
import { VNode, Slots, RenderNode, MountedVNode } from './vdom'
import {
Data,
ComponentOptions,
ComponentClassOptions,
ComponentPropsOptions,
WatchOptions
} from './componentOptions'
import { setupWatcher } from './componentWatch'
import { ReactiveEffect, DebuggerEvent, ComputedGetter } from '@vue/observer'
import { nextTick } from '@vue/scheduler'
import { ErrorTypes } from './errorHandling'
import { initializeComponentInstance } from './componentInstance'
import { EventEmitter, invokeListeners } from './optional/eventEmitter'
import { warn } from './warning'
import { ComponentProxy } from './componentProxy'
// public component instance type
export interface Component<P = {}, D = {}> extends PublicInstanceMethods {
readonly $el: any
readonly $vnode: MountedVNode
readonly $parentVNode: MountedVNode
readonly $data: D
readonly $props: Readonly<P>
readonly $attrs: Readonly<Data>
readonly $slots: Slots
readonly $root: Component
readonly $parent: Component
readonly $children: Component[]
readonly $options: ComponentOptions<P, D, this>
readonly $refs: Record<string | symbol, any>
readonly $proxy: this
}
interface PublicInstanceMethods {
$forceUpdate(): void
$nextTick(fn: () => any): Promise<void>
$watch(
keyOrFn: string | ((this: this) => any),
cb: (this: this, newValue: any, oldValue: any) => void,
options?: WatchOptions
): () => void
$emit(name: string, ...payload: any[]): void
}
export interface APIMethods<P = {}, D = {}> {
data(): Partial<D>
render(props: Readonly<P>, slots: Slots, attrs: Data, parentVNode: VNode): any
}
export interface LifecycleMethods {
beforeCreate(): void
created(): void
beforeMount(): void
mounted(): void
beforeUpdate(vnode: VNode): void
updated(vnode: VNode): void
beforeUnmount(): void
unmounted(): void
errorCaptured(): (
err: Error,
type: ErrorTypes,
instance: ComponentInstance | null,
vnode: VNode
) => boolean | void
activated(): void
deactivated(): void
renderTracked(e: DebuggerEvent): void
renderTriggered(e: DebuggerEvent): void
}
export interface ComponentClass extends ComponentClassOptions {
options?: ComponentOptions
new <P = {}, D = {}>(): Component<P, D>
}
export interface FunctionalComponent<P = {}> {
(props: P, slots: Slots, attrs: Data, parentVNode: VNode): any
props?: ComponentPropsOptions<P>
displayName?: string
}
export type ComponentType = ComponentClass | FunctionalComponent
// Internal type that represents a mounted instance.
// It extends ComponentImplementation with mounted instance properties.
export interface ComponentInstance<P = {}, D = {}>
extends ComponentImplementation,
Partial<APIMethods<P, D>>,
Partial<LifecycleMethods> {
constructor: ComponentClass
render: APIMethods<P, D>['render']
$vnode: MountedVNode
$data: D
$props: P
$attrs: Data
$slots: Slots
$root: ComponentProxy
$children: ComponentProxy[]
$options: ComponentOptions<P, D>
$proxy: ComponentProxy<this>
_update: ReactiveEffect
_queueJob: ((fn: () => void) => void)
}
// actual implementation of the component
class ComponentImplementation implements PublicInstanceMethods {
get $el(): any {
const el = this.$vnode && this.$vnode.el
return typeof el === 'function' ? (el as any)() : el
}
$vnode: VNode | null = null
$parentVNode: VNode | null = null
$data: Data | null = null
$props: Data | null = null
$attrs: Data | null = null
$slots: Slots | null = null
$root: ComponentProxy | null = null
$parent: ComponentProxy | null = null
$children: ComponentProxy[] = []
$options: ComponentOptions | null = null
$refs: Record<string, ComponentInstance | RenderNode> = {}
$proxy: ComponentProxy<this> | null = null
_rawData: Data | null = null
_computedGetters: Record<string, ComputedGetter> | null = null
_watchHandles: Set<ReactiveEffect> | null = null
_mounted: boolean = false
_unmounted: boolean = false
_events: { [event: string]: Function[] | null } | null = null
_update: ReactiveEffect | null = null
_queueJob: ((fn: () => void) => void) | null = null
_isVue: boolean = true
_inactiveRoot: boolean = false
constructor(props?: object) {
if (props === void 0) {
// When invoked without any arguments, this is the default path where
// we initiailize a proper component instance. Note the returned value
// here is actually a proxy of the raw instance (and will be the `this`
// context) in all sub-class methods, including the constructor!
return initializeComponentInstance(this as any) as any
} else {
// the presence of the props argument indicates that this class is being
// instantiated as a mixin, and should expose the props on itself
// so that the extended class constructor (and property initializers) can
// access $props.
this.$props = props
Object.assign(this, props)
}
if (__COMPAT__) {
;(this as any)._eventEmitter = new EventEmitter(this)
}
}
// necessary to tell this apart from a functional
render(...args: any[]): any {
if (__DEV__) {
const name =
(this.$options && this.$options.displayName) || this.constructor.name
warn(`Class component \`${name}\` is missing render() method.`)
}
}
// to be set by renderer during mount
$forceUpdate: () => void = NOOP
$nextTick(fn: () => any): Promise<void> {
return nextTick(fn)
}
$watch(
keyOrFn: string | ((this: this) => any),
cb: (this: this, newValue: any, oldValue: any) => void,
options?: WatchOptions
): () => void {
return setupWatcher(this as any, keyOrFn, cb, options)
}
$emit(name: string, ...payload: any[]) {
const parentData =
(this.$parentVNode && this.$parentVNode.data) || EMPTY_OBJ
const parentListener =
parentData['on' + name] || parentData['on' + name.toLowerCase()]
if (parentListener) {
invokeListeners(parentListener, payload)
}
}
}
// legacy event emitter interface exposed on component instances
if (__COMPAT__) {
const p = ComponentImplementation.prototype as any
;['on', 'off', 'once'].forEach(key => {
p['$' + key] = function(...args: any[]) {
this._eventEmitter[key](...args)
return this
}
})
const emit = p.$emit
p.$emit = function(...args: any[]) {
emit.call(this, ...args)
this._eventEmitter.emit(...args)
return this
}
}
// the exported Component has the implementation details of the actual
// ComponentImplementation class but with proper type inference of ComponentClass.
export const Component = ComponentImplementation as ComponentClass

View File

@ -1,31 +0,0 @@
import { NOOP, isFunction } from '@vue/shared'
import { computed, stop, ComputedGetter } from '@vue/observer'
import { ComponentInstance } from './component'
import { ComponentComputedOptions } from './componentOptions'
export type ComputedHandles = Record<string, ComputedGetter>
export function initializeComputed(
instance: ComponentInstance,
computedOptions: ComponentComputedOptions | undefined
) {
if (!computedOptions) {
return
}
const handles: ComputedHandles = (instance._computedGetters = {})
const proxy = instance.$proxy
for (const key in computedOptions) {
const option = computedOptions[key]
const getter = isFunction(option) ? option : option.get || NOOP
handles[key] = computed(getter, proxy)
}
}
export function teardownComputed(instance: ComponentInstance) {
const handles = instance._computedGetters
if (handles !== null) {
for (const key in handles) {
stop(handles[key].effect)
}
}
}

View File

@ -1,106 +0,0 @@
import { VNode, MountedVNode } from './vdom'
import { ComponentInstance, ComponentClass } from './component'
import { initializeState } from './componentState'
import { initializeProps } from './componentProps'
import { initializeWatch, teardownWatch } from './componentWatch'
import { initializeComputed, teardownComputed } from './componentComputed'
import { ComponentProxy, createRenderProxy } from './componentProxy'
import { resolveComponentOptionsFromClass } from './componentOptions'
import { VNodeFlags } from './flags'
import { ErrorTypes, callLifecycleHookWithHandler } from './errorHandling'
import { stop } from '@vue/observer'
import { EMPTY_OBJ } from '@vue/shared'
let currentVNode: VNode | null = null
let currentContextVNode: VNode | null = null
export function createComponentInstance(vnode: VNode): ComponentInstance {
// component instance creation is done in two steps.
// first, `initializeComponentInstance` is called inside base component
// constructor as the instance is created so that the extended component's
// constructor has access to public properties and most importantly props.
// we are storing the vnodes in variables here so that there's no need to
// always pass args in super()
currentVNode = vnode
currentContextVNode = vnode.contextVNode
const Component = vnode.tag as ComponentClass
const instanceProxy = new Component() as ComponentProxy
const instance = instanceProxy._self
// then we finish the initialization by collecting properties set on the
// instance
const {
$options: { created, computed, watch }
} = instance
initializeState(instance, !Component.fromOptions)
initializeComputed(instance, computed)
initializeWatch(instance, watch)
instance.$slots = currentVNode.slots || EMPTY_OBJ
if (created) {
callLifecycleHookWithHandler(created, instanceProxy, ErrorTypes.CREATED)
}
currentVNode = currentContextVNode = null
return instance
}
// this is called inside the base component's constructor
// it initializes all the way up to props so that they are available
// inside the extended component's constructor, and returns the proxy of the
// raw instance.
export function initializeComponentInstance<T extends ComponentInstance>(
instance: T
): ComponentProxy<T> {
if (__DEV__ && currentVNode === null) {
throw new Error(
`Component classes are not meant to be manually instantiated.`
)
}
instance.$options = resolveComponentOptionsFromClass(instance.constructor)
instance.$parentVNode = currentVNode as MountedVNode
// renderProxy
const proxy = (instance.$proxy = createRenderProxy(instance))
// parent chain management
if (currentContextVNode !== null) {
// locate first non-functional parent
while (currentContextVNode !== null) {
if ((currentContextVNode.flags & VNodeFlags.COMPONENT_STATEFUL) > 0) {
const parentComponent = (currentContextVNode as VNode)
.children as ComponentInstance
instance.$parent = parentComponent.$proxy
instance.$root = parentComponent.$root
parentComponent.$children.push(proxy)
break
}
currentContextVNode = currentContextVNode.contextVNode
}
} else {
instance.$root = proxy
}
// beforeCreate hook is called right in the constructor
const { beforeCreate, props } = instance.$options
if (beforeCreate) {
callLifecycleHookWithHandler(beforeCreate, proxy, ErrorTypes.BEFORE_CREATE)
}
initializeProps(instance, props, (currentVNode as VNode).data)
return proxy
}
export function teardownComponentInstance(instance: ComponentInstance) {
const parentComponent = instance.$parent && instance.$parent._self
if (parentComponent && !parentComponent._unmounted) {
parentComponent.$children.splice(
parentComponent.$children.indexOf(instance.$proxy),
1
)
}
stop(instance._update)
teardownComputed(instance)
teardownWatch(instance)
}

View File

@ -1,283 +0,0 @@
import {
Component,
ComponentInstance,
ComponentClass,
APIMethods,
LifecycleMethods
} from './component'
import { isArray, isObject, isFunction } from '@vue/shared'
import { normalizePropsOptions } from './componentProps'
import { warn } from './warning'
import { h } from './h'
export type Data = Record<string, any>
export interface ComponentClassOptions<P = {}, This = ComponentInstance> {
props?: ComponentPropsOptions<P>
computed?: ComponentComputedOptions<This>
watch?: ComponentWatchOptions<This>
displayName?: string
fromOptions?: boolean
}
export interface ComponentOptions<
P = {},
D = {},
This = ComponentInstance<P, D>
>
extends ComponentClassOptions<P, This>,
Partial<APIMethods<P, D>>,
Partial<LifecycleMethods> {
// TODO other options
readonly [key: string]: any
}
export type ComponentPropsOptions<P = Data> = {
[K in keyof P]: PropValidator<P[K]>
}
export type Prop<T> = { (): T } | { new (...args: any[]): T & object }
export type PropType<T> = Prop<T> | Prop<T>[]
export type PropValidator<T> = PropOptions<T> | PropType<T>
export interface PropOptions<T = any> {
type?: PropType<T> | true | null
required?: boolean
default?: T | null | undefined | (() => T | null | undefined)
validator?(value: T): boolean
}
export interface ComponentComputedOptions<This = ComponentInstance> {
[key: string]: ((this: This, c: This) => any) | SingleComputedOptions<This>
}
type SingleComputedOptions<This> = {
get: (this: This, c: This) => any
set?: (value: any) => void
cache?: boolean
}
export interface ComponentWatchOptions<This = ComponentInstance> {
[key: string]: ComponentWatchOption<This>
}
export type ComponentWatchOption<This = ComponentInstance> =
| WatchHandler<This>
| WatchHandler<This>[]
| WatchOptionsWithHandler<This>
| string
export type WatchHandler<This = any> = (
this: This,
val: any,
oldVal: any
) => void
export interface WatchOptionsWithHandler<This = any> extends WatchOptions {
handler: WatchHandler<This>
}
export interface WatchOptions {
sync?: boolean
deep?: boolean
immediate?: boolean
}
type ReservedKeys = { [K in keyof (APIMethods & LifecycleMethods)]: 1 }
export const reservedMethods: ReservedKeys = {
data: 1,
render: 1,
beforeCreate: 1,
created: 1,
beforeMount: 1,
mounted: 1,
beforeUpdate: 1,
updated: 1,
beforeUnmount: 1,
unmounted: 1,
errorCaptured: 1,
activated: 1,
deactivated: 1,
renderTracked: 1,
renderTriggered: 1
}
export function isReservedKey(key: string): boolean {
return key[0] === '_' || key[0] === '$' || reservedMethods.hasOwnProperty(key)
}
// This is a special marker from the @prop decorator.
// The decorator stores prop options on the Class' prototype as __prop_xxx
const propPrefixRE = /^__prop_/
// This is called in the base component constructor and the return value is
// set on the instance as $options.
export function resolveComponentOptionsFromClass(
Class: ComponentClass
): ComponentOptions {
if (Class.hasOwnProperty('options')) {
return Class.options as ComponentOptions
}
let options = {} as any
const staticDescriptors = Object.getOwnPropertyDescriptors(Class)
for (const key in staticDescriptors) {
const { enumerable, get, value } = staticDescriptors[key]
if (enumerable || get) {
options[key] = get ? get() : value
}
}
// pre-normalize array props options into object.
// we may need to attach more props to it (declared by decorators)
if (Array.isArray(options.props)) {
options.props = normalizePropsOptions(options.props)
}
const instanceDescriptors = Object.getOwnPropertyDescriptors(Class.prototype)
for (const key in instanceDescriptors) {
const { get, value } = instanceDescriptors[key]
if (get) {
// computed properties
;(options.computed || (options.computed = {}))[key] = get
// there's no need to do anything for the setter
// as it's already defined on the prototype
} else if (isFunction(value) && key !== 'constructor') {
if (key in reservedMethods) {
// lifecycle hooks / reserved methods
options[key] = value
} else {
// normal methods
;(options.methods || (options.methods = {}))[key] = value
}
} else if (propPrefixRE.test(key)) {
// decorator-declared props
const propName = key.replace(propPrefixRE, '')
;(options.props || (options.props = {}))[propName] = value
}
}
// post-normalize all prop options into same object format
if (options.props) {
options.props = normalizePropsOptions(options.props)
}
const ParentClass = Object.getPrototypeOf(Class)
if (ParentClass !== Component) {
const parentOptions = resolveComponentOptionsFromClass(ParentClass)
options = mergeComponentOptions(parentOptions, options)
}
Class.options = options
return options
}
export function createComponentClassFromOptions(
options: ComponentOptions
): ComponentClass {
class AnonymousComponent extends Component {
static options = options
// indicate this component was created from options
static fromOptions = true
}
const proto = AnonymousComponent.prototype as any
for (const key in options) {
const value = options[key]
if (key === 'render') {
if (__COMPAT__) {
options.render = function() {
return value.call(this, h)
}
}
// so that we can call instance.render directly
proto.render = options.render
} else if (key === 'computed') {
// create computed setters on prototype
// (getters are handled by the render proxy)
for (const computedKey in value) {
const computed = value[computedKey]
const set = isObject(computed) && computed.set
if (set) {
Object.defineProperty(proto, computedKey, {
configurable: true,
set
})
}
}
} else if (key === 'methods') {
for (const method in value) {
if (__DEV__ && proto.hasOwnProperty(method)) {
warn(
`Object syntax contains method name that conflicts with ` +
`lifecycle hook: "${method}"`
)
}
proto[method] = value[method]
}
} else if (__COMPAT__) {
if (key === 'name') {
options.displayName = value
} else if (key === 'render') {
options.render = function() {
return value.call(this, h)
}
} else if (key === 'beforeDestroy') {
options.beforeUnmount = value
} else if (key === 'destroyed') {
options.unmounted = value
}
}
}
return AnonymousComponent as ComponentClass
}
export function mergeComponentOptions(to: any, from: any): ComponentOptions {
const res: any = Object.assign({}, to)
if (from.mixins) {
from.mixins.forEach((mixin: any) => {
from = mergeComponentOptions(from, mixin)
})
}
for (const key in from) {
const value = from[key]
const existing = res[key]
if (isFunction(value) && isFunction(existing)) {
if (key === 'data') {
// for data we need to merge the returned value
res[key] = mergeDataFn(existing, value)
} else if (/^render|^errorCaptured/.test(key)) {
// render, renderTracked, renderTriggered & errorCaptured
// are never merged
res[key] = value
} else {
// merge lifecycle hooks
res[key] = mergeLifecycleHooks(existing, value)
}
} else if (isArray(value) && isArray(existing)) {
res[key] = existing.concat(value)
} else if (isObject(value) && isObject(existing)) {
res[key] = Object.assign({}, existing, value)
} else {
res[key] = value
}
}
return res
}
export function mergeLifecycleHooks(a: Function, b: Function): Function {
return function(...args: any[]) {
a.call(this, ...args)
b.call(this, ...args)
}
}
export function mergeDataFn(a: Function, b: Function): Function {
// TODO: backwards compat requires recursive merge,
// but maybe we should just warn if we detect clashing keys
return function() {
return Object.assign(a.call(this), b.call(this))
}
}

View File

@ -1,309 +0,0 @@
import { immutable, unwrap } from '@vue/observer'
import { ComponentInstance } from './component'
import {
Data,
PropOptions,
Prop,
PropType,
ComponentPropsOptions,
isReservedKey
} from './componentOptions'
import {
EMPTY_OBJ,
camelize,
hyphenate,
capitalize,
isString,
isFunction,
isArray,
isObject
} from '@vue/shared'
import { warn } from './warning'
const enum BooleanFlags {
shouldCast = '1',
shouldCastTrue = '2'
}
type NormalizedProp = PropOptions & {
[BooleanFlags.shouldCast]?: boolean
[BooleanFlags.shouldCastTrue]?: boolean
}
type NormalizedPropsOptions = Record<string, NormalizedProp>
export function initializeProps(
instance: ComponentInstance,
options: NormalizedPropsOptions | undefined,
data: Data | null
) {
const { 0: props, 1: attrs } = resolveProps(data, options)
instance.$props = __DEV__ ? immutable(props) : props
instance.$attrs = options
? __DEV__
? immutable(attrs)
: attrs
: instance.$props
}
// resolve raw VNode data.
// - filter out reserved keys (key, ref, slots)
// - extract class and style into $attrs (to be merged onto child
// component root)
// - for the rest:
// - if has declared props: put declared ones in `props`, the rest in `attrs`
// - else: everything goes in `props`.
const EMPTY_PROPS = [EMPTY_OBJ, EMPTY_OBJ] as [Data, Data]
export function resolveProps(
rawData: any,
_options: NormalizedPropsOptions | void
): [Data, Data] {
const hasDeclaredProps = _options != null
const options = _options as NormalizedPropsOptions
if (!rawData && !hasDeclaredProps) {
return EMPTY_PROPS
}
const props: any = {}
let attrs: any = void 0
if (rawData != null) {
for (const key in rawData) {
// key, ref, slots are reserved
if (key === 'key' || key === 'ref' || key === 'slots') {
continue
}
// any non-declared data are put into a separate `attrs` object
// for spreading
if (hasDeclaredProps && !options.hasOwnProperty(key)) {
;(attrs || (attrs = {}))[key] = rawData[key]
} else {
props[key] = rawData[key]
}
}
}
// set default values, cast booleans & run validators
if (hasDeclaredProps) {
for (const key in options) {
let opt = options[key]
if (opt == null) continue
const isAbsent = !props.hasOwnProperty(key)
const hasDefault = opt.hasOwnProperty('default')
const currentValue = props[key]
// default values
if (hasDefault && currentValue === undefined) {
const defaultValue = opt.default
props[key] = isFunction(defaultValue) ? defaultValue() : defaultValue
}
// boolean casting
if (opt[BooleanFlags.shouldCast]) {
if (isAbsent && !hasDefault) {
props[key] = false
} else if (
opt[BooleanFlags.shouldCastTrue] &&
(currentValue === '' || currentValue === hyphenate(key))
) {
props[key] = true
}
}
// runtime validation
if (__DEV__ && rawData) {
validateProp(key, unwrap(rawData[key]), opt, isAbsent)
}
}
} else {
// if component has no declared props, $attrs === $props
attrs = props
}
return [props, attrs]
}
export function normalizePropsOptions(
raw: ComponentPropsOptions | void
): NormalizedPropsOptions | void {
if (!raw) {
return
}
const normalized: NormalizedPropsOptions = {}
if (isArray(raw)) {
for (let i = 0; i < raw.length; i++) {
if (__DEV__ && !isString(raw[i])) {
warn(`props must be strings when using array syntax.`, raw[i])
}
const normalizedKey = camelize(raw[i])
if (!isReservedKey(normalizedKey)) {
normalized[normalizedKey] = EMPTY_OBJ
} else if (__DEV__) {
warn(`Invalid prop name: "${normalizedKey}" is a reserved property.`)
}
}
} else {
if (__DEV__ && !isObject(raw)) {
warn(`invalid props options`, raw)
}
for (const key in raw) {
const normalizedKey = camelize(key)
if (!isReservedKey(normalizedKey)) {
const opt = raw[key]
const prop = (normalized[normalizedKey] =
isArray(opt) || isFunction(opt) ? { type: opt } : opt)
if (prop) {
const booleanIndex = getTypeIndex(Boolean, prop.type)
const stringIndex = getTypeIndex(String, prop.type)
;(prop as NormalizedProp)[BooleanFlags.shouldCast] = booleanIndex > -1
;(prop as NormalizedProp)[BooleanFlags.shouldCastTrue] =
booleanIndex < stringIndex
}
} else if (__DEV__) {
warn(`Invalid prop name: "${normalizedKey}" is a reserved property.`)
}
}
}
return normalized
}
// use function string name to check type constructors
// so that it works across vms / iframes.
function getType(ctor: Prop<any>): string {
const match = ctor && ctor.toString().match(/^\s*function (\w+)/)
return match ? match[1] : ''
}
function isSameType(a: Prop<any>, b: Prop<any>): boolean {
return getType(a) === getType(b)
}
function getTypeIndex(
type: Prop<any>,
expectedTypes: PropType<any> | void | null | true
): number {
if (isArray(expectedTypes)) {
for (let i = 0, len = expectedTypes.length; i < len; i++) {
if (isSameType(expectedTypes[i], type)) {
return i
}
}
} else if (isObject(expectedTypes)) {
return isSameType(expectedTypes, type) ? 0 : -1
}
return -1
}
type AssertionResult = {
valid: boolean
expectedType: string
}
function validateProp(
name: string,
value: any,
prop: PropOptions<any>,
isAbsent: boolean
) {
const { type, required, validator } = prop
// required!
if (required && isAbsent) {
warn('Missing required prop: "' + name + '"')
return
}
// missing but optional
if (value == null && !prop.required) {
return
}
// type check
if (type != null && type !== true) {
let isValid = false
const types = isArray(type) ? type : [type]
const expectedTypes = []
// value is valid as long as one of the specified types match
for (let i = 0; i < types.length && !isValid; i++) {
const { valid, expectedType } = assertType(value, types[i])
expectedTypes.push(expectedType || '')
isValid = valid
}
if (!isValid) {
warn(getInvalidTypeMessage(name, value, expectedTypes))
return
}
}
// custom validator
if (validator && !validator(value)) {
warn('Invalid prop: custom validator check failed for prop "' + name + '".')
}
}
const simpleCheckRE = /^(String|Number|Boolean|Function|Symbol)$/
function assertType(value: any, type: Prop<any>): AssertionResult {
let valid
const expectedType = getType(type)
if (simpleCheckRE.test(expectedType)) {
const t = typeof value
valid = t === expectedType.toLowerCase()
// for primitive wrapper objects
if (!valid && t === 'object') {
valid = value instanceof type
}
} else if (expectedType === 'Object') {
valid = toRawType(value) === 'Object'
} else if (expectedType === 'Array') {
valid = isArray(value)
} else {
valid = value instanceof type
}
return {
valid,
expectedType
}
}
function getInvalidTypeMessage(
name: string,
value: any,
expectedTypes: string[]
): string {
let message =
`Invalid prop: type check failed for prop "${name}".` +
` Expected ${expectedTypes.map(capitalize).join(', ')}`
const expectedType = expectedTypes[0]
const receivedType = toRawType(value)
const expectedValue = styleValue(value, expectedType)
const receivedValue = styleValue(value, receivedType)
// check if we need to specify expected value
if (
expectedTypes.length === 1 &&
isExplicable(expectedType) &&
!isBoolean(expectedType, receivedType)
) {
message += ` with value ${expectedValue}`
}
message += `, got ${receivedType} `
// check if we need to specify received value
if (isExplicable(receivedType)) {
message += `with value ${receivedValue}.`
}
return message
}
function styleValue(value: any, type: string): string {
if (type === 'String') {
return `"${value}"`
} else if (type === 'Number') {
return `${Number(value)}`
} else {
return `${value}`
}
}
function toRawType(value: any): string {
return Object.prototype.toString.call(value).slice(8, -1)
}
function isExplicable(type: string): boolean {
const explicitTypes = ['string', 'number', 'boolean']
return explicitTypes.some(elem => type.toLowerCase() === elem)
}
function isBoolean(...args: string[]): boolean {
return args.some(elem => elem.toLowerCase() === 'boolean')
}

View File

@ -1,96 +0,0 @@
import { ComponentInstance } from './component'
import { isFunction } from '@vue/shared'
import { isRendering } from './componentRenderUtils'
import { isReservedKey, reservedMethods } from './componentOptions'
import { warn } from './warning'
const bindCache = new WeakMap()
// TODO: bound methods should also capture/handle errors
function getBoundMethod(fn: Function, target: any, receiver: any): Function {
let boundMethodsForTarget = bindCache.get(target)
if (boundMethodsForTarget === void 0) {
bindCache.set(target, (boundMethodsForTarget = new Map()))
}
let boundFn = boundMethodsForTarget.get(fn)
if (boundFn === void 0) {
boundMethodsForTarget.set(fn, (boundFn = fn.bind(receiver)))
}
return boundFn
}
const renderProxyHandlers = {
get(target: ComponentInstance<any, any>, key: string, receiver: any) {
let i: any
if (key === '_self') {
return target
} else if ((i = target._rawData) !== null && i.hasOwnProperty(key)) {
// data
// make sure to return from $data to register dependency
return target.$data[key]
} else if ((i = target.$options.props) != null && i.hasOwnProperty(key)) {
// props are only proxied if declared
return target.$props[key]
} else if (
(i = target._computedGetters) !== null &&
i.hasOwnProperty(key)
) {
// computed
return i[key]()
} else if (key[0] !== '_') {
if (__DEV__ && isRendering) {
if (key in reservedMethods) {
warn(
`"${key}" is a reserved method / lifecycle hook and should not be ` +
`used as a normal method during render.`
)
} else if (!(key in target)) {
warn(
`property "${key}" was accessed during render but does not exist ` +
`on instance.`
)
}
}
const value = Reflect.get(target, key, receiver)
if (key !== 'constructor' && isFunction(value)) {
// auto bind
return getBoundMethod(value, target, receiver)
} else {
return value
}
}
},
set(
target: ComponentInstance<any, any>,
key: string,
value: any,
receiver: any
): boolean {
let i: any
if (__DEV__) {
if (isReservedKey(key) && key in target) {
warn(`failed setting property "${key}": reserved fields are immutable.`)
return false
}
if ((i = target.$options.props) != null && i.hasOwnProperty(key)) {
warn(`failed setting property "${key}": props are immutable.`)
return false
}
}
if ((i = target._rawData) !== null && i.hasOwnProperty(key)) {
target.$data[key] = value
return true
} else {
return Reflect.set(target, key, value, receiver)
}
}
}
export type ComponentProxy<T = ComponentInstance> = T & { _self: T }
export function createRenderProxy<T extends ComponentInstance>(
instance: T
): ComponentProxy<T> {
debugger
return new Proxy(instance, renderProxyHandlers) as any
}

View File

@ -1,154 +0,0 @@
import { VNode, createFragment, createTextVNode, cloneVNode } from './vdom'
import { ComponentInstance, FunctionalComponent } from './component'
import { resolveProps } from './componentProps'
import { handleError, ErrorTypes } from './errorHandling'
import { VNodeFlags, ChildrenFlags } from './flags'
import { EMPTY_OBJ, isArray, isObject } from '@vue/shared'
export let isRendering = false
export function renderInstanceRoot(instance: ComponentInstance): VNode {
let vnode
const { render, $proxy, $props, $slots, $attrs, $parentVNode } = instance
if (__DEV__) {
isRendering = true
}
try {
vnode = render.call($proxy, $props, $slots, $attrs, $parentVNode)
} catch (err) {
if (__DEV__) {
isRendering = false
}
handleError(err, instance, ErrorTypes.RENDER)
}
if (__DEV__) {
isRendering = false
}
return normalizeComponentRoot(vnode, $parentVNode)
}
export function renderFunctionalRoot(vnode: VNode): VNode {
const render = vnode.tag as FunctionalComponent
const { 0: props, 1: attrs } = resolveProps(vnode.data, render.props)
let subTree
try {
subTree = render(props, vnode.slots || EMPTY_OBJ, attrs, vnode)
} catch (err) {
handleError(err, vnode, ErrorTypes.RENDER)
}
return normalizeComponentRoot(subTree, vnode)
}
function normalizeComponentRoot(
vnode: any,
componentVNode: VNode | null
): VNode {
if (vnode == null) {
vnode = createTextVNode('')
} else if (!isObject(vnode)) {
vnode = createTextVNode(vnode + '')
} else if (isArray(vnode)) {
if (vnode.length === 1) {
vnode = normalizeComponentRoot(vnode[0], componentVNode)
} else {
vnode = createFragment(vnode)
}
} else {
const { el, flags } = vnode
if (
componentVNode &&
(flags & VNodeFlags.COMPONENT || flags & VNodeFlags.ELEMENT)
) {
if (el) {
vnode = cloneVNode(vnode as VNode)
}
if (flags & VNodeFlags.COMPONENT) {
vnode.parentVNode = componentVNode
}
} else if (el) {
vnode = cloneVNode(vnode as VNode)
}
}
return vnode
}
export function shouldUpdateComponent(
prevVNode: VNode,
nextVNode: VNode
): boolean {
const { data: prevProps, childFlags: prevChildFlags } = prevVNode
const { data: nextProps, childFlags: nextChildFlags } = nextVNode
// If has different slots content, or has non-compiled slots,
// the child needs to be force updated.
if (
prevChildFlags !== nextChildFlags ||
(nextChildFlags & ChildrenFlags.DYNAMIC_SLOTS) > 0
) {
return true
}
if (prevProps === nextProps) {
return false
}
if (prevProps === null) {
return nextProps !== null
}
if (nextProps === null) {
return prevProps !== null
}
const nextKeys = Object.keys(nextProps)
if (nextKeys.length !== Object.keys(prevProps).length) {
return true
}
for (let i = 0; i < nextKeys.length; i++) {
const key = nextKeys[i]
if (nextProps[key] !== prevProps[key]) {
return true
}
}
return false
}
// DEV only
export function getReasonForComponentUpdate(
prevVNode: VNode,
nextVNode: VNode
): any {
const reasons = []
const { childFlags: prevChildFlags } = prevVNode
const { childFlags: nextChildFlags } = nextVNode
if (
prevChildFlags !== nextChildFlags ||
(nextChildFlags & ChildrenFlags.DYNAMIC_SLOTS) > 0
) {
reasons.push({
type: `slots may have changed`,
tip: `use function slots + $stable: true to avoid slot-triggered child updates.`
})
}
const prevProps = prevVNode.data || EMPTY_OBJ
const nextProps = nextVNode.data || EMPTY_OBJ
for (const key in nextProps) {
if (nextProps[key] !== prevProps[key]) {
reasons.push({
type: 'prop changed',
key,
value: nextProps[key],
oldValue: prevProps[key]
})
}
}
for (const key in prevProps) {
if (!(key in nextProps)) {
reasons.push({
type: 'prop changed',
key,
value: undefined,
oldValue: prevProps[key]
})
}
}
return {
type: 'triggered by parent',
reasons
}
}

View File

@ -1,31 +0,0 @@
import { ComponentInstance } from './component'
import { observable } from '@vue/observer'
import { isReservedKey } from './componentOptions'
export function initializeState(
instance: ComponentInstance,
shouldExtractInitializers: boolean
) {
const { data } = instance.$options
const rawData = (instance._rawData = (data ? data.call(instance) : {}) as any)
if (shouldExtractInitializers) {
extractInitializers(instance, rawData)
}
instance.$data = observable(rawData || {})
}
// extract properties initialized in a component's constructor
export function extractInitializers(
instance: ComponentInstance,
data: any = {}
): any {
const keys = Object.keys(instance)
const props = instance.$props
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
if (!isReservedKey(key) && !props.hasOwnProperty(key)) {
data[key] = (instance as any)[key]
}
}
return data
}

View File

@ -1,143 +0,0 @@
import {
EMPTY_OBJ,
NOOP,
isFunction,
isArray,
isString,
isObject
} from '@vue/shared'
import { ComponentInstance } from './component'
import { ComponentWatchOptions, WatchOptions } from './componentOptions'
import { effect, stop } from '@vue/observer'
import { queueJob } from '@vue/scheduler'
import { handleError, ErrorTypes } from './errorHandling'
import { warn } from './warning'
export function initializeWatch(
instance: ComponentInstance,
options: ComponentWatchOptions | undefined
) {
if (options !== void 0) {
for (const key in options) {
const opt = options[key]
if (isArray(opt)) {
opt.forEach(o => setupWatcher(instance, key, o))
} else if (isFunction(opt)) {
setupWatcher(instance, key, opt)
} else if (isString(opt)) {
setupWatcher(instance, key, (instance as any)[opt])
} else if (opt.handler) {
setupWatcher(instance, key, opt.handler, opt)
}
}
}
}
export function setupWatcher(
instance: ComponentInstance,
keyOrFn: string | Function,
cb: (newValue: any, oldValue: any) => void,
options: WatchOptions = EMPTY_OBJ as WatchOptions
): () => void {
const handles = instance._watchHandles || (instance._watchHandles = new Set())
const proxy = instance.$proxy
const rawGetter = isString(keyOrFn)
? parseDotPath(keyOrFn, proxy)
: () => keyOrFn.call(proxy)
if (__DEV__ && rawGetter === NOOP) {
warn(
`Failed watching expression: "${keyOrFn}". ` +
`Watch expressions can only be dot-delimited paths. ` +
`For more complex expressions, use $watch with a function instead.`
)
}
const getter = options.deep ? () => traverse(rawGetter()) : rawGetter
let oldValue: any
const applyCb = () => {
const newValue = runner()
if (options.deep || newValue !== oldValue) {
try {
cb.call(instance.$proxy, newValue, oldValue)
} catch (e) {
handleError(e, instance, ErrorTypes.WATCH_CALLBACK)
}
oldValue = newValue
}
}
const runner = effect(getter, {
lazy: true,
scheduler: options.sync
? applyCb
: () => {
// defer watch callback using the scheduler so that multiple mutations
// result in one call only.
queueJob(applyCb)
}
})
oldValue = runner()
handles.add(runner)
if (options.immediate) {
cb.call(instance.$proxy, oldValue, undefined)
}
return () => {
stop(runner)
handles.delete(runner)
}
}
export function teardownWatch(instance: ComponentInstance) {
if (instance._watchHandles !== null) {
instance._watchHandles.forEach(stop)
}
}
const bailRE = /[^\w.$]/
function parseDotPath(path: string, ctx: any): Function {
if (bailRE.test(path)) {
return NOOP
}
const segments = path.split('.')
if (segments.length === 1) {
return () => ctx[path]
} else {
return () => {
let obj = ctx
for (let i = 0; i < segments.length; i++) {
if (!obj) return
obj = obj[segments[i]]
}
return obj
}
}
}
function traverse(value: any, seen: Set<any> = new Set()) {
if (!isObject(value) || seen.has(value)) {
return
}
seen.add(value)
if (isArray(value)) {
for (let i = 0; i < value.length; i++) {
traverse(value[i], seen)
}
} else if (value instanceof Map || value instanceof Set) {
;(value as any).forEach((v: any) => {
traverse(v, seen)
})
} else {
for (const key in value) {
traverse(value[key], seen)
}
}
return value
}

File diff suppressed because it is too large Load Diff

View File

@ -1,134 +0,0 @@
import { ComponentInstance } from './component'
import { warn, pushWarningContext, popWarningContext } from './warning'
import { VNode } from './vdom'
import { VNodeFlags } from './flags'
import { ComponentProxy } from './componentProxy'
export const enum ErrorTypes {
BEFORE_CREATE = 1,
CREATED,
BEFORE_MOUNT,
MOUNTED,
BEFORE_UPDATE,
UPDATED,
BEFORE_UNMOUNT,
UNMOUNTED,
ACTIVATED,
DEACTIVATED,
ERROR_CAPTURED,
RENDER,
RENDER_TRACKED,
RENDER_TRIGGERED,
WATCH_CALLBACK,
NATIVE_EVENT_HANDLER,
COMPONENT_EVENT_HANDLER,
SCHEDULER
}
const ErrorTypeStrings: Record<number, string> = {
[ErrorTypes.BEFORE_CREATE]: 'in beforeCreate lifecycle hook',
[ErrorTypes.CREATED]: 'in created lifecycle hook',
[ErrorTypes.BEFORE_MOUNT]: 'in beforeMount lifecycle hook',
[ErrorTypes.MOUNTED]: 'in mounted lifecycle hook',
[ErrorTypes.BEFORE_UPDATE]: 'in beforeUpdate lifecycle hook',
[ErrorTypes.UPDATED]: 'in updated lifecycle hook',
[ErrorTypes.BEFORE_UNMOUNT]: 'in beforeUnmount lifecycle hook',
[ErrorTypes.UNMOUNTED]: 'in unmounted lifecycle hook',
[ErrorTypes.ACTIVATED]: 'in activated lifecycle hook',
[ErrorTypes.DEACTIVATED]: 'in deactivated lifecycle hook',
[ErrorTypes.ERROR_CAPTURED]: 'in errorCaptured lifecycle hook',
[ErrorTypes.RENDER]: 'in render function',
[ErrorTypes.RENDER_TRACKED]: 'in renderTracked debug hook',
[ErrorTypes.RENDER_TRIGGERED]: 'in renderTriggered debug hook',
[ErrorTypes.WATCH_CALLBACK]: 'in watcher callback',
[ErrorTypes.NATIVE_EVENT_HANDLER]: 'in native event handler',
[ErrorTypes.COMPONENT_EVENT_HANDLER]: 'in component event handler',
[ErrorTypes.SCHEDULER]:
'when flushing updates. This may be a Vue internals bug.'
}
export function callLifecycleHookWithHandler(
hook: Function,
instanceProxy: ComponentProxy,
type: ErrorTypes,
arg?: any
) {
try {
const res = hook.call(instanceProxy, arg)
if (res && !res._isVue && typeof res.then === 'function') {
;(res as Promise<any>).catch(err => {
handleError(err, instanceProxy._self, type)
})
}
} catch (err) {
handleError(err, instanceProxy._self, type)
}
}
export function handleError(
err: Error,
instance: ComponentInstance | VNode | null,
type: ErrorTypes
) {
const isFunctional = instance && (instance as VNode)._isVNode
const contextVNode =
instance &&
((isFunctional
? instance
: (instance as ComponentInstance).$parentVNode) as VNode | null)
let cur: ComponentInstance | null = null
if (isFunctional) {
let vnode = instance as VNode | null
while (vnode && !(vnode.flags & VNodeFlags.COMPONENT_STATEFUL)) {
vnode = vnode.contextVNode
}
if (vnode) {
cur = vnode.children as ComponentInstance
}
} else if (instance) {
const parent = (instance as ComponentInstance).$parent
cur = parent && parent._self
}
while (cur) {
const handler = cur.errorCaptured
if (handler) {
try {
const captured = handler.call(
cur,
err,
type,
isFunctional ? null : instance
)
if (captured) return
} catch (err2) {
logError(err2, ErrorTypes.ERROR_CAPTURED, contextVNode)
}
}
cur = cur.$parent && cur.$parent._self
}
logError(err, type, contextVNode)
}
function logError(err: Error, type: ErrorTypes, contextVNode: VNode | null) {
if (__DEV__) {
const info = ErrorTypeStrings[type]
if (contextVNode) {
pushWarningContext(contextVNode)
}
if (/private field/.test(err.message)) {
warn(
`Private fields cannot be accessed directly on \`this\` in a component ` +
`class because they cannot be tunneled through Proxies. ` +
`Use \`this._self.#field\` instead.`
)
} else {
warn(`Unhandled error${info ? ` ${info}` : ``}`)
}
console.error(err)
if (contextVNode) {
popWarningContext()
}
} else {
throw err
}
}

View File

@ -1,35 +0,0 @@
// vnode flags
export const enum VNodeFlags {
ELEMENT_HTML = 1,
ELEMENT_SVG = 1 << 1,
COMPONENT_STATEFUL_NORMAL = 1 << 2,
COMPONENT_STATEFUL_SHOULD_KEEP_ALIVE = 1 << 3,
COMPONENT_STATEFUL_KEPT_ALIVE = 1 << 4,
COMPONENT_FUNCTIONAL = 1 << 5,
TEXT = 1 << 6,
FRAGMENT = 1 << 7,
PORTAL = 1 << 8,
// masks (only use for bitwise checks, do not use equal checks or assign)
ELEMENT = ELEMENT_HTML | ELEMENT_SVG,
COMPONENT_STATEFUL = COMPONENT_STATEFUL_NORMAL |
COMPONENT_STATEFUL_SHOULD_KEEP_ALIVE |
COMPONENT_STATEFUL_KEPT_ALIVE,
COMPONENT = COMPONENT_STATEFUL | COMPONENT_FUNCTIONAL
}
export const enum ChildrenFlags {
UNKNOWN_CHILDREN = 0,
NO_CHILDREN = 1,
SINGLE_VNODE = 1 << 1,
KEYED_VNODES = 1 << 2,
NONE_KEYED_VNODES = 1 << 3,
STABLE_SLOTS = 1 << 4,
DYNAMIC_SLOTS = 1 << 5,
// masks
HAS_SLOTS = STABLE_SLOTS | DYNAMIC_SLOTS,
MULTIPLE_VNODES = KEYED_VNODES | NONE_KEYED_VNODES
}

View File

@ -1,168 +1,84 @@
import { ChildrenFlags } from './flags'
import { ComponentClass, FunctionalComponent, Component } from './component'
import { ComponentOptions } from './componentOptions'
import {
VNode,
createElementVNode,
createComponentVNode,
createFragment,
createPortal,
VNodeData,
BuiltInProps,
Key
} from './vdom'
import { isObservable } from '@vue/observer'
import { warn } from './warning'
import { isString, isArray, isFunction, isObject } from '@vue/shared'
export const Fragment = Symbol('Fragment')
export const Text = Symbol('Text')
export const Empty = Symbol('Empty')
export const Fragment = Symbol()
export const Portal = Symbol()
type RawChildType = VNode | string | number | boolean | null | undefined
export type RawSlots = {
$stable?: boolean
[name: string]: RawChildType | (() => RawChildrenType)
}
export type RawChildrenType = RawChildType | RawChildType[]
export type ElementType =
type VNodeTypes =
| string
| FunctionalComponent
| ComponentClass
| ComponentOptions
| Function
| typeof Fragment
| typeof Portal
| typeof Text
| typeof Empty
// This is used to differentiate the data object from
// vnodes and arrays
type Differ = { _isVNode?: never; [Symbol.iterator]?: never }
export type VNodeChild = VNode | string | number | null
export interface VNodeChildren extends Array<VNodeChildren | VNodeChild> {}
type OptionsComponent<P> =
| (ComponentOptions<P> & { template: string })
| (ComponentOptions<P> & { render: Function })
// TODO improve return type with props information
interface createElement {
// element
(tag: string, children?: RawChildrenType): VNode
(
tag: string,
// TODO support native element properties
data?: VNodeData & Differ | null,
children?: RawChildrenType | RawSlots
): VNode
// fragment
(tag: typeof Fragment, children?: RawChildrenType): VNode
(
tag: typeof Fragment,
data?: ({ key?: Key } & Differ) | null,
children?: RawChildrenType | RawSlots
): VNode
// portal
(tag: typeof Portal, children?: RawChildrenType): VNode
(
tag: typeof Portal,
data?: ({ target: any } & BuiltInProps & Differ) | null,
children?: RawChildrenType | RawSlots
): VNode
// object
<P>(tag: OptionsComponent<P>, children?: RawChildrenType): VNode
<P>(
tag: OptionsComponent<P>,
data?: (P & BuiltInProps & Differ) | null,
children?: RawChildrenType | RawSlots
): VNode
// functional
<P>(tag: FunctionalComponent<P>, children?: RawChildrenType): VNode
<P>(
tag: FunctionalComponent<P>,
data?: (P & BuiltInProps & Differ) | null,
children?: RawChildrenType | RawSlots
): VNode
// class
<P>(tag: new () => Component<P>, children?: RawChildrenType): VNode
<P>(
tag: new () => Component<P>,
data?: (P & BuiltInProps & Differ) | null,
children?: RawChildrenType | RawSlots
): VNode
export interface VNode {
type: VNodeTypes
props: { [key: string]: any } | null
key: string | number | null
children: string | VNodeChildren | null
patchFlag: number | null
dynamicProps: string[] | null
dynamicChildren: VNode[] | null
}
export const h = ((tag: ElementType, data?: any, children?: any): VNode => {
if (data !== null && (isArray(data) || !isObject(data) || data._isVNode)) {
children = data
data = null
}
const blockStack: (VNode[])[] = []
if (data === void 0) data = null
if (children === void 0) children = null
// open block
export function openBlock() {
blockStack.push([])
}
// if value is observable, create a clone of original
// so that we can normalize its class/style
// since this guard is only placed here, this means any direct createXXXVnode
// functions only accept fresh data objects.
if (isObservable(data)) {
data = Object.assign({}, data)
}
let shouldTrack = true
let key = null
let ref = null
let portalTarget = null
if (data != null) {
if (data.slots != null) {
children = data.slots
}
if (data.key != null) {
;({ key } = data)
}
if (data.ref != null) {
;({ ref } = data)
}
if (data.target != null) {
portalTarget = data.target
}
}
// block
export function createBlock(
type: VNodeTypes,
props?: { [key: string]: any } | null,
children?: any,
patchFlag?: number,
dynamicProps?: string[]
): VNode {
// avoid a block with optFlag tracking itself
shouldTrack = false
const vnode = createVNode(type, props, children, patchFlag, dynamicProps)
shouldTrack = true
vnode.dynamicChildren = blockStack.pop() || null
// a block is always going to be patched
trackDynamicNode(vnode)
return vnode
}
if (isString(tag)) {
// element
return createElementVNode(
tag,
data,
children,
ChildrenFlags.UNKNOWN_CHILDREN,
key,
ref
)
} else if (tag === Fragment) {
if (__DEV__ && ref) {
warn('Ref cannot be used on Fragments. Use it on inner elements instead.')
}
return createFragment(children, ChildrenFlags.UNKNOWN_CHILDREN, key)
} else if (tag === Portal) {
if (__DEV__ && !portalTarget) {
warn('Portal must have a target: ', portalTarget)
}
return createPortal(
portalTarget,
children,
ChildrenFlags.UNKNOWN_CHILDREN,
key,
ref
)
} else {
if (__DEV__ && !isFunction(tag) && !isObject(tag)) {
warn('Invalid component passed to h(): ', tag)
}
// component
return createComponentVNode(
tag,
data,
children,
ChildrenFlags.UNKNOWN_CHILDREN,
key,
ref
)
// element
export function createVNode(
type: VNodeTypes,
props: { [key: string]: any } | null = null,
children: any = null,
patchFlag: number | null = null,
dynamicProps: string[] | null = null
): VNode {
const vnode: VNode = {
type,
props,
key: props && props.key,
children,
patchFlag,
dynamicProps,
dynamicChildren: null
}
}) as createElement
if (patchFlag != null && shouldTrack) {
trackDynamicNode(vnode)
}
return vnode
}
function trackDynamicNode(vnode: VNode) {
const currentBlockDynamicNodes = blockStack[blockStack.length - 1]
if (currentBlockDynamicNodes) {
currentBlockDynamicNodes.push(vnode)
}
}
export function cloneVNode(vnode: VNode): VNode {
// TODO
}

View File

@ -1,41 +1,5 @@
// Core API
export { h, Fragment, Portal } from './h'
export { Component } from './component'
export {
cloneVNode,
createElementVNode,
createComponentVNode,
createTextVNode,
createFragment,
createPortal
} from './vdom'
export {
createRenderer,
NodeOps,
PatchDataFunction,
RendererOptions
} from './createRenderer'
export { TEXT, CLASS, STYLE, PROPS, KEYED, UNKEYED } from './patchFlags'
// Observer API
export * from '@vue/observer'
export { openBlock, createBlock, createVNode, Fragment, Text, Empty } from './h'
// Scheduler API
export { nextTick } from '@vue/scheduler'
// Optional APIs
// these are imported on-demand and can be tree-shaken
export { createAsyncComponent } from './optional/asyncComponent'
export { KeepAlive } from './optional/keepAlive'
export { applyDirectives } from './optional/directives'
export { mixins } from './optional/mixins'
export { EventEmitter } from './optional/eventEmitter'
export { memoize } from './optional/memoize'
// flags & types
export { ComponentType, ComponentClass, FunctionalComponent } from './component'
export { VNodeFlags, ChildrenFlags } from './flags'
export { VNode, Slots } from './vdom'
// Internal API, for libraries or renderers that need to perform low level work
export * from './componentOptions'
export { createComponentInstance } from './componentInstance'
export { createRenderer } from './createRenderer'

View File

@ -1,100 +0,0 @@
import { ChildrenFlags } from '../flags'
import { createComponentVNode, Slots } from '../vdom'
import { Component, ComponentType, ComponentClass } from '../component'
import { unwrap } from '@vue/observer'
import { isFunction } from '@vue/shared'
interface AsyncComponentFactory {
(): Promise<ComponentType>
resolved?: ComponentType
}
interface AsyncComponentFullOptions {
factory: AsyncComponentFactory
loading?: ComponentType
error?: ComponentType
delay?: number
timeout?: number
}
type AsyncComponentOptions = AsyncComponentFactory | AsyncComponentFullOptions
export function createAsyncComponent(
options: AsyncComponentOptions
): ComponentClass {
if (isFunction(options)) {
options = { factory: options }
}
const {
factory,
timeout,
delay = 200,
loading: loadingComp,
error: errorComp
} = options
return class AsyncContainer extends Component {
comp: ComponentType | null = null
err: Error | null = null
delayed: boolean = false
timedOut: boolean = false
// doing this in beforeMount so this is non-SSR only
beforeMount() {
if (factory.resolved) {
this.comp = factory.resolved
} else {
factory()
.then(resolved => {
this.comp = factory.resolved = resolved
})
.catch(err => {
this.err = err
})
}
if (timeout != null) {
setTimeout(() => {
this.timedOut = true
}, timeout)
}
if (delay != null) {
this.delayed = true
setTimeout(() => {
this.delayed = false
}, delay)
}
}
render(props: any, slots: Slots) {
if (this.err || (this.timedOut && !this.comp)) {
const error =
this.err || new Error(`Async component timed out after ${timeout}ms.`)
return errorComp
? createComponentVNode(
errorComp,
{ error },
null,
ChildrenFlags.NO_CHILDREN
)
: null
} else if (this.comp) {
return createComponentVNode(
this.comp,
unwrap(props),
slots,
ChildrenFlags.STABLE_SLOTS
)
} else {
return loadingComp && !this.delayed
? createComponentVNode(
loadingComp,
null,
null,
ChildrenFlags.NO_CHILDREN
)
: null
}
}
} as ComponentClass
}

View File

@ -1,108 +0,0 @@
/**
Runtime helper for applying directives to a vnode. Example usage:
const comp = resolveComponent(this, 'comp')
const foo = resolveDirective(this, 'foo')
const bar = resolveDirective(this, 'bar')
return applyDirectives(
h(comp),
this,
[foo, this.x],
[bar, this.y]
)
*/
import { VNode, cloneVNode, VNodeData } from '../vdom'
import { ComponentInstance } from '../component'
import { EMPTY_OBJ } from '@vue/shared'
interface DirectiveBinding {
instance: ComponentInstance
value?: any
oldValue?: any
arg?: string
modifiers?: DirectiveModifiers
}
type DirectiveHook = (
el: any,
binding: DirectiveBinding,
vnode: VNode,
prevVNode: VNode | void
) => void
interface Directive {
beforeMount: DirectiveHook
mounted: DirectiveHook
beforeUpdate: DirectiveHook
updated: DirectiveHook
beforeUnmount: DirectiveHook
unmounted: DirectiveHook
}
type DirectiveModifiers = Record<string, boolean>
const valueCache = new WeakMap<Directive, WeakMap<any, any>>()
export function applyDirective(
data: VNodeData,
instance: ComponentInstance,
directive: Directive,
value?: any,
arg?: string,
modifiers?: DirectiveModifiers
) {
let valueCacheForDir = valueCache.get(directive) as WeakMap<VNode, any>
if (!valueCacheForDir) {
valueCacheForDir = new WeakMap<VNode, any>()
valueCache.set(directive, valueCacheForDir)
}
for (const key in directive) {
const hook = directive[key as keyof Directive]
const hookKey = `vnode` + key[0].toUpperCase() + key.slice(1)
const vnodeHook = (vnode: VNode, prevVNode?: VNode) => {
let oldValue
if (prevVNode !== void 0) {
oldValue = valueCacheForDir.get(prevVNode)
valueCacheForDir.delete(prevVNode)
}
valueCacheForDir.set(vnode, value)
hook(
vnode.el,
{
instance,
value,
oldValue,
arg,
modifiers
},
vnode,
prevVNode
)
}
const existing = data[hookKey]
data[hookKey] = existing
? [].concat(existing as any, vnodeHook as any)
: vnodeHook
}
}
type DirectiveArguments = [
Directive,
any,
string | undefined,
DirectiveModifiers | undefined
][]
export function applyDirectives(
vnode: VNode,
instance: ComponentInstance,
...directives: DirectiveArguments
) {
vnode = cloneVNode(vnode, EMPTY_OBJ)
for (let i = 0; i < directives.length; i++) {
applyDirective(vnode.data as VNodeData, instance, ...directives[i])
}
return vnode
}

View File

@ -1,76 +0,0 @@
import { isArray } from '@vue/shared'
export class EventEmitter {
ctx: any
events: { [event: string]: Function[] | null } = {}
constructor(ctx: any) {
this.ctx = ctx
}
// eventEmitter interface
on(event: string, fn: Function) {
if (isArray(event)) {
for (let i = 0; i < event.length; i++) {
this.on(event[i], fn)
}
} else {
const { events } = this
;(events[event] || (events[event] = [])).push(fn)
}
}
once(event: string, fn: Function) {
const onceFn = (...args: any[]) => {
this.off(event, onceFn)
fn.apply(this, args)
}
;(onceFn as any).fn = fn
this.on(event, onceFn)
}
off(event?: string, fn?: Function) {
if (!event && !fn) {
this.events = {}
} else if (isArray(event)) {
for (let i = 0; i < event.length; i++) {
this.off(event[i], fn)
}
} else if (!fn) {
this.events[event as string] = null
} else {
const fns = this.events[event as string]
if (fns) {
for (let i = 0; i < fns.length; i++) {
const f = fns[i]
if (fn === f || fn === (f as any).fn) {
fns.splice(i, 1)
break
}
}
}
}
}
emit(name: string, ...payload: any[]) {
const handlers = this.events[name]
if (handlers) {
invokeListeners(handlers, payload, this.ctx)
}
}
}
export function invokeListeners(
value: Function | Function[],
payload: any[],
ctx: any = null
) {
// TODO handle error
if (isArray(value)) {
for (let i = 0; i < value.length; i++) {
value[i].call(ctx, ...payload)
}
} else {
value.call(ctx, ...payload)
}
}

View File

@ -1,134 +0,0 @@
import { Component, ComponentClass, ComponentInstance } from '../component'
import { VNode, Slots, cloneVNode } from '../vdom'
import { VNodeFlags } from '../flags'
import { warn } from '../warning'
import { isString, isArray } from '@vue/shared'
type MatchPattern = string | RegExp | string[] | RegExp[]
interface KeepAliveProps {
include?: MatchPattern
exclude?: MatchPattern
max?: number | string
}
type CacheKey = string | number | ComponentClass
type Cache = Map<CacheKey, VNode>
export const KeepAliveSymbol = Symbol()
export class KeepAlive extends Component<KeepAliveProps> {
private cache: Cache
private keys: Set<CacheKey>
created() {
this.cache = new Map()
this.keys = new Set()
}
// to be set in createRenderer when instance is created
$unmount: (instance: ComponentInstance) => void
beforeUnmount() {
this.cache.forEach(vnode => {
// change flag so it can be properly unmounted
vnode.flags = VNodeFlags.COMPONENT_STATEFUL_NORMAL
this.$unmount(vnode.children as ComponentInstance)
})
}
pruneCache(filter?: (name: string) => boolean) {
this.cache.forEach((vnode, key) => {
const name = getName(vnode.tag as ComponentClass)
if (name && (!filter || !filter(name))) {
this.pruneCacheEntry(key)
}
})
}
pruneCacheEntry(key: CacheKey) {
const cached = this.cache.get(key) as VNode
const current = this.$vnode
if (!current || cached.tag !== current.tag) {
this.$unmount(cached.children as ComponentInstance)
}
this.cache.delete(key)
this.keys.delete(key)
}
render(props: KeepAliveProps, slots: Slots) {
if (!slots.default) {
return
}
const children = slots.default()
let vnode = children[0]
if (children.length > 1) {
if (__DEV__) {
warn(`KeepAlive can only have a single child.`)
}
return children
} else if ((vnode.flags & VNodeFlags.COMPONENT_STATEFUL) === 0) {
return children
}
const comp = vnode.tag as ComponentClass
const name = getName(comp)
const { include, exclude, max } = props
if (
(include && (!name || !matches(include, name))) ||
(exclude && name && matches(exclude, name))
) {
return vnode
}
const { cache, keys } = this
const key = vnode.key == null ? comp : vnode.key
const cached = cache.get(key)
// clone vnode if it's reused because we are going to mutate its flags
if (vnode.el) {
vnode = cloneVNode(vnode)
}
cache.set(key, vnode)
if (cached) {
vnode.children = cached.children
// avoid vnode being mounted as fresh
vnode.flags |= VNodeFlags.COMPONENT_STATEFUL_KEPT_ALIVE
// make this key the freshest
keys.delete(key)
keys.add(key)
} else {
keys.add(key)
// prune oldest entry
if (max && keys.size > parseInt(max as string, 10)) {
this.pruneCacheEntry(Array.from(this.keys)[0])
}
}
// avoid vnode being unmounted
vnode.flags |= VNodeFlags.COMPONENT_STATEFUL_SHOULD_KEEP_ALIVE
return vnode
}
}
// mark constructor
// we use a symbol instead of comparing to the constructor itself
// so that the implementation can be tree-shaken
;(KeepAlive as any)[KeepAliveSymbol] = true
function getName(comp: ComponentClass): string | void {
return comp.displayName || comp.name
}
function matches(pattern: MatchPattern, name: string): boolean {
if (isArray(pattern)) {
return (pattern as any).some((p: string | RegExp) => matches(p, name))
} else if (isString(pattern)) {
return pattern.split(',').indexOf(name) > -1
} else if (pattern.test) {
return pattern.test(name)
}
/* istanbul ignore next */
return false
}

View File

@ -1,56 +0,0 @@
// Used for memoizing trees inside render functions.
//
// Example (equivalent of v-once):
//
// render() {
// return memoize(h('div', this.msg), this, 0)
// }
//
// Memoize baesd on keys:
//
// render() {
// return memoize(h('div', this.msg + this.count), this, 0, [this.msg])
// }
// TODO how does this work in v-for?
// probably need to take vnode key into consideration
import { Component } from '../component'
import { warn } from '../warning'
const memoizeMap = new WeakMap()
export function memoize<T>(
getter: () => T,
instance: Component,
id: number,
keys?: any[]
): T {
if (__DEV__ && arguments.length > 3 && !Array.isArray(keys)) {
warn(
`keys passed to v-memo or memoize must be an array. Got ${String(keys)}`
)
}
let storage = memoizeMap.get(instance)
if (!storage) {
storage = []
memoizeMap.set(instance, storage)
}
const record = storage[id]
if (!record) {
const value = getter()
storage[id] = [value, keys]
return value
} else {
const [prevValue, prevKeys] = record
record[1] = keys
if (keys) {
for (let i = 0; i < keys.length; i++) {
if (keys[i] !== prevKeys[i]) {
return (record[0] = getter())
}
}
}
return prevValue
}
}

View File

@ -1,84 +0,0 @@
import { Component } from '../component'
import { createComponentClassFromOptions } from '../componentOptions'
import {
ComponentOptions,
resolveComponentOptionsFromClass,
mergeComponentOptions,
mergeDataFn
} from '../componentOptions'
import { normalizePropsOptions } from '../componentProps'
import { extractInitializers } from '../componentState'
import { isFunction } from '@vue/shared'
interface ComponentConstructor<This = Component> {
new (): This
}
interface ComponentConstructorWithMixins<This> {
new <P = {}, D = {}>(): This & { $data: D } & D & { $props: Readonly<P> } & P
}
// mind = blown
// https://stackoverflow.com/questions/50374908/transform-union-type-to-intersection-type
type UnionToIntersection<U> = (U extends any
? (k: U) => void
: never) extends ((k: infer I) => void)
? I
: never
type ExtractInstance<T> = T extends (infer U)[]
? UnionToIntersection<U extends ComponentConstructor<infer V> ? V : never>
: never
export function mixins<
T extends ComponentConstructor[] = [],
V = ExtractInstance<T>
>(...args: T): ComponentConstructorWithMixins<V>
export function mixins(...args: any[]): any {
let options: ComponentOptions = {}
args.forEach(mixin => {
if (isFunction(mixin)) {
const Class = mixin
mixin = resolveComponentOptionsFromClass(Class)
// in order to extract properties initialized in the mixin's constructor,
// we create an instance of it and pass in the actual props - this
// short-circuits the normal component initialization and allows us to
// relatively-cheaply extract the properties added in the constructor.
function extractData() {
return extractInitializers(new Class(this.$props))
}
const { data } = mixin
mixin.data = data ? mergeDataFn(data, extractData) : extractData
} else {
mixin.props = normalizePropsOptions(mixin.props)
}
options = mergeComponentOptions(options, mixin)
})
return createComponentClassFromOptions(options)
}
/* Example usage
class Foo extends Component<{ foo: number }> {
test() {
}
}
class Bar extends Component<{ bar: string }> {
ok() {
}
}
class Baz extends mixins(Foo, Bar)<{ baz: number }> {
created() {
this.foo
this.bar
this.baz
this.test()
this.ok()
}
}
*/

View File

@ -0,0 +1,6 @@
export const TEXT = 1
export const CLASS = 1 << 1
export const STYLE = 1 << 2
export const PROPS = 1 << 3
export const KEYED = 1 << 4
export const UNKEYED = 1 << 5

View File

@ -1,449 +0,0 @@
import {
ComponentInstance,
ComponentClass,
FunctionalComponent
} from './component'
import { VNodeFlags, ChildrenFlags } from './flags'
import { createComponentClassFromOptions } from './componentOptions'
import { EMPTY_OBJ, isObject, isArray, isFunction, isString } from '@vue/shared'
import { RawChildrenType, RawSlots } from './h'
import { FunctionalHandle } from './createRenderer'
const handlersRE = /^on|^vnode/
const STABLE_SLOTS_HINT = '$stable'
// Vue core is platform agnostic, so we are not using Element for "DOM" nodes.
export interface RenderNode {
vnode?: VNode | null
// technically this doesn't exist on platform render nodes,
// but we list it here so that TS can figure out union types
$f: false
}
export interface VNode {
_isVNode: true
flags: VNodeFlags
tag: string | FunctionalComponent | ComponentClass | RenderNode | null
data: VNodeData | null
children: VNodeChildren
childFlags: ChildrenFlags
key: Key | null
ref: Ref | null
slots: Slots | null
// only on mounted nodes
el: RenderNode | null
// only on mounted component nodes that is also a root node (HOCs)
// points to parent component's placeholder vnode
// this is used to update vnode.el for nested HOCs.
parentVNode: VNode | null
// only on mounted component nodes
// points to the parent stateful/functional component's placeholder node
contextVNode: VNode | null
// only on mounted functional component nodes
// a consistent handle so that a functional component can be identified
// by the scheduler
handle: FunctionalHandle | null
// only on cloned vnodes, points to the original cloned vnode
clonedFrom: VNode | null
}
export interface MountedVNode extends VNode {
el: RenderNode
}
export interface BuiltInProps {
key?: Key | null
ref?: Ref | null
slots?: RawSlots | null
}
export type VNodeData = {
[key: string]: any
} & BuiltInProps
export type VNodeChildren =
| VNode[] // ELEMENT | PORTAL
| ComponentInstance // COMPONENT_STATEFUL
| VNode // COMPONENT_FUNCTIONAL
| string // TEXT
| null
export type Key = string | number
export type Ref = (t: RenderNode | ComponentInstance | null) => void
export type Slot = (...args: any[]) => VNode[]
export type Slots = Readonly<{
[name: string]: Slot
}>
export function createVNode(
flags: VNodeFlags,
tag: string | FunctionalComponent | ComponentClass | RenderNode | null,
data: VNodeData | null,
children: RawChildrenType | null,
childFlags: ChildrenFlags,
key: Key | null | undefined,
ref: Ref | null | undefined,
slots: Slots | null | undefined
): VNode {
const vnode: VNode = {
_isVNode: true,
flags,
tag,
data,
children: children as VNodeChildren,
childFlags,
key: key === void 0 ? null : key,
ref: ref === void 0 ? null : ref,
slots: slots === void 0 ? null : slots,
el: null,
parentVNode: null,
contextVNode: null,
handle: null,
clonedFrom: null
}
if (childFlags === ChildrenFlags.UNKNOWN_CHILDREN) {
normalizeChildren(vnode, children)
}
return vnode
}
export function createElementVNode(
tag: string,
data: VNodeData | null,
children: RawChildrenType | null,
childFlags: ChildrenFlags,
key?: Key | null,
ref?: Ref | null
) {
const flags = tag === 'svg' ? VNodeFlags.ELEMENT_SVG : VNodeFlags.ELEMENT_HTML
if (data !== null) {
normalizeClassAndStyle(data)
}
return createVNode(flags, tag, data, children, childFlags, key, ref, null)
}
function normalizeClassAndStyle(data: VNodeData) {
if (data.class != null) {
data.class = normalizeClass(data.class)
}
if (data.style != null) {
data.style = normalizeStyle(data.style)
}
}
function normalizeStyle(value: any): Record<string, string | number> | void {
if (isArray(value)) {
const res: Record<string, string | number> = {}
for (let i = 0; i < value.length; i++) {
const normalized = normalizeStyle(value[i])
if (normalized) {
for (const key in normalized) {
res[key] = normalized[key]
}
}
}
return res
} else if (isObject(value)) {
return value
}
}
function normalizeClass(value: any): string {
let res = ''
if (isString(value)) {
res = value
} else if (isArray(value)) {
for (let i = 0; i < value.length; i++) {
res += normalizeClass(value[i]) + ' '
}
} else if (isObject(value)) {
for (const name in value) {
if (value[name]) {
res += name + ' '
}
}
}
return res.trim()
}
export function createComponentVNode(
comp: any,
data: VNodeData | null,
children: RawChildrenType | Slots,
childFlags: ChildrenFlags,
key?: Key | null,
ref?: Ref | null
) {
// resolve type
let flags: VNodeFlags
// flags
if (isObject(comp)) {
if (comp.functional) {
// object literal functional
flags = VNodeFlags.COMPONENT_FUNCTIONAL
const { render } = comp
if (!comp._normalized) {
render.pure = comp.pure
render.props = comp.props
comp._normalized = true
}
comp = render
} else {
// object literal stateful
flags = VNodeFlags.COMPONENT_STATEFUL_NORMAL
comp =
comp._normalized ||
(comp._normalized = createComponentClassFromOptions(comp))
}
} else {
// assumes comp is function here now
if (__DEV__ && !isFunction(comp)) {
// TODO warn invalid comp value in dev
}
if (comp.prototype && comp.prototype.render) {
flags = VNodeFlags.COMPONENT_STATEFUL_NORMAL
} else {
flags = VNodeFlags.COMPONENT_FUNCTIONAL
}
}
if (__DEV__ && flags === VNodeFlags.COMPONENT_FUNCTIONAL && ref) {
// TODO warn functional component cannot have ref
}
// slots
let slots: any
if (childFlags === ChildrenFlags.STABLE_SLOTS) {
slots = children
} else if (childFlags === ChildrenFlags.UNKNOWN_CHILDREN) {
childFlags = children
? ChildrenFlags.DYNAMIC_SLOTS
: ChildrenFlags.NO_CHILDREN
if (children != null) {
if (isFunction(children)) {
// function as children
slots = { default: children }
} else if (isObject(children) && !(children as any)._isVNode) {
// slot object as children
slots = children
// special manual optimization hint for raw render fn users
if (slots[STABLE_SLOTS_HINT]) {
childFlags = ChildrenFlags.STABLE_SLOTS
}
} else {
slots = { default: () => children }
}
slots = normalizeSlots(slots)
}
}
// class & style
if (data !== null) {
normalizeClassAndStyle(data)
}
return createVNode(
flags,
comp,
data,
null, // to be set during mount
childFlags,
key,
ref,
slots
)
}
export function createTextVNode(text: string): VNode {
return createVNode(
VNodeFlags.TEXT,
null,
null,
text == null ? '' : text,
ChildrenFlags.NO_CHILDREN,
null,
null,
null
)
}
export function createFragment(
children: RawChildrenType,
childFlags?: ChildrenFlags,
key?: Key | null
) {
return createVNode(
VNodeFlags.FRAGMENT,
null,
null,
children,
childFlags === void 0 ? ChildrenFlags.UNKNOWN_CHILDREN : childFlags,
key,
null,
null
)
}
export function createPortal(
target: RenderNode | string,
children: RawChildrenType,
childFlags?: ChildrenFlags,
key?: Key | null,
ref?: Ref | null
): VNode {
return createVNode(
VNodeFlags.PORTAL,
target,
null,
children,
childFlags === void 0 ? ChildrenFlags.UNKNOWN_CHILDREN : childFlags,
key,
ref,
null
)
}
export function cloneVNode(vnode: VNode, extraData?: VNodeData): VNode {
const { flags, data } = vnode
if (flags & VNodeFlags.ELEMENT || flags & VNodeFlags.COMPONENT) {
let clonedData = data
if (extraData != null) {
clonedData = {}
if (data != null) {
for (const key in data) {
clonedData[key] = data[key]
}
}
if (extraData !== EMPTY_OBJ) {
for (const key in extraData) {
if (key === 'class') {
clonedData.class = normalizeClass([
clonedData.class,
extraData.class
])
} else if (key === 'style') {
clonedData.style = normalizeStyle([
clonedData.style,
extraData.style
])
} else if (handlersRE.test(key)) {
// on*, vnode*
const existing = clonedData[key]
clonedData[key] = existing
? [].concat(existing, extraData[key])
: extraData[key]
} else {
clonedData[key] = extraData[key]
}
}
}
}
const cloned = createVNode(
flags,
vnode.tag,
clonedData,
vnode.children as RawChildrenType,
vnode.childFlags,
vnode.key,
vnode.ref,
vnode.slots
)
cloned.clonedFrom = vnode.clonedFrom || vnode
return cloned
} else if (flags & VNodeFlags.TEXT) {
return createTextVNode(vnode.children as string)
} else {
return vnode
}
}
function normalizeChildren(vnode: VNode, children: any) {
let childFlags
if (isArray(children)) {
const { length } = children
if (length === 0) {
childFlags = ChildrenFlags.NO_CHILDREN
children = null
} else if (length === 1) {
childFlags = ChildrenFlags.SINGLE_VNODE
children = children[0]
if (children.el) {
children = cloneVNode(children)
}
} else {
childFlags = ChildrenFlags.KEYED_VNODES
children = normalizeVNodes(children)
}
} else if (children == null) {
childFlags = ChildrenFlags.NO_CHILDREN
} else if (children._isVNode) {
childFlags = ChildrenFlags.SINGLE_VNODE
if (children.el) {
children = cloneVNode(children)
}
} else {
// primitives or invalid values, cast to string
childFlags = ChildrenFlags.SINGLE_VNODE
children = createTextVNode(children + '')
}
vnode.children = children
vnode.childFlags = childFlags
}
export function normalizeVNodes(
children: any[],
newChildren: VNode[] = [],
currentPrefix: string = ''
): VNode[] {
for (let i = 0; i < children.length; i++) {
const child = children[i]
let newChild
if (child == null) {
newChild = createTextVNode('')
} else if (child._isVNode) {
newChild = child.el ? cloneVNode(child) : child
} else if (isArray(child)) {
normalizeVNodes(child, newChildren, currentPrefix + i + '|')
} else {
newChild = createTextVNode(child + '')
}
if (newChild) {
if (newChild.key == null) {
newChild.key = currentPrefix + i
}
newChildren.push(newChild)
}
}
return newChildren
}
// ensure all slot functions return Arrays
function normalizeSlots(slots: { [name: string]: any }): Slots {
if (slots._normalized) {
return slots
}
const normalized = { _normalized: true } as any
for (const name in slots) {
if (name === STABLE_SLOTS_HINT) {
continue
}
normalized[name] = (...args: any[]) => normalizeSlot(slots[name](...args))
}
return normalized
}
function normalizeSlot(value: any): VNode[] {
if (value == null) {
return [createTextVNode('')]
} else if (isArray(value)) {
return normalizeVNodes(value)
} else if (value._isVNode) {
return [value]
} else {
return [createTextVNode(value + '')]
}
}

View File

@ -1,131 +0,0 @@
import { ComponentType, ComponentClass, FunctionalComponent } from './component'
import { EMPTY_OBJ, isString } from '@vue/shared'
import { VNode } from './vdom'
import { Data } from './componentOptions'
let stack: VNode[] = []
type TraceEntry = {
type: VNode
recurseCount: number
}
type ComponentTraceStack = TraceEntry[]
export function pushWarningContext(vnode: VNode) {
stack.push(vnode)
}
export function popWarningContext() {
stack.pop()
}
export function warn(msg: string, ...args: any[]) {
// TODO warn handler
console.warn(`[Vue warn]: ${msg}`, ...args)
const trace = getComponentTrace()
if (!trace.length) {
return
}
if (console.groupCollapsed) {
console.groupCollapsed('at', ...formatTraceEntry(trace[0]))
const logs: string[] = []
trace.slice(1).forEach((entry, i) => {
if (i !== 0) logs.push('\n')
logs.push(...formatTraceEntry(entry, i + 1))
})
console.log(...logs)
console.groupEnd()
} else {
const logs: string[] = []
trace.forEach((entry, i) => {
const formatted = formatTraceEntry(entry, i)
if (i === 0) {
logs.push('at', ...formatted)
} else {
logs.push('\n', ...formatted)
}
})
console.log(...logs)
}
}
function getComponentTrace(): ComponentTraceStack {
let current: VNode | null | undefined = stack[stack.length - 1]
if (!current) {
return []
}
// we can't just use the stack because it will be incomplete during updates
// that did not start from the root. Re-construct the parent chain using
// contextVNode information.
const normlaizedStack: ComponentTraceStack = []
while (current) {
const last = normlaizedStack[0]
if (last && last.type === current) {
last.recurseCount++
} else {
normlaizedStack.push({
type: current,
recurseCount: 0
})
}
current = current.contextVNode
}
return normlaizedStack
}
function formatTraceEntry(
{ type, recurseCount }: TraceEntry,
depth: number = 0
): string[] {
const padding = depth === 0 ? '' : ' '.repeat(depth * 2 + 1)
const postfix =
recurseCount > 0 ? `... (${recurseCount} recursive calls)` : ``
const open = padding + `<${formatComponentName(type.tag as ComponentType)}`
const close = `>` + postfix
return type.data ? [open, ...formatProps(type.data), close] : [open + close]
}
const classifyRE = /(?:^|[-_])(\w)/g
const classify = (str: string): string =>
str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '')
function formatComponentName(c: ComponentType, file?: string): string {
let name: string
if (c.prototype && c.prototype.render) {
// stateful
const cc = c as ComponentClass
const options = cc.options || EMPTY_OBJ
name = options.displayName || cc.name
} else {
// functional
const fc = c as FunctionalComponent
name = fc.displayName || fc.name
}
if (file && name === 'AnonymousComponent') {
const match = file.match(/([^/\\]+)\.vue$/)
if (match) {
name = match[1]
}
}
return classify(name)
}
function formatProps(props: Data) {
const res = []
for (const key in props) {
const value = props[key]
if (isString(value)) {
res.push(`${key}=${JSON.stringify(value)}`)
} else {
res.push(`${key}=`, value)
}
}
return res
}