跳到主要内容

官方实现

2024年03月15日
柏拉文
越努力,越幸运

一、packages/runtime-dom/src/index.ts createApp()


export const createApp = ((...args) => {
const app = ensureRenderer().createApp(...args)

if (__DEV__) {
injectNativeTagCheck(app)
injectCompilerOptionsCheck(app)
}

const { mount } = app
app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
const container = normalizeContainer(containerOrSelector)
if (!container) return

const component = app._component
if (!isFunction(component) && !component.render && !component.template) {
// __UNSAFE__
// Reason: potential execution of JS expressions in in-DOM template.
// The user must make sure the in-DOM template is trusted. If it's
// rendered by the server, the template should not contain any user data.
component.template = container.innerHTML
// 2.x compat check
if (__COMPAT__ && __DEV__) {
for (let i = 0; i < container.attributes.length; i++) {
const attr = container.attributes[i]
if (attr.name !== 'v-cloak' && /^(v-|:|@)/.test(attr.name)) {
compatUtils.warnDeprecation(
DeprecationTypes.GLOBAL_MOUNT_CONTAINER,
null
)
break
}
}
}
}

// clear content before mounting
container.innerHTML = ''
const proxy = mount(container, false, container instanceof SVGElement)
if (container instanceof Element) {
container.removeAttribute('v-cloak')
container.setAttribute('data-v-app', '')
}
return proxy
}

return app
}) as CreateAppFunction<Element>

二、packages/runtime-dom/src/index.ts ensureRenderer()


function ensureRenderer() {
return (
renderer ||
(renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions))
)
}

三、packages/runtime-core/src/renderer.ts createRenderer()


export function createRenderer<
HostNode = RendererNode,
HostElement = RendererElement
>(options: RendererOptions<HostNode, HostElement>) {
return baseCreateRenderer<HostNode, HostElement>(options)
}

四、packages/runtime-core/src/renderer.ts baseCreateRenderer()


function baseCreateRenderer(
options: RendererOptions,
createHydrationFns?: typeof createHydrationFunctions
): any {
// compile-time feature flags check
if (__ESM_BUNDLER__ && !__TEST__) {
initFeatureFlags()
}

const target = getGlobalThis()
target.__VUE__ = true
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
setDevtoolsHook(target.__VUE_DEVTOOLS_GLOBAL_HOOK__, target)
}

const {
insert: hostInsert,
remove: hostRemove,
patchProp: hostPatchProp,
createElement: hostCreateElement,
createText: hostCreateText,
createComment: hostCreateComment,
setText: hostSetText,
setElementText: hostSetElementText,
parentNode: hostParentNode,
nextSibling: hostNextSibling,
setScopeId: hostSetScopeId = NOOP,
insertStaticContent: hostInsertStaticContent
} = options

// Note: functions inside this closure should use `const xxx = () => {}`
// style in order to prevent being inlined by minifiers.
const patch: PatchFn = (
) => {
}

const processText: ProcessTextOrCommentFn = (n1, n2, container, anchor) => {
}

const processCommentNode: ProcessTextOrCommentFn = (
) => {
}

const mountStaticNode = (
) => {
}

/**
* Dev / HMR only
*/
const patchStaticNode = (
) => {
}

const moveStaticNode = (
) => {
}

const processElement = (
) => {
}

const mountElement = (
) => {
}

const setScopeId = (
) => {
}

const mountChildren: MountChildrenFn = (
) => {
}

const patchElement = (
) => {
}

// The fast path for blocks.
const patchBlockChildren: PatchBlockChildrenFn = (
) => {
}

const processFragment = (
) => {
}

const processComponent = (
) => {
}

const mountComponent: MountComponentFn = (
) => {
}

const updateComponent = (n1: VNode, n2: VNode, optimized: boolean) => {
}

const setupRenderEffect: SetupRenderEffectFn = (
) => {
}

const updateComponentPreRender = (
) => {
}

const patchChildren: PatchChildrenFn = (
) => {
}

const patchUnkeyedChildren = (
) => {
}

// can be all-keyed or mixed
const patchKeyedChildren = (
) => {
}

const move: MoveFn = (
) => {
}

const unmount: UnmountFn = (
) => {
}

const remove: RemoveFn = vnode => {
}

const removeFragment = (cur: RendererNode, end: RendererNode) => {
}

const unmountComponent = (
) => {
}

const unmountChildren: UnmountChildrenFn = (

) => {
}

const getNextHostNode: NextFn = vnode => {}

const render: RootRenderFunction = (vnode, container, isSVG) => {
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null, null, true)
}
} else {
patch(container._vnode || null, vnode, container, null, null, null, isSVG)
}
flushPreFlushCbs()
flushPostFlushCbs()
container._vnode = vnode
}

return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
}
}

五、packages/runtime-core/src/apiCreateApp.ts createAppAPI()


export function createAppAPI<HostElement>(
render: RootRenderFunction<HostElement>,
hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
return function createApp(rootComponent, rootProps = null) {
if (!isFunction(rootComponent)) {
rootComponent = extend({}, rootComponent)
}

if (rootProps != null && !isObject(rootProps)) {
__DEV__ && warn(`root props passed to app.mount() must be an object.`)
rootProps = null
}

const context = createAppContext()

// TODO remove in 3.4
if (__DEV__) {
Object.defineProperty(context.config, 'unwrapInjectedRef', {
get() {
return true
},
set() {
warn(
`app.config.unwrapInjectedRef has been deprecated. ` +
`3.3 now always unwraps injected refs in Options API.`
)
}
})
}

const installedPlugins = new Set()

let isMounted = false

const app: App = (context.app = {
_uid: uid++,
_component: rootComponent as ConcreteComponent,
_props: rootProps,
_container: null,
_context: context,
_instance: null,

version,

get config() {
return context.config
},

set config(v) {
if (__DEV__) {
warn(
`app.config cannot be replaced. Modify individual options instead.`
)
}
},

use(plugin: Plugin, ...options: any[]) {
if (installedPlugins.has(plugin)) {
__DEV__ && warn(`Plugin has already been applied to target app.`)
} else if (plugin && isFunction(plugin.install)) {
installedPlugins.add(plugin)
plugin.install(app, ...options)
} else if (isFunction(plugin)) {
installedPlugins.add(plugin)
plugin(app, ...options)
} else if (__DEV__) {
warn(
`A plugin must either be a function or an object with an "install" ` +
`function.`
)
}
return app
},

mixin(mixin: ComponentOptions) {
if (__FEATURE_OPTIONS_API__) {
if (!context.mixins.includes(mixin)) {
context.mixins.push(mixin)
} else if (__DEV__) {
warn(
'Mixin has already been applied to target app' +
(mixin.name ? `: ${mixin.name}` : '')
)
}
} else if (__DEV__) {
warn('Mixins are only available in builds supporting Options API')
}
return app
},

component(name: string, component?: Component): any {
if (__DEV__) {
validateComponentName(name, context.config)
}
if (!component) {
return context.components[name]
}
if (__DEV__ && context.components[name]) {
warn(`Component "${name}" has already been registered in target app.`)
}
context.components[name] = component
return app
},

directive(name: string, directive?: Directive) {
if (__DEV__) {
validateDirectiveName(name)
}

if (!directive) {
return context.directives[name] as any
}
if (__DEV__ && context.directives[name]) {
warn(`Directive "${name}" has already been registered in target app.`)
}
context.directives[name] = directive
return app
},

mount(
rootContainer: HostElement,
isHydrate?: boolean,
isSVG?: boolean
): any {
if (!isMounted) {
// #5571
if (__DEV__ && (rootContainer as any).__vue_app__) {
warn(
`There is already an app instance mounted on the host container.\n` +
` If you want to mount another app on the same host container,` +
` you need to unmount the previous app by calling \`app.unmount()\` first.`
)
}
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

// HMR root reload
if (__DEV__) {
context.reload = () => {
render(cloneVNode(vnode), rootContainer, isSVG)
}
}

if (isHydrate && hydrate) {
hydrate(vnode as VNode<Node, Element>, rootContainer as any)
} else {
render(vnode, rootContainer, isSVG)
}
isMounted = true
app._container = rootContainer
// for devtools and telemetry
;(rootContainer as any).__vue_app__ = app

if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
app._instance = vnode.component
devtoolsInitApp(app, version)
}

return getExposeProxy(vnode.component!) || vnode.component!.proxy
} else if (__DEV__) {
warn(
`App has already been mounted.\n` +
`If you want to remount the same app, move your app creation logic ` +
`into a factory function and create fresh app instances for each ` +
`mount - e.g. \`const createMyApp = () => createApp(App)\``
)
}
},

unmount() {
if (isMounted) {
render(null, app._container)
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
app._instance = null
devtoolsUnmountApp(app)
}
delete app._container.__vue_app__
} else if (__DEV__) {
warn(`Cannot unmount an app that is not mounted.`)
}
},

provide(key, value) {
if (__DEV__ && (key as string | symbol) in context.provides) {
warn(
`App already provides property with key "${String(key)}". ` +
`It will be overwritten with the new value.`
)
}

context.provides[key as string | symbol] = value

return app
},

runWithContext(fn) {
currentApp = app
try {
return fn()
} finally {
currentApp = null
}
}
})

if (__COMPAT__) {
installAppCompatProperties(app, context, render)
}

return app
}
}