diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts
deleted file mode 100644
index 4e2aff24..00000000
--- a/packages/runtime-core/src/component.ts
+++ /dev/null
@@ -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
extends PublicInstanceMethods {
- readonly $el: any
- readonly $vnode: MountedVNode
- readonly $parentVNode: MountedVNode
- readonly $data: D
- readonly $props: Readonly
- readonly $attrs: Readonly
- readonly $slots: Slots
- readonly $root: Component
- readonly $parent: Component
- readonly $children: Component[]
- readonly $options: ComponentOptions
- readonly $refs: Record
- readonly $proxy: this
-}
-
-interface PublicInstanceMethods {
- $forceUpdate(): void
- $nextTick(fn: () => any): Promise
- $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 {
- data(): Partial
- render(props: Readonly, 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
(): Component
-}
-
-export interface FunctionalComponent
{
- (props: P, slots: Slots, attrs: Data, parentVNode: VNode): any
- props?: ComponentPropsOptions
- displayName?: string
-}
-
-export type ComponentType = ComponentClass | FunctionalComponent
-
-// Internal type that represents a mounted instance.
-// It extends ComponentImplementation with mounted instance properties.
-export interface ComponentInstance
- extends ComponentImplementation,
- Partial>,
- Partial {
- constructor: ComponentClass
- render: APIMethods['render']
-
- $vnode: MountedVNode
- $data: D
- $props: P
- $attrs: Data
- $slots: Slots
- $root: ComponentProxy
- $children: ComponentProxy[]
- $options: ComponentOptions
- $proxy: ComponentProxy
-
- _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 = {}
- $proxy: ComponentProxy | null = null
-
- _rawData: Data | null = null
- _computedGetters: Record | null = null
- _watchHandles: Set | 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 {
- 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
diff --git a/packages/runtime-core/src/componentComputed.ts b/packages/runtime-core/src/componentComputed.ts
deleted file mode 100644
index 0cc7796e..00000000
--- a/packages/runtime-core/src/componentComputed.ts
+++ /dev/null
@@ -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
-
-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)
- }
- }
-}
diff --git a/packages/runtime-core/src/componentInstance.ts b/packages/runtime-core/src/componentInstance.ts
deleted file mode 100644
index f0dc70f4..00000000
--- a/packages/runtime-core/src/componentInstance.ts
+++ /dev/null
@@ -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(
- instance: T
-): ComponentProxy {
- 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)
-}
diff --git a/packages/runtime-core/src/componentOptions.ts b/packages/runtime-core/src/componentOptions.ts
deleted file mode 100644
index 6ce1092b..00000000
--- a/packages/runtime-core/src/componentOptions.ts
+++ /dev/null
@@ -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
-
-export interface ComponentClassOptions {
- props?: ComponentPropsOptions
- computed?: ComponentComputedOptions
- watch?: ComponentWatchOptions
- displayName?: string
- fromOptions?: boolean
-}
-
-export interface ComponentOptions<
- P = {},
- D = {},
- This = ComponentInstance
->
- extends ComponentClassOptions
,
- Partial>,
- Partial {
- // TODO other options
- readonly [key: string]: any
-}
-
-export type ComponentPropsOptions = {
- [K in keyof P]: PropValidator
-}
-
-export type Prop = { (): T } | { new (...args: any[]): T & object }
-
-export type PropType = Prop | Prop[]
-
-export type PropValidator = PropOptions | PropType
-
-export interface PropOptions {
- type?: PropType | true | null
- required?: boolean
- default?: T | null | undefined | (() => T | null | undefined)
- validator?(value: T): boolean
-}
-
-export interface ComponentComputedOptions {
- [key: string]: ((this: This, c: This) => any) | SingleComputedOptions
-}
-
-type SingleComputedOptions = {
- get: (this: This, c: This) => any
- set?: (value: any) => void
- cache?: boolean
-}
-
-export interface ComponentWatchOptions {
- [key: string]: ComponentWatchOption
-}
-
-export type ComponentWatchOption =
- | WatchHandler
- | WatchHandler[]
- | WatchOptionsWithHandler
- | string
-
-export type WatchHandler = (
- this: This,
- val: any,
- oldVal: any
-) => void
-
-export interface WatchOptionsWithHandler extends WatchOptions {
- handler: WatchHandler
-}
-
-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))
- }
-}
diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts
deleted file mode 100644
index ba800495..00000000
--- a/packages/runtime-core/src/componentProps.ts
+++ /dev/null
@@ -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
-
-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): string {
- const match = ctor && ctor.toString().match(/^\s*function (\w+)/)
- return match ? match[1] : ''
-}
-
-function isSameType(a: Prop, b: Prop): boolean {
- return getType(a) === getType(b)
-}
-
-function getTypeIndex(
- type: Prop,
- expectedTypes: PropType | 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,
- 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): 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')
-}
diff --git a/packages/runtime-core/src/componentProxy.ts b/packages/runtime-core/src/componentProxy.ts
deleted file mode 100644
index b65cfe23..00000000
--- a/packages/runtime-core/src/componentProxy.ts
+++ /dev/null
@@ -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, 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,
- 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 & { _self: T }
-
-export function createRenderProxy(
- instance: T
-): ComponentProxy {
- debugger
- return new Proxy(instance, renderProxyHandlers) as any
-}
diff --git a/packages/runtime-core/src/componentRenderUtils.ts b/packages/runtime-core/src/componentRenderUtils.ts
deleted file mode 100644
index d327d0fe..00000000
--- a/packages/runtime-core/src/componentRenderUtils.ts
+++ /dev/null
@@ -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
- }
-}
diff --git a/packages/runtime-core/src/componentState.ts b/packages/runtime-core/src/componentState.ts
deleted file mode 100644
index fec671ea..00000000
--- a/packages/runtime-core/src/componentState.ts
+++ /dev/null
@@ -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
-}
diff --git a/packages/runtime-core/src/componentWatch.ts b/packages/runtime-core/src/componentWatch.ts
deleted file mode 100644
index ad607e53..00000000
--- a/packages/runtime-core/src/componentWatch.ts
+++ /dev/null
@@ -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 = 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
-}
diff --git a/packages/runtime-core/src/createRenderer.ts b/packages/runtime-core/src/createRenderer.ts
index cd7927d6..e09e8ce3 100644
--- a/packages/runtime-core/src/createRenderer.ts
+++ b/packages/runtime-core/src/createRenderer.ts
@@ -1,1613 +1,300 @@
-import {
- effect as createReactiveEffect,
- stop as stopReactiveEffect,
- ReactiveEffect,
- immutable,
- ReactiveEffectOptions
-} from '@vue/observer'
-import {
- queueJob,
- handleSchedulerError,
- nextTick,
- queuePostEffect,
- flushEffects,
- queueNodeOp
-} from '@vue/scheduler'
-import { VNodeFlags, ChildrenFlags } from './flags'
-import { EMPTY_OBJ, reservedPropRE, isString } from '@vue/shared'
-import {
- VNode,
- MountedVNode,
- RenderNode,
- createTextVNode,
- cloneVNode,
- VNodeChildren
-} from './vdom'
-import { ComponentInstance } from './component'
-import {
- createComponentInstance,
- teardownComponentInstance
-} from './componentInstance'
-import {
- renderInstanceRoot,
- renderFunctionalRoot,
- shouldUpdateComponent,
- getReasonForComponentUpdate
-} from './componentRenderUtils'
-import { KeepAliveSymbol } from './optional/keepAlive'
-import { pushWarningContext, popWarningContext, warn } from './warning'
-import { resolveProps } from './componentProps'
-import {
- handleError,
- ErrorTypes,
- callLifecycleHookWithHandler
-} from './errorHandling'
+// TODO:
+// - app context
+// - component
+// - lifecycle
+// - refs
+// - reused nodes
+// - hydration
-export interface NodeOps {
- createElement: (tag: string, isSVG?: boolean) => any
- createText: (text: string) => any
- setText: (node: any, text: string) => void
- appendChild: (parent: any, child: any) => void
- insertBefore: (parent: any, child: any, ref: any) => void
- removeChild: (parent: any, child: any) => void
- clearContent: (node: any) => void
- parentNode: (node: any) => any
- nextSibling: (node: any) => any
- querySelector: (selector: string) => any
-}
+import { Text, Fragment, Empty, createVNode } from './h.js'
-export interface PatchDataFunction {
- (
- el: any,
- key: string,
- prevValue: any,
- nextValue: any,
- preVNode: VNode | null,
- nextVNode: VNode,
- isSVG: boolean,
- // passed for DOM operations that removes child content
- // e.g. innerHTML & textContent
- unmountChildren: (children: VNode[], childFlags: ChildrenFlags) => void
- ): void
-}
+import { TEXT, CLASS, STYLE, PROPS, KEYED, UNKEYED } from './patchFlags'
-export interface RendererOptions {
- nodeOps: NodeOps
- patchData: PatchDataFunction
-}
+const emptyArr: any[] = []
+const emptyObj = {}
-export interface FunctionalHandle {
- prev: VNode
- next: VNode
- update: ReactiveEffect
- container: RenderNode | null
-}
+const isSameType = (n1, n2) => n1.type === n2.type && n1.key === n2.key
-handleSchedulerError(err => handleError(err, null, ErrorTypes.SCHEDULER))
-
-// The whole mounting / patching / unmouting logic is placed inside this
-// single function so that we can create multiple renderes with different
-// platform definitions. This allows for use cases like creating a test
-// renderer alongside an actual renderer.
-export function createRenderer(options: RendererOptions) {
+export function createRenderer(hostConfig) {
const {
- nodeOps: {
- createElement: platformCreateElement,
- createText: platformCreateText,
- setText: platformSetText,
- appendChild: platformAppendChild,
- insertBefore: platformInsertBefore,
- removeChild: platformRemoveChild,
- clearContent: platformClearContent,
- parentNode: platformParentNode,
- nextSibling: platformNextSibling,
- querySelector: platformQuerySelector
- },
- patchData: platformPatchData
- } = options
+ insert,
+ remove,
+ patchProp: hostPatchProp,
+ createElement: hostCreateElement,
+ createText: hostCreateText,
+ createComment: hostCreateComment,
+ setText: hostSetText,
+ setElementText: hostSetElementText,
+ nextSibling: hostNextSibling
+ } = hostConfig
- function queueInsertOrAppend(
- container: RenderNode,
- newNode: RenderNode,
- refNode: RenderNode | null
- ) {
- if (refNode === null) {
- queueNodeOp([platformAppendChild, container, newNode])
+ function patch(n1, n2, container, anchor, optimized) {
+ // patching & not same type, unmount old tree
+ if (n1 != null && !isSameType(n1, n2)) {
+ anchor = hostNextSibling(n1.el)
+ unmount(n1, true)
+ n1 = null
+ }
+
+ const { type } = n2
+ if (type === Text) {
+ processText(n1, n2, container, anchor)
+ } else if (type === Empty) {
+ processEmptyNode(n1, n2, container, anchor)
+ } else if (type === Fragment) {
+ processFragment(n1, n2, container, anchor, optimized)
+ } else if (typeof type === 'function') {
+ // TODO Component
} else {
- queueNodeOp([platformInsertBefore, container, newNode, refNode])
+ processElement(n1, n2, container, anchor, optimized)
}
}
- // mounting ------------------------------------------------------------------
-
- function mount(
- vnode: VNode,
- container: RenderNode | null,
- contextVNode: MountedVNode | null,
- isSVG: boolean,
- endNode: RenderNode | null,
- ownerArray?: VNode[],
- index?: number
- ) {
- const { flags } = vnode
- if (flags & VNodeFlags.ELEMENT) {
- mountElement(vnode, container, contextVNode, isSVG, endNode)
- } else if (flags & VNodeFlags.COMPONENT) {
- mountComponent(vnode, container, contextVNode, isSVG, endNode)
- } else if (flags & VNodeFlags.TEXT) {
- mountText(vnode, container, endNode)
- } else if (flags & VNodeFlags.FRAGMENT) {
- mountFragment(vnode, container, contextVNode, isSVG, endNode)
- } else if (flags & VNodeFlags.PORTAL) {
- mountPortal(vnode, container, contextVNode)
- }
- }
-
- function mountArrayChildren(
- children: VNode[],
- container: RenderNode | null,
- contextVNode: MountedVNode | null,
- isSVG: boolean,
- endNode: RenderNode | null
- ) {
- for (let i = 0; i < children.length; i++) {
- const child = getNextVNode(children, i)
- mount(child, container, contextVNode, isSVG, endNode)
- }
- }
-
- function mountElement(
- vnode: VNode,
- container: RenderNode | null,
- contextVNode: MountedVNode | null,
- isSVG: boolean,
- endNode: RenderNode | null
- ) {
- const { flags, tag, data, children, childFlags, ref } = vnode
- isSVG = isSVG || (flags & VNodeFlags.ELEMENT_SVG) > 0
- // element creation is not deferred since it doesn't produce
- // user-affecting side effects until inserted into the DOM
- const el = (vnode.el = platformCreateElement(tag as string, isSVG))
- if (data != null) {
- for (const key in data) {
- if (!reservedPropRE.test(key)) {
- platformPatchData(
- el,
- key,
- null,
- data[key],
- null,
- vnode,
- isSVG,
- unmountChildren
- )
- }
- }
- if (data.vnodeBeforeMount) {
- data.vnodeBeforeMount(vnode)
- }
- }
- if (childFlags !== ChildrenFlags.NO_CHILDREN) {
- const hasSVGChildren = isSVG && tag !== 'foreignObject'
- if (childFlags & ChildrenFlags.SINGLE_VNODE) {
- mount(children as VNode, el, contextVNode, hasSVGChildren, null)
- } else if (childFlags & ChildrenFlags.MULTIPLE_VNODES) {
- mountArrayChildren(
- children as VNode[],
- el,
- contextVNode,
- hasSVGChildren,
- null
- )
- }
- }
- if (container != null) {
- queueInsertOrAppend(container, el, endNode)
- }
- if (ref) {
- queuePostEffect(() => {
- ref(el)
- })
- }
- if (data != null && data.vnodeMounted) {
- queuePostEffect(() => {
- data.vnodeMounted(vnode)
- })
- }
- }
-
- function mountComponent(
- vnode: VNode,
- container: RenderNode | null,
- contextVNode: MountedVNode | null,
- isSVG: boolean,
- endNode: RenderNode | null
- ) {
- vnode.contextVNode = contextVNode
- const { flags } = vnode
- if (flags & VNodeFlags.COMPONENT_STATEFUL) {
- mountStatefulComponent(vnode, container, isSVG, endNode)
+ function processText(n1, n2, container, anchor) {
+ if (n1 == null) {
+ insert((n2.el = hostCreateText(n2.children)), container, anchor)
} else {
- mountFunctionalComponent(vnode, container, isSVG, endNode)
+ const el = (n2.el = n1.el)
+ if (n2.children !== n1.children) {
+ hostSetText(el, n2, children)
+ }
}
}
- function mountStatefulComponent(
- vnode: VNode,
- container: RenderNode | null,
- isSVG: boolean,
- endNode: RenderNode | null
- ) {
- if (vnode.flags & VNodeFlags.COMPONENT_STATEFUL_KEPT_ALIVE) {
- // kept-alive
- activateComponentInstance(vnode, container, endNode)
+ function processEmptyNode(n1, n2, container, anchor) {
+ if (n1 == null) {
+ insert((n2.el = hostCreateComment('')), container, anchor)
} else {
- if (__COMPAT__) {
- mountComponentInstance(vnode, container, isSVG, endNode)
- } else {
- queueJob(() => mountComponentInstance(vnode, container, isSVG, endNode))
- }
+ n2.el = n1.el
}
}
- function mountFunctionalComponent(
- vnode: VNode,
- container: RenderNode | null,
- isSVG: boolean,
- endNode: RenderNode | null
- ) {
- if (__DEV__ && vnode.ref) {
- warn(
- `cannot use ref on a functional component because there is no ` +
- `instance to reference to.`
- )
- }
-
- const handle: FunctionalHandle = (vnode.handle = {
- prev: vnode,
- next: null as any,
- update: null as any,
- container
- })
-
- const doMount = () => {
- handle.update = createReactiveEffect(
- () => {
- if (!handle.next) {
- // initial mount
- if (__DEV__) {
- pushWarningContext(vnode)
- }
- const subTree = (vnode.children = renderFunctionalRoot(vnode))
- queuePostEffect(() => {
- vnode.el = subTree.el as RenderNode
- })
- mount(subTree, container, vnode as MountedVNode, isSVG, endNode)
- handle.next = vnode
- if (__DEV__) {
- popWarningContext()
- }
- } else {
- updateFunctionalComponent(handle, isSVG)
- }
- },
- {
- scheduler: queueJob
- }
- )
- }
-
- // we are using vnode.ref to store the functional component's update job
- if (__COMPAT__) {
- doMount()
+ function processElement(n1, n2, container, anchor, optimized) {
+ // mount
+ if (n1 == null) {
+ mountElement(n2, container, anchor)
} else {
- queueJob(() => {
- doMount()
- // cleanup if mount is invalidated before committed
- return () => {
- stopReactiveEffect(handle.update)
- }
- })
+ patchElement(n1, n2, container, optimized)
}
}
- function updateFunctionalComponent(handle: FunctionalHandle, isSVG: boolean) {
- const { prev, next } = handle
- if (__DEV__) {
- pushWarningContext(next)
- }
- const prevTree = prev.children as MountedVNode
- const nextTree = (next.children = renderFunctionalRoot(next))
- queuePostEffect(() => {
- next.el = nextTree.el
- })
- patch(
- prevTree,
- nextTree,
- handle.container as RenderNode,
- next as MountedVNode,
- isSVG
- )
- if (__DEV__) {
- popWarningContext()
- }
- }
-
- function mountText(
- vnode: VNode,
- container: RenderNode | null,
- endNode: RenderNode | null
- ) {
- const el = (vnode.el = platformCreateText(vnode.children as string))
- if (container != null) {
- queueInsertOrAppend(container, el, endNode)
- }
- }
-
- function mountFragment(
- vnode: VNode,
- container: RenderNode | null,
- contextVNode: MountedVNode | null,
- isSVG: boolean,
- endNode: RenderNode | null
- ) {
- const { children, childFlags } = vnode
- switch (childFlags) {
- case ChildrenFlags.SINGLE_VNODE:
- queuePostEffect(() => {
- vnode.el = (children as MountedVNode).el
- })
- mount(children as VNode, container, contextVNode, isSVG, endNode)
- break
- case ChildrenFlags.NO_CHILDREN:
- const placeholder = createTextVNode('')
- mountText(placeholder, container, null)
- vnode.el = placeholder.el
- break
- default:
- queuePostEffect(() => {
- vnode.el = (children as MountedVNode[])[0].el
- })
- mountArrayChildren(
- children as VNode[],
- container,
- contextVNode,
- isSVG,
- endNode
- )
- }
- }
-
- function mountPortal(
- vnode: VNode,
- container: RenderNode | null,
- contextVNode: MountedVNode | null
- ) {
- const { tag, children, childFlags, ref } = vnode
- const target = isString(tag) ? platformQuerySelector(tag) : tag
-
- if (__DEV__ && !target) {
- // TODO warn poartal target not found
- }
-
- if (childFlags & ChildrenFlags.SINGLE_VNODE) {
- mount(children as VNode, target as RenderNode, contextVNode, false, null)
- } else if (childFlags & ChildrenFlags.MULTIPLE_VNODES) {
- mountArrayChildren(
- children as VNode[],
- target as RenderNode,
- contextVNode,
- false,
- null
- )
- }
- if (ref) {
- queuePostEffect(() => {
- ref(target)
- })
- }
- const placeholder = createTextVNode('')
- mountText(placeholder, container, null)
- vnode.el = placeholder.el
- }
-
- // patching ------------------------------------------------------------------
-
- function queuePatchData(
- el: RenderNode | (() => RenderNode),
- key: string,
- prevValue: any,
- nextValue: any,
- preVNode: VNode | null,
- nextVNode: VNode,
- isSVG: boolean
- ) {
- if (!reservedPropRE.test(key)) {
- queueNodeOp([
- platformPatchData,
- el,
- key,
- prevValue,
- nextValue,
- preVNode,
- nextVNode,
- isSVG,
- unmountChildren
- ])
- }
- }
-
- function patch(
- prevVNode: MountedVNode,
- nextVNode: VNode,
- container: RenderNode,
- contextVNode: MountedVNode | null,
- isSVG: boolean
- ) {
- const nextFlags = nextVNode.flags
- const prevFlags = prevVNode.flags
-
- if (prevFlags !== nextFlags) {
- replaceVNode(prevVNode, nextVNode, container, contextVNode, isSVG)
- } else if (nextFlags & VNodeFlags.ELEMENT) {
- patchElement(prevVNode, nextVNode, container, contextVNode, isSVG)
- } else if (nextFlags & VNodeFlags.COMPONENT) {
- patchComponent(prevVNode, nextVNode, container, contextVNode, isSVG)
- } else if (nextFlags & VNodeFlags.TEXT) {
- patchText(prevVNode, nextVNode)
- } else if (nextFlags & VNodeFlags.FRAGMENT) {
- patchFragment(prevVNode, nextVNode, container, contextVNode, isSVG)
- } else if (nextFlags & VNodeFlags.PORTAL) {
- patchPortal(prevVNode, nextVNode, contextVNode)
- }
- }
-
- function patchElement(
- prevVNode: MountedVNode,
- nextVNode: VNode,
- container: RenderNode,
- contextVNode: MountedVNode | null,
- isSVG: boolean
- ) {
- const { flags, tag, clonedFrom } = nextVNode
-
- // cloned vnodes pointing to the same original.
- // these are hoisted static trees so just skip entirely
- if (
- clonedFrom !== null &&
- (clonedFrom === prevVNode || clonedFrom === prevVNode.clonedFrom)
- ) {
- nextVNode.el = prevVNode.el
- return
- }
-
- isSVG = isSVG || (flags & VNodeFlags.ELEMENT_SVG) > 0
-
- if (prevVNode.tag !== tag) {
- replaceVNode(prevVNode, nextVNode, container, contextVNode, isSVG)
- return
- }
-
- const el = (nextVNode.el = prevVNode.el)
- const prevData = prevVNode.data
- const nextData = nextVNode.data
-
- if (nextData != null && nextData.vnodeBeforeUpdate) {
- nextData.vnodeBeforeUpdate(nextVNode, prevVNode)
- }
-
- // patch data
- if (prevData !== nextData) {
- const prevDataOrEmpty = prevData || EMPTY_OBJ
- const nextDataOrEmpty = nextData || EMPTY_OBJ
- if (nextDataOrEmpty !== EMPTY_OBJ) {
- for (const key in nextDataOrEmpty) {
- const prevValue = prevDataOrEmpty[key]
- const nextValue = nextDataOrEmpty[key]
- if (prevValue !== nextValue) {
- queuePatchData(
- el,
- key,
- prevValue,
- nextValue,
- prevVNode,
- nextVNode,
- isSVG
- )
- }
- }
- }
- if (prevDataOrEmpty !== EMPTY_OBJ) {
- for (const key in prevDataOrEmpty) {
- const prevValue = prevDataOrEmpty[key]
- if (prevValue != null && !nextDataOrEmpty.hasOwnProperty(key)) {
- queuePatchData(
- el,
- key,
- prevValue,
- null,
- prevVNode,
- nextVNode,
- isSVG
- )
- }
- }
+ function mountElement(vnode, container, anchor) {
+ const el = (vnode.el = hostCreateElement(vnode.type))
+ if (vnode.props != null) {
+ for (const key in vnode.props) {
+ hostPatchProp(el, key, vnode.props[key], null)
}
}
-
- // children
- patchChildren(
- prevVNode.childFlags,
- nextVNode.childFlags,
- prevVNode.children,
- nextVNode.children,
- el,
- contextVNode,
- isSVG && nextVNode.tag !== 'foreignObject',
- null
- )
-
- if (nextData != null && nextData.vnodeUpdated) {
- // TODO fix me
- // vnodeUpdatedHooks.push(() => {
- // nextData.vnodeUpdated(nextVNode, prevVNode)
- // })
- }
- }
-
- function patchComponent(
- prevVNode: MountedVNode,
- nextVNode: VNode,
- container: RenderNode,
- contextVNode: MountedVNode | null,
- isSVG: boolean
- ) {
- nextVNode.contextVNode = contextVNode
- const { tag, flags } = nextVNode
- if (tag !== prevVNode.tag) {
- replaceVNode(prevVNode, nextVNode, container, contextVNode, isSVG)
- } else if (flags & VNodeFlags.COMPONENT_STATEFUL) {
- patchStatefulComponent(prevVNode, nextVNode)
+ if (typeof vnode.children === 'string') {
+ hostSetElementText(el, vnode.children)
} else {
- patchFunctionalComponent(prevVNode, nextVNode, container)
+ mountChildren(vnode.children, el)
+ }
+ insert(el, container, anchor)
+ }
+
+ function mountChildren(children, container, anchor, start = 0) {
+ for (let i = start; i < children.length; i++) {
+ const child = (children[i] = normalizeChild(children[i]))
+ patch(null, child, container, anchor)
}
}
- function patchStatefulComponent(prevVNode: MountedVNode, nextVNode: VNode) {
- const { data: prevData } = prevVNode
- const { data: nextData, slots: nextSlots } = nextVNode
-
- const instance = (nextVNode.children =
- prevVNode.children) as ComponentInstance
-
- if (nextData !== prevData) {
- const { 0: props, 1: attrs } = resolveProps(
- nextData,
- instance.$options.props
- )
- instance.$props = __DEV__ ? immutable(props) : props
- instance.$attrs = __DEV__ ? immutable(attrs) : attrs
- }
- instance.$slots = nextSlots || EMPTY_OBJ
- instance.$parentVNode = nextVNode as MountedVNode
-
- if (shouldUpdateComponent(prevVNode, nextVNode)) {
- if (__DEV__ && instance.$options.renderTriggered) {
- callLifecycleHookWithHandler(
- instance.$options.renderTriggered,
- instance.$proxy,
- ErrorTypes.RENDER_TRIGGERED,
- getReasonForComponentUpdate(prevVNode, nextVNode)
- )
- }
- instance.$forceUpdate()
- } else if (instance.$vnode.flags & VNodeFlags.COMPONENT) {
- instance.$vnode.contextVNode = nextVNode
- }
- nextVNode.el = instance.$vnode.el
- }
-
- function patchFunctionalComponent(
- prevVNode: MountedVNode,
- nextVNode: VNode,
- container: RenderNode
- ) {
- const prevTree = prevVNode.children as VNode
- const handle = (nextVNode.handle = prevVNode.handle as FunctionalHandle)
- handle.prev = prevVNode
- handle.next = nextVNode
- handle.container = container
-
- if (shouldUpdateComponent(prevVNode, nextVNode)) {
- queueJob(handle.update)
- } else if (prevTree.flags & VNodeFlags.COMPONENT) {
- // functional component returned another component
- prevTree.contextVNode = nextVNode
- }
- }
-
- function patchFragment(
- prevVNode: MountedVNode,
- nextVNode: VNode,
- container: RenderNode,
- contextVNode: MountedVNode | null,
- isSVG: boolean
- ) {
- // determine the tail node of the previous fragment,
- // then retrieve its next sibling to use as the end node for patchChildren.
- const endNode = platformNextSibling(getVNodeLastEl(prevVNode))
- const { childFlags, children } = nextVNode
- queuePostEffect(() => {
- switch (childFlags) {
- case ChildrenFlags.SINGLE_VNODE:
- nextVNode.el = (children as MountedVNode).el
- break
- case ChildrenFlags.NO_CHILDREN:
- nextVNode.el = prevVNode.el
- break
- default:
- nextVNode.el = (children as MountedVNode[])[0].el
- }
- })
- patchChildren(
- prevVNode.childFlags,
- childFlags,
- prevVNode.children,
- children,
- container,
- contextVNode,
- isSVG,
- endNode
- )
- }
-
- function getVNodeLastEl(vnode: MountedVNode): RenderNode {
- const { el, flags, children, childFlags } = vnode
- if (flags & VNodeFlags.FRAGMENT) {
- if (childFlags & ChildrenFlags.SINGLE_VNODE) {
- return getVNodeLastEl(children as MountedVNode)
- } else if (childFlags & ChildrenFlags.MULTIPLE_VNODES) {
- return getVNodeLastEl(
- (children as MountedVNode[])[(children as MountedVNode[]).length - 1]
- )
- } else {
- return el
- }
+ function normalizeChild(child) {
+ // empty placeholder
+ if (child == null) {
+ return createVNode(Empty)
+ } else if (typeof child === 'string' || typeof child === 'number') {
+ return createVNode(Text, null, child + '')
+ } else if (Array.isArray(child)) {
+ return createVNode(Fragment, null, child)
} else {
- return el
+ return child
}
}
- function patchText(prevVNode: MountedVNode, nextVNode: VNode) {
- const el = (nextVNode.el = prevVNode.el) as RenderNode
- const nextText = nextVNode.children
- if (nextText !== prevVNode.children) {
- queueNodeOp([platformSetText, el, nextText])
- }
- }
+ function patchElement(n1, n2, container, optimized) {
+ const el = (n2.el = n1.el)
+ const { patchFlag, dynamicChildren } = n2
+ const oldProps = (n1 && n1.props) || emptyObj
+ const newProps = n2.props || emptyObj
- function patchPortal(
- prevVNode: MountedVNode,
- nextVNode: VNode,
- contextVNode: MountedVNode | null
- ) {
- const prevContainer = prevVNode.tag as RenderNode
- const nextContainer = nextVNode.tag as RenderNode
- const nextChildren = nextVNode.children
- patchChildren(
- prevVNode.childFlags,
- nextVNode.childFlags,
- prevVNode.children,
- nextChildren,
- prevContainer,
- contextVNode,
- false,
- null
- )
- nextVNode.el = prevVNode.el
- if (nextContainer !== prevContainer) {
- switch (nextVNode.childFlags) {
- case ChildrenFlags.SINGLE_VNODE:
- insertVNode(nextChildren as MountedVNode, nextContainer, null)
- break
- case ChildrenFlags.NO_CHILDREN:
- break
- default:
- for (let i = 0; i < (nextChildren as MountedVNode[]).length; i++) {
- insertVNode(
- (nextChildren as MountedVNode[])[i],
- nextContainer,
- null
- )
- }
- break
+ if (patchFlag != null) {
+ // the presence of a patchFlag means this element's render code was
+ // generated by the compiler and can take the fast path.
+ // in this path old node and new node are guaranteed to have the same shape
+ // (i.e. at the exact same position in the source template)
+
+ // class
+ // this flag is matched when the element has dynamic class bindings.
+ if (patchFlag & CLASS) {
+ // TODO handle full class API, potentially optimize at compilation stage?
+ if (oldProps.class !== newProps.class) {
+ el.className = newProps.class
+ }
}
- }
- }
- function replaceVNode(
- prevVNode: MountedVNode,
- nextVNode: VNode,
- container: RenderNode,
- contextVNode: MountedVNode | null,
- isSVG: boolean
- ) {
- const refNode = platformNextSibling(getVNodeLastEl(prevVNode))
- queueRemoveVNode(prevVNode, container)
- mount(nextVNode, container, contextVNode, isSVG, refNode)
- }
+ // style
+ // this flag is matched when the element has dynamic style bindings
+ // TODO separate static and dynamic styles?
+ if (patchFlag & STYLE) {
+ setStyles(el.style, oldProps.style, newProps.style)
+ }
- function patchChildren(
- prevChildFlags: ChildrenFlags,
- nextChildFlags: ChildrenFlags,
- prevChildren: VNodeChildren,
- nextChildren: VNodeChildren,
- container: RenderNode,
- contextVNode: MountedVNode | null,
- isSVG: boolean,
- endNode: RenderNode | null
- ) {
- switch (prevChildFlags) {
- case ChildrenFlags.SINGLE_VNODE:
- switch (nextChildFlags) {
- case ChildrenFlags.SINGLE_VNODE:
- patch(
- prevChildren as MountedVNode,
- nextChildren as VNode,
- container,
- contextVNode,
- isSVG
- )
- break
- case ChildrenFlags.NO_CHILDREN:
- queueRemoveVNode(prevChildren as MountedVNode, container)
- break
- default:
- queueRemoveVNode(prevChildren as MountedVNode, container)
- mountArrayChildren(
- nextChildren as VNode[],
- container,
- contextVNode,
- isSVG,
- endNode
- )
- break
- }
- break
- case ChildrenFlags.NO_CHILDREN:
- switch (nextChildFlags) {
- case ChildrenFlags.SINGLE_VNODE:
- mount(
- nextChildren as VNode,
- container,
- contextVNode,
- isSVG,
- endNode
- )
- break
- case ChildrenFlags.NO_CHILDREN:
- break
- default:
- mountArrayChildren(
- nextChildren as VNode[],
- container,
- contextVNode,
- isSVG,
- endNode
- )
- break
- }
- break
- default:
- // MULTIPLE_CHILDREN
- if (nextChildFlags === ChildrenFlags.SINGLE_VNODE) {
- queueRemoveChildren(
- prevChildren as MountedVNode[],
- container,
- endNode
- )
- mount(nextChildren as VNode, container, contextVNode, isSVG, endNode)
- } else if (nextChildFlags === ChildrenFlags.NO_CHILDREN) {
- queueRemoveChildren(
- prevChildren as MountedVNode[],
- container,
- endNode
- )
- } else {
- const prevLength = (prevChildren as VNode[]).length
- const nextLength = (nextChildren as VNode[]).length
- if (prevLength === 0) {
- if (nextLength > 0) {
- mountArrayChildren(
- nextChildren as VNode[],
- container,
- contextVNode,
- isSVG,
- endNode
- )
- }
- } else if (nextLength === 0) {
- queueRemoveChildren(
- prevChildren as MountedVNode[],
- container,
- endNode
- )
- } else if (
- prevChildFlags === ChildrenFlags.KEYED_VNODES &&
- nextChildFlags === ChildrenFlags.KEYED_VNODES
- ) {
- patchKeyedChildren(
- prevChildren as MountedVNode[],
- nextChildren as VNode[],
- container,
- prevLength,
- nextLength,
- contextVNode,
- isSVG,
- endNode
- )
- } else {
- patchNonKeyedChildren(
- prevChildren as MountedVNode[],
- nextChildren as VNode[],
- container,
- prevLength,
- nextLength,
- contextVNode,
- isSVG,
- endNode
- )
+ // props
+ // This flag is matched when the element has dynamic prop/attr bindings
+ // other than class and style. The keys of dynamic prop/attrs are saved for
+ // faster iteration.
+ // Note dynamic keys like :[foo]="bar" will cause this optimization to
+ // bail out and go through a full diff because we need to unset the old key
+ if (patchFlag & PROPS) {
+ const propsToUpdate = n2.dynamicProps
+ for (let i = 0; i < propsToUpdate.length; i++) {
+ const key = propsToUpdate[i]
+ const prev = oldProps[key]
+ const next = newProps[key]
+ if (prev !== next) {
+ hostPatchProp(el, key, next, prev)
}
}
- break
+ }
+
+ // text
+ // This flag is matched when the element has only dynamic text children.
+ // this flag is terminal (i.e. skips children diffing).
+ if (patchFlag & TEXT) {
+ if (n1.children !== n2.children) {
+ hostSetElementText(el, n2.children)
+ }
+ return // terminal
+ }
+ } else if (!optimized) {
+ // unoptimized, full diff
+ patchProps(el, oldProps, newProps)
+ }
+
+ if (dynamicChildren != null) {
+ // children fast path
+ const olddynamicChildren = n1.dynamicChildren
+ for (let i = 0; i < dynamicChildren.length; i++) {
+ patch(olddynamicChildren[i], dynamicChildren[i], el, null, true)
+ }
+ } else if (!optimized) {
+ // full diff
+ patchChildren(n1, n2, el)
}
}
- function patchNonKeyedChildren(
- prevChildren: MountedVNode[],
- nextChildren: VNode[],
- container: RenderNode,
- prevLength: number,
- nextLength: number,
- contextVNode: MountedVNode | null,
- isSVG: boolean,
- endNode: RenderNode | null
- ) {
- const commonLength = prevLength > nextLength ? nextLength : prevLength
- let i = 0
- let nextChild
- let prevChild
- for (i; i < commonLength; i++) {
- nextChild = getNextVNode(nextChildren, i)
- prevChild = prevChildren[i]
- patch(prevChild, nextChild, container, contextVNode, isSVG)
- prevChildren[i] = nextChild as MountedVNode
- }
- if (prevLength < nextLength) {
- for (i = commonLength; i < nextLength; i++) {
- nextChild = getNextVNode(nextChildren, i)
- mount(nextChild, container, contextVNode, isSVG, endNode)
+ function patchProps(el, oldProps, newProps) {
+ if (oldProps !== newProps) {
+ for (const key in newProps) {
+ const next = newProps[key]
+ const prev = oldProps[key]
+ if (next !== prev) {
+ hostPatchProp(el, key, next, prev)
+ }
}
- } else if (prevLength > nextLength) {
- for (i = commonLength; i < prevLength; i++) {
- queueRemoveVNode(prevChildren[i], container)
+ if (oldProps !== emptyObj) {
+ for (const key in oldProps) {
+ if (!(key in newProps)) {
+ hostPatchProp(el, key, null, null)
+ }
+ }
}
}
}
- function patchKeyedChildren(
- prevChildren: MountedVNode[],
- nextChildren: VNode[],
- container: RenderNode,
- prevLength: number,
- nextLength: number,
- contextVNode: MountedVNode | null,
- isSVG: boolean,
- endNode: RenderNode | null
- ) {
- let prevEnd = prevLength - 1
- let nextEnd = nextLength - 1
- let i
- let j = 0
- let prevVNode = prevChildren[j]
- let nextVNode = getNextVNode(nextChildren, j)
- let nextPos
-
- outer: {
- // Sync nodes with the same key at the beginning.
- while (prevVNode.key === nextVNode.key) {
- patch(prevVNode, nextVNode, container, contextVNode, isSVG)
- prevChildren[j] = nextVNode as MountedVNode
- j++
- if (j > prevEnd || j > nextEnd) {
- break outer
- }
- prevVNode = prevChildren[j]
- nextVNode = getNextVNode(nextChildren, j)
- }
-
- prevVNode = prevChildren[prevEnd]
- nextVNode = getNextVNode(nextChildren, nextEnd)
-
- // Sync nodes with the same key at the end.
- while (prevVNode.key === nextVNode.key) {
- patch(prevVNode, nextVNode, container, contextVNode, isSVG)
- prevChildren[prevEnd] = nextVNode as MountedVNode
- prevEnd--
- nextEnd--
- if (j > prevEnd || j > nextEnd) {
- break outer
- }
- prevVNode = prevChildren[prevEnd]
- nextVNode = getNextVNode(nextChildren, nextEnd)
- }
- }
-
- if (j > prevEnd) {
- if (j <= nextEnd) {
- nextPos = nextEnd + 1
- const nextNode =
- nextPos < nextLength ? nextChildren[nextPos].el : endNode
- while (j <= nextEnd) {
- nextVNode = getNextVNode(nextChildren, j)
- j++
- mount(nextVNode, container, contextVNode, isSVG, nextNode)
- }
- }
- } else if (j > nextEnd) {
- while (j <= prevEnd) {
- queueRemoveVNode(prevChildren[j++], container)
- }
+ function processFragment(n1, n2, container, anchor, optimized) {
+ const fragmentAnchor = (n2.el = n1 ? n1.el : document.createComment(''))
+ if (n1 == null) {
+ insert(fragmentAnchor, container, anchor)
+ mountChildren(n2.children, container, fragmentAnchor)
} else {
- let prevStart = j
- const nextStart = j
- const prevLeft = prevEnd - j + 1
- const nextLeft = nextEnd - j + 1
- const sources: number[] = []
- for (i = 0; i < nextLeft; i++) {
- sources.push(0)
- }
- // Keep track if its possible to remove whole DOM using textContent = ''
- let canRemoveWholeContent = prevLeft === prevLength
- let moved = false
- let pos = 0
- let patched = 0
-
- // When sizes are small, just loop them through
- if (nextLength < 4 || (prevLeft | nextLeft) < 32) {
- for (i = prevStart; i <= prevEnd; i++) {
- prevVNode = prevChildren[i]
- if (patched < nextLeft) {
- for (j = nextStart; j <= nextEnd; j++) {
- nextVNode = getNextVNode(nextChildren, j)
- if (prevVNode.key === nextVNode.key) {
- sources[j - nextStart] = i + 1
- if (canRemoveWholeContent) {
- canRemoveWholeContent = false
- while (i > prevStart) {
- queueRemoveVNode(prevChildren[prevStart++], container)
- }
- }
- if (pos > j) {
- moved = true
- } else {
- pos = j
- }
- patch(prevVNode, nextVNode, container, contextVNode, isSVG)
- patched++
- break
- }
- }
- if (!canRemoveWholeContent && j > nextEnd) {
- queueRemoveVNode(prevVNode, container)
- }
- } else if (!canRemoveWholeContent) {
- queueRemoveVNode(prevVNode, container)
- }
- }
- } else {
- const keyIndex: Record = {}
-
- // Map keys by their index
- for (i = nextStart; i <= nextEnd; i++) {
- keyIndex[nextChildren[i].key as string] = i
- }
-
- // Try to patch same keys
- for (i = prevStart; i <= prevEnd; i++) {
- prevVNode = prevChildren[i]
-
- if (patched < nextLeft) {
- j = keyIndex[prevVNode.key as string]
-
- if (j !== void 0) {
- if (canRemoveWholeContent) {
- canRemoveWholeContent = false
- while (i > prevStart) {
- queueRemoveVNode(prevChildren[prevStart++], container)
- }
- }
- nextVNode = getNextVNode(nextChildren, j)
- sources[j - nextStart] = i + 1
- if (pos > j) {
- moved = true
- } else {
- pos = j
- }
- patch(prevVNode, nextVNode, container, contextVNode, isSVG)
- patched++
- } else if (!canRemoveWholeContent) {
- queueRemoveVNode(prevVNode, container)
- }
- } else if (!canRemoveWholeContent) {
- queueRemoveVNode(prevVNode, container)
- }
- }
- }
- // fast-path: if nothing patched remove all old and add all new
- if (canRemoveWholeContent) {
- queueRemoveChildren(prevChildren as MountedVNode[], container, endNode)
- mountArrayChildren(
- nextChildren,
- container,
- contextVNode,
- isSVG,
- endNode
- )
- } else {
- if (moved) {
- const seq = lis(sources)
- j = seq.length - 1
- for (i = nextLeft - 1; i >= 0; i--) {
- if (sources[i] === 0) {
- pos = i + nextStart
- nextVNode = getNextVNode(nextChildren, pos)
- nextPos = pos + 1
- mount(
- nextVNode,
- container,
- contextVNode,
- isSVG,
- nextPos < nextLength ? nextChildren[nextPos].el : endNode
- )
- } else if (j < 0 || i !== seq[j]) {
- pos = i + nextStart
- nextVNode = nextChildren[pos]
- nextPos = pos + 1
- insertVNode(
- nextVNode as MountedVNode,
- container,
- nextPos < nextLength ? nextChildren[nextPos].el : endNode
- )
- } else {
- j--
- }
- }
- } else if (patched !== nextLeft) {
- // when patched count doesn't match b length we need to insert those
- // new ones loop backwards so we can use insertBefore
- for (i = nextLeft - 1; i >= 0; i--) {
- if (sources[i] === 0) {
- pos = i + nextStart
- nextVNode = getNextVNode(nextChildren, pos)
- nextPos = pos + 1
- mount(
- nextVNode,
- container,
- contextVNode,
- isSVG,
- nextPos < nextLength ? nextChildren[nextPos].el : endNode
- )
- }
- }
- }
- }
+ patchChildren(n1, n2, container, fragmentAnchor, optimized)
}
}
- function insertVNode(
- vnode: MountedVNode,
- container: RenderNode,
- refNode: RenderNode | null
- ) {
- const { flags, childFlags, children } = vnode
- if (flags & VNodeFlags.FRAGMENT) {
- switch (childFlags) {
- case ChildrenFlags.SINGLE_VNODE:
- insertVNode(children as MountedVNode, container, refNode)
- break
- case ChildrenFlags.NO_CHILDREN:
- break
- default:
- for (let i = 0; i < (children as MountedVNode[]).length; i++) {
- insertVNode((children as MountedVNode[])[i], container, refNode)
- }
- }
- } else {
- queueInsertOrAppend(container, vnode.el as RenderNode, refNode)
- }
- }
+ function patchChildren(n1, n2, container, anchor, optimized) {
+ const c1 = n1 && n1.children
+ const c2 = n2.children
- // unmounting ----------------------------------------------------------------
-
- function unmount(vnode: MountedVNode) {
- const { flags, data, children, childFlags, ref, handle } = vnode
- const isElement = flags & VNodeFlags.ELEMENT
- if (isElement || flags & VNodeFlags.FRAGMENT) {
- if (isElement && data != null && data.vnodeBeforeUnmount) {
- data.vnodeBeforeUnmount(vnode)
- }
- unmountChildren(children as VNodeChildren, childFlags)
- if (isElement && data != null && data.vnodeUnmounted) {
- data.vnodeUnmounted(vnode)
- }
- } else if (flags & VNodeFlags.COMPONENT) {
- if (flags & VNodeFlags.COMPONENT_STATEFUL) {
- if (flags & VNodeFlags.COMPONENT_STATEFUL_SHOULD_KEEP_ALIVE) {
- deactivateComponentInstance(children as ComponentInstance)
- } else {
- unmountComponentInstance(children as ComponentInstance)
- }
- } else {
- // functional
- stopReactiveEffect((handle as FunctionalHandle).update)
- unmount(children as MountedVNode)
- }
- } else if (flags & VNodeFlags.PORTAL) {
- if (childFlags & ChildrenFlags.MULTIPLE_VNODES) {
- queueRemoveChildren(
- children as MountedVNode[],
- vnode.tag as RenderNode,
- null
- )
- } else if (childFlags === ChildrenFlags.SINGLE_VNODE) {
- queueRemoveVNode(children as MountedVNode, vnode.tag as RenderNode)
- }
- }
- if (ref) {
- ref(null)
- }
- }
-
- function unmountChildren(children: VNodeChildren, childFlags: ChildrenFlags) {
- if (childFlags & ChildrenFlags.MULTIPLE_VNODES) {
- unmountArrayChildren(children as MountedVNode[])
- } else if (childFlags === ChildrenFlags.SINGLE_VNODE) {
- unmount(children as MountedVNode)
- }
- }
-
- function unmountArrayChildren(children: MountedVNode[]) {
- for (let i = 0; i < children.length; i++) {
- unmount(children[i])
- }
- }
-
- function queueRemoveVNode(vnode: MountedVNode, container: RenderNode) {
- queueNodeOp([removeVNode, vnode, container])
- }
-
- function removeVNode(vnode: MountedVNode, container: RenderNode) {
- unmount(vnode)
- const { el, flags, children, childFlags } = vnode
- if (container && el) {
- if (flags & VNodeFlags.FRAGMENT) {
- switch (childFlags) {
- case ChildrenFlags.SINGLE_VNODE:
- removeVNode(children as MountedVNode, container)
- break
- case ChildrenFlags.NO_CHILDREN:
- platformRemoveChild(container, el)
- break
- default:
- for (let i = 0; i < (children as MountedVNode[]).length; i++) {
- removeVNode((children as MountedVNode[])[i], container)
- }
- }
- } else {
- platformRemoveChild(container, el)
- }
- ;(vnode as any).el = null
- }
- }
-
- function queueRemoveChildren(
- children: MountedVNode[],
- container: RenderNode,
- refNode: RenderNode | null
- ) {
- queueNodeOp([removeChildren, children, container, refNode])
- }
-
- function removeChildren(
- children: MountedVNode[],
- container: RenderNode,
- refNode: RenderNode | null
- ) {
- unmountArrayChildren(children)
- if (refNode === null) {
- platformClearContent(container)
- } else {
- for (let i = 0; i < children.length; i++) {
- removeVNode(children[i], container)
- }
- }
- }
-
- // Component lifecycle -------------------------------------------------------
-
- function mountComponentInstance(
- vnode: VNode,
- container: RenderNode | null,
- isSVG: boolean,
- endNode: RenderNode | null
- ): Function {
- if (__DEV__) {
- pushWarningContext(vnode)
- }
-
- // a vnode may already have an instance if this is a compat call with
- // new Vue()
- const instance = ((__COMPAT__ && vnode.children) ||
- (vnode.children = createComponentInstance(
- vnode as any
- ))) as ComponentInstance
-
- // inject platform-specific unmount to keep-alive container
- if ((vnode.tag as any)[KeepAliveSymbol] === true) {
- ;(instance as any).$unmount = unmountComponentInstance
- }
-
- const {
- $proxy,
- $options: { beforeMount, mounted, renderTracked, renderTriggered }
- } = instance
-
- instance.$forceUpdate = () => {
- queueJob(instance._update)
- }
-
- const effectOptions: ReactiveEffectOptions = {
- scheduler: queueJob
- }
-
- if (__DEV__) {
- if (renderTracked) {
- effectOptions.onTrack = event => {
- callLifecycleHookWithHandler(
- renderTracked,
- $proxy,
- ErrorTypes.RENDER_TRACKED,
- event
- )
- }
- }
- if (renderTriggered) {
- effectOptions.onTrigger = event => {
- callLifecycleHookWithHandler(
- renderTriggered,
- $proxy,
- ErrorTypes.RENDER_TRIGGERED,
- event
- )
- }
- }
- }
-
- instance._update = createReactiveEffect(() => {
- if (instance._unmounted) {
+ // fast path
+ const { patchFlag } = n2
+ if (patchFlag != null) {
+ if (patchFlag & KEYED) {
+ // this could be either fully-keyed or mixed (some keyed some not)
+ patchKeyedChildren(c1, c2, container, anchor, optimized)
+ return
+ } else if (patchFlag & UNKEYED) {
+ // unkeyed
+ patchUnkeyedChildren(c1, c2, container, anchor, optimized)
return
}
- if (instance._mounted) {
- updateComponentInstance(instance, isSVG)
- } else {
- if (beforeMount) {
- callLifecycleHookWithHandler(
- beforeMount,
- $proxy,
- ErrorTypes.BEFORE_MOUNT
- )
- }
+ }
- instance.$vnode = renderInstanceRoot(instance) as MountedVNode
-
- queuePostEffect(() => {
- vnode.el = instance.$vnode.el
- if (__COMPAT__) {
- // expose __vue__ for devtools
- ;(vnode.el as any).__vue__ = instance
- }
- if (vnode.ref) {
- vnode.ref($proxy)
- }
- if (mounted) {
- callLifecycleHookWithHandler(mounted, $proxy, ErrorTypes.MOUNTED)
- }
- instance._mounted = true
- })
-
- mount(instance.$vnode, container, vnode as MountedVNode, isSVG, endNode)
- }
- }, effectOptions)
-
- if (__DEV__) {
- popWarningContext()
- }
-
- // cleanup if mount is invalidated before committed
- return () => {
- teardownComponentInstance(instance)
- }
- }
-
- function updateComponentInstance(
- instance: ComponentInstance,
- isSVG: boolean
- ) {
- if (__DEV__ && instance.$parentVNode) {
- pushWarningContext(instance.$parentVNode as VNode)
- }
-
- const {
- $vnode: prevVNode,
- $parentVNode,
- $proxy,
- $options: { beforeUpdate }
- } = instance
- if (beforeUpdate) {
- callLifecycleHookWithHandler(
- beforeUpdate,
- $proxy,
- ErrorTypes.BEFORE_UPDATE,
- prevVNode
- )
- }
-
- const nextVNode = renderInstanceRoot(instance) as MountedVNode
-
- queuePostEffect(() => {
- instance.$vnode = nextVNode
- const el = nextVNode.el as RenderNode
- if (__COMPAT__) {
- // expose __vue__ for devtools
- ;(el as any).__vue__ = instance
- }
- // recursively update contextVNode el for nested HOCs
- if ((nextVNode.flags & VNodeFlags.PORTAL) === 0) {
- let vnode = $parentVNode
- while (vnode !== null) {
- if ((vnode.flags & VNodeFlags.COMPONENT) > 0) {
- vnode.el = el
- }
- vnode = vnode.contextVNode
- }
- }
- const { updated } = instance.$options
- if (updated) {
- callLifecycleHookWithHandler(
- updated,
- $proxy,
- ErrorTypes.UPDATED,
- nextVNode
- )
- }
-
- // TODO fix me
- // if (vnodeUpdatedHooks.length > 0) {
- // const vnodeUpdatedHooksForCurrentInstance = vnodeUpdatedHooks.slice()
- // vnodeUpdatedHooks.length = 0
- // for (let i = 0; i < vnodeUpdatedHooksForCurrentInstance.length; i++) {
- // vnodeUpdatedHooksForCurrentInstance[i]()
- // }
- // }
- })
-
- const container = platformParentNode(prevVNode.el) as RenderNode
- patch(prevVNode, nextVNode, container, $parentVNode as MountedVNode, isSVG)
-
- if (__DEV__ && instance.$parentVNode) {
- popWarningContext()
- }
- }
-
- function unmountComponentInstance(instance: ComponentInstance) {
- if (instance._unmounted) {
- return
- }
- const {
- $vnode,
- $proxy,
- $options: { beforeUnmount, unmounted }
- } = instance
- if (beforeUnmount) {
- callLifecycleHookWithHandler(
- beforeUnmount,
- $proxy,
- ErrorTypes.BEFORE_UNMOUNT
- )
- }
- if ($vnode) {
- unmount($vnode)
- }
- teardownComponentInstance(instance)
- instance._unmounted = true
- if (unmounted) {
- callLifecycleHookWithHandler(unmounted, $proxy, ErrorTypes.UNMOUNTED)
- }
- }
-
- // Keep Alive ----------------------------------------------------------------
-
- function activateComponentInstance(
- vnode: VNode,
- container: RenderNode | null,
- endNode: RenderNode | null
- ) {
- if (__DEV__) {
- pushWarningContext(vnode)
- }
- const instance = vnode.children as ComponentInstance
- vnode.el = instance.$el as RenderNode
- if (container != null) {
- insertVNode(instance.$vnode, container, endNode)
- }
- if (__DEV__) {
- popWarningContext()
- }
- queuePostEffect(() => {
- callActivatedHook(instance, true)
- })
- }
-
- function callActivatedHook(instance: ComponentInstance, asRoot: boolean) {
- // 1. check if we are inside an inactive parent tree.
- if (asRoot) {
- instance._inactiveRoot = false
- if (isInInactiveTree(instance)) return
- }
- if (asRoot || !instance._inactiveRoot) {
- // 2. recursively call activated on child tree, depth-first
- const {
- $children,
- $proxy,
- $options: { activated }
- } = instance
- for (let i = 0; i < $children.length; i++) {
- callActivatedHook($children[i], false)
- }
- if (activated) {
- callLifecycleHookWithHandler(activated, $proxy, ErrorTypes.ACTIVATED)
- }
- }
- }
-
- function deactivateComponentInstance(instance: ComponentInstance) {
- callDeactivateHook(instance, true)
- }
-
- function callDeactivateHook(instance: ComponentInstance, asRoot: boolean) {
- if (asRoot) {
- instance._inactiveRoot = true
- if (isInInactiveTree(instance)) return
- }
- if (asRoot || !instance._inactiveRoot) {
- // 2. recursively call deactivated on child tree, depth-first
- const {
- $children,
- $proxy,
- $options: { deactivated }
- } = instance
- for (let i = 0; i < $children.length; i++) {
- callDeactivateHook($children[i], false)
- }
- if (deactivated) {
- callLifecycleHookWithHandler(
- deactivated,
- $proxy,
- ErrorTypes.DEACTIVATED
- )
- }
- }
- }
-
- function isInInactiveTree(instance: ComponentInstance): boolean {
- while ((instance = instance.$parent as any) !== null) {
- if (instance._inactiveRoot) return true
- }
- return false
- }
-
- // TODO hydrating ------------------------------------------------------------
-
- // API -----------------------------------------------------------------------
-
- function render(vnode: VNode | null, container: any) {
- const prevVNode = container.vnode
- if (vnode && vnode.el) {
- vnode = cloneVNode(vnode)
- }
- if (prevVNode == null) {
- if (vnode) {
- mount(vnode, container, null, false, null)
- container.vnode = vnode
+ if (typeof c2 === 'string') {
+ // text children fast path
+ if (Array.isArray(c1)) {
+ unmountChildren(c1, false)
}
+ hostSetElementText(container, c2)
} else {
- if (vnode) {
- patch(prevVNode, vnode, container, null, false)
- container.vnode = vnode
+ if (typeof c1 === 'string') {
+ hostSetElementText('')
+ mountChildren(c2, container, anchor)
} else {
- queueRemoveVNode(prevVNode, container)
- container.vnode = null
+ // two arrays, cannot assume anything, do full diff
+ patchKeyedChildren(c1, c2, container, anchor, optimized)
}
}
- if (__COMPAT__) {
- flushEffects()
- return vnode && vnode.flags & VNodeFlags.COMPONENT_STATEFUL
- ? (vnode.children as ComponentInstance).$proxy
- : null
+ }
+
+ function patchUnkeyedChildren(c1, c2, container, anchor, optimized) {
+ c1 = c1 || emptyArr
+ c2 = c2 || emptyArr
+ const oldLength = c1.length
+ const newLength = c2.length
+ const commonLength = Math.min(oldLength, newLength)
+ let i
+ for (i = 0; i < commonLength; i++) {
+ const nextChild = (c2[i] = normalizeChild(c2[i]))
+ patch(c1[i], nextChild, container, null, optimized)
+ }
+ if (oldLength > newLength) {
+ // remove old
+ unmountChildren(c1, commonLength, true)
} else {
- return nextTick(() => {
- return vnode && vnode.flags & VNodeFlags.COMPONENT_STATEFUL
- ? (vnode.children as ComponentInstance).$proxy
- : null
- })
+ // mount new
+ mountChildren(c2, container, anchor, commonLength)
}
}
- return { render }
-}
+ // can be all-keyed or mixed
+ function patchKeyedChildren(c1, c2, container, anchor, optimized) {
+ // TODO
+ patchUnkeyedChildren(c1, c2, container, anchor, optimized)
+ }
-// Utils -----------------------------------------------------------------------
-
-// retrieves a vnode from a children array, making sure to clone it if the
-// vnode is already mounted.
-function getNextVNode(ownerArray: VNode[], index: number): VNode {
- const vnode = ownerArray[index]
- return vnode.el === null ? vnode : (ownerArray[index] = cloneVNode(vnode))
-}
-
-// https://en.wikipedia.org/wiki/Longest_increasing_subsequence
-function lis(arr: number[]): number[] {
- const p = arr.slice()
- const result = [0]
- let i
- let j
- let u
- let v
- let c
- const len = arr.length
- for (i = 0; i < len; i++) {
- const arrI = arr[i]
- if (arrI !== 0) {
- j = result[result.length - 1]
- if (arr[j] < arrI) {
- p[i] = j
- result.push(i)
- continue
- }
- u = 0
- v = result.length - 1
- while (u < v) {
- c = ((u + v) / 2) | 0
- if (arr[result[c]] < arrI) {
- u = c + 1
- } else {
- v = c
- }
- }
- if (arrI < arr[result[u]]) {
- if (u > 0) {
- p[i] = result[u - 1]
- }
- result[u] = i
+ function unmount(vnode, doRemove) {
+ if (doRemove) {
+ if (vnode.type === Fragment) {
+ unmountChildren(vnode.children, 0, doRemove)
}
+ remove(vnode.el)
+ }
+ if (Array.isArray(vnode.children)) {
+ unmountChildren(vnode.children)
}
}
- u = result.length
- v = result[u - 1]
- while (u-- > 0) {
- result[u] = v
- v = p[v]
+
+ function unmountChildren(children, start = 0, doRemove) {
+ for (let i = start; i < children.length; i++) {
+ unmount(children[i], doRemove)
+ }
+ }
+
+ return function render(vnode, dom) {
+ patch(dom._vnode, vnode, dom)
+ return (dom._vnode = vnode)
}
- return result
}
diff --git a/packages/runtime-core/src/errorHandling.ts b/packages/runtime-core/src/errorHandling.ts
deleted file mode 100644
index 2a0abd81..00000000
--- a/packages/runtime-core/src/errorHandling.ts
+++ /dev/null
@@ -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 = {
- [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).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
- }
-}
diff --git a/packages/runtime-core/src/flags.ts b/packages/runtime-core/src/flags.ts
deleted file mode 100644
index 159bd9a6..00000000
--- a/packages/runtime-core/src/flags.ts
+++ /dev/null
@@ -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
-}
diff --git a/packages/runtime-core/src/h.ts b/packages/runtime-core/src/h.ts
index c2dc3be4..095191c9 100644
--- a/packages/runtime-core/src/h.ts
+++ b/packages/runtime-core/src/h.ts
@@ -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 {}
-type OptionsComponent =
- | (ComponentOptions
& { template: string })
- | (ComponentOptions
& { 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
-
(tag: OptionsComponent
, children?: RawChildrenType): VNode
-
(
- tag: OptionsComponent
,
- data?: (P & BuiltInProps & Differ) | null,
- children?: RawChildrenType | RawSlots
- ): VNode
- // functional
-
(tag: FunctionalComponent
, children?: RawChildrenType): VNode
-
(
- tag: FunctionalComponent
,
- data?: (P & BuiltInProps & Differ) | null,
- children?: RawChildrenType | RawSlots
- ): VNode
- // class
-
(tag: new () => Component
, children?: RawChildrenType): VNode
-
(
- tag: new () => Component
,
- 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
+}
diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts
index be2cb6cc..1ea0a177 100644
--- a/packages/runtime-core/src/index.ts
+++ b/packages/runtime-core/src/index.ts
@@ -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'
diff --git a/packages/runtime-core/src/optional/asyncComponent.ts b/packages/runtime-core/src/optional/asyncComponent.ts
deleted file mode 100644
index 61920571..00000000
--- a/packages/runtime-core/src/optional/asyncComponent.ts
+++ /dev/null
@@ -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
- 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
-}
diff --git a/packages/runtime-core/src/optional/await.ts b/packages/runtime-core/src/optional/await.ts
deleted file mode 100644
index e69de29b..00000000
diff --git a/packages/runtime-core/src/optional/directives.ts b/packages/runtime-core/src/optional/directives.ts
deleted file mode 100644
index ba09cee6..00000000
--- a/packages/runtime-core/src/optional/directives.ts
+++ /dev/null
@@ -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
-
-const valueCache = new WeakMap>()
-
-export function applyDirective(
- data: VNodeData,
- instance: ComponentInstance,
- directive: Directive,
- value?: any,
- arg?: string,
- modifiers?: DirectiveModifiers
-) {
- let valueCacheForDir = valueCache.get(directive) as WeakMap
- if (!valueCacheForDir) {
- valueCacheForDir = new WeakMap()
- 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
-}
diff --git a/packages/runtime-core/src/optional/eventEmitter.ts b/packages/runtime-core/src/optional/eventEmitter.ts
deleted file mode 100644
index 2fba5fc3..00000000
--- a/packages/runtime-core/src/optional/eventEmitter.ts
+++ /dev/null
@@ -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)
- }
-}
diff --git a/packages/runtime-core/src/optional/keepAlive.ts b/packages/runtime-core/src/optional/keepAlive.ts
deleted file mode 100644
index 83595883..00000000
--- a/packages/runtime-core/src/optional/keepAlive.ts
+++ /dev/null
@@ -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
-
-export const KeepAliveSymbol = Symbol()
-
-export class KeepAlive extends Component {
- private cache: Cache
- private keys: Set
-
- 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
-}
diff --git a/packages/runtime-core/src/optional/memoize.ts b/packages/runtime-core/src/optional/memoize.ts
deleted file mode 100644
index d1a77b63..00000000
--- a/packages/runtime-core/src/optional/memoize.ts
+++ /dev/null
@@ -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(
- 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
- }
-}
diff --git a/packages/runtime-core/src/optional/mixins.ts b/packages/runtime-core/src/optional/mixins.ts
deleted file mode 100644
index 86eb6cbb..00000000
--- a/packages/runtime-core/src/optional/mixins.ts
+++ /dev/null
@@ -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 {
- new (): This
-}
-
-interface ComponentConstructorWithMixins {
- new (): This & { $data: D } & D & { $props: Readonly
} & P
-}
-
-// mind = blown
-// https://stackoverflow.com/questions/50374908/transform-union-type-to-intersection-type
-type UnionToIntersection = (U extends any
- ? (k: U) => void
- : never) extends ((k: infer I) => void)
- ? I
- : never
-
-type ExtractInstance = T extends (infer U)[]
- ? UnionToIntersection ? V : never>
- : never
-
-export function mixins<
- T extends ComponentConstructor[] = [],
- V = ExtractInstance
->(...args: T): ComponentConstructorWithMixins
-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()
- }
-}
-
-*/
diff --git a/packages/runtime-core/src/patchFlags.ts b/packages/runtime-core/src/patchFlags.ts
new file mode 100644
index 00000000..5d6a0b7f
--- /dev/null
+++ b/packages/runtime-core/src/patchFlags.ts
@@ -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
diff --git a/packages/runtime-core/src/vdom.ts b/packages/runtime-core/src/vdom.ts
deleted file mode 100644
index f0fd2282..00000000
--- a/packages/runtime-core/src/vdom.ts
+++ /dev/null
@@ -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 | void {
- if (isArray(value)) {
- const res: Record = {}
- 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 + '')]
- }
-}
diff --git a/packages/runtime-core/src/warning.ts b/packages/runtime-core/src/warning.ts
deleted file mode 100644
index 4bc22a06..00000000
--- a/packages/runtime-core/src/warning.ts
+++ /dev/null
@@ -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
-}