Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add createApp #415

Merged
merged 1 commit into from
Jun 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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')
})
})