Skip to content

Commit 391a0d9

Browse files
authored
feat: add createApp (#415)
1 parent a6af7d4 commit 391a0d9

File tree

4 files changed

+141
-0
lines changed

4 files changed

+141
-0
lines changed

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,27 @@ watch(() => {
369369

370370
</details>
371371

372+
### createApp
373+
374+
<details>
375+
<summary>
376+
⚠️ <code>createApp()</code> is global
377+
</summary>
378+
379+
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.
380+
381+
```ts
382+
const app1 = createApp(RootComponent1)
383+
app1.component('Foo', Foo) // equivalent to Vue.component('Foo', Foo)
384+
app1.use(VueRouter) // equivalent to Vue.use(VueRouter)
385+
386+
const app2 = createApp(RootComponent2)
387+
app2.component('Bar', Bar) // equivalent to Vue.use('Bar', Bar)
388+
```
389+
390+
</details>
391+
392+
372393
### Missing APIs
373394

374395
The following APIs introduced in Vue 3 are not available in this plugin.

src/createApp.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import type Vue from 'vue'
2+
import { VueConstructor } from 'vue/types/umd'
3+
import { getCurrentVue } from './runtimeContext'
4+
import { warn } from './utils'
5+
6+
export interface App {
7+
config: VueConstructor['config']
8+
use: VueConstructor['use']
9+
mixin: VueConstructor['mixin']
10+
component: VueConstructor['component']
11+
directive: VueConstructor['directive']
12+
mount: Vue['$mount']
13+
unmount: Vue['$destroy']
14+
}
15+
16+
export function createApp(rootComponent: any, rootProps: any = undefined): App {
17+
const V = getCurrentVue()!
18+
19+
let mountedVM: Vue | undefined = undefined
20+
21+
return {
22+
config: V.config,
23+
use: V.use.bind(V),
24+
mixin: V.mixin.bind(V),
25+
component: V.component.bind(V),
26+
directive: V.directive.bind(V),
27+
mount: (el, hydrating) => {
28+
if (!mountedVM) {
29+
mountedVM = new V({ propsData: rootProps, ...rootComponent })
30+
mountedVM.$mount(el, hydrating)
31+
return mountedVM
32+
} else {
33+
if (__DEV__) {
34+
warn(
35+
`App has already been mounted.\n` +
36+
`If you want to remount the same app, move your app creation logic ` +
37+
`into a factory function and create fresh app instances for each ` +
38+
`mount - e.g. \`const createMyApp = () => createApp(App)\``
39+
)
40+
}
41+
return mountedVM
42+
}
43+
},
44+
unmount: () => {
45+
if (mountedVM) {
46+
mountedVM.$destroy()
47+
mountedVM = undefined
48+
} else if (__DEV__) {
49+
warn(`Cannot unmount an app that is not mounted.`)
50+
}
51+
},
52+
}
53+
}

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const VueCompositionAPI = {
1414
}
1515

1616
export default VueCompositionAPI
17+
export { createApp } from './createApp'
1718
export { nextTick } from './nextTick'
1819
export { createElement as h } from './createElement'
1920
export { getCurrentInstance } from './runtimeContext'

test/createApp.spec.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
const Vue = require('vue/dist/vue.common.js')
2+
const { createApp, defineComponent, ref, h } = require('../src')
3+
4+
describe('createApp', () => {
5+
it('should work', async () => {
6+
const vm = new Vue({
7+
setup() {
8+
return {
9+
a: ref(1),
10+
}
11+
},
12+
template: '<p>{{a}}</p>',
13+
}).$mount()
14+
15+
await Vue.nextTick()
16+
expect(vm.a).toBe(1)
17+
expect(vm.$el.textContent).toBe('1')
18+
})
19+
20+
it('should work with rootProps', async () => {
21+
const app = createApp(
22+
defineComponent({
23+
props: {
24+
msg: String,
25+
},
26+
template: '<p>{{msg}}</p>',
27+
}),
28+
{
29+
msg: 'foobar',
30+
}
31+
)
32+
const vm = app.mount()
33+
34+
await Vue.nextTick()
35+
expect(vm.$el.textContent).toBe('foobar')
36+
})
37+
38+
it('should work with components', async () => {
39+
const Foo = defineComponent({
40+
props: {
41+
msg: {
42+
type: String,
43+
required: true,
44+
},
45+
},
46+
template: '<p>{{msg}}</p>',
47+
})
48+
49+
const app = createApp(
50+
defineComponent({
51+
props: {
52+
msg: String,
53+
},
54+
template: '<Foo :msg="msg"/>',
55+
}),
56+
{
57+
msg: 'foobar',
58+
}
59+
)
60+
app.component('Foo', Foo)
61+
const vm = app.mount()
62+
63+
await Vue.nextTick()
64+
expect(vm.$el.textContent).toBe('foobar')
65+
})
66+
})

0 commit comments

Comments
 (0)