-
-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(nuxt):
usePreviewMode
composable (#21705)
- Loading branch information
1 parent
f0442d0
commit 98aa2c2
Showing
11 changed files
with
361 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
--- | ||
title: "usePreviewMode" | ||
description: "Use usePreviewMode to check and control preview mode in Nuxt" | ||
--- | ||
|
||
# `usePreviewMode` | ||
|
||
You can use the built-in `usePreviewMode` composable to access and control preview state in Nuxt. If the composable detects preview mode it will automatically force any updates necessary for [`useAsyncData`](/docs/api/composables/use-async-data) and [`useFetch`](/docs/api/composables/use-fetch) to rerender preview content. | ||
|
||
```js | ||
const { enabled, state } = usePreviewMode() | ||
``` | ||
|
||
## Options | ||
|
||
### Custom `enable` check | ||
|
||
You can specify a custom way to enable preview mode. By default the `usePreviewMode` composable will enable preview mode if there is a `preview` param in url that is equal to `true` (for example, `http://localhost:3000?preview=true`). You can wrap the `usePreviewMode` into custom composable, to keep options consistent across usages and prevent any errors. | ||
|
||
```js | ||
export function useMyPreviewMode () { | ||
return usePreviewMode({ | ||
shouldEnable: () => { | ||
return !!route.query.customPreview | ||
} | ||
}); | ||
}``` | ||
|
||
### Modify default state | ||
|
||
`usePreviewMode` will try to store the value of a `token` param from url in state. You can modify this state and it will be available for all [`usePreviewMode`](/docs/api/composables/use-preview-mode) calls. | ||
|
||
```js | ||
const data1 = ref('data1') | ||
|
||
const { enabled, state } = usePreviewMode({ | ||
getState: (currentState) => { | ||
return { data1, data2: 'data2' } | ||
} | ||
}) | ||
``` | ||
|
||
::alert{icon=👉} | ||
The `getState` function will append returned values to current state, so be careful not to accidentally overwrite important state. | ||
:: | ||
|
||
## Example | ||
|
||
```vue [pages/some-page.vue] | ||
<script setup> | ||
const route = useRoute() | ||
const { enabled, state } = usePreviewMode({ | ||
shouldEnable: () => { | ||
return route.query.customPreview === 'true' | ||
}, | ||
}) | ||
const { data } = await useFetch('/api/preview', { | ||
query: { | ||
apiKey: state.token | ||
} | ||
}) | ||
</script> | ||
<template> | ||
<div> | ||
Some base content | ||
<p v-if="enabled"> | ||
Only preview content: {{ state.token }} | ||
<br> | ||
<button @click="enabled = false"> | ||
disable preview mode | ||
</button> | ||
</p> | ||
</div> | ||
</template> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import { toRef, watch } from 'vue' | ||
|
||
import { useState } from './state' | ||
import { refreshNuxtData } from './asyncData' | ||
import { useRoute, useRouter } from './router' | ||
|
||
interface Preview { | ||
enabled: boolean | ||
state: Record<any, unknown> | ||
_initialized?: boolean | ||
} | ||
|
||
interface PreviewModeOptions<S> { | ||
shouldEnable?: (state: Preview['state']) => boolean, | ||
getState?: (state: Preview['state']) => S, | ||
} | ||
|
||
type EnteredState = Record<any, unknown> | null | undefined | void | ||
|
||
let unregisterRefreshHook: (() => any) | undefined | ||
|
||
/** @since 3.11.0 */ | ||
export function usePreviewMode<S extends EnteredState> (options: PreviewModeOptions<S> = {}) { | ||
const preview = useState<Preview>('_preview-state', () => ({ | ||
enabled: false, | ||
state: {} | ||
})) | ||
|
||
if (preview.value._initialized) { | ||
return { | ||
enabled: toRef(preview.value, 'enabled'), | ||
state: preview.value.state as S extends void ? Preview['state'] : (NonNullable<S> & Preview['state']), | ||
} | ||
} | ||
|
||
if (import.meta.client) { | ||
preview.value._initialized = true | ||
} | ||
|
||
if (!preview.value.enabled) { | ||
const shouldEnable = options.shouldEnable ?? defaultShouldEnable | ||
const result = shouldEnable(preview.value.state) | ||
|
||
if (typeof result === 'boolean') { preview.value.enabled = result } | ||
} | ||
|
||
watch(() => preview.value.enabled, (value) => { | ||
if (value) { | ||
const getState = options.getState ?? getDefaultState | ||
const newState = getState(preview.value.state) | ||
|
||
if (newState !== preview.value.state) { | ||
Object.assign(preview.value.state, newState) | ||
} | ||
|
||
if (import.meta.client && !unregisterRefreshHook) { | ||
refreshNuxtData() | ||
|
||
unregisterRefreshHook = useRouter().afterEach((() => refreshNuxtData())) | ||
} | ||
} else if (unregisterRefreshHook) { | ||
unregisterRefreshHook() | ||
|
||
unregisterRefreshHook = undefined | ||
} | ||
}, { immediate: true, flush: 'sync' }) | ||
|
||
return { | ||
enabled: toRef(preview.value, 'enabled'), | ||
state: preview.value.state as S extends void ? Preview['state'] : (NonNullable<S> & Preview['state']), | ||
} | ||
} | ||
|
||
function defaultShouldEnable () { | ||
const route = useRoute() | ||
const previewQueryName = 'preview' | ||
|
||
return route.query[previewQueryName] === 'true' | ||
} | ||
|
||
function getDefaultState (state: Preview['state']) { | ||
if (state.token !== undefined) { | ||
return state | ||
} | ||
|
||
const route = useRoute() | ||
|
||
state.token = Array.isArray(route.query.token) ? route.query.token[0] : route.query.token | ||
|
||
return state | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
<script setup> | ||
const { enabled: isPreview } = usePreviewMode() | ||
const { data } = await useAsyncData(async () => { | ||
await new Promise(resolve => setTimeout(resolve, 200)) | ||
const fetchedOnClient = process.client | ||
console.log(fetchedOnClient) | ||
return { fetchedOnClient } | ||
}) | ||
</script> | ||
|
||
<template> | ||
<div> | ||
<NuxtLink | ||
id="use-fetch-check" | ||
href="/preview/with-use-fetch" | ||
> | ||
check useFetch | ||
</NuxtLink> | ||
|
||
<p | ||
v-if="data && data.fetchedOnClient" | ||
id="fetched-on-client" | ||
> | ||
fetched on client | ||
</p> | ||
|
||
<p | ||
v-if="isPreview" | ||
id="preview-mode" | ||
> | ||
preview mode enabled | ||
</p> | ||
</div> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<script setup> | ||
const route = useRoute() | ||
const { enabled } = usePreviewMode({ | ||
shouldEnable: () => { | ||
return !!route.query.customPreview | ||
} | ||
}) | ||
</script> | ||
|
||
<template> | ||
<div> | ||
<p id="enabled"> | ||
{{ enabled }} | ||
</p> | ||
</div> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
<script setup> | ||
const data1 = ref('data1') | ||
const { enabled, state } = usePreviewMode({ | ||
getState: () => { | ||
return { data1, data2: 'data2' } | ||
} | ||
}) | ||
onMounted(() => { | ||
data1.value = 'data1 updated' | ||
}) | ||
</script> | ||
|
||
<template> | ||
<div> | ||
<NuxtLink | ||
id="with-use-fetch" | ||
to="/preview/with-use-fetch" | ||
> | ||
fetch check | ||
</NuxtLink> | ||
|
||
<p id="data1"> | ||
{{ state.data1 }} | ||
</p> | ||
|
||
<p id="data2"> | ||
{{ state.data2 }} | ||
</p> | ||
|
||
<button | ||
id="toggle-preview" | ||
@click="enabled = !enabled" | ||
> | ||
toggle preview mode | ||
</button> | ||
</div> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<script setup> | ||
const { enabled, state } = usePreviewMode() | ||
const { data } = await useFetch('/api/preview', { | ||
query: { | ||
apiKey: state.token || undefined | ||
} | ||
}) | ||
</script> | ||
|
||
<template> | ||
<div> | ||
<p id="enabled"> | ||
{{ enabled }} | ||
</p> | ||
|
||
<p id="token-check"> | ||
{{ state.token }} | ||
</p> | ||
|
||
<p id="correct-api-key-check"> | ||
{{ data && data.hehe }} | ||
</p> | ||
</div> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
const apiKeyName = 'apiKey' | ||
const apiKey = 'hehe' | ||
|
||
export default defineEventHandler((event) => { | ||
return { | ||
hehe: getQuery(event)[apiKeyName] === apiKey | ||
} | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters