--- /dev/null
+import {
+ ComponentOptions,
+ Component,
+ ComponentRenderProxy,
+ Data,
+ ComponentInstance
+} from './component'
+import { Directive } from './directives'
+import { HostNode, RootRenderFunction } from './createRenderer'
+import { InjectionKey } from './apiInject'
+import { isFunction } from '@vue/shared'
+import { warn } from './warning'
+import { createVNode } from './vnode'
+
+export interface App {
+ config: AppConfig
+ use(plugin: Plugin, options?: any): this
+ mixin(mixin: ComponentOptions): this
+ component(name: string): Component | undefined
+ component(name: string, component: Component): this
+ directive(name: string): Directive | undefined
+ directive(name: string, directive: Directive): this
+ mount(
+ rootComponent: Component,
+ rootContainer: string | HostNode,
+ rootProps?: Data
+ ): ComponentRenderProxy
+ provide<T>(key: InjectionKey<T> | string, value: T): void
+}
+
+export interface AppConfig {
+ silent: boolean
+ devtools: boolean
+ performance: boolean
+ errorHandler?: (
+ err: Error,
+ instance: ComponentRenderProxy,
+ info: string
+ ) => void
+ warnHandler?: (
+ msg: string,
+ instance: ComponentRenderProxy,
+ trace: string
+ ) => void
+ ignoredElements: Array<string | RegExp>
+ keyCodes: Record<string, number | number[]>
+ optionMergeStrategies: {
+ [key: string]: (
+ parent: any,
+ child: any,
+ instance: ComponentRenderProxy
+ ) => any
+ }
+}
+
+export interface AppContext {
+ config: AppConfig
+ mixins: ComponentOptions[]
+ components: Record<string, Component>
+ directives: Record<string, Directive>
+ provides: Record<string | symbol, any>
+}
+
+type PluginInstallFunction = (app: App) => any
+
+type Plugin =
+ | PluginInstallFunction
+ | {
+ install: PluginInstallFunction
+ }
+
+export function createAppContext(): AppContext {
+ return {
+ config: {
+ silent: false,
+ devtools: true,
+ performance: false,
+ errorHandler: undefined,
+ warnHandler: undefined,
+ ignoredElements: [],
+ keyCodes: {},
+ optionMergeStrategies: {}
+ },
+ mixins: [],
+ components: {},
+ directives: {},
+ provides: {}
+ }
+}
+
+export function createAppAPI(render: RootRenderFunction): () => App {
+ return function createApp(): App {
+ const context = createAppContext()
+
+ const app: App = {
+ get config() {
+ return context.config
+ },
+
+ set config(v) {
+ warn(
+ `app.config cannot be replaced. Modify individual options instead.`
+ )
+ },
+
+ use(plugin: Plugin) {
+ if (isFunction(plugin)) {
+ plugin(app)
+ } else if (isFunction(plugin.install)) {
+ plugin.install(app)
+ } else if (__DEV__) {
+ warn(
+ `A plugin must either be a function or an object with an "install" ` +
+ `function.`
+ )
+ }
+ return app
+ },
+
+ mixin(mixin: ComponentOptions) {
+ context.mixins.push(mixin)
+ return app
+ },
+
+ component(name: string, component?: Component) {
+ // TODO component name validation
+ if (!component) {
+ return context.components[name] as any
+ } else {
+ context.components[name] = component
+ return app
+ }
+ },
+
+ directive(name: string, directive?: Directive) {
+ // TODO directive name validation
+ if (!directive) {
+ return context.directives[name] as any
+ } else {
+ context.directives[name] = directive
+ return app
+ }
+ },
+
+ mount(rootComponent, rootContainer, rootProps?: Data) {
+ const vnode = createVNode(rootComponent, rootProps)
+ // store app context on the root VNode.
+ // this will be set on the root instance on initial mount.
+ vnode.appContext = context
+ render(vnode, rootContainer)
+ return (vnode.component as ComponentInstance)
+ .renderProxy as ComponentRenderProxy
+ },
+
+ provide(key, value) {
+ if (__DEV__ && key in context.provides) {
+ warn(
+ `App already provides property with key "${key}". ` +
+ `It will be overwritten with the new value.`
+ )
+ }
+ context.provides[key as any] = value
+ }
+ }
+
+ return app
+ }
+}
callWithErrorHandling,
callWithAsyncErrorHandling
} from './errorHandling'
+import { AppContext, createAppContext } from './apiCreateApp'
export type Data = { [key: string]: unknown }
displayName?: string
}
+export type Component = ComponentOptions | FunctionalComponent
+
type LifecycleHook = Function[] | null
export const enum LifecycleHooks {
export type ComponentInstance<P = Data, S = Data> = {
type: FunctionalComponent | ComponentOptions
parent: ComponentInstance | null
+ appContext: AppContext
root: ComponentInstance
vnode: VNode
next: VNode | null
return isFunction(options) ? { setup: options } : (options as any)
}
+const emptyAppContext = createAppContext()
+
export function createComponentInstance(
vnode: VNode,
parent: ComponentInstance | null
const instance = {
vnode,
parent,
+ // inherit parent app context - or - if root, adopt from root vnode
+ appContext:
+ (parent ? parent.appContext : vnode.appContext) || emptyAppContext,
type: vnode.type as any,
root: null as any, // set later so it can point to itself
next: null,
querySelector(selector: string): HostNode | null
}
-export function createRenderer(options: RendererOptions) {
+export type RootRenderFunction = (
+ vnode: VNode | null,
+ dom: HostNode | string
+) => void
+
+export function createRenderer(options: RendererOptions): RootRenderFunction {
const {
insert: hostInsert,
remove: hostRemove,
}
}
- return function render(vnode: VNode | null, dom: HostNode): VNode | null {
+ return function render(vnode: VNode | null, dom: HostNode | string) {
+ if (isString(dom)) {
+ if (isFunction(hostQuerySelector)) {
+ dom = hostQuerySelector(dom)
+ if (!dom) {
+ if (__DEV__) {
+ warn(
+ `Failed to locate root container: ` +
+ `querySelector returned null.`
+ )
+ }
+ return
+ }
+ } else {
+ if (__DEV__) {
+ warn(
+ `Failed to locate root container: ` +
+ `target platform does not support querySelector.`
+ )
+ }
+ return
+ }
+ }
if (vnode == null) {
+ debugger
if (dom._vnode) {
unmount(dom._vnode, null, true)
}
patch(dom._vnode, vnode, dom)
}
flushPostFlushCbs()
- return (dom._vnode = vnode)
+ dom._vnode = vnode
}
}
ComponentRenderProxy
} from './component'
import { callWithAsyncErrorHandling, ErrorTypes } from './errorHandling'
+import { HostNode } from './createRenderer'
export interface DirectiveBinding {
instance: ComponentRenderProxy | null
}
export type DirectiveHook = (
- el: any,
+ el: HostNode,
binding: DirectiveBinding,
vnode: VNode,
prevVNode: VNode | null
export { getCurrentInstance } from './component'
// For custom renderers
+export { createAppAPI } from './apiCreateApp'
export { createRenderer } from './createRenderer'
export {
handleError,
import { PatchFlags } from './patchFlags'
import { ShapeFlags } from './shapeFlags'
import { isReactive } from '@vue/reactivity'
+import { AppContext } from './apiCreateApp'
export const Fragment = Symbol('Fragment')
export const Text = Symbol('Text')
patchFlag: number
dynamicProps: string[] | null
dynamicChildren: VNode[] | null
+
+ // application root node only
+ appContext: AppContext | null
}
// Since v-if and v-for are the two possible ways node structure can dynamically
shapeFlag,
patchFlag,
dynamicProps,
- dynamicChildren: null
+ dynamicChildren: null,
+ appContext: null
}
normalizeChildren(vnode, children)
patchFlag: vnode.patchFlag,
dynamicProps: vnode.dynamicProps,
dynamicChildren: vnode.dynamicChildren,
+ appContext: vnode.appContext,
// these should be set to null since they should only be present on
// mounted VNodes. If they are somehow not null, this means we have
-import { createRenderer, VNode } from '@vue/runtime-core'
+import { createRenderer, VNode, createAppAPI } from '@vue/runtime-core'
import { nodeOps } from './nodeOps'
import { patchProp } from './patchProp'
export const render = createRenderer({
patchProp,
...nodeOps
-}) as (vnode: VNode | null, container: HTMLElement) => VNode
+}) as (vnode: VNode | null, container: HTMLElement) => void
+
+export const createApp = createAppAPI(render)
// re-export everything from core
// h, Component, reactivity API, nextTick, flags & types
-import { createRenderer, VNode } from '@vue/runtime-core'
+import { createRenderer, VNode, createAppAPI } from '@vue/runtime-core'
import { nodeOps, TestElement } from './nodeOps'
import { patchProp } from './patchProp'
import { serializeInner } from './serialize'
export const render = createRenderer({
patchProp,
...nodeOps
-}) as (node: VNode | null, container: TestElement) => VNode
+}) as (node: VNode | null, container: TestElement) => void
+
+export const createApp = createAppAPI(render)
// convenience for one-off render validations
export function renderToString(vnode: VNode) {