Skip to content

Commit b137661

Browse files
committed
feat(app): app.runWithContext()
Allows accessing globals provided at the app level with `app.provide()` via a regular `inject()` call as long as you have access to the application. Useful for Pinia, vue-router, Nuxt, Quasar, and other advanced use cases. - vuejs/pinia#1784
1 parent 1fa3d95 commit b137661

File tree

3 files changed

+46
-4
lines changed

3 files changed

+46
-4
lines changed

packages/runtime-core/__tests__/apiCreateApp.spec.ts

+16
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,22 @@ describe('api: createApp', () => {
109109
expect(`App already provides property with key "bar".`).toHaveBeenWarned()
110110
})
111111

112+
test('runWithContext', () => {
113+
const app = createApp({
114+
setup() {
115+
provide('foo', 'should not be seen')
116+
return () => h('div')
117+
}
118+
})
119+
app.provide('foo', 1)
120+
121+
expect(app.runWithContext(() => inject('foo'))).toBe(1)
122+
123+
// ensure the context is restored
124+
inject('foo')
125+
expect('inject() can only be used inside setup').toHaveBeenWarned()
126+
})
127+
112128
test('component', () => {
113129
const Root = {
114130
// local override

packages/runtime-core/src/apiCreateApp.ts

+22
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,14 @@ export interface App<HostElement = any> {
5151
unmount(): void
5252
provide<T>(key: InjectionKey<T> | string, value: T): this
5353

54+
/**
55+
* Runs a function with the app as active instance. This allows using of `inject()` within the function to get access
56+
* to variables provided via `app.provide()`.
57+
*
58+
* @param fn - function to run with the app as active instance
59+
*/
60+
runWithContext<T>(fn: () => T): T
61+
5462
// internal, but we need to expose these for the server-renderer and devtools
5563
_uid: number
5664
_component: ConcreteComponent
@@ -370,6 +378,15 @@ export function createAppAPI<HostElement>(
370378
context.provides[key as string | symbol] = value
371379

372380
return app
381+
},
382+
383+
runWithContext(fn) {
384+
currentApp = this
385+
try {
386+
return fn()
387+
} finally {
388+
currentApp = null
389+
}
373390
}
374391
})
375392

@@ -380,3 +397,8 @@ export function createAppAPI<HostElement>(
380397
return app
381398
}
382399
}
400+
401+
/**
402+
* @internal Used to identify the current app when using `inject()` within `app.runWithContext()`.
403+
*/
404+
export let currentApp: App<unknown> | null = null

packages/runtime-core/src/apiInject.ts

+8-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { isFunction } from '@vue/shared'
22
import { currentInstance } from './component'
33
import { currentRenderingInstance } from './componentRenderContext'
4+
import { App, currentApp } from './apiCreateApp'
45
import { warn } from './warning'
56

67
export interface InjectionKey<T> extends Symbol {}
@@ -44,14 +45,17 @@ export function inject(
4445
treatDefaultAsFactory = false
4546
) {
4647
// fallback to `currentRenderingInstance` so that this can be called in
47-
// a functional component
48-
const instance = currentInstance || currentRenderingInstance
48+
// a functional component and to currentApp so it can be called within `app.runWithContext()`
49+
const instance = currentInstance || currentRenderingInstance || currentApp
50+
4951
if (instance) {
5052
// #2400
5153
// to support `app.use` plugins,
5254
// fallback to appContext's `provides` if the instance is at root
5355
const provides =
54-
instance.parent == null
56+
'mount' in instance // checks if instance is an App
57+
? instance._context.provides
58+
: instance.parent == null
5559
? instance.vnode.appContext && instance.vnode.appContext.provides
5660
: instance.parent.provides
5761

@@ -60,7 +64,7 @@ export function inject(
6064
return provides[key as string]
6165
} else if (arguments.length > 1) {
6266
return treatDefaultAsFactory && isFunction(defaultValue)
63-
? defaultValue.call(instance.proxy)
67+
? defaultValue.call((instance as App & { proxy: undefined }).proxy)
6468
: defaultValue
6569
} else if (__DEV__) {
6670
warn(`injection "${String(key)}" not found.`)

0 commit comments

Comments
 (0)