Skip to content

Commit

Permalink
feat: add createApp (#415)
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Jun 30, 2020
1 parent a6af7d4 commit 391a0d9
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 0 deletions.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,27 @@ watch(() => {

</details>

### createApp

<details>
<summary>
⚠️ <code>createApp()</code> is global
</summary>

In Vue 3, `createApp()` is introduced to provide context(plugin, components, etc.) isolation between app instances. Due the the design of Vue 2, in this plugin, we provide `createApp()` as a forward compatible API which is just an alias of the global.

```ts
const app1 = createApp(RootComponent1)
app1.component('Foo', Foo) // equivalent to Vue.component('Foo', Foo)
app1.use(VueRouter) // equivalent to Vue.use(VueRouter)

const app2 = createApp(RootComponent2)
app2.component('Bar', Bar) // equivalent to Vue.use('Bar', Bar)
```

</details>


### Missing APIs

The following APIs introduced in Vue 3 are not available in this plugin.
Expand Down
53 changes: 53 additions & 0 deletions src/createApp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type Vue from 'vue'
import { VueConstructor } from 'vue/types/umd'
import { getCurrentVue } from './runtimeContext'
import { warn } from './utils'

export interface App {
config: VueConstructor['config']
use: VueConstructor['use']
mixin: VueConstructor['mixin']
component: VueConstructor['component']
directive: VueConstructor['directive']
mount: Vue['$mount']
unmount: Vue['$destroy']
}

export function createApp(rootComponent: any, rootProps: any = undefined): App {
const V = getCurrentVue()!

let mountedVM: Vue | undefined = undefined

return {
config: V.config,
use: V.use.bind(V),
mixin: V.mixin.bind(V),
component: V.component.bind(V),
directive: V.directive.bind(V),
mount: (el, hydrating) => {
if (!mountedVM) {
mountedVM = new V({ propsData: rootProps, ...rootComponent })
mountedVM.$mount(el, hydrating)
return mountedVM
} 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)\``
)
}
return mountedVM
}
},
unmount: () => {
if (mountedVM) {
mountedVM.$destroy()
mountedVM = undefined
} else if (__DEV__) {
warn(`Cannot unmount an app that is not mounted.`)
}
},
}
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const VueCompositionAPI = {
}

export default VueCompositionAPI
export { createApp } from './createApp'
export { nextTick } from './nextTick'
export { createElement as h } from './createElement'
export { getCurrentInstance } from './runtimeContext'
Expand Down
66 changes: 66 additions & 0 deletions test/createApp.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
const Vue = require('vue/dist/vue.common.js')
const { createApp, defineComponent, ref, h } = require('../src')

describe('createApp', () => {
it('should work', async () => {
const vm = new Vue({
setup() {
return {
a: ref(1),
}
},
template: '<p>{{a}}</p>',
}).$mount()

await Vue.nextTick()
expect(vm.a).toBe(1)
expect(vm.$el.textContent).toBe('1')
})

it('should work with rootProps', async () => {
const app = createApp(
defineComponent({
props: {
msg: String,
},
template: '<p>{{msg}}</p>',
}),
{
msg: 'foobar',
}
)
const vm = app.mount()

await Vue.nextTick()
expect(vm.$el.textContent).toBe('foobar')
})

it('should work with components', async () => {
const Foo = defineComponent({
props: {
msg: {
type: String,
required: true,
},
},
template: '<p>{{msg}}</p>',
})

const app = createApp(
defineComponent({
props: {
msg: String,
},
template: '<Foo :msg="msg"/>',
}),
{
msg: 'foobar',
}
)
app.component('Foo', Foo)
const vm = app.mount()

await Vue.nextTick()
expect(vm.$el.textContent).toBe('foobar')
})
})

0 comments on commit 391a0d9

Please sign in to comment.