From eba772b103d27414c84be83cf39f742b38c44a30 Mon Sep 17 00:00:00 2001 From: drodichkin Date: Thu, 16 Feb 2023 19:41:10 +0300 Subject: [PATCH 01/11] Fix: always store context in unctx and allow to not lose context on async calls --- package.json | 3 +- src/runtime/composables/context.ts | 76 ++++++++++++++++++++++-------- src/runtime/composables/index.ts | 2 +- yarn.lock | 49 ++++++++++++++++++- 4 files changed, 107 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index 2751119c..ed8243eb 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,8 @@ "fs-extra": "^9.1.0", "magic-string": "^0.27.0", "pathe": "^1.0.0", - "ufo": "^1.0.1" + "ufo": "^1.0.1", + "unctx": "^2.1.2" }, "devDependencies": { "@babel/traverse": "^7.20.5", diff --git a/src/runtime/composables/context.ts b/src/runtime/composables/context.ts index 44ae1fab..926f4361 100644 --- a/src/runtime/composables/context.ts +++ b/src/runtime/composables/context.ts @@ -4,6 +4,7 @@ import type { Ref } from 'vue' import type { Context } from '@nuxt/types' import type { Route } from 'vue-router' +import { getContext } from 'unctx' import { globalNuxt } from '@nuxtjs/composition-api/dist/runtime/globals' import { getCurrentInstance } from './utils' @@ -30,6 +31,31 @@ interface UseContextReturn params: Ref } +const nuxtAppCtx = getContext('nuxt-app') + +/** + * Ensures that the setup function passed in has access to the Nuxt instance via `useContext`. + * + * @param context useContext response + * @param setup The function to call + * @param args Function's arguments + */ +export function callWithContext any>( + context: UseContextReturn, + setup: T, + args?: Parameters +) { + const fn: () => ReturnType = () => + args ? setup(...(args as Parameters)) : setup() + if (process.server) { + return nuxtAppCtx.callAsync(context, fn) + } else { + // In client side we could assume nuxt app is singleton + nuxtAppCtx.set(context) + return fn() + } +} + /** * `useContext` will return the Nuxt context. * @example @@ -45,26 +71,36 @@ interface UseContextReturn ``` */ export const useContext = (): UseContextReturn => { - const vm = getCurrentInstance() - if (!vm) throw new Error('This must be called within a setup function.') + const nuxtAppInstance = nuxtAppCtx.tryUse() + if (!nuxtAppInstance) { + const vm = getCurrentInstance() + if (!vm) { + throw new Error('This must be called within a setup function.') + } - return { - ...(vm[globalNuxt] || vm.$options).context, - /** - * @deprecated To smooth your upgrade to Nuxt 3, it is recommended not to access `route` from `useContext` but rather to use the `useRoute` helper function. - */ - route: computed(() => vm.$route), - /** - * @deprecated To smooth your upgrade to Nuxt 3, it is recommended not to access `query` from `useContext` but rather to use the `useRoute` helper function. - */ - query: computed(() => vm.$route.query), - /** - * @deprecated To smooth your upgrade to Nuxt 3, it is recommended not to access `from` from `useContext` but rather to use the `useRoute` helper function. - */ - from: computed(() => (vm[globalNuxt] || vm.$options).context.from), - /** - * @deprecated To smooth your upgrade to Nuxt 3, it is recommended not to access `params` from `useContext` but rather to use the `useRoute` helper function. - */ - params: computed(() => vm.$route.params), + const context = { + ...(vm[globalNuxt] || vm.$options).context, + /** + * @deprecated To smooth your upgrade to Nuxt 3, it is recommended not to access `route` from `useContext` but rather to use the `useRoute` helper function. + */ + route: computed(() => vm.$route), + /** + * @deprecated To smooth your upgrade to Nuxt 3, it is recommended not to access `query` from `useContext` but rather to use the `useRoute` helper function. + */ + query: computed(() => vm.$route.query), + /** + * @deprecated To smooth your upgrade to Nuxt 3, it is recommended not to access `from` from `useContext` but rather to use the `useRoute` helper function. + */ + from: computed(() => (vm[globalNuxt] || vm.$options).context.from), + /** + * @deprecated To smooth your upgrade to Nuxt 3, it is recommended not to access `params` from `useContext` but rather to use the `useRoute` helper function. + */ + params: computed(() => vm.$route.params), + } + + nuxtAppCtx.set(context) + return context } + + return nuxtAppInstance } diff --git a/src/runtime/composables/index.ts b/src/runtime/composables/index.ts index 18a31130..2089c86c 100644 --- a/src/runtime/composables/index.ts +++ b/src/runtime/composables/index.ts @@ -1,6 +1,6 @@ export { useAsync } from './async' export { defineComponent } from './component' -export { useContext, withContext } from './context' +export { callWithContext, useContext, withContext } from './context' export * from './defineHelpers' export { useFetch } from './fetch' export { globalPlugin, onGlobalSetup, setMetaPlugin } from './hooks' diff --git a/yarn.lock b/yarn.lock index ac95e2b4..be6bf0c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2214,6 +2214,11 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.46.tgz#0fb6bfbbeabd7a30880504993369c4bf1deab1fe" integrity sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg== +"@types/estree@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2" + integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== + "@types/etag@1.8.0": version "1.8.0" resolved "https://registry.yarnpkg.com/@types/etag/-/etag-1.8.0.tgz#37f0b1f3ea46da7ae319bbedb607e375b4c99f7e" @@ -2925,6 +2930,11 @@ acorn@^8.8.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73" integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA== +acorn@^8.8.2: + version "8.8.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" + integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== + add-stream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/add-stream/-/add-stream-1.0.0.tgz#6a7990437ca736d5e1288db92bd3266d5f5cb2aa" @@ -3991,7 +4001,7 @@ chokidar@^2.1.8: optionalDependencies: fsevents "^1.2.7" -chokidar@^3.4.1, chokidar@^3.4.2, chokidar@^3.5.1, chokidar@^3.5.2: +chokidar@^3.4.1, chokidar@^3.4.2, chokidar@^3.5.1, chokidar@^3.5.2, chokidar@^3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== @@ -6018,6 +6028,13 @@ estree-walker@^2.0.1, estree-walker@^2.0.2: resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== +estree-walker@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d" + integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== + dependencies: + "@types/estree" "^1.0.0" + esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -13515,6 +13532,16 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +unctx@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/unctx/-/unctx-2.1.2.tgz#12d34c540ef4fbaffb2a3b38a0697e42b152d478" + integrity sha512-KK18aLRKe3OlbPyHbXAkIWSU3xK8GInomXfA7fzDMGFXQ1crX1UWrCzKesVXeUyHIayHUrnTvf87IPCKMyeKTg== + dependencies: + acorn "^8.8.2" + estree-walker "^3.0.3" + magic-string "^0.27.0" + unplugin "^1.0.1" + unfetch@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be" @@ -13611,6 +13638,16 @@ unpipe@1.0.0, unpipe@~1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== +unplugin@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unplugin/-/unplugin-1.1.0.tgz#96a14aa52d7637a56a88dec6baf4a73902f2db87" + integrity sha512-I8obQ8Rs/hnkxokRV6g8JKOQFgYNnTd9DL58vcSt5IJ9AkK8wbrtsnzD5hi4BJlvcY536JzfEXj9L6h7j559/A== + dependencies: + acorn "^8.8.2" + chokidar "^3.5.3" + webpack-sources "^3.2.3" + webpack-virtual-modules "^0.5.0" + unquote@^1.1.1, unquote@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" @@ -14063,6 +14100,16 @@ webpack-sources@^1.0.1, webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack- source-list-map "^2.0.0" source-map "~0.6.1" +webpack-sources@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" + integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== + +webpack-virtual-modules@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.5.0.tgz#362f14738a56dae107937ab98ea7062e8bdd3b6c" + integrity sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw== + webpack@^4.46.0: version "4.46.0" resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.46.0.tgz#bf9b4404ea20a073605e0a011d188d77cb6ad542" From 1e6c6c40109af66331567ea2231675a42eb07416 Mon Sep 17 00:00:00 2001 From: drodichkin Date: Thu, 16 Feb 2023 21:37:35 +0300 Subject: [PATCH 02/11] Fix/Change wrappers to use context --- src/runtime/composables/index.ts | 9 ++++- src/runtime/composables/wrappers.ts | 55 +++++++++++++++++++++++++---- 2 files changed, 57 insertions(+), 7 deletions(-) diff --git a/src/runtime/composables/index.ts b/src/runtime/composables/index.ts index 2089c86c..71cbb234 100644 --- a/src/runtime/composables/index.ts +++ b/src/runtime/composables/index.ts @@ -8,6 +8,13 @@ export { useMeta } from './meta' export { reqRef, reqSsrRef } from './req-ref' export { ssrRef, shallowSsrRef, setSSRContext, ssrPromise } from './ssr-ref' export { useStatic } from './static' -export { useRoute, useRouter, useStore, wrapProperty } from './wrappers' +export { + useRoute, + useRouter, + useStore, + wrapProperty, + wrapContextProperty, + useRedirect, +} from './wrappers' export * from './vue' diff --git a/src/runtime/composables/wrappers.ts b/src/runtime/composables/wrappers.ts index d6821a27..f6015321 100644 --- a/src/runtime/composables/wrappers.ts +++ b/src/runtime/composables/wrappers.ts @@ -1,7 +1,10 @@ import { computed, ComputedRef, InjectionKey } from 'vue' import type { Store } from 'vuex' +import type { VueRouter } from 'vue-router/types/router' +import { useContext } from './context' import { getCurrentInstance } from './utils' +import { Context } from '@nuxt/types' /** * You might want to create a custom helper to 'convert' a non-Composition API property to a Composition-ready one. `wrapProperty` enables you to do that easily, returning either a computed or a bare property as required. @@ -27,6 +30,27 @@ export const wrapProperty = < } } +/** + * You might want to create a custom helper to 'convert' a non-Composition Context property to a Composition-ready one. `wrapProperty` enables you to do that easily, returning either a computed or a bare property as required. + * @param property the name of the property you would like to access. For example, `store` to access `context.store`. + * @param makeComputed a boolean indicating whether the helper function should return a computed property or not. Defaults to `true`. + */ +export const wrapContextProperty = < + K extends keyof Context, + T extends boolean = true +>( + property: K, + makeComputed?: T +) => { + return (): T extends true ? ComputedRef : Context[K] => { + const context = useContext() + + return makeComputed !== false + ? (computed(() => context[property]) as any) + : context[property] + } +} + /** * Gain access to the router just like using this.$router in a non-Composition API manner. * @example @@ -41,7 +65,29 @@ export const wrapProperty = < }) ``` */ -export const useRouter = wrapProperty('$router', false) +export const useRouter = (): VueRouter => { + const contextRouter = useContext().app.router + if (contextRouter) return contextRouter + + const vm = getCurrentInstance() + if (!vm) throw new Error('This must be called within a setup function.') + return vm.$router +} + +/** + * Gain safe access to the redirect method from Context + * @example + ```ts + import { defineComponent, useRedirect } from '@nuxtjs/composition-api' + + export default defineComponent({ + setup() { + useRedirect('/') + } + }) + ``` + */ +export const useRedirect = wrapContextProperty('redirect') /** * Returns `this.$route`, wrapped in a computed - so accessible from `.value`. @@ -57,7 +103,7 @@ export const useRouter = wrapProperty('$router', false) }) ``` */ -export const useRoute = wrapProperty('$route') +export const useRoute = wrapContextProperty('route') /** * Gain access to the store just like using this.$store in a non-Composition API manner. You can also provide an injection key or custom type to get back a semi-typed store: @@ -82,8 +128,5 @@ export const useRoute = wrapProperty('$route') ``` */ export const useStore = (key?: InjectionKey): Store => { - const vm = getCurrentInstance() - if (!vm) throw new Error('This must be called within a setup function.') - - return vm.$store + return useContext().store } From 0eb2f8ebea1211eee010992b0a5d30af2d759321 Mon Sep 17 00:00:00 2001 From: daniluk4000 Date: Thu, 16 Feb 2023 22:06:33 +0300 Subject: [PATCH 03/11] Fix/Remove recursive route --- src/runtime/composables/wrappers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/runtime/composables/wrappers.ts b/src/runtime/composables/wrappers.ts index f6015321..86d7ddfe 100644 --- a/src/runtime/composables/wrappers.ts +++ b/src/runtime/composables/wrappers.ts @@ -1,4 +1,4 @@ -import { computed, ComputedRef, InjectionKey } from 'vue' +import { computed, ComputedRef, InjectionKey, isRef } from 'vue' import type { Store } from 'vuex' import type { VueRouter } from 'vue-router/types/router' import { useContext } from './context' @@ -45,7 +45,7 @@ export const wrapContextProperty = < return (): T extends true ? ComputedRef : Context[K] => { const context = useContext() - return makeComputed !== false + return makeComputed !== false && !isRef(context[property]) ? (computed(() => context[property]) as any) : context[property] } From c1b510e88bba2314dd97881388ddc88de002ea16 Mon Sep 17 00:00:00 2001 From: daniluk4000 Date: Thu, 16 Feb 2023 22:43:08 +0300 Subject: [PATCH 04/11] Fix/Never set global stuff on SSR --- src/runtime/composables/context.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runtime/composables/context.ts b/src/runtime/composables/context.ts index 926f4361..b00b2f7b 100644 --- a/src/runtime/composables/context.ts +++ b/src/runtime/composables/context.ts @@ -72,6 +72,7 @@ export function callWithContext any>( */ export const useContext = (): UseContextReturn => { const nuxtAppInstance = nuxtAppCtx.tryUse() + if (!nuxtAppInstance) { const vm = getCurrentInstance() if (!vm) { @@ -98,7 +99,6 @@ export const useContext = (): UseContextReturn => { params: computed(() => vm.$route.params), } - nuxtAppCtx.set(context) return context } From b35d0af931fbacafef9853a59368a7d2565f7512 Mon Sep 17 00:00:00 2001 From: daniluk4000 Date: Sun, 19 Feb 2023 20:55:46 +0300 Subject: [PATCH 05/11] Refactor/Rename nuxt app to nuxt ctx, always set context to global on CSR --- src/runtime/composables/context.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/runtime/composables/context.ts b/src/runtime/composables/context.ts index b00b2f7b..6da5dac6 100644 --- a/src/runtime/composables/context.ts +++ b/src/runtime/composables/context.ts @@ -31,7 +31,7 @@ interface UseContextReturn params: Ref } -const nuxtAppCtx = getContext('nuxt-app') +const nuxtCtx = getContext('nuxt-context') /** * Ensures that the setup function passed in has access to the Nuxt instance via `useContext`. @@ -48,10 +48,10 @@ export function callWithContext any>( const fn: () => ReturnType = () => args ? setup(...(args as Parameters)) : setup() if (process.server) { - return nuxtAppCtx.callAsync(context, fn) + return nuxtCtx.callAsync(context, fn) } else { // In client side we could assume nuxt app is singleton - nuxtAppCtx.set(context) + nuxtCtx.set(context) return fn() } } @@ -71,9 +71,9 @@ export function callWithContext any>( ``` */ export const useContext = (): UseContextReturn => { - const nuxtAppInstance = nuxtAppCtx.tryUse() + const nuxtContext = nuxtCtx.tryUse() - if (!nuxtAppInstance) { + if (!nuxtContext) { const vm = getCurrentInstance() if (!vm) { throw new Error('This must be called within a setup function.') @@ -99,8 +99,9 @@ export const useContext = (): UseContextReturn => { params: computed(() => vm.$route.params), } + if (process.client) nuxtCtx.set(context) return context } - return nuxtAppInstance + return nuxtContext } From bce1ff2ad571f6685f2e1ab2994c7a373c5cc2fc Mon Sep 17 00:00:00 2001 From: daniluk4000 Date: Mon, 20 Feb 2023 12:28:54 +0300 Subject: [PATCH 06/11] Chore/Add callWithContext to docs --- docs/pages/en/6.API/3.callWithContext.md | 44 +++++++++++++++++++ .../6.API/{3.useStatic.md => 4.useStatic.md} | 0 docs/pages/en/6.API/{4.wrap.md => 5.wrap.md} | 0 3 files changed, 44 insertions(+) create mode 100644 docs/pages/en/6.API/3.callWithContext.md rename docs/pages/en/6.API/{3.useStatic.md => 4.useStatic.md} (100%) rename docs/pages/en/6.API/{4.wrap.md => 5.wrap.md} (100%) diff --git a/docs/pages/en/6.API/3.callWithContext.md b/docs/pages/en/6.API/3.callWithContext.md new file mode 100644 index 00000000..ac2167a8 --- /dev/null +++ b/docs/pages/en/6.API/3.callWithContext.md @@ -0,0 +1,44 @@ +--- +title: callWithContext +description: 'Ensures Context will always be available inside called Composable' +--- + +This function equals one of Nuxt Bridge/Nuxt 3 internals: `callWithNuxt`. +It accepts `useContext()` response as first argument, function-to-call as second and function's arguments as third. +When you call this function, you can always be sure that function-to-call will have access to `useContext`, `useRoute`, e.t.c. methods. + +Example of usage with useAsync: +```ts +import { defineComponent, useContext, callWithContext } from '@nuxtjs/composition-api' +import { useMyComposable, useSecondComposable } from '../composables' + +export default defineComponent({ + setup() { + const context = useContext() + + useAsync(async () => { + try { + //Context is lost after you call first await on SSR + const firstAwait = await useMyComposable({ option: true }) + //This one depends on firstAwait and calls useContext inside of it + const secondAwait = await callWithContext(context, useSecondComposable, [{ option: true }]) + + return { + firstAwait, + secondAwait, + } + } + catch (e) { + //Wait for logging system response etc + await callWithContext(context, useProcessError, [{ error: e }]) + throw e + } + }) + }, +}) +``` + +:::alert{type="info"} +Note: after first call of useContext on Client Side context will be stored as global. You will not have to call function to access returned in setup composables on client side. +::: + diff --git a/docs/pages/en/6.API/3.useStatic.md b/docs/pages/en/6.API/4.useStatic.md similarity index 100% rename from docs/pages/en/6.API/3.useStatic.md rename to docs/pages/en/6.API/4.useStatic.md diff --git a/docs/pages/en/6.API/4.wrap.md b/docs/pages/en/6.API/5.wrap.md similarity index 100% rename from docs/pages/en/6.API/4.wrap.md rename to docs/pages/en/6.API/5.wrap.md From 3535f3bd6b999fb0fac666244f071d0f5af54802 Mon Sep 17 00:00:00 2001 From: daniluk4000 Date: Mon, 20 Mar 2023 18:48:40 +0300 Subject: [PATCH 07/11] Fix: avoid context conflicts on client side --- src/runtime/composables/context.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runtime/composables/context.ts b/src/runtime/composables/context.ts index 6da5dac6..d8ea9747 100644 --- a/src/runtime/composables/context.ts +++ b/src/runtime/composables/context.ts @@ -51,7 +51,7 @@ export function callWithContext any>( return nuxtCtx.callAsync(context, fn) } else { // In client side we could assume nuxt app is singleton - nuxtCtx.set(context) + if (!nuxtCtx.tryUse()) nuxtCtx.set(context) return fn() } } From f9ad7e23be1131dec96aec91430d0d3dcd258e19 Mon Sep 17 00:00:00 2001 From: daniluk4000 Date: Wed, 7 Jun 2023 17:10:41 +0300 Subject: [PATCH 08/11] Fix/Route composables reactivity is lost --- docs/pages/en/6.API/3.callWithContext.md | 49 ++++++++++++++---------- package.json | 4 +- src/runtime/composables/context.ts | 7 ++-- src/runtime/composables/wrappers.ts | 11 +----- yarn.lock | 5 +++ 5 files changed, 42 insertions(+), 34 deletions(-) diff --git a/docs/pages/en/6.API/3.callWithContext.md b/docs/pages/en/6.API/3.callWithContext.md index ac2167a8..840c33b2 100644 --- a/docs/pages/en/6.API/3.callWithContext.md +++ b/docs/pages/en/6.API/3.callWithContext.md @@ -3,37 +3,45 @@ title: callWithContext description: 'Ensures Context will always be available inside called Composable' --- -This function equals one of Nuxt Bridge/Nuxt 3 internals: `callWithNuxt`. +This function equals one of Nuxt Bridge/Nuxt 3 internals: `callWithNuxt`. It accepts `useContext()` response as first argument, function-to-call as second and function's arguments as third. When you call this function, you can always be sure that function-to-call will have access to `useContext`, `useRoute`, e.t.c. methods. Example of usage with useAsync: + ```ts -import { defineComponent, useContext, callWithContext } from '@nuxtjs/composition-api' +import { + defineComponent, + useContext, + callWithContext, +} from '@nuxtjs/composition-api' import { useMyComposable, useSecondComposable } from '../composables' export default defineComponent({ setup() { - const context = useContext() + const context = useContext() - useAsync(async () => { - try { - //Context is lost after you call first await on SSR - const firstAwait = await useMyComposable({ option: true }) - //This one depends on firstAwait and calls useContext inside of it - const secondAwait = await callWithContext(context, useSecondComposable, [{ option: true }]) + useAsync(async () => { + try { + //Context is lost after you call first await on SSR + const firstAwait = await useMyComposable({ option: true }) + //This one depends on firstAwait and calls useContext inside of it + const secondAwait = await callWithContext( + context, + useSecondComposable, + [{ option: true }] + ) - return { - firstAwait, - secondAwait, - } - } - catch (e) { - //Wait for logging system response etc - await callWithContext(context, useProcessError, [{ error: e }]) - throw e - } - }) + return { + firstAwait, + secondAwait, + } + } catch (e) { + //Wait for logging system response etc + await callWithContext(context, useProcessError, [{ error: e }]) + throw e + } + }) }, }) ``` @@ -41,4 +49,3 @@ export default defineComponent({ :::alert{type="info"} Note: after first call of useContext on Client Side context will be stored as global. You will not have to call function to access returned in setup composables on client side. ::: - diff --git a/package.json b/package.json index ed8243eb..2d05a309 100644 --- a/package.json +++ b/package.json @@ -103,12 +103,14 @@ "tsd": "^0.25.0", "typescript": "4.9.4", "vitest": "^0.25.7", + "vue-router": "^3.6.5", "yorkie": "^2.0.0" }, "peerDependencies": { "@nuxt/vue-app": "^2.15", "nuxt": "^2.15", - "vue": "^2.7.14" + "vue": "^2.7.14", + "vue-router": "^3.6" }, "engines": { "node": ">=v14.13.0" diff --git a/src/runtime/composables/context.ts b/src/runtime/composables/context.ts index d8ea9747..d491e3b7 100644 --- a/src/runtime/composables/context.ts +++ b/src/runtime/composables/context.ts @@ -3,6 +3,7 @@ import { computed } from 'vue' import type { Ref } from 'vue' import type { Context } from '@nuxt/types' import type { Route } from 'vue-router' +import { useRoute } from 'vue-router/composables' import { getContext } from 'unctx' import { globalNuxt } from '@nuxtjs/composition-api/dist/runtime/globals' @@ -84,11 +85,11 @@ export const useContext = (): UseContextReturn => { /** * @deprecated To smooth your upgrade to Nuxt 3, it is recommended not to access `route` from `useContext` but rather to use the `useRoute` helper function. */ - route: computed(() => vm.$route), + route: computed(() => useRoute()), /** * @deprecated To smooth your upgrade to Nuxt 3, it is recommended not to access `query` from `useContext` but rather to use the `useRoute` helper function. */ - query: computed(() => vm.$route.query), + query: computed(() => useRoute().query), /** * @deprecated To smooth your upgrade to Nuxt 3, it is recommended not to access `from` from `useContext` but rather to use the `useRoute` helper function. */ @@ -96,7 +97,7 @@ export const useContext = (): UseContextReturn => { /** * @deprecated To smooth your upgrade to Nuxt 3, it is recommended not to access `params` from `useContext` but rather to use the `useRoute` helper function. */ - params: computed(() => vm.$route.params), + params: computed(() => useRoute().params), } if (process.client) nuxtCtx.set(context) diff --git a/src/runtime/composables/wrappers.ts b/src/runtime/composables/wrappers.ts index 86d7ddfe..1cf03312 100644 --- a/src/runtime/composables/wrappers.ts +++ b/src/runtime/composables/wrappers.ts @@ -1,7 +1,7 @@ import { computed, ComputedRef, InjectionKey, isRef } from 'vue' import type { Store } from 'vuex' -import type { VueRouter } from 'vue-router/types/router' import { useContext } from './context' +import * as VueRouterComposables from 'vue-router/composables' import { getCurrentInstance } from './utils' import { Context } from '@nuxt/types' @@ -65,14 +65,7 @@ export const wrapContextProperty = < }) ``` */ -export const useRouter = (): VueRouter => { - const contextRouter = useContext().app.router - if (contextRouter) return contextRouter - - const vm = getCurrentInstance() - if (!vm) throw new Error('This must be called within a setup function.') - return vm.$router -} +export const useRouter = VueRouterComposables.useRouter /** * Gain safe access to the redirect method from Context diff --git a/yarn.lock b/yarn.lock index be6bf0c3..06d202aa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13946,6 +13946,11 @@ vue-router@^3.5.1: resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.5.4.tgz#c453c0b36bc75554de066fefc3f2a9c3212aca70" integrity sha512-x+/DLAJZv2mcQ7glH2oV9ze8uPwcI+H+GgTgTmb5I55bCgY3+vXWIsqbYUzbBSZnwFHEJku4eoaH/x98veyymQ== +vue-router@^3.6.5: + version "3.6.5" + resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.6.5.tgz#95847d52b9a7e3f1361cb605c8e6441f202afad8" + integrity sha512-VYXZQLtjuvKxxcshuRAwjHnciqZVoXAjTjcqBTz4rKc8qih9g9pI3hbDjmqXaHdgL3v8pV6P8Z335XvHzESxLQ== + vue-server-renderer@^2.6.12: version "2.7.4" resolved "https://registry.yarnpkg.com/vue-server-renderer/-/vue-server-renderer-2.7.4.tgz#7a24713377af939511cb925b2e495548ac035cc4" From 0910c25437bc3252a997559705587666ef595cbd Mon Sep 17 00:00:00 2001 From: daniluk4000 Date: Wed, 7 Jun 2023 18:19:34 +0300 Subject: [PATCH 09/11] Fix/Route composables reactivity --- src/runtime/composables/context.ts | 20 ++++++++++---------- src/runtime/composables/wrappers.ts | 12 ++++++++++-- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/runtime/composables/context.ts b/src/runtime/composables/context.ts index d491e3b7..7e1f1f2d 100644 --- a/src/runtime/composables/context.ts +++ b/src/runtime/composables/context.ts @@ -8,6 +8,7 @@ import { useRoute } from 'vue-router/composables' import { getContext } from 'unctx' import { globalNuxt } from '@nuxtjs/composition-api/dist/runtime/globals' import { getCurrentInstance } from './utils' +import { Vue } from 'vue/types/vue' interface ContextCallback { (context: Context): void @@ -48,13 +49,7 @@ export function callWithContext any>( ) { const fn: () => ReturnType = () => args ? setup(...(args as Parameters)) : setup() - if (process.server) { - return nuxtCtx.callAsync(context, fn) - } else { - // In client side we could assume nuxt app is singleton - if (!nuxtCtx.tryUse()) nuxtCtx.set(context) - return fn() - } + return nuxtCtx.callAsync(context, fn) } /** @@ -80,16 +75,21 @@ export const useContext = (): UseContextReturn => { throw new Error('This must be called within a setup function.') } + const root = vm.$root as unknown as { _$route: typeof vm.$root['$route'] } + + // Call of vue-router initialization of _$route + if (!root._$route) useRoute() + const context = { ...(vm[globalNuxt] || vm.$options).context, /** * @deprecated To smooth your upgrade to Nuxt 3, it is recommended not to access `route` from `useContext` but rather to use the `useRoute` helper function. */ - route: computed(() => useRoute()), + route: computed(() => root._$route), /** * @deprecated To smooth your upgrade to Nuxt 3, it is recommended not to access `query` from `useContext` but rather to use the `useRoute` helper function. */ - query: computed(() => useRoute().query), + query: computed(() => root._$route.query), /** * @deprecated To smooth your upgrade to Nuxt 3, it is recommended not to access `from` from `useContext` but rather to use the `useRoute` helper function. */ @@ -97,7 +97,7 @@ export const useContext = (): UseContextReturn => { /** * @deprecated To smooth your upgrade to Nuxt 3, it is recommended not to access `params` from `useContext` but rather to use the `useRoute` helper function. */ - params: computed(() => useRoute().params), + params: computed(() => root._$route.params), } if (process.client) nuxtCtx.set(context) diff --git a/src/runtime/composables/wrappers.ts b/src/runtime/composables/wrappers.ts index 1cf03312..06aba679 100644 --- a/src/runtime/composables/wrappers.ts +++ b/src/runtime/composables/wrappers.ts @@ -1,7 +1,6 @@ import { computed, ComputedRef, InjectionKey, isRef } from 'vue' import type { Store } from 'vuex' import { useContext } from './context' -import * as VueRouterComposables from 'vue-router/composables' import { getCurrentInstance } from './utils' import { Context } from '@nuxt/types' @@ -65,7 +64,16 @@ export const wrapContextProperty = < }) ``` */ -export const useRouter = VueRouterComposables.useRouter +export const useRouter = (): VueRouter => { + const vm = getCurrentInstance() + + if (vm) return useRouter() + + const contextRouter = useContext().app.router + if (contextRouter) return contextRouter + + throw new Error('This must be called within a setup function.') +} /** * Gain safe access to the redirect method from Context From 134b1466109f083a249a0fbcb8c19fc49dc391b0 Mon Sep 17 00:00:00 2001 From: daniluk4000 Date: Wed, 7 Jun 2023 18:26:12 +0300 Subject: [PATCH 10/11] Fix/Types --- src/runtime/composables/wrappers.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/runtime/composables/wrappers.ts b/src/runtime/composables/wrappers.ts index 06aba679..ecafb34f 100644 --- a/src/runtime/composables/wrappers.ts +++ b/src/runtime/composables/wrappers.ts @@ -4,6 +4,8 @@ import { useContext } from './context' import { getCurrentInstance } from './utils' import { Context } from '@nuxt/types' +import type { VueRouter } from 'vue-router/types/router' +import { useRouter as useVueRouter } from 'vue-router/composables' /** * You might want to create a custom helper to 'convert' a non-Composition API property to a Composition-ready one. `wrapProperty` enables you to do that easily, returning either a computed or a bare property as required. @@ -66,8 +68,7 @@ export const wrapContextProperty = < */ export const useRouter = (): VueRouter => { const vm = getCurrentInstance() - - if (vm) return useRouter() + if (vm) return useVueRouter() const contextRouter = useContext().app.router if (contextRouter) return contextRouter From f76a12646a78f00989e465ea3457dd64785a1fce Mon Sep 17 00:00:00 2001 From: daniluk4000 Date: Wed, 7 Jun 2023 18:27:08 +0300 Subject: [PATCH 11/11] Fix/Types --- src/runtime/composables/wrappers.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/runtime/composables/wrappers.ts b/src/runtime/composables/wrappers.ts index ecafb34f..35b77cde 100644 --- a/src/runtime/composables/wrappers.ts +++ b/src/runtime/composables/wrappers.ts @@ -4,7 +4,6 @@ import { useContext } from './context' import { getCurrentInstance } from './utils' import { Context } from '@nuxt/types' -import type { VueRouter } from 'vue-router/types/router' import { useRouter as useVueRouter } from 'vue-router/composables' /**