mirror of
https://github.com/nuxt/nuxt.git
synced 2025-02-24 01:19:58 +00:00
feat: introduce dangerouslyLoadCLientComponent
This commit is contained in:
parent
15782f7a71
commit
a3f76bfcb6
@ -27,6 +27,7 @@ const SLOT_FALLBACK_RE = /<div nuxt-slot-fallback-start="([^"]*)"[^>]*><\/div>((
|
|||||||
|
|
||||||
let id = 0
|
let id = 0
|
||||||
const getId = import.meta.client ? () => (id++).toString() : randomUUID
|
const getId = import.meta.client ? () => (id++).toString() : randomUUID
|
||||||
|
|
||||||
const components = import.meta.client ? new Map<string, Component>() : undefined
|
const components = import.meta.client ? new Map<string, Component>() : undefined
|
||||||
|
|
||||||
async function loadComponents (source = '/', paths: Record<string, string>) {
|
async function loadComponents (source = '/', paths: Record<string, string>) {
|
||||||
@ -63,15 +64,22 @@ export default defineComponent({
|
|||||||
source: {
|
source: {
|
||||||
type: String,
|
type: String,
|
||||||
default: () => undefined
|
default: () => undefined
|
||||||
|
},
|
||||||
|
dangerouslyLoadClientComponents: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async setup (props, { slots }) {
|
async setup (props, { slots }) {
|
||||||
|
const key = ref(0)
|
||||||
|
const canLoadClientComponent = computed(() => props.dangerouslyLoadClientComponents || !props.source)
|
||||||
const error = ref<unknown>(null)
|
const error = ref<unknown>(null)
|
||||||
const config = useRuntimeConfig()
|
const config = useRuntimeConfig()
|
||||||
const nuxtApp = useNuxtApp()
|
const nuxtApp = useNuxtApp()
|
||||||
const hashId = computed(() => hash([props.name, props.props, props.context, props.source]))
|
const hashId = computed(() => hash([props.name, props.props, props.context, props.source]))
|
||||||
const instance = getCurrentInstance()!
|
const instance = getCurrentInstance()!
|
||||||
const event = useRequestEvent()
|
const event = useRequestEvent()
|
||||||
|
|
||||||
// TODO: remove use of `$fetch.raw` when nitro 503 issues on windows dev server are resolved
|
// TODO: remove use of `$fetch.raw` when nitro 503 issues on windows dev server are resolved
|
||||||
const eventFetch = import.meta.server ? event.fetch : import.meta.dev ? $fetch.raw : globalThis.fetch
|
const eventFetch = import.meta.server ? event.fetch : import.meta.dev ? $fetch.raw : globalThis.fetch
|
||||||
const mounted = ref(false)
|
const mounted = ref(false)
|
||||||
@ -90,6 +98,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ssrHTML = ref<string>('')
|
const ssrHTML = ref<string>('')
|
||||||
|
|
||||||
if (import.meta.client) {
|
if (import.meta.client) {
|
||||||
const renderedHTML = getFragmentHTML(instance.vnode?.el ?? null).join('')
|
const renderedHTML = getFragmentHTML(instance.vnode?.el ?? null).join('')
|
||||||
if (renderedHTML && nuxtApp.isHydrating) {
|
if (renderedHTML && nuxtApp.isHydrating) {
|
||||||
@ -107,18 +116,25 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
ssrHTML.value = renderedHTML
|
ssrHTML.value = renderedHTML
|
||||||
}
|
}
|
||||||
|
|
||||||
const slotProps = computed(() => getSlotProps(ssrHTML.value))
|
const slotProps = computed(() => getSlotProps(ssrHTML.value))
|
||||||
const uid = ref<string>(ssrHTML.value.match(SSR_UID_RE)?.[1] ?? randomUUID())
|
const uid = ref<string>(ssrHTML.value.match(SSR_UID_RE)?.[1] ?? randomUUID())
|
||||||
const availableSlots = computed(() => [...ssrHTML.value.matchAll(SLOTNAME_RE)].map(m => m[1]))
|
const availableSlots = computed(() => [...ssrHTML.value.matchAll(SLOTNAME_RE)].map(m => m[1]))
|
||||||
|
|
||||||
// no need for reactivity
|
// no need for reactivity
|
||||||
// eslint-disable-next-line vue/no-setup-props-destructure
|
const interactivePayload: Pick<NuxtIslandResponse, 'chunks' | 'props' | 'teleports'> = import.meta.client && nuxtApp.isHydrating ? toRaw(nuxtApp.payload.data)[`${props.name}_${hashId.value}_interactive`] : {}
|
||||||
let interactiveProps: Record<string, Record<string, any>> = import.meta.client && nuxtApp.isHydrating ? toRaw(nuxtApp.payload.data[`${props.name}_${hashId.value}_interactive`].props) : {}
|
|
||||||
const interactiveChunksList = import.meta.client && nuxtApp.isHydrating ? nuxtApp.payload.data[`${props.name}_${hashId.value}_interactive`].chunks : {}
|
|
||||||
let interactiveTeleports: Record<string, string> = {}
|
|
||||||
const html = computed(() => {
|
const html = computed(() => {
|
||||||
const currentSlots = Object.keys(slots)
|
const currentSlots = Object.keys(slots)
|
||||||
const html = ssrHTML.value
|
let html = ssrHTML.value
|
||||||
|
|
||||||
|
if (import.meta.client && !canLoadClientComponent.value && Object.keys(interactivePayload.teleports).length) {
|
||||||
|
for (const key in interactivePayload.teleports) {
|
||||||
|
html = html.replace(new RegExp(`<div [^>]*nuxt-ssr-client="${key}"[^>]*>`), (full) => {
|
||||||
|
return full + interactivePayload.teleports[key]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return html.replace(SLOT_FALLBACK_RE, (full, slotName, content) => {
|
return html.replace(SLOT_FALLBACK_RE, (full, slotName, content) => {
|
||||||
// remove fallback to insert slots
|
// remove fallback to insert slots
|
||||||
@ -128,6 +144,7 @@ export default defineComponent({
|
|||||||
return content
|
return content
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
function setUid () {
|
function setUid () {
|
||||||
uid.value = ssrHTML.value.match(SSR_UID_RE)?.[1] ?? getId() as string
|
uid.value = ssrHTML.value.match(SSR_UID_RE)?.[1] ?? getId() as string
|
||||||
}
|
}
|
||||||
@ -162,7 +179,7 @@ export default defineComponent({
|
|||||||
setPayload(key, result)
|
setPayload(key, result)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
const key = ref(0)
|
|
||||||
async function fetchComponent (force = false) {
|
async function fetchComponent (force = false) {
|
||||||
nuxtApp[pKey] = nuxtApp[pKey] || {}
|
nuxtApp[pKey] = nuxtApp[pKey] || {}
|
||||||
if (!nuxtApp[pKey][uid.value]) {
|
if (!nuxtApp[pKey][uid.value]) {
|
||||||
@ -179,21 +196,25 @@ export default defineComponent({
|
|||||||
})
|
})
|
||||||
key.value++
|
key.value++
|
||||||
error.value = null
|
error.value = null
|
||||||
|
|
||||||
|
nuxtApp.payload.data[`${props.name}_${hashId.value}_interactive`] = {
|
||||||
|
chunks: res.chunks,
|
||||||
|
props: res.props,
|
||||||
|
teleports: res.teleports
|
||||||
|
}
|
||||||
|
if (import.meta.client) {
|
||||||
|
if (canLoadClientComponent.value) {
|
||||||
|
await loadComponents(props.source, res.chunks)
|
||||||
|
}
|
||||||
|
interactivePayload.props = res.props
|
||||||
|
}
|
||||||
|
interactivePayload.teleports = res.teleports
|
||||||
|
interactivePayload.chunks = res.chunks
|
||||||
|
|
||||||
if (import.meta.client) {
|
if (import.meta.client) {
|
||||||
// must await next tick for Teleport to work correctly with static node re-rendering
|
// must await next tick for Teleport to work correctly with static node re-rendering
|
||||||
await nextTick()
|
await nextTick()
|
||||||
}
|
}
|
||||||
nuxtApp.payload.data[`${props.name}_${hashId.value}_interactive`] = {
|
|
||||||
chunks: res.chunks,
|
|
||||||
props: res.props
|
|
||||||
}
|
|
||||||
|
|
||||||
if (import.meta.client) {
|
|
||||||
await loadComponents(props.source, res.chunks)
|
|
||||||
interactiveProps = res.props
|
|
||||||
} else {
|
|
||||||
interactiveTeleports = res.teleports || {}
|
|
||||||
}
|
|
||||||
|
|
||||||
setUid()
|
setUid()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -215,8 +236,8 @@ export default defineComponent({
|
|||||||
fetchComponent()
|
fetchComponent()
|
||||||
} else if (import.meta.server || !nuxtApp.isHydrating) {
|
} else if (import.meta.server || !nuxtApp.isHydrating) {
|
||||||
await fetchComponent()
|
await fetchComponent()
|
||||||
} else if (nuxtApp.isHydrating) {
|
} else if (nuxtApp.isHydrating && canLoadClientComponent.value) {
|
||||||
await loadComponents(props.source, interactiveChunksList)
|
await loadComponents(props.source, interactivePayload.chunks)
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@ -237,14 +258,14 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (import.meta.server) {
|
if (import.meta.server) {
|
||||||
for (const [id, html] of Object.entries(interactiveTeleports)) {
|
for (const [id, html] of Object.entries(interactivePayload.teleports)) {
|
||||||
nodes.push(createVNode(Teleport, { to: `uid=${uid.value};client=${id}` }, {
|
nodes.push(createVNode(Teleport, { to: `uid=${uid.value};client=${id}` }, {
|
||||||
default: () => [createStaticVNode(html, 1)]
|
default: () => [createStaticVNode(html, 1)]
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (import.meta.client && html.value.includes('nuxt-ssr-client')) {
|
if (import.meta.client && canLoadClientComponent.value && html.value.includes('nuxt-ssr-client')) {
|
||||||
for (const [id, props] of Object.entries(interactiveProps)) {
|
for (const [id, props] of Object.entries(interactivePayload.props)) {
|
||||||
// @ts-expect-error _ is the component's default export in build chunks
|
// @ts-expect-error _ is the component's default export in build chunks
|
||||||
const component = components!.get(id.split('-')[0])!._ ?? components!.get(id.split('-')[0])!
|
const component = components!.get(id.split('-')[0])!._ ?? components!.get(id.split('-')[0])!
|
||||||
const vnode = createVNode(Teleport, { to: `[nuxt-ssr-component-uid='${uid.value}'] [nuxt-ssr-client="${id}"]` }, {
|
const vnode = createVNode(Teleport, { to: `[nuxt-ssr-component-uid='${uid.value}'] [nuxt-ssr-client="${id}"]` }, {
|
||||||
|
@ -6,12 +6,13 @@
|
|||||||
{{ count }}
|
{{ count }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="border: solid 1px red;">
|
<SugarCounter :multiplier="1" />
|
||||||
|
<div style="border: solid 1px red;">
|
||||||
The component bellow is not a slot but declared as interactive
|
The component bellow is not a slot but declared as interactive
|
||||||
|
|
||||||
<SugarCounter nuxt-client :multiplier="1" />
|
<SugarCounter nuxt-client :multiplier="1" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
Loading…
Reference in New Issue
Block a user