diff --git a/packages/nuxt/src/app/components/TeleportIfClient.ts b/packages/nuxt/src/app/components/TeleportIfClient.ts
new file mode 100644
index 0000000000..8974b4088b
--- /dev/null
+++ b/packages/nuxt/src/app/components/TeleportIfClient.ts
@@ -0,0 +1,54 @@
+import { Teleport, defineComponent, h } from 'vue'
+import { useNuxtApp } from '#app'
+import { relative } from 'path'
+
+/**
+ * component only used with componentsIsland
+ * this teleport the component in SSR only if
+ */
+export default defineComponent({
+ name: 'TeleportIfClient',
+ props: {
+ to: String,
+ nuxtClient: {
+ type: Boolean,
+ default: false
+ },
+ /**
+ * ONLY used in dev mode since we use build:manifest result in production
+ * do not pass any value in production
+ */
+ rootDir: {
+ type: String,
+ default: null
+ }
+ },
+ setup (props, { slots }) {
+
+ const app = useNuxtApp()
+
+ const islandContext = app.ssrContext!.islandContext
+
+ const slot = slots.default!()[0]
+ console.log(slot)
+ if (process.dev) {
+ console.log(app)
+ const path = '__nuxt/' + relative(props.rootDir, slot.type.__file)
+
+ islandContext.chunks[slot.type.__name] = path
+ }
+ islandContext.propsData[props.to] = slot.props || {}
+ // todo set prop in payload
+ return () => {
+
+ if (props.nuxtClient) {
+ return [h('div', {
+ style: 'display: contents;',
+ 'nuxt-ssr-client': props.to
+ }, [slot]), h(Teleport, { to: props.to }, slot)]
+ }
+
+ return slot
+ }
+ }
+})
diff --git a/packages/nuxt/src/app/components/nuxt-island.ts b/packages/nuxt/src/app/components/nuxt-island.ts
index c268265d6c..b8c054302e 100644
--- a/packages/nuxt/src/app/components/nuxt-island.ts
+++ b/packages/nuxt/src/app/components/nuxt-island.ts
@@ -9,6 +9,7 @@ import type { FetchResponse } from 'ofetch'
// eslint-disable-next-line import/no-restricted-paths
import type { NuxtIslandResponse } from '../../core/runtime/nitro/renderer'
+
import { getFragmentHTML, getSlotProps } from './utils'
import { useNuxtApp, useRuntimeConfig } from '#app/nuxt'
import { useRequestEvent } from '#app/composables/ssr'
@@ -21,6 +22,23 @@ const SLOT_FALLBACK_RE = /
]*><\/div>((
let id = 0
const getId = process.client ? () => (id++).toString() : randomUUID
+const components = process.client ? new Map
() : undefined
+
+async function loadComponents (paths: Record) {
+ const promises = []
+
+ debugger
+ for (const component in paths) {
+ if (!(components!.has(component))) {
+ promises.push((async () => {
+ const c = await import('http://localhost:3000/__nuxt/components/SugarCounter.vue')
+ debugger
+ components!.set(component, c.default ?? c)
+ })())
+ }
+ }
+ await Promise.all(promises)
+}
export default defineComponent({
name: 'NuxtIsland',
@@ -71,7 +89,9 @@ export default defineComponent({
head: {
link: [],
style: []
- }
+ },
+ chunks: {},
+ props: {}
})
}
ssrHTML.value = renderedHTML ?? ''
@@ -90,6 +110,9 @@ export default defineComponent({
return content
})
})
+
+ // no need for reactivity
+ let interactiveComponentsList = {}
function setUid () {
uid.value = ssrHTML.value.match(SSR_UID_RE)?.[1] ?? getId() as string
}
@@ -142,6 +165,11 @@ export default defineComponent({
await nextTick()
}
setUid()
+
+ if (process.client) {
+ await loadComponents(res.chunks)
+ interactiveComponentsList = res.props
+ }
}
if (import.meta.hot) {
@@ -163,6 +191,7 @@ export default defineComponent({
const nodes = [createVNode(Fragment, {
key: key.value
}, [h(createStaticVNode(html.value, 1))])]
+
if (uid.value && (mounted.value || nuxtApp.isHydrating || process.server)) {
for (const slot in slots) {
if (availableSlots.value.includes(slot)) {
@@ -171,6 +200,15 @@ export default defineComponent({
}))
}
}
+ if (process.client && html.value.includes('nuxt-ssr-client') && mounted.value) {
+
+ for (const id in interactiveComponentsList) {
+ const vnode = createVNode(Teleport, { to: `[nuxt-ssr-component-uid='${uid.value}'] [nuxt-ssr-client="${id}"]` }, {
+ default: () => [h(components!.get(id.split('-')[0]), interactiveComponentsList[id])]
+ })
+ nodes.push(vnode)
+ }
+ }
}
return nodes
}
diff --git a/packages/nuxt/src/components/islandsTransform.ts b/packages/nuxt/src/components/islandsTransform.ts
index 1cdc89a831..42a2624cc0 100644
--- a/packages/nuxt/src/components/islandsTransform.ts
+++ b/packages/nuxt/src/components/islandsTransform.ts
@@ -4,10 +4,16 @@ import { parseURL } from 'ufo'
import { createUnplugin } from 'unplugin'
import MagicString from 'magic-string'
import { ELEMENT_NODE, parse, walk } from 'ultrahtml'
+import { hash } from 'ohash'
import { isVue } from '../core/utils'
interface ServerOnlyComponentTransformPluginOptions {
getComponents: () => Component[]
+ /**
+ * passed down to `TeleportIfClient`
+ * should be done only in dev mode as we use build:manifest result in production
+ */
+ rootDir?: string
}
const SCRIPT_RE = /