refactor: adjust createApp related API signatures

BREAKING CHANGE: `createApp` API has been adjusted.

  - `createApp()` now accepts the root component, and optionally a props
  object to pass to the root component.
  - `app.mount()` now accepts a single argument (the root container)
  - `app.unmount()` no longer requires arguments.

  New behavior looks like the following:

  ``` js
  const app = createApp(RootComponent)
  app.mount('#app')
  app.unmount()
  ```
This commit is contained in:
Evan You 2020-01-23 15:05:38 -05:00
parent eacd390992
commit c07751fd36
25 changed files with 276 additions and 326 deletions

View File

@ -30,18 +30,18 @@ describe('api: createApp', () => {
} }
const root1 = nodeOps.createElement('div') const root1 = nodeOps.createElement('div')
createApp().mount(Comp, root1) createApp(Comp).mount(root1)
expect(serializeInner(root1)).toBe(`0`) expect(serializeInner(root1)).toBe(`0`)
// mount with props // mount with props
const root2 = nodeOps.createElement('div') const root2 = nodeOps.createElement('div')
const app2 = createApp() const app2 = createApp(Comp, { count: 1 })
app2.mount(Comp, root2, { count: 1 }) app2.mount(root2)
expect(serializeInner(root2)).toBe(`1`) expect(serializeInner(root2)).toBe(`1`)
// remount warning // remount warning
const root3 = nodeOps.createElement('div') const root3 = nodeOps.createElement('div')
app2.mount(Comp, root3) app2.mount(root3)
expect(serializeInner(root3)).toBe(``) expect(serializeInner(root3)).toBe(``)
expect(`already been mounted`).toHaveBeenWarned() expect(`already been mounted`).toHaveBeenWarned()
}) })
@ -59,18 +59,14 @@ describe('api: createApp', () => {
} }
const root = nodeOps.createElement('div') const root = nodeOps.createElement('div')
const app = createApp() const app = createApp(Comp)
app.mount(Comp, root) app.mount(root)
app.unmount(root) app.unmount(root)
expect(serializeInner(root)).toBe(``) expect(serializeInner(root)).toBe(``)
}) })
test('provide', () => { test('provide', () => {
const app = createApp()
app.provide('foo', 1)
app.provide('bar', 2)
const Root = { const Root = {
setup() { setup() {
// test override // test override
@ -87,25 +83,16 @@ describe('api: createApp', () => {
} }
} }
const app = createApp(Root)
app.provide('foo', 1)
app.provide('bar', 2)
const root = nodeOps.createElement('div') const root = nodeOps.createElement('div')
app.mount(Root, root) app.mount(root)
expect(serializeInner(root)).toBe(`3,2`) expect(serializeInner(root)).toBe(`3,2`)
}) })
test('component', () => { test('component', () => {
const app = createApp()
const FooBar = () => 'foobar!'
app.component('FooBar', FooBar)
expect(app.component('FooBar')).toBe(FooBar)
app.component('BarBaz', () => 'barbaz!')
app.component('BarBaz', () => 'barbaz!')
expect(
'Component "BarBaz" has already been registered in target app.'
).toHaveBeenWarnedTimes(1)
const Root = { const Root = {
// local override // local override
components: { components: {
@ -122,33 +109,29 @@ describe('api: createApp', () => {
} }
} }
const app = createApp(Root)
const FooBar = () => 'foobar!'
app.component('FooBar', FooBar)
expect(app.component('FooBar')).toBe(FooBar)
app.component('BarBaz', () => 'barbaz!')
app.component('BarBaz', () => 'barbaz!')
expect(
'Component "BarBaz" has already been registered in target app.'
).toHaveBeenWarnedTimes(1)
const root = nodeOps.createElement('div') const root = nodeOps.createElement('div')
app.mount(Root, root) app.mount(root)
expect(serializeInner(root)).toBe(`<div>foobar!barbaz-local!</div>`) expect(serializeInner(root)).toBe(`<div>foobar!barbaz-local!</div>`)
}) })
test('directive', () => { test('directive', () => {
const app = createApp()
const spy1 = jest.fn() const spy1 = jest.fn()
const spy2 = jest.fn() const spy2 = jest.fn()
const spy3 = jest.fn() const spy3 = jest.fn()
const FooBar = { mounted: spy1 }
app.directive('FooBar', FooBar)
expect(app.directive('FooBar')).toBe(FooBar)
app.directive('BarBaz', {
mounted: spy2
})
app.directive('BarBaz', {
mounted: spy2
})
expect(
'Directive "BarBaz" has already been registered in target app.'
).toHaveBeenWarnedTimes(1)
const Root = { const Root = {
// local override // local override
directives: { directives: {
@ -165,8 +148,25 @@ describe('api: createApp', () => {
} }
} }
const app = createApp(Root)
const FooBar = { mounted: spy1 }
app.directive('FooBar', FooBar)
expect(app.directive('FooBar')).toBe(FooBar)
app.directive('BarBaz', {
mounted: spy2
})
app.directive('BarBaz', {
mounted: spy2
})
expect(
'Directive "BarBaz" has already been registered in target app.'
).toHaveBeenWarnedTimes(1)
const root = nodeOps.createElement('div') const root = nodeOps.createElement('div')
app.mount(Root, root) app.mount(root)
expect(spy1).toHaveBeenCalled() expect(spy1).toHaveBeenCalled()
expect(spy2).not.toHaveBeenCalled() expect(spy2).not.toHaveBeenCalled()
expect(spy3).toHaveBeenCalled() expect(spy3).toHaveBeenCalled()
@ -232,7 +232,7 @@ describe('api: createApp', () => {
} }
} }
const app = createApp() const app = createApp(Comp)
app.mixin(mixinA) app.mixin(mixinA)
app.mixin(mixinB) app.mixin(mixinB)
@ -246,7 +246,7 @@ describe('api: createApp', () => {
).toHaveBeenWarnedTimes(1) ).toHaveBeenWarnedTimes(1)
const root = nodeOps.createElement('div') const root = nodeOps.createElement('div')
app.mount(Comp, root) app.mount(root)
expect(serializeInner(root)).toBe(`123`) expect(serializeInner(root)).toBe(`123`)
expect(calls).toEqual([ expect(calls).toEqual([
@ -272,11 +272,6 @@ describe('api: createApp', () => {
} }
const PluginD: any = undefined const PluginD: any = undefined
const app = createApp()
app.use(PluginA)
app.use(PluginB, 1, 1)
app.use(PluginC)
const Root = { const Root = {
setup() { setup() {
const foo = inject('foo') const foo = inject('foo')
@ -284,8 +279,14 @@ describe('api: createApp', () => {
return () => `${foo},${bar}` return () => `${foo},${bar}`
} }
} }
const app = createApp(Root)
app.use(PluginA)
app.use(PluginB, 1, 1)
app.use(PluginC)
const root = nodeOps.createElement('div') const root = nodeOps.createElement('div')
app.mount(Root, root) app.mount(root)
expect(serializeInner(root)).toBe(`1,2`) expect(serializeInner(root)).toBe(`1,2`)
app.use(PluginA) app.use(PluginA)
@ -301,18 +302,14 @@ describe('api: createApp', () => {
}) })
test('config.errorHandler', () => { test('config.errorHandler', () => {
const app = createApp()
const error = new Error() const error = new Error()
const count = ref(0) const count = ref(0)
const handler = (app.config.errorHandler = jest.fn( const handler = jest.fn((err, instance, info) => {
(err, instance, info) => {
expect(err).toBe(error) expect(err).toBe(error)
expect((instance as any).count).toBe(count.value) expect((instance as any).count).toBe(count.value)
expect(info).toBe(`render function`) expect(info).toBe(`render function`)
} })
))
const Root = { const Root = {
setup() { setup() {
@ -326,21 +323,19 @@ describe('api: createApp', () => {
} }
} }
app.mount(Root, nodeOps.createElement('div')) const app = createApp(Root)
app.config.errorHandler = handler
app.mount(nodeOps.createElement('div'))
expect(handler).toHaveBeenCalled() expect(handler).toHaveBeenCalled()
}) })
test('config.warnHandler', () => { test('config.warnHandler', () => {
const app = createApp()
let ctx: any let ctx: any
const handler = jest.fn((msg, instance, trace) => {
const handler = (app.config.warnHandler = jest.fn(
(msg, instance, trace) => {
expect(msg).toMatch(`Component is missing template or render function`) expect(msg).toMatch(`Component is missing template or render function`)
expect(instance).toBe(ctx.proxy) expect(instance).toBe(ctx.proxy)
expect(trace).toMatch(`Hello`) expect(trace).toMatch(`Hello`)
} })
))
const Root = { const Root = {
name: 'Hello', name: 'Hello',
@ -349,7 +344,9 @@ describe('api: createApp', () => {
} }
} }
app.mount(Root, nodeOps.createElement('div')) const app = createApp(Root)
app.config.warnHandler = handler
app.mount(nodeOps.createElement('div'))
expect(handler).toHaveBeenCalledTimes(1) expect(handler).toHaveBeenCalledTimes(1)
}) })
@ -357,104 +354,82 @@ describe('api: createApp', () => {
const isNativeTag = jest.fn(tag => tag === 'div') const isNativeTag = jest.fn(tag => tag === 'div')
test('Component.name', () => { test('Component.name', () => {
const app = createApp()
Object.defineProperty(app.config, 'isNativeTag', {
value: isNativeTag,
writable: false
})
const Root = { const Root = {
name: 'div', name: 'div',
setup() {
return {
count: ref(0)
}
},
render() { render() {
return null return null
} }
} }
app.mount(Root, nodeOps.createElement('div')) const app = createApp(Root)
Object.defineProperty(app.config, 'isNativeTag', {
value: isNativeTag,
writable: false
})
app.mount(nodeOps.createElement('div'))
expect( expect(
`Do not use built-in or reserved HTML elements as component id: div` `Do not use built-in or reserved HTML elements as component id: div`
).toHaveBeenWarned() ).toHaveBeenWarned()
}) })
test('Component.components', () => { test('Component.components', () => {
const app = createApp()
Object.defineProperty(app.config, 'isNativeTag', {
value: isNativeTag,
writable: false
})
const Root = { const Root = {
components: { components: {
div: () => 'div' div: () => 'div'
}, },
setup() {
return {
count: ref(0)
}
},
render() { render() {
return null return null
} }
} }
app.mount(Root, nodeOps.createElement('div')) const app = createApp(Root)
Object.defineProperty(app.config, 'isNativeTag', {
value: isNativeTag,
writable: false
})
app.mount(nodeOps.createElement('div'))
expect( expect(
`Do not use built-in or reserved HTML elements as component id: div` `Do not use built-in or reserved HTML elements as component id: div`
).toHaveBeenWarned() ).toHaveBeenWarned()
}) })
test('Component.directives', () => { test('Component.directives', () => {
const app = createApp()
Object.defineProperty(app.config, 'isNativeTag', {
value: isNativeTag,
writable: false
})
const Root = { const Root = {
directives: { directives: {
bind: () => {} bind: () => {}
}, },
setup() {
return {
count: ref(0)
}
},
render() { render() {
return null return null
} }
} }
app.mount(Root, nodeOps.createElement('div')) const app = createApp(Root)
Object.defineProperty(app.config, 'isNativeTag', {
value: isNativeTag,
writable: false
})
app.mount(nodeOps.createElement('div'))
expect( expect(
`Do not use built-in directive ids as custom directive id: bind` `Do not use built-in directive ids as custom directive id: bind`
).toHaveBeenWarned() ).toHaveBeenWarned()
}) })
test('register using app.component', () => { test('register using app.component', () => {
const app = createApp() const app = createApp({
render() {}
})
Object.defineProperty(app.config, 'isNativeTag', { Object.defineProperty(app.config, 'isNativeTag', {
value: isNativeTag, value: isNativeTag,
writable: false writable: false
}) })
const Root = {
setup() {
return {
count: ref(0)
}
},
render() {
return null
}
}
app.component('div', () => 'div') app.component('div', () => 'div')
app.mount(Root, nodeOps.createElement('div')) app.mount(nodeOps.createElement('div'))
expect( expect(
`Do not use built-in or reserved HTML elements as component id: div` `Do not use built-in or reserved HTML elements as component id: div`
).toHaveBeenWarned() ).toHaveBeenWarned()

View File

@ -1,4 +1,4 @@
import { createApp, getCurrentInstance, nodeOps } from '@vue/runtime-test' import { h, render, getCurrentInstance, nodeOps } from '@vue/runtime-test'
import { mockWarn } from '@vue/shared' import { mockWarn } from '@vue/shared'
import { ComponentInternalInstance } from '../src/component' import { ComponentInternalInstance } from '../src/component'
@ -6,7 +6,6 @@ describe('component: proxy', () => {
mockWarn() mockWarn()
test('data', () => { test('data', () => {
const app = createApp()
let instance: ComponentInternalInstance let instance: ComponentInternalInstance
let instanceProxy: any let instanceProxy: any
const Comp = { const Comp = {
@ -23,14 +22,13 @@ describe('component: proxy', () => {
return null return null
} }
} }
app.mount(Comp, nodeOps.createElement('div')) render(h(Comp), nodeOps.createElement('div'))
expect(instanceProxy.foo).toBe(1) expect(instanceProxy.foo).toBe(1)
instanceProxy.foo = 2 instanceProxy.foo = 2
expect(instance!.data.foo).toBe(2) expect(instance!.data.foo).toBe(2)
}) })
test('renderContext', () => { test('renderContext', () => {
const app = createApp()
let instance: ComponentInternalInstance let instance: ComponentInternalInstance
let instanceProxy: any let instanceProxy: any
const Comp = { const Comp = {
@ -47,14 +45,13 @@ describe('component: proxy', () => {
return null return null
} }
} }
app.mount(Comp, nodeOps.createElement('div')) render(h(Comp), nodeOps.createElement('div'))
expect(instanceProxy.foo).toBe(1) expect(instanceProxy.foo).toBe(1)
instanceProxy.foo = 2 instanceProxy.foo = 2
expect(instance!.renderContext.foo).toBe(2) expect(instance!.renderContext.foo).toBe(2)
}) })
test('propsProxy', () => { test('propsProxy', () => {
const app = createApp()
let instance: ComponentInternalInstance let instance: ComponentInternalInstance
let instanceProxy: any let instanceProxy: any
const Comp = { const Comp = {
@ -72,7 +69,7 @@ describe('component: proxy', () => {
instanceProxy = this instanceProxy = this
} }
} }
app.mount(Comp, nodeOps.createElement('div')) render(h(Comp), nodeOps.createElement('div'))
expect(instanceProxy.foo).toBe(1) expect(instanceProxy.foo).toBe(1)
expect(instance!.propsProxy!.foo).toBe(1) expect(instance!.propsProxy!.foo).toBe(1)
expect(() => (instanceProxy.foo = 2)).toThrow(TypeError) expect(() => (instanceProxy.foo = 2)).toThrow(TypeError)
@ -80,7 +77,6 @@ describe('component: proxy', () => {
}) })
test('public properties', () => { test('public properties', () => {
const app = createApp()
let instance: ComponentInternalInstance let instance: ComponentInternalInstance
let instanceProxy: any let instanceProxy: any
const Comp = { const Comp = {
@ -92,7 +88,7 @@ describe('component: proxy', () => {
instanceProxy = this instanceProxy = this
} }
} }
app.mount(Comp, nodeOps.createElement('div')) render(h(Comp), nodeOps.createElement('div'))
expect(instanceProxy.$data).toBe(instance!.data) expect(instanceProxy.$data).toBe(instance!.data)
expect(instanceProxy.$props).toBe(instance!.propsProxy) expect(instanceProxy.$props).toBe(instance!.propsProxy)
expect(instanceProxy.$attrs).toBe(instance!.attrs) expect(instanceProxy.$attrs).toBe(instance!.attrs)
@ -108,7 +104,6 @@ describe('component: proxy', () => {
}) })
test('sink', async () => { test('sink', async () => {
const app = createApp()
let instance: ComponentInternalInstance let instance: ComponentInternalInstance
let instanceProxy: any let instanceProxy: any
const Comp = { const Comp = {
@ -120,14 +115,13 @@ describe('component: proxy', () => {
instanceProxy = this instanceProxy = this
} }
} }
app.mount(Comp, nodeOps.createElement('div')) render(h(Comp), nodeOps.createElement('div'))
instanceProxy.foo = 1 instanceProxy.foo = 1
expect(instanceProxy.foo).toBe(1) expect(instanceProxy.foo).toBe(1)
expect(instance!.sink.foo).toBe(1) expect(instance!.sink.foo).toBe(1)
}) })
test('has check', () => { test('has check', () => {
const app = createApp()
let instanceProxy: any let instanceProxy: any
const Comp = { const Comp = {
render() {}, render() {},
@ -148,7 +142,7 @@ describe('component: proxy', () => {
instanceProxy = this instanceProxy = this
} }
} }
app.mount(Comp, nodeOps.createElement('div'), { msg: 'hello' }) render(h(Comp, { msg: 'hello' }), nodeOps.createElement('div'))
// props // props
expect('msg' in instanceProxy).toBe(true) expect('msg' in instanceProxy).toBe(true)

View File

@ -12,7 +12,6 @@ import { mockWarn } from '@vue/shared'
describe('resolveAssets', () => { describe('resolveAssets', () => {
test('should work', () => { test('should work', () => {
const app = createApp()
const FooBar = () => null const FooBar = () => null
const BarBaz = { mounted: () => null } const BarBaz = { mounted: () => null }
@ -49,8 +48,9 @@ describe('resolveAssets', () => {
} }
} }
const app = createApp(Root)
const root = nodeOps.createElement('div') const root = nodeOps.createElement('div')
app.mount(Root, root) app.mount(root)
expect(component1!).toBe(FooBar) expect(component1!).toBe(FooBar)
expect(component2!).toBe(FooBar) expect(component2!).toBe(FooBar)
expect(component3!).toBe(FooBar) expect(component3!).toBe(FooBar)
@ -78,7 +78,6 @@ describe('resolveAssets', () => {
}) })
test('not exist', () => { test('not exist', () => {
const app = createApp()
const Root = { const Root = {
setup() { setup() {
resolveComponent('foo') resolveComponent('foo')
@ -87,14 +86,14 @@ describe('resolveAssets', () => {
} }
} }
const app = createApp(Root)
const root = nodeOps.createElement('div') const root = nodeOps.createElement('div')
app.mount(Root, root) app.mount(root)
expect('Failed to resolve component: foo').toHaveBeenWarned() expect('Failed to resolve component: foo').toHaveBeenWarned()
expect('Failed to resolve directive: bar').toHaveBeenWarned() expect('Failed to resolve directive: bar').toHaveBeenWarned()
}) })
test('resolve dynamic component', () => { test('resolve dynamic component', () => {
const app = createApp()
const dynamicComponents = { const dynamicComponents = {
foo: () => 'foo', foo: () => 'foo',
bar: () => 'bar', bar: () => 'bar',
@ -112,8 +111,10 @@ describe('resolveAssets', () => {
} }
} }
} }
const app = createApp(Root)
const root = nodeOps.createElement('div') const root = nodeOps.createElement('div')
app.mount(Root, root) app.mount(root)
expect(foo).toBe(dynamicComponents.foo) expect(foo).toBe(dynamicComponents.foo)
expect(bar).toBe(dynamicComponents.bar) expect(bar).toBe(dynamicComponents.bar)
expect(baz).toBe(dynamicComponents.baz) expect(baz).toBe(dynamicComponents.baz)

View File

@ -16,16 +16,11 @@ export interface App<HostElement = any> {
component(name: string, component: Component): this component(name: string, component: Component): this
directive(name: string): Directive | undefined directive(name: string): Directive | undefined
directive(name: string, directive: Directive): this directive(name: string, directive: Directive): this
mount( mount(rootContainer: HostElement | string): ComponentPublicInstance
rootComponent:
| Component
// for compatibility with defineComponent() return types
| { new (): ComponentPublicInstance<any, any, any, any, any> },
rootContainer: HostElement | string,
rootProps?: Data
): ComponentPublicInstance
unmount(rootContainer: HostElement | string): void unmount(rootContainer: HostElement | string): void
provide<T>(key: InjectionKey<T> | string, value: T): this provide<T>(key: InjectionKey<T> | string, value: T): this
rootComponent: Component
rootContainer: HostElement | null
} }
export interface AppConfig { export interface AppConfig {
@ -79,16 +74,30 @@ export function createAppContext(): AppContext {
} }
} }
export type CreateAppFunction<HostElement> = (
rootComponent:
| Component
// for compatibility with defineComponent() return types
| { new (): ComponentPublicInstance<any, any, any, any, any> },
rootProps?: Data | null
) => App<HostElement>
export function createAppAPI<HostNode, HostElement>( export function createAppAPI<HostNode, HostElement>(
render: RootRenderFunction<HostNode, HostElement> render: RootRenderFunction<HostNode, HostElement>
): () => App<HostElement> { ): CreateAppFunction<HostElement> {
return function createApp(): App { return function createApp(
rootComponent: Component,
rootProps?: Data | null
): App {
const context = createAppContext() const context = createAppContext()
const installedPlugins = new Set() const installedPlugins = new Set()
let isMounted = false let isMounted = false
const app: App = { const app: App = {
rootComponent,
rootContainer: null,
get config() { get config() {
return context.config return context.config
}, },
@ -165,11 +174,7 @@ export function createAppAPI<HostNode, HostElement>(
return app return app
}, },
mount( mount(rootContainer: HostElement): any {
rootComponent: Component,
rootContainer: HostElement,
rootProps?: Data | null
): any {
if (!isMounted) { if (!isMounted) {
if (rootProps != null && !isObject(rootProps)) { if (rootProps != null && !isObject(rootProps)) {
__DEV__ && __DEV__ &&
@ -190,6 +195,7 @@ export function createAppAPI<HostNode, HostElement>(
render(vnode, rootContainer) render(vnode, rootContainer)
isMounted = true isMounted = true
app.rootContainer = rootContainer
return vnode.component!.proxy return vnode.component!.proxy
} else if (__DEV__) { } else if (__DEV__) {
warn( warn(
@ -198,8 +204,12 @@ export function createAppAPI<HostNode, HostElement>(
} }
}, },
unmount(rootContainer: HostElement) { unmount() {
render(null, rootContainer) if (isMounted) {
render(null, app.rootContainer!)
} else if (__DEV__) {
warn(`Cannot unmount an app that is not mounted.`)
}
}, },
provide(key, value) { provide(key, value) {

View File

@ -99,7 +99,13 @@ export { registerRuntimeCompiler } from './component'
// Types ----------------------------------------------------------------------- // Types -----------------------------------------------------------------------
export { App, AppConfig, AppContext, Plugin } from './apiCreateApp' export {
App,
AppConfig,
AppContext,
Plugin,
CreateAppFunction
} from './apiCreateApp'
export { VNode, VNodeTypes, VNodeProps } from './vnode' export { VNode, VNodeTypes, VNodeProps } from './vnode'
export { export {
Component, Component,

View File

@ -46,7 +46,7 @@ import { ShapeFlags } from './shapeFlags'
import { pushWarningContext, popWarningContext, warn } from './warning' import { pushWarningContext, popWarningContext, warn } from './warning'
import { invokeDirectiveHook } from './directives' import { invokeDirectiveHook } from './directives'
import { ComponentPublicInstance } from './componentProxy' import { ComponentPublicInstance } from './componentProxy'
import { App, createAppAPI } from './apiCreateApp' import { createAppAPI, CreateAppFunction } from './apiCreateApp'
import { import {
SuspenseBoundary, SuspenseBoundary,
queueEffectWithSuspense, queueEffectWithSuspense,
@ -174,7 +174,7 @@ export function createRenderer<
options: RendererOptions<HostNode, HostElement> options: RendererOptions<HostNode, HostElement>
): { ): {
render: RootRenderFunction<HostNode, HostElement> render: RootRenderFunction<HostNode, HostElement>
createApp: () => App<HostElement> createApp: CreateAppFunction<HostElement>
} { } {
type HostVNode = VNode<HostNode, HostElement> type HostVNode = VNode<HostNode, HostElement>
type HostVNodeChildren = VNodeChildren<HostNode, HostElement> type HostVNodeChildren = VNodeChildren<HostNode, HostElement>

View File

@ -1,6 +1,6 @@
import { import {
createApp,
h, h,
render,
nextTick, nextTick,
defineComponent, defineComponent,
vModelDynamic, vModelDynamic,
@ -20,10 +20,9 @@ const setValue = function(this: any, value: any) {
this.value = value this.value = value
} }
let app: any, root: any let root: any
beforeEach(() => { beforeEach(() => {
app = createApp()
root = document.createElement('div') as any root = document.createElement('div') as any
}) })
@ -44,9 +43,9 @@ describe('vModel', () => {
] ]
} }
}) })
app.mount(component, root) render(h(component), root)
const input = root.querySelector('input') const input = root.querySelector('input')!
const data = root._vnode.component.data const data = root._vnode.component.data
input.value = 'foo' input.value = 'foo'
@ -75,7 +74,7 @@ describe('vModel', () => {
] ]
} }
}) })
app.mount(component, root) render(h(component), root)
const input = root.querySelector('textarea') const input = root.querySelector('textarea')
const data = root._vnode.component.data const data = root._vnode.component.data
@ -136,7 +135,7 @@ describe('vModel', () => {
] ]
} }
}) })
app.mount(component, root) render(h(component), root)
const number = root.querySelector('.number') const number = root.querySelector('.number')
const trim = root.querySelector('.trim') const trim = root.querySelector('.trim')
@ -176,7 +175,7 @@ describe('vModel', () => {
] ]
} }
}) })
app.mount(component, root) render(h(component), root)
const input = root.querySelector('input') const input = root.querySelector('input')
const data = root._vnode.component.data const data = root._vnode.component.data
@ -219,7 +218,7 @@ describe('vModel', () => {
] ]
} }
}) })
app.mount(component, root) render(h(component), root)
const input = root.querySelector('input') const input = root.querySelector('input')
const data = root._vnode.component.data const data = root._vnode.component.data
@ -262,7 +261,7 @@ describe('vModel', () => {
] ]
} }
}) })
app.mount(component, root) render(h(component), root)
const input = root.querySelector('input') const input = root.querySelector('input')
const data = root._vnode.component.data const data = root._vnode.component.data
@ -314,7 +313,7 @@ describe('vModel', () => {
] ]
} }
}) })
app.mount(component, root) render(h(component), root)
const foo = root.querySelector('.foo') const foo = root.querySelector('.foo')
const bar = root.querySelector('.bar') const bar = root.querySelector('.bar')
@ -384,7 +383,7 @@ describe('vModel', () => {
] ]
} }
}) })
app.mount(component, root) render(h(component), root)
const foo = root.querySelector('.foo') const foo = root.querySelector('.foo')
const bar = root.querySelector('.bar') const bar = root.querySelector('.bar')
@ -437,7 +436,7 @@ describe('vModel', () => {
] ]
} }
}) })
app.mount(component, root) render(h(component), root)
const input = root.querySelector('select') const input = root.querySelector('select')
const foo = root.querySelector('option[value=foo]') const foo = root.querySelector('option[value=foo]')
@ -494,7 +493,7 @@ describe('vModel', () => {
] ]
} }
}) })
app.mount(component, root) render(h(component), root)
const input = root.querySelector('select') const input = root.querySelector('select')
const foo = root.querySelector('option[value=foo]') const foo = root.querySelector('option[value=foo]')

View File

@ -5,16 +5,15 @@ import {
nextTick, nextTick,
VNode VNode
} from '@vue/runtime-core' } from '@vue/runtime-core'
import { createApp, vShow } from '@vue/runtime-dom' import { render, vShow } from '@vue/runtime-dom'
const withVShow = (node: VNode, exp: any) => const withVShow = (node: VNode, exp: any) =>
withDirectives(node, [[vShow, exp]]) withDirectives(node, [[vShow, exp]])
let app: any, root: any let root: any
beforeEach(() => { beforeEach(() => {
app = createApp() root = document.createElement('div')
root = document.createElement('div') as any
}) })
describe('runtime-dom: v-show directive', () => { describe('runtime-dom: v-show directive', () => {
@ -27,7 +26,7 @@ describe('runtime-dom: v-show directive', () => {
return [withVShow(h('div'), this.value)] return [withVShow(h('div'), this.value)]
} }
}) })
app.mount(component, root) render(h(component), root)
const $div = root.querySelector('div') const $div = root.querySelector('div')
@ -43,7 +42,7 @@ describe('runtime-dom: v-show directive', () => {
return [withVShow(h('div'), this.value)] return [withVShow(h('div'), this.value)]
} }
}) })
app.mount(component, root) render(h(component), root)
const $div = root.querySelector('div') const $div = root.querySelector('div')
@ -59,7 +58,7 @@ describe('runtime-dom: v-show directive', () => {
return [withVShow(h('div'), this.value)] return [withVShow(h('div'), this.value)]
} }
}) })
app.mount(component, root) render(h(component), root)
const $div = root.querySelector('div') const $div = root.querySelector('div')
const data = root._vnode.component.data const data = root._vnode.component.data
@ -110,7 +109,7 @@ describe('runtime-dom: v-show directive', () => {
] ]
} }
}) })
app.mount(component, root) render(h(component), root)
const $div = root.querySelector('div') const $div = root.querySelector('div')
const data = root._vnode.component.data const data = root._vnode.component.data

View File

@ -1,8 +1,8 @@
import { import {
createRenderer, createRenderer,
warn, warn,
App, RootRenderFunction,
RootRenderFunction CreateAppFunction
} from '@vue/runtime-core' } from '@vue/runtime-core'
import { nodeOps } from './nodeOps' import { nodeOps } from './nodeOps'
import { patchProp } from './patchProp' import { patchProp } from './patchProp'
@ -17,8 +17,8 @@ const { render: baseRender, createApp: baseCreateApp } = createRenderer({
// use explicit type casts here to avoid import() calls in rolled-up d.ts // use explicit type casts here to avoid import() calls in rolled-up d.ts
export const render = baseRender as RootRenderFunction<Node, Element> export const render = baseRender as RootRenderFunction<Node, Element>
export const createApp = (): App<Element> => { export const createApp: CreateAppFunction<Element> = (...args) => {
const app = baseCreateApp() const app = baseCreateApp(...args)
if (__DEV__) { if (__DEV__) {
// Inject `isNativeTag` // Inject `isNativeTag`
@ -29,8 +29,8 @@ export const createApp = (): App<Element> => {
}) })
} }
const { mount, unmount } = app const { mount } = app
app.mount = (component, container, props): any => { app.mount = (container): any => {
if (isString(container)) { if (isString(container)) {
container = document.querySelector(container)! container = document.querySelector(container)!
if (!container) { if (!container) {
@ -39,6 +39,7 @@ export const createApp = (): App<Element> => {
return return
} }
} }
const component = app.rootComponent
if ( if (
__RUNTIME_COMPILE__ && __RUNTIME_COMPILE__ &&
!isFunction(component) && !isFunction(component) &&
@ -49,19 +50,7 @@ export const createApp = (): App<Element> => {
} }
// clear content before mounting // clear content before mounting
container.innerHTML = '' container.innerHTML = ''
return mount(component, container, props) return mount(container)
}
app.unmount = container => {
if (isString(container)) {
container = document.querySelector(container)!
if (!container) {
__DEV__ &&
warn(`Failed to unmount app: mount target selector returned null.`)
return
}
}
unmount(container)
} }
return app return app

View File

@ -2,7 +2,7 @@ import {
createRenderer, createRenderer,
VNode, VNode,
RootRenderFunction, RootRenderFunction,
App CreateAppFunction
} from '@vue/runtime-core' } from '@vue/runtime-core'
import { nodeOps, TestNode, TestElement } from './nodeOps' import { nodeOps, TestNode, TestElement } from './nodeOps'
import { patchProp } from './patchProp' import { patchProp } from './patchProp'
@ -14,7 +14,7 @@ const { render: baseRender, createApp: baseCreateApp } = createRenderer({
}) })
export const render = baseRender as RootRenderFunction<TestNode, TestElement> export const render = baseRender as RootRenderFunction<TestNode, TestElement>
export const createApp = baseCreateApp as () => App<TestElement> export const createApp = baseCreateApp as CreateAppFunction<TestElement>
// convenience for one-off render validations // convenience for one-off render validations
export function renderToString(vnode: VNode) { export function renderToString(vnode: VNode) {

View File

@ -14,7 +14,7 @@ describe('compiler + runtime integration', () => {
} }
} }
} }
createApp().mount(App, container) createApp(App).mount(container)
expect(container.innerHTML).toBe(`0`) expect(container.innerHTML).toBe(`0`)
}) })
@ -33,7 +33,7 @@ describe('compiler + runtime integration', () => {
} }
} }
} }
createApp().mount(App, container) createApp(App).mount(container)
expect(container.innerHTML).toBe(`0`) expect(container.innerHTML).toBe(`0`)
}) })
@ -51,7 +51,7 @@ describe('compiler + runtime integration', () => {
} }
} }
} }
createApp().mount(App, container) createApp(App).mount(container)
expect(container.innerHTML).toBe(`0`) expect(container.innerHTML).toBe(`0`)
}) })
@ -60,7 +60,7 @@ describe('compiler + runtime integration', () => {
const App = { const App = {
template: `<div v-if>` template: `<div v-if>`
} }
createApp().mount(App, container) createApp(App).mount(container)
expect( expect(
`Template compilation error: Element is missing end tag` `Template compilation error: Element is missing end tag`
).toHaveBeenWarned() ).toHaveBeenWarned()
@ -78,26 +78,24 @@ describe('compiler + runtime integration', () => {
}) })
it('should support custom element', () => { it('should support custom element', () => {
const app = createApp() const app = createApp({
const container = document.createElement('div')
const App = {
template: '<custom></custom>' template: '<custom></custom>'
} })
const container = document.createElement('div')
app.config.isCustomElement = tag => tag === 'custom' app.config.isCustomElement = tag => tag === 'custom'
app.mount(App, container) app.mount(container)
expect(container.innerHTML).toBe('<custom></custom>') expect(container.innerHTML).toBe('<custom></custom>')
}) })
it('should support using element innerHTML as template', () => { it('should support using element innerHTML as template', () => {
const app = createApp() const app = createApp({
const container = document.createElement('div')
container.innerHTML = '{{msg}}'
const App = {
data: { data: {
msg: 'hello' msg: 'hello'
} }
} })
app.mount(App, container) const container = document.createElement('div')
container.innerHTML = '{{msg}}'
app.mount(container)
expect(container.innerHTML).toBe('hello') expect(container.innerHTML).toBe('hello')
}) })
}) })

View File

@ -24,12 +24,12 @@
<script> <script>
const API_URL = `https://api.github.com/repos/vuejs/vue-next/commits?per_page=3&sha=` const API_URL = `https://api.github.com/repos/vuejs/vue-next/commits?per_page=3&sha=`
const App = { Vue.createApp({
data: { data: () => ({
branches: ['master', 'sync'], branches: ['master', 'sync'],
currentBranch: 'master', currentBranch: 'master',
commits: null commits: null
}, }),
created() { created() {
this.fetchData() this.fetchData()
@ -55,9 +55,7 @@ const App = {
return v.replace(/T|Z/g, ' ') return v.replace(/T|Z/g, ' ')
} }
} }
} }).mount('#demo')
Vue.createApp().mount(App, '#demo')
</script> </script>
<style> <style>

View File

@ -87,11 +87,11 @@ const DemoGrid = {
</div> </div>
<!-- App script --> <!-- App script -->
<script> <script>
const App = { Vue.createApp({
components: { components: {
DemoGrid DemoGrid
}, },
data: { data: () => ({
searchQuery: '', searchQuery: '',
gridColumns: ['name', 'power'], gridColumns: ['name', 'power'],
gridData: [ gridData: [
@ -100,10 +100,8 @@ const App = {
{ name: 'Jackie Chan', power: 7000 }, { name: 'Jackie Chan', power: 7000 },
{ name: 'Jet Li', power: 8000 } { name: 'Jet Li', power: 8000 }
] ]
} })
} }).mount('#demo')
Vue.createApp().mount(App, '#demo')
</script> </script>
<style> <style>

View File

@ -10,10 +10,10 @@
<script> <script>
const delay = window.location.hash === '#test' ? 16 : 300 const delay = window.location.hash === '#test' ? 16 : 300
const App = { Vue.createApp({
data: { data: () => ({
input: '# hello' input: '# hello'
}, }),
computed: { computed: {
compiledMarkdown() { compiledMarkdown() {
return marked(this.input, { sanitize: true }) return marked(this.input, { sanitize: true })
@ -24,9 +24,7 @@ const App = {
this.input = e.target.value this.input = e.target.value
}, delay) }, delay)
} }
} }).mount('#editor')
Vue.createApp().mount(App, '#editor')
</script> </script>
<style> <style>

View File

@ -97,14 +97,15 @@ const globalStats = [
{ label: 'E', value: 100 }, { label: 'E', value: 100 },
{ label: 'F', value: 100 } { label: 'F', value: 100 }
] ]
const App = {
Vue.createApp({
components: { components: {
Polygraph Polygraph
}, },
data: { data: () => ({
newLabel: '', newLabel: '',
stats: globalStats stats: globalStats
}, }),
methods: { methods: {
add(e) { add(e) {
e.preventDefault() e.preventDefault()
@ -123,9 +124,7 @@ const App = {
} }
} }
} }
} }).mount('#demo')
Vue.createApp().mount(App, '#demo')
</script> </script>
<style> <style>

View File

@ -82,14 +82,14 @@ const filters = {
} }
} }
const App = { Vue.createApp({
// app initial state // app initial state
data: { data: () => ({
todos: todoStorage.fetch(), todos: todoStorage.fetch(),
newTodo: '', newTodo: '',
editedTodo: null, editedTodo: null,
visibility: 'all' visibility: 'all'
}, }),
// watch todos change for localStorage persistence // watch todos change for localStorage persistence
watch: { watch: {
@ -192,7 +192,5 @@ const App = {
} }
} }
} }
} }).mount('#app')
Vue.createApp().mount(App, '#app')
</script> </script>

View File

@ -69,12 +69,7 @@ const TreeItem = {
</ul> </ul>
<script> <script>
const App = { const treeData = {
components: {
TreeItem
},
data: {
treeData: {
name: 'My Tree', name: 'My Tree',
children: [ children: [
{ name: 'hello' }, { name: 'hello' },
@ -101,11 +96,16 @@ const App = {
] ]
} }
] ]
}
}
} }
Vue.createApp().mount(App, '#demo') Vue.createApp({
components: {
TreeItem
},
data: () => ({
treeData
})
}).mount('#demo')
</script> </script>
<style> <style>

View File

@ -32,7 +32,7 @@ const truncate = v => {
const formatDate = v => v.replace(/T|Z/g, ' ') const formatDate = v => v.replace(/T|Z/g, ' ')
const App = { createApp({
setup() { setup() {
const currentBranch = ref('master') const currentBranch = ref('master')
const commits = ref(null) const commits = ref(null)
@ -54,9 +54,7 @@ const App = {
formatDate formatDate
} }
} }
} }).mount('#demo')
createApp().mount(App, '#demo')
</script> </script>
<style> <style>

View File

@ -93,11 +93,11 @@ const DemoGrid = {
</div> </div>
<!-- App script --> <!-- App script -->
<script> <script>
const App = { Vue.createApp({
components: { components: {
DemoGrid DemoGrid
}, },
data: { data: () => ({
searchQuery: '', searchQuery: '',
gridColumns: ['name', 'power'], gridColumns: ['name', 'power'],
gridData: [ gridData: [
@ -106,10 +106,8 @@ const App = {
{ name: 'Jackie Chan', power: 7000 }, { name: 'Jackie Chan', power: 7000 },
{ name: 'Jet Li', power: 8000 } { name: 'Jet Li', power: 8000 }
] ]
} })
} }).mount('#demo')
Vue.createApp().mount(App, '#demo')
</script> </script>
<style> <style>

View File

@ -11,7 +11,7 @@
const delay = window.location.hash === '#test' ? 16 : 300 const delay = window.location.hash === '#test' ? 16 : 300
const { ref, computed } = Vue const { ref, computed } = Vue
const App = { Vue.createApp({
setup() { setup() {
const input = ref('# hello') const input = ref('# hello')
const output = computed(() => marked(input.value, { sanitize: true })) const output = computed(() => marked(input.value, { sanitize: true }))
@ -23,9 +23,7 @@ const App = {
update update
} }
} }
} }).mount('#editor')
Vue.createApp().mount(App, '#editor')
</script> </script>
<style> <style>

View File

@ -100,7 +100,8 @@ const globalStats = [
{ label: 'E', value: 100 }, { label: 'E', value: 100 },
{ label: 'F', value: 100 } { label: 'F', value: 100 }
] ]
const App = {
createApp({
components: { components: {
Polygraph Polygraph
}, },
@ -133,9 +134,7 @@ const App = {
remove remove
} }
} }
} }).mount('#demo')
createApp().mount(App, '#demo')
</script> </script>
<style> <style>

View File

@ -90,7 +90,7 @@ function pluralize (n) {
return n === 1 ? 'item' : 'items' return n === 1 ? 'item' : 'items'
} }
const App = { createApp({
setup () { setup () {
const state = reactive({ const state = reactive({
todos: todoStorage.fetch(), todos: todoStorage.fetch(),
@ -202,7 +202,5 @@ const App = {
} }
} }
} }
} }).mount('#app')
createApp().mount(App, '#app')
</script> </script>

View File

@ -72,12 +72,7 @@ const TreeItem = {
</ul> </ul>
<script> <script>
const App = { const treeData = {
components: {
TreeItem
},
data: {
treeData: {
name: 'My Tree', name: 'My Tree',
children: [ children: [
{ name: 'hello' }, { name: 'hello' },
@ -104,11 +99,16 @@ const App = {
] ]
} }
] ]
}
}
} }
Vue.createApp().mount(App, '#demo') Vue.createApp({
components: {
TreeItem
},
data: () => ({
treeData
})
}).mount('#demo')
</script> </script>
<style> <style>

View File

@ -24,7 +24,7 @@ const Item = {
template: `<div>{{ msg }} <button @click="$emit('rm')">x</button></div>` template: `<div>{{ msg }} <button @click="$emit('rm')">x</button></div>`
} }
const App = { Vue.createApp({
components: { components: {
Item Item
}, },
@ -51,9 +51,7 @@ const App = {
} }
} }
} }
} }).mount('#app')
Vue.createApp().mount(App, '#app')
</script> </script>
<style> <style>

View File

@ -56,13 +56,12 @@ const Modal = {
</div> </div>
<script> <script>
const App = { Vue.createApp({
components: { Modal }, components: { Modal },
data: { data: () => ({
showModal: false showModal: false
} })
} }).mount('#app')
Vue.createApp().mount(App, '#app')
</script> </script>
<style> <style>