diff --git a/packages/nuxt/src/components/templates.ts b/packages/nuxt/src/components/templates.ts index 826c84dada..d90909c453 100644 --- a/packages/nuxt/src/components/templates.ts +++ b/packages/nuxt/src/components/templates.ts @@ -89,7 +89,7 @@ export const componentsIslandsTemplate: NuxtTemplate return [ 'import { defineAsyncComponent } from \'vue\'', - 'export const islandComponents = {', + 'export const islandComponents = import.meta.client ? {} : {', islands.map( (c) => { const exp = c.export === 'default' ? 'c.default || c' : `c['${c.export}']` diff --git a/packages/nuxt/src/core/templates.ts b/packages/nuxt/src/core/templates.ts index e36c944987..1d4797cb26 100644 --- a/packages/nuxt/src/core/templates.ts +++ b/packages/nuxt/src/core/templates.ts @@ -40,7 +40,9 @@ export const appComponentTemplate: NuxtTemplate = { // TODO: Use an alias export const rootComponentTemplate: NuxtTemplate = { filename: 'root-component.mjs', - getContents: ctx => genExport(ctx.app.rootComponent!, ['default']) + // TODO: fix upstream in vite - this ensures that vite generates a module graph for islands + // but should not be necessary (and has a warmup performance cost). See https://github.com/nuxt/nuxt/pull/24584. + getContents: ctx => (ctx.nuxt.options.dev ? "import '#build/components.islands.mjs';\n" : '') + genExport(ctx.app.rootComponent!, ['default']) } // TODO: Use an alias export const errorComponentTemplate: NuxtTemplate = { diff --git a/test/fixtures/basic/components/islands/HmrComponent.vue b/test/fixtures/basic/components/islands/HmrComponent.vue new file mode 100644 index 0000000000..cbfae371e7 --- /dev/null +++ b/test/fixtures/basic/components/islands/HmrComponent.vue @@ -0,0 +1,9 @@ + + + diff --git a/test/fixtures/basic/pages/server-component-hmr.vue b/test/fixtures/basic/pages/server-component-hmr.vue new file mode 100644 index 0000000000..ad37a3b86d --- /dev/null +++ b/test/fixtures/basic/pages/server-component-hmr.vue @@ -0,0 +1,5 @@ + diff --git a/test/hmr.test.ts b/test/hmr.test.ts index 59a9b743c3..3a282c6b82 100644 --- a/test/hmr.test.ts +++ b/test/hmr.test.ts @@ -100,6 +100,52 @@ if (process.env.TEST_ENV !== 'built' && !isWindows) { true ) }) + + it('should HMR islands', async () => { + const { page, pageErrors, consoleLogs } = await renderPage('/server-component-hmr') + + let hmrId = 0 + const resolveHmrId = async () => { + const node = await page.$('#hmr-id') + const text = await node?.innerText() || '' + return Number(text?.trim().split(':')[1].trim()) + } + const componentPath = join(fixturePath, 'components/islands/HmrComponent.vue') + const triggerHmr = async () => fsp.writeFile( + componentPath, + (await fsp.readFile(componentPath, 'utf8')) + .replace(`ref(${hmrId++})`, `ref(${hmrId})`) + ) + + // initial state + await expectWithPolling( + resolveHmrId, + 0, + ) + + // first edit + await triggerHmr() + await expectWithPolling( + resolveHmrId, + 1, + ) + + // just in-case + await triggerHmr() + await expectWithPolling( + resolveHmrId, + 2, + ) + + // ensure no errors + const consoleLogErrors = consoleLogs.filter(i => i.type === 'error') + const consoleLogWarnings = consoleLogs.filter(i => i.type === 'warn') + expect(pageErrors).toEqual([]) + expect(consoleLogErrors).toEqual([]) + expect(consoleLogWarnings).toEqual([]) + + await page.close() + }, 60_000) }) } else { describe.skip('hmr', () => {})