From 6fe41f703a472eab6a16f4b40001fbcbd0b6b6ed Mon Sep 17 00:00:00 2001 From: Dan Popescu Date: Thu, 6 Jun 2024 15:17:04 +0300 Subject: [PATCH] feat: replace `ClientOnly` component with `clientOnly` function with TS support #67, #82 BREAKING CHANGE: `ClientOnly` component is removed, please use the new `clientOnly` function to create components that only load and render on client side --- examples/full/readme.md | 2 +- packages/vike-vue/README.md | 6 +-- packages/vike-vue/package.json | 11 ++-- .../vike-vue/src/components/ClientOnly.vue | 36 ------------- packages/vike-vue/src/utils/clientOnly.ts | 53 +++++++++++++++++++ packages/vike-vue/vite.config.js | 2 +- 6 files changed, 62 insertions(+), 48 deletions(-) delete mode 100644 packages/vike-vue/src/components/ClientOnly.vue create mode 100644 packages/vike-vue/src/utils/clientOnly.ts diff --git a/examples/full/readme.md b/examples/full/readme.md index 8d496120..01d4b119 100644 --- a/examples/full/readme.md +++ b/examples/full/readme.md @@ -2,7 +2,7 @@ Full-fledged example of using `vike-vue`, showcasing: - [Layout](https://vike.dev/Layout) - Fetching data with [`data()`](https://vike.dev/data) -- [``](https://vike.dev/ClientOnly) +- [`clientOnly`](https://vike.dev/clientOnly) - [Toggling SSR](https://vike.dev/ssr) on a per-page basis. - [Markdown](https://vike.dev/markdown) - [Route Function](https://vike.dev/route-function) diff --git a/packages/vike-vue/README.md b/packages/vike-vue/README.md index ccf6c0f1..f0f5a649 100644 --- a/packages/vike-vue/README.md +++ b/packages/vike-vue/README.md @@ -97,11 +97,11 @@ All hooks are [cumulative](https://vike.dev/meta#api), so you can add your own h * [`usePageContext()`](https://vike.dev/usePageContext): Access the [`pageContext` object](https://vike.dev/pageContext) from any component. -## Components +## Utilities -`vike-vue` introduces the following new components: +`vike-vue` introduces the following new utility functions: -* [`ClientOnly`](https://vike.dev/ClientOnly): Wrapper to render and load a component only on the client-side. +* [`clientOnly`](https://vike.dev/clientOnly): Creates a wrapper component to load and render a component only on the client-side. ## Teleports diff --git a/packages/vike-vue/package.json b/packages/vike-vue/package.json index 82c2d5a0..4d82cc2d 100644 --- a/packages/vike-vue/package.json +++ b/packages/vike-vue/package.json @@ -11,10 +11,7 @@ "./renderer/onRenderClient": "./dist/renderer/onRenderClient.js", "./usePageContext": "./dist/hooks/usePageContext.js", "./useData": "./dist/hooks/useData.js", - "./ClientOnly": { - "default": "./dist/components/ClientOnly.js", - "types": "./dist/components/ClientOnly.vue.d.ts" - }, + "./clientOnly": "./dist/utils/clientOnly.js", "./types": { "default": "./dist/types/index.js", "types": "./dist/types/index.d.ts" @@ -60,14 +57,14 @@ "useData": [ "./dist/hooks/useData.d.ts" ], + "clientOnly": [ + "./dist/utils/clientOnly.d.ts" + ], "renderer/onRenderHtml": [ "./dist/renderer/onRenderHtml.d.ts" ], "renderer/onRenderClient": [ "./dist/renderer/onRenderClient.d.ts" - ], - "ClientOnly": [ - "./dist/components/ClientOnly.vue.d.ts" ] } }, diff --git a/packages/vike-vue/src/components/ClientOnly.vue b/packages/vike-vue/src/components/ClientOnly.vue deleted file mode 100644 index 3fffeb06..00000000 --- a/packages/vike-vue/src/components/ClientOnly.vue +++ /dev/null @@ -1,36 +0,0 @@ - - - diff --git a/packages/vike-vue/src/utils/clientOnly.ts b/packages/vike-vue/src/utils/clientOnly.ts new file mode 100644 index 00000000..2a524141 --- /dev/null +++ b/packages/vike-vue/src/utils/clientOnly.ts @@ -0,0 +1,53 @@ +import { h, shallowRef, defineComponent, onBeforeMount } from 'vue' +import type { Component, SlotsType } from 'vue' + +type MaybePromise = T | Promise +type ComponentResolved = MaybePromise + +type ClientOnlySlots = { + fallback?: {}; + 'client-only-fallback'?: {}; +} + +export function clientOnly( + source: ComponentResolved | (() => ComponentResolved), +) { + const clientOnlyComponent = defineComponent({ + inheritAttrs: false, + + setup(_, { attrs, slots }) { + const resolvedComp = shallowRef(null) + + onBeforeMount(() => { + const loader = source instanceof Function ? source : () => source + Promise.resolve(loader()) + .then((component) => { + resolvedComp.value = 'default' in component ? component.default : component + }) + .catch((e) => { + console.error('Component loading failed:', e) + throw e + }) + }) + + const cleanSlots = (slots: ClientOnlySlots) => { + const cleaned = { ...slots } + if (slots[ 'client-only-fallback' ]) { + delete cleaned['client-only-fallback'] + } else { + delete cleaned.fallback + } + return cleaned + } + + return () => + resolvedComp.value !== null + ? h(resolvedComp.value, attrs, cleanSlots(slots as ClientOnlySlots)) + : slots['client-only-fallback']?.() + }, + + slots: Object as SlotsType, + }) + + return clientOnlyComponent as typeof clientOnlyComponent & T +} diff --git a/packages/vike-vue/vite.config.js b/packages/vike-vue/vite.config.js index 8d145c0b..953c5275 100644 --- a/packages/vike-vue/vite.config.js +++ b/packages/vike-vue/vite.config.js @@ -14,10 +14,10 @@ export default defineConfig({ ['+config']: resolve(__dirname, './src/+config.ts'), ['renderer/onRenderClient']: resolve(__dirname, './src/renderer/onRenderClient.ts'), ['renderer/onRenderHtml']: resolve(__dirname, './src/renderer/onRenderHtml.ts'), + ['utils/clientOnly']: resolve(__dirname, './src/utils/clientOnly.ts'), ['types/index']: resolve(__dirname, './src/types/index.ts'), ['hooks/usePageContext']: resolve(__dirname, './src/hooks/usePageContext.ts'), ['hooks/useData']: resolve(__dirname, './src/hooks/useData.ts'), - ['components/ClientOnly']: resolve(__dirname, './src/components/ClientOnly.vue'), }, formats: ['es'], },