feat(nitro): support for rendering ssr teleports to body (#3909)

Co-authored-by: pooya parsa <pyapar@gmail.com>
This commit is contained in:
Daniel Roe 2022-04-01 15:06:48 +01:00 committed by GitHub
parent ade3378a00
commit fdd38f958c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 141 additions and 2 deletions

View File

@ -16,6 +16,7 @@
"vue/multi-word-component-names": "off", "vue/multi-word-component-names": "off",
"vue/one-component-per-file": "off", "vue/one-component-per-file": "off",
"vue/require-default-prop": "off", "vue/require-default-prop": "off",
"vue/no-multiple-template-root": "off",
"jsdoc/require-jsdoc": "off", "jsdoc/require-jsdoc": "off",
"jsdoc/require-param": "off", "jsdoc/require-param": "off",
"jsdoc/require-returns": "off", "jsdoc/require-returns": "off",

View File

@ -0,0 +1,34 @@
# Teleports
Vue 3 provides the [`<Teleport>` component](https://vuejs.org/guide/built-ins/teleport.html) which allows content to be rendered elsewhere in the DOM, outside of the Vue application.
The `to` target of `<Teleport>` expects a CSS selector string or an actual DOM node. Nuxt currently has SSR support for teleports to `body` only, with client-side support for other targets using a `<ClientOnly>` wrapper.
## Example: body teleport
```vue
<template>
<button @click="open = true">
Open Modal
</button>
<Teleport to="body">
<div v-if="open" class="modal">
<p>Hello from the modal!</p>
<button @click="open = false">
Close
</button>
</div>
</Teleport>
</template>
```
## Example: client-side teleport
```vue
<ClientOnly>
<Teleport to="#some-selector">
<!-- content -->
</Teleport>
</ClientOnly>
</template>
```

View File

@ -0,0 +1,15 @@
---
template: Example
---
# Teleport
Vue 3 provides the [`<Teleport>` component](https://vuejs.org/guide/built-ins/teleport.html) which allows content to be rendered elsewhere in the DOM, outside of the Vue application.
This example shows how to use the `<Teleport>` with client-side and server-side rendering.
::alert{type=info icon=👉}
Learn more about [teleports](/docs/usage/teleports).
::
::sandbox{repo="nuxt/framework" branch="main" dir="examples/app/teleport" file="app.vue"}

View File

@ -0,0 +1,22 @@
<template>
<NuxtExampleLayout example="app/teleport">
<div>
<!-- SSR Teleport -->
<Teleport to="body">
SSR Teleport
</Teleport>
<!-- Client Teleport -->
<ClientOnly>
<Teleport to="body">
<div>
Hello from a client-side teleport!
</div>
</Teleport>
</ClientOnly>
<!-- Modal Example -->
<MyModal />
</div>
</NuxtExampleLayout>
</template>

View File

@ -0,0 +1,34 @@
<script>
export default {
data () {
return {
open: false
}
}
}
</script>
<template>
<NButton @click="open = true">
Open Modal
</NButton>
<Teleport to="body">
<NCard v-if="open" class="modal p4">
<p>Hello from the modal!</p>
<NButton @click="open = false">
Close
</NButton>
</NCard>
</Teleport>
</template>
<style scoped>
.modal {
position: fixed;
z-index: 999;
top: 20%;
left: 50%;
width: 300px;
margin-left: -150px;
}
</style>

View File

@ -0,0 +1,7 @@
import { defineNuxtConfig } from 'nuxt3'
export default defineNuxtConfig({
modules: [
'@nuxt/ui'
]
})

View File

@ -0,0 +1,13 @@
{
"name": "example-teleport",
"private": true,
"scripts": {
"build": "nuxi build",
"dev": "nuxi dev",
"start": "nuxi preview"
},
"devDependencies": {
"@nuxt/ui": "npm:@nuxt/ui-edge@latest",
"nuxt3": "latest"
}
}

View File

@ -0,0 +1,3 @@
{
"extends": "./.nuxt/tsconfig.json"
}

View File

@ -158,6 +158,7 @@ async function renderHTML (payload, rendered, ssrContext) {
HEAD: headTags + HEAD: headTags +
rendered.renderResourceHints() + rendered.renderStyles() + (ssrContext.styles || ''), rendered.renderResourceHints() + rendered.renderStyles() + (ssrContext.styles || ''),
BODY_ATTRS: bodyAttrs, BODY_ATTRS: bodyAttrs,
BODY_PREPEND: ssrContext.teleports?.body || '',
APP: bodyScriptsPrepend + html + state + rendered.renderScripts() + bodyScripts APP: bodyScriptsPrepend + html + state + rendered.renderScripts() + bodyScripts
}) })
} }
@ -171,7 +172,7 @@ function _interopDefault (e) {
} }
function cachedImport <M> (importer: () => Promise<M>) { function cachedImport <M> (importer: () => Promise<M>) {
return cachedResult(() => importer().then(_interopDefault)) return cachedResult(() => importer().then(_interopDefault)) as () => Promise<M>
} }
function cachedResult <T> (fn: () => Promise<T>): () => Promise<T> { function cachedResult <T> (fn: () => Promise<T>): () => Promise<T> {

View File

@ -80,7 +80,7 @@ export const appViewTemplate = {
{{ HEAD }} {{ HEAD }}
</head> </head>
<body {{ BODY_ATTRS }}> <body {{ BODY_ATTRS }}>{{ BODY_PREPEND }}
{{ APP }} {{ APP }}
</body> </body>

View File

@ -10592,6 +10592,15 @@ __metadata:
languageName: unknown languageName: unknown
linkType: soft linkType: soft
"example-teleport@workspace:examples/app/teleport":
version: 0.0.0-use.local
resolution: "example-teleport@workspace:examples/app/teleport"
dependencies:
"@nuxt/ui": "npm:@nuxt/ui-edge@latest"
nuxt3: latest
languageName: unknown
linkType: soft
"example-test@workspace:examples/advanced/test": "example-test@workspace:examples/advanced/test":
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "example-test@workspace:examples/advanced/test" resolution: "example-test@workspace:examples/advanced/test"