From 1f0a9d1b2f3b6b0fc547854eada770a6d1a8399d Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 16 Aug 2022 09:39:37 +0200 Subject: [PATCH 01/10] feat(nuxt): allow programmatically prefetching global components --- packages/nuxt/src/app/nuxt.ts | 1 + packages/nuxt/src/components/templates.ts | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/nuxt/src/app/nuxt.ts b/packages/nuxt/src/app/nuxt.ts index b24bda3c8d5..1e729eb467a 100644 --- a/packages/nuxt/src/app/nuxt.ts +++ b/packages/nuxt/src/app/nuxt.ts @@ -31,6 +31,7 @@ export interface RuntimeNuxtHooks { 'app:error': (err: any) => HookResult 'app:error:cleared': (options: { redirect?: string }) => HookResult 'app:data:refresh': (keys?: string[]) => HookResult + 'components:prefetch': (component: string | string[]) => HookResult 'page:start': (Component?: VNode) => HookResult 'page:finish': (Component?: VNode) => HookResult 'meta:register': (metaRenderers: Array<(nuxt: NuxtApp) => NuxtMeta | Promise>) => HookResult diff --git a/packages/nuxt/src/components/templates.ts b/packages/nuxt/src/components/templates.ts index bae8b67d0a0..c01958d3d42 100644 --- a/packages/nuxt/src/components/templates.ts +++ b/packages/nuxt/src/components/templates.ts @@ -11,7 +11,7 @@ export interface ComponentsTemplateContext { } export type ImportMagicCommentsOptions = { - chunkName:string + chunkName: string prefetch?: boolean | number preload?: boolean | number } @@ -40,7 +40,21 @@ const components = ${genObjectFromRawEntries(globalComponents.map((c) => { return [c.pascalName, `defineAsyncComponent(${genDynamicImport(c.filePath, { comment })}.then(c => ${exp}))`] }))} +function prefetchAsyncComponent (component) { + if (component?.__asyncLoader && !component.__asyncResolved) { + return component.__asyncLoader() + } +} + export default defineNuxtPlugin(nuxtApp => { + nuxtApp.hook('components:prefetch', async (components) => { + components = Array.isArray(components) ? components : [components] + await Promise.all(components.map((name) => { + if (name in components) { + return prefetchAsyncComponent(components[name]) + } + })) + }) for (const name in components) { nuxtApp.vueApp.component(name, components[name]) nuxtApp.vueApp.component('Lazy' + name, components[name]) From 0f93478298f5486d1b1a3837096b014bf6d98d56 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 16 Aug 2022 09:44:40 +0200 Subject: [PATCH 02/10] fix: wrap in client-only --- packages/nuxt/src/components/templates.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/nuxt/src/components/templates.ts b/packages/nuxt/src/components/templates.ts index c01958d3d42..3dff451df45 100644 --- a/packages/nuxt/src/components/templates.ts +++ b/packages/nuxt/src/components/templates.ts @@ -47,14 +47,16 @@ function prefetchAsyncComponent (component) { } export default defineNuxtPlugin(nuxtApp => { - nuxtApp.hook('components:prefetch', async (components) => { - components = Array.isArray(components) ? components : [components] - await Promise.all(components.map((name) => { - if (name in components) { - return prefetchAsyncComponent(components[name]) - } - })) - }) + if (process.client) { + nuxtApp.hook('components:prefetch', async (components) => { + components = Array.isArray(components) ? components : [components] + await Promise.all(components.map((name) => { + if (name in components) { + return prefetchAsyncComponent(components[name]) + } + })) + }) + } for (const name in components) { nuxtApp.vueApp.component(name, components[name]) nuxtApp.vueApp.component('Lazy' + name, components[name]) From 9882d61f0755687a18b42a73834265695a953760 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 16 Aug 2022 10:26:41 +0200 Subject: [PATCH 03/10] feat: expose `prefetchComponents` utility for use on client-side --- .../3.api/1.composables/prefetch-components.md | 17 +++++++++++++++++ packages/nuxt/src/app/composables/index.ts | 1 + packages/nuxt/src/app/composables/prefetch.ts | 14 ++++++++++++++ packages/nuxt/src/auto-imports/presets.ts | 3 ++- 4 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 docs/content/3.api/1.composables/prefetch-components.md create mode 100644 packages/nuxt/src/app/composables/prefetch.ts diff --git a/docs/content/3.api/1.composables/prefetch-components.md b/docs/content/3.api/1.composables/prefetch-components.md new file mode 100644 index 00000000000..112382612e3 --- /dev/null +++ b/docs/content/3.api/1.composables/prefetch-components.md @@ -0,0 +1,17 @@ +# `prefetchComponents` + +Nuxt provides composables and utilities to give you fine-grained control over prefetching JavaScript in your client application. + +You can use `prefetchComponents` to manually prefetch individual components that have been registered globally in your Nuxt app. (By default Nuxt registers these as async components.) You must use the Pascal-cased version of the component name. + +`prefetchComponents` can only be called within component setup functions, plugins, and route middleware. + +```js +await prefetchComponents('MyGlobalComponent') + +await prefetchComponents(['MyGlobalComponent1', 'MyGlobalComponent2']) +``` + +::alert{icon=👉} +On server, `prefetchComponents` will have no effect. +:: diff --git a/packages/nuxt/src/app/composables/index.ts b/packages/nuxt/src/app/composables/index.ts index 92bcf1b0d19..bd4a22e5d5a 100644 --- a/packages/nuxt/src/app/composables/index.ts +++ b/packages/nuxt/src/app/composables/index.ts @@ -12,3 +12,4 @@ export type { CookieOptions, CookieRef } from './cookie' export { useRequestHeaders, useRequestEvent, setResponseStatus } from './ssr' export { abortNavigation, addRouteMiddleware, defineNuxtRouteMiddleware, navigateTo, useRoute, useActiveRoute, useRouter } from './router' export type { AddRouteMiddlewareOptions, RouteMiddleware } from './router' +export { prefetchComponents } from './prefetch' diff --git a/packages/nuxt/src/app/composables/prefetch.ts b/packages/nuxt/src/app/composables/prefetch.ts new file mode 100644 index 00000000000..b26e1d186ff --- /dev/null +++ b/packages/nuxt/src/app/composables/prefetch.ts @@ -0,0 +1,14 @@ +import { useNuxtApp } from '#app' + +/** + * Prefetch a component or components that have been globally registered + * with the Nuxt component integration. + * + * @note This does not work for components you have globally registered yourself. + * @param components Pascal-cased name or names of components to prefetch + */ +export const prefetchComponents = (components: string | string[]) => { + if (process.server) { return } + + return useNuxtApp().callHook('components:prefetch', components) +} diff --git a/packages/nuxt/src/auto-imports/presets.ts b/packages/nuxt/src/auto-imports/presets.ts index 81d94856c4f..f36c6726859 100644 --- a/packages/nuxt/src/auto-imports/presets.ts +++ b/packages/nuxt/src/auto-imports/presets.ts @@ -49,7 +49,8 @@ export const appPreset = defineUnimportPreset({ 'isNuxtError', 'useError', 'createError', - 'defineNuxtLink' + 'defineNuxtLink', + 'prefetchComponents' ] }) From 16dd3547c814eede0b3dbba893b3afc989f03abf Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 23 Aug 2022 11:35:15 +0100 Subject: [PATCH 04/10] wip --- packages/nuxt/src/app/composables/prefetch.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/nuxt/src/app/composables/prefetch.ts b/packages/nuxt/src/app/composables/prefetch.ts index b26e1d186ff..f596b234213 100644 --- a/packages/nuxt/src/app/composables/prefetch.ts +++ b/packages/nuxt/src/app/composables/prefetch.ts @@ -1,5 +1,12 @@ +import type { Component } from 'vue' import { useNuxtApp } from '#app' +function prefetchAsyncComponent (component: Component) { + if ((component as any)?.__asyncLoader && !(component as any).__asyncResolved) { + return (component as any).__asyncLoader() + } +} + /** * Prefetch a component or components that have been globally registered * with the Nuxt component integration. @@ -7,8 +14,11 @@ import { useNuxtApp } from '#app' * @note This does not work for components you have globally registered yourself. * @param components Pascal-cased name or names of components to prefetch */ -export const prefetchComponents = (components: string | string[]) => { +export const prefetchComponents = async (components: string | string[]) => { if (process.server) { return } - return useNuxtApp().callHook('components:prefetch', components) + components = Array.isArray(components) ? components : [components] + await Promise.all(components.map((name) => { + return name in components && prefetchAsyncComponent(components[name]) + })) } From fa77b70a5a1afb536a7813e6961b7d41a8fd40eb Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 23 Aug 2022 11:40:40 +0100 Subject: [PATCH 05/10] refactor: use vue._context to prefetch components as tree-shakeable composable --- .../3.api/1.composables/prefetch-components.md | 2 -- packages/nuxt/src/app/composables/prefetch.ts | 5 ++--- packages/nuxt/src/app/nuxt.ts | 1 - packages/nuxt/src/components/templates.ts | 16 ---------------- 4 files changed, 2 insertions(+), 22 deletions(-) diff --git a/docs/content/3.api/1.composables/prefetch-components.md b/docs/content/3.api/1.composables/prefetch-components.md index 112382612e3..7539396bd7e 100644 --- a/docs/content/3.api/1.composables/prefetch-components.md +++ b/docs/content/3.api/1.composables/prefetch-components.md @@ -4,8 +4,6 @@ Nuxt provides composables and utilities to give you fine-grained control over pr You can use `prefetchComponents` to manually prefetch individual components that have been registered globally in your Nuxt app. (By default Nuxt registers these as async components.) You must use the Pascal-cased version of the component name. -`prefetchComponents` can only be called within component setup functions, plugins, and route middleware. - ```js await prefetchComponents('MyGlobalComponent') diff --git a/packages/nuxt/src/app/composables/prefetch.ts b/packages/nuxt/src/app/composables/prefetch.ts index f596b234213..a47b140fadd 100644 --- a/packages/nuxt/src/app/composables/prefetch.ts +++ b/packages/nuxt/src/app/composables/prefetch.ts @@ -16,9 +16,8 @@ function prefetchAsyncComponent (component: Component) { */ export const prefetchComponents = async (components: string | string[]) => { if (process.server) { return } + const nuxtApp = useNuxtApp() components = Array.isArray(components) ? components : [components] - await Promise.all(components.map((name) => { - return name in components && prefetchAsyncComponent(components[name]) - })) + await Promise.all(components.map(name => prefetchAsyncComponent(nuxtApp.vueApp._context.components[name]))) } diff --git a/packages/nuxt/src/app/nuxt.ts b/packages/nuxt/src/app/nuxt.ts index d70838217cf..0795cfee85e 100644 --- a/packages/nuxt/src/app/nuxt.ts +++ b/packages/nuxt/src/app/nuxt.ts @@ -31,7 +31,6 @@ export interface RuntimeNuxtHooks { 'app:error': (err: any) => HookResult 'app:error:cleared': (options: { redirect?: string }) => HookResult 'app:data:refresh': (keys?: string[]) => HookResult - 'components:prefetch': (component: string | string[]) => HookResult 'page:start': (Component?: VNode) => HookResult 'page:finish': (Component?: VNode) => HookResult 'meta:register': (metaRenderers: Array<(nuxt: NuxtApp) => NuxtMeta | Promise>) => HookResult diff --git a/packages/nuxt/src/components/templates.ts b/packages/nuxt/src/components/templates.ts index 55b86098ea8..d74119e1060 100644 --- a/packages/nuxt/src/components/templates.ts +++ b/packages/nuxt/src/components/templates.ts @@ -40,23 +40,7 @@ const components = ${genObjectFromRawEntries(globalComponents.map((c) => { return [c.pascalName, `defineAsyncComponent(${genDynamicImport(c.filePath, { comment })}.then(c => ${exp}))`] }))} -function prefetchAsyncComponent (component) { - if (component?.__asyncLoader && !component.__asyncResolved) { - return component.__asyncLoader() - } -} - export default defineNuxtPlugin(nuxtApp => { - if (process.client) { - nuxtApp.hook('components:prefetch', async (components) => { - components = Array.isArray(components) ? components : [components] - await Promise.all(components.map((name) => { - if (name in components) { - return prefetchAsyncComponent(components[name]) - } - })) - }) - } for (const name in components) { nuxtApp.vueApp.component(name, components[name]) nuxtApp.vueApp.component('Lazy' + name, components[name]) From 0c759b9b718216296234189a8000a68e91bebc66 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 23 Aug 2022 11:50:36 +0100 Subject: [PATCH 06/10] test: add fixture --- test/basic.test.ts | 6 ++++++ test/fixtures/basic/pages/prefetch/components.vue | 9 +++++++++ 2 files changed, 15 insertions(+) create mode 100644 test/fixtures/basic/pages/prefetch/components.vue diff --git a/test/basic.test.ts b/test/basic.test.ts index cee7e7c7e82..e69626feb02 100644 --- a/test/basic.test.ts +++ b/test/basic.test.ts @@ -360,6 +360,12 @@ describe('automatically keyed composables', () => { }) }) +describe('prefetching', () => { + it('should prefetch components', async () => { + await expectNoClientErrors('/prefetch/components') + }) +}) + if (process.env.NUXT_TEST_DEV) { describe('detecting invalid root nodes', () => { it('should detect invalid root nodes in pages', async () => { diff --git a/test/fixtures/basic/pages/prefetch/components.vue b/test/fixtures/basic/pages/prefetch/components.vue new file mode 100644 index 00000000000..18ab2001b4f --- /dev/null +++ b/test/fixtures/basic/pages/prefetch/components.vue @@ -0,0 +1,9 @@ + + + From a07ca97d2589e65d0d4b9de8fa53ffc7770030f9 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 23 Aug 2022 11:54:34 +0100 Subject: [PATCH 07/10] test: await prefetch --- test/fixtures/basic/pages/prefetch/components.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/fixtures/basic/pages/prefetch/components.vue b/test/fixtures/basic/pages/prefetch/components.vue index 18ab2001b4f..6b9fe525f0e 100644 --- a/test/fixtures/basic/pages/prefetch/components.vue +++ b/test/fixtures/basic/pages/prefetch/components.vue @@ -1,5 +1,5 @@