feat: createApp / appContext
This commit is contained in:
parent
aac807bc63
commit
32713f8fce
168
packages/runtime-core/src/apiCreateApp.ts
Normal file
168
packages/runtime-core/src/apiCreateApp.ts
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,7 @@ import {
|
|||||||
callWithErrorHandling,
|
callWithErrorHandling,
|
||||||
callWithAsyncErrorHandling
|
callWithAsyncErrorHandling
|
||||||
} from './errorHandling'
|
} from './errorHandling'
|
||||||
|
import { AppContext, createAppContext } from './apiCreateApp'
|
||||||
|
|
||||||
export type Data = { [key: string]: unknown }
|
export type Data = { [key: string]: unknown }
|
||||||
|
|
||||||
@ -79,6 +80,8 @@ export interface FunctionalComponent<P = {}> {
|
|||||||
displayName?: string
|
displayName?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Component = ComponentOptions | FunctionalComponent
|
||||||
|
|
||||||
type LifecycleHook = Function[] | null
|
type LifecycleHook = Function[] | null
|
||||||
|
|
||||||
export const enum LifecycleHooks {
|
export const enum LifecycleHooks {
|
||||||
@ -107,6 +110,7 @@ interface SetupContext {
|
|||||||
export type ComponentInstance<P = Data, S = Data> = {
|
export type ComponentInstance<P = Data, S = Data> = {
|
||||||
type: FunctionalComponent | ComponentOptions
|
type: FunctionalComponent | ComponentOptions
|
||||||
parent: ComponentInstance | null
|
parent: ComponentInstance | null
|
||||||
|
appContext: AppContext
|
||||||
root: ComponentInstance
|
root: ComponentInstance
|
||||||
vnode: VNode
|
vnode: VNode
|
||||||
next: VNode | null
|
next: VNode | null
|
||||||
@ -184,6 +188,8 @@ export function createComponent(options: any) {
|
|||||||
return isFunction(options) ? { setup: options } : (options as any)
|
return isFunction(options) ? { setup: options } : (options as any)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const emptyAppContext = createAppContext()
|
||||||
|
|
||||||
export function createComponentInstance(
|
export function createComponentInstance(
|
||||||
vnode: VNode,
|
vnode: VNode,
|
||||||
parent: ComponentInstance | null
|
parent: ComponentInstance | null
|
||||||
@ -191,6 +197,9 @@ export function createComponentInstance(
|
|||||||
const instance = {
|
const instance = {
|
||||||
vnode,
|
vnode,
|
||||||
parent,
|
parent,
|
||||||
|
// inherit parent app context - or - if root, adopt from root vnode
|
||||||
|
appContext:
|
||||||
|
(parent ? parent.appContext : vnode.appContext) || emptyAppContext,
|
||||||
type: vnode.type as any,
|
type: vnode.type as any,
|
||||||
root: null as any, // set later so it can point to itself
|
root: null as any, // set later so it can point to itself
|
||||||
next: null,
|
next: null,
|
||||||
|
@ -93,7 +93,12 @@ export interface RendererOptions {
|
|||||||
querySelector(selector: string): HostNode | 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 {
|
const {
|
||||||
insert: hostInsert,
|
insert: hostInsert,
|
||||||
remove: hostRemove,
|
remove: hostRemove,
|
||||||
@ -1152,8 +1157,31 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
if (vnode == null) {
|
||||||
|
debugger
|
||||||
if (dom._vnode) {
|
if (dom._vnode) {
|
||||||
unmount(dom._vnode, null, true)
|
unmount(dom._vnode, null, true)
|
||||||
}
|
}
|
||||||
@ -1161,7 +1189,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
patch(dom._vnode, vnode, dom)
|
patch(dom._vnode, vnode, dom)
|
||||||
}
|
}
|
||||||
flushPostFlushCbs()
|
flushPostFlushCbs()
|
||||||
return (dom._vnode = vnode)
|
dom._vnode = vnode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ import {
|
|||||||
ComponentRenderProxy
|
ComponentRenderProxy
|
||||||
} from './component'
|
} from './component'
|
||||||
import { callWithAsyncErrorHandling, ErrorTypes } from './errorHandling'
|
import { callWithAsyncErrorHandling, ErrorTypes } from './errorHandling'
|
||||||
|
import { HostNode } from './createRenderer'
|
||||||
|
|
||||||
export interface DirectiveBinding {
|
export interface DirectiveBinding {
|
||||||
instance: ComponentRenderProxy | null
|
instance: ComponentRenderProxy | null
|
||||||
@ -31,7 +32,7 @@ export interface DirectiveBinding {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type DirectiveHook = (
|
export type DirectiveHook = (
|
||||||
el: any,
|
el: HostNode,
|
||||||
binding: DirectiveBinding,
|
binding: DirectiveBinding,
|
||||||
vnode: VNode,
|
vnode: VNode,
|
||||||
prevVNode: VNode | null
|
prevVNode: VNode | null
|
||||||
|
@ -28,6 +28,7 @@ export { PublicShapeFlags as ShapeFlags } from './shapeFlags'
|
|||||||
export { getCurrentInstance } from './component'
|
export { getCurrentInstance } from './component'
|
||||||
|
|
||||||
// For custom renderers
|
// For custom renderers
|
||||||
|
export { createAppAPI } from './apiCreateApp'
|
||||||
export { createRenderer } from './createRenderer'
|
export { createRenderer } from './createRenderer'
|
||||||
export {
|
export {
|
||||||
handleError,
|
handleError,
|
||||||
|
@ -12,6 +12,7 @@ import { RawSlots } from './componentSlots'
|
|||||||
import { PatchFlags } from './patchFlags'
|
import { PatchFlags } from './patchFlags'
|
||||||
import { ShapeFlags } from './shapeFlags'
|
import { ShapeFlags } from './shapeFlags'
|
||||||
import { isReactive } from '@vue/reactivity'
|
import { isReactive } from '@vue/reactivity'
|
||||||
|
import { AppContext } from './apiCreateApp'
|
||||||
|
|
||||||
export const Fragment = Symbol('Fragment')
|
export const Fragment = Symbol('Fragment')
|
||||||
export const Text = Symbol('Text')
|
export const Text = Symbol('Text')
|
||||||
@ -50,6 +51,9 @@ export interface VNode {
|
|||||||
patchFlag: number
|
patchFlag: number
|
||||||
dynamicProps: string[] | null
|
dynamicProps: string[] | null
|
||||||
dynamicChildren: VNode[] | 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
|
// Since v-if and v-for are the two possible ways node structure can dynamically
|
||||||
@ -152,7 +156,8 @@ export function createVNode(
|
|||||||
shapeFlag,
|
shapeFlag,
|
||||||
patchFlag,
|
patchFlag,
|
||||||
dynamicProps,
|
dynamicProps,
|
||||||
dynamicChildren: null
|
dynamicChildren: null,
|
||||||
|
appContext: null
|
||||||
}
|
}
|
||||||
|
|
||||||
normalizeChildren(vnode, children)
|
normalizeChildren(vnode, children)
|
||||||
@ -192,6 +197,7 @@ export function cloneVNode(vnode: VNode): VNode {
|
|||||||
patchFlag: vnode.patchFlag,
|
patchFlag: vnode.patchFlag,
|
||||||
dynamicProps: vnode.dynamicProps,
|
dynamicProps: vnode.dynamicProps,
|
||||||
dynamicChildren: vnode.dynamicChildren,
|
dynamicChildren: vnode.dynamicChildren,
|
||||||
|
appContext: vnode.appContext,
|
||||||
|
|
||||||
// these should be set to null since they should only be present on
|
// 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
|
// mounted VNodes. If they are somehow not null, this means we have
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import { createRenderer, VNode } from '@vue/runtime-core'
|
import { createRenderer, VNode, createAppAPI } from '@vue/runtime-core'
|
||||||
import { nodeOps } from './nodeOps'
|
import { nodeOps } from './nodeOps'
|
||||||
import { patchProp } from './patchProp'
|
import { patchProp } from './patchProp'
|
||||||
|
|
||||||
export const render = createRenderer({
|
export const render = createRenderer({
|
||||||
patchProp,
|
patchProp,
|
||||||
...nodeOps
|
...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
|
// re-export everything from core
|
||||||
// h, Component, reactivity API, nextTick, flags & types
|
// h, Component, reactivity API, nextTick, flags & types
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { createRenderer, VNode } from '@vue/runtime-core'
|
import { createRenderer, VNode, createAppAPI } from '@vue/runtime-core'
|
||||||
import { nodeOps, TestElement } from './nodeOps'
|
import { nodeOps, TestElement } from './nodeOps'
|
||||||
import { patchProp } from './patchProp'
|
import { patchProp } from './patchProp'
|
||||||
import { serializeInner } from './serialize'
|
import { serializeInner } from './serialize'
|
||||||
@ -6,7 +6,9 @@ import { serializeInner } from './serialize'
|
|||||||
export const render = createRenderer({
|
export const render = createRenderer({
|
||||||
patchProp,
|
patchProp,
|
||||||
...nodeOps
|
...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
|
// convenience for one-off render validations
|
||||||
export function renderToString(vnode: VNode) {
|
export function renderToString(vnode: VNode) {
|
||||||
|
Loading…
Reference in New Issue
Block a user