diff --git a/.changeset/beige-students-compete.md b/.changeset/beige-students-compete.md new file mode 100644 index 000000000000..06588695c307 --- /dev/null +++ b/.changeset/beige-students-compete.md @@ -0,0 +1,5 @@ +--- +'@astrojs/vue': patch +--- + +Fixes a case where IDs generated by `useId()` (introduced in Vue 3.5) would not be unique between islands diff --git a/packages/integrations/vue/client.js b/packages/integrations/vue/client.js index 58ee11d5c3d3..b3935752c08c 100644 --- a/packages/integrations/vue/client.js +++ b/packages/integrations/vue/client.js @@ -40,6 +40,7 @@ export default (element) => return content; }, }); + app.config.idPrefix = element.getAttribute('prefix'); await setup(app); app.mount(element, isHydrate); appMap.set(element, appInstance); diff --git a/packages/integrations/vue/context.js b/packages/integrations/vue/context.js new file mode 100644 index 000000000000..80a569ce6c1e --- /dev/null +++ b/packages/integrations/vue/context.js @@ -0,0 +1,24 @@ +const contexts = new WeakMap(); + +const ID_PREFIX = 'v'; + +function getContext(rendererContextResult) { + if (contexts.has(rendererContextResult)) { + return contexts.get(rendererContextResult); + } + const ctx = { + currentIndex: 0, + get id() { + return ID_PREFIX + this.currentIndex.toString(); + }, + }; + contexts.set(rendererContextResult, ctx); + return ctx; +} + +export function incrementId(rendererContextResult) { + const ctx = getContext(rendererContextResult); + const id = ctx.id; + ctx.currentIndex++; + return id; +} diff --git a/packages/integrations/vue/server.js b/packages/integrations/vue/server.js index afdc9e8a2dae..5b7f6cb5056d 100644 --- a/packages/integrations/vue/server.js +++ b/packages/integrations/vue/server.js @@ -2,12 +2,19 @@ import { setup } from 'virtual:@astrojs/vue/app'; import { createSSRApp, h } from 'vue'; import { renderToString } from 'vue/server-renderer'; import StaticHtml from './static-html.js'; +import { incrementId } from './context.js'; function check(Component) { return !!Component['ssrRender'] || !!Component['__ssrInlineRender']; } async function renderToStaticMarkup(Component, inputProps, slotted, metadata) { + let prefix; + if (this && this.result) { + prefix = incrementId(this.result); + } + const attrs = { prefix }; + const slots = {}; const props = { ...inputProps }; delete props.slot; @@ -21,9 +28,10 @@ async function renderToStaticMarkup(Component, inputProps, slotted, metadata) { }); } const app = createSSRApp({ render: () => h(Component, props, slots) }); + app.config.idPrefix = prefix; await setup(app); const html = await renderToString(app); - return { html }; + return { html, attrs }; } export default { diff --git a/packages/integrations/vue/test/basics.test.js b/packages/integrations/vue/test/basics.test.js index 4eb2b987c313..d54ea66b652e 100644 --- a/packages/integrations/vue/test/basics.test.js +++ b/packages/integrations/vue/test/basics.test.js @@ -30,4 +30,13 @@ describe('Basics', () => { assert.notEqual(img, undefined); assert.equal(img.getAttribute('src'), '/light_walrus.avif'); }); + + it('Should generate unique ids when using useId()', async () => { + const data = await fixture.readFile('/index.html'); + const { document } = parseHTML(data); + + const els = document.querySelectorAll('.vue-use-id'); + assert.equal(els.length, 2); + assert.notEqual(els[0].getAttribute('id'), els[1].getAttribute('id')); + }); }); diff --git a/packages/integrations/vue/test/fixtures/basics/src/components/WithId.vue b/packages/integrations/vue/test/fixtures/basics/src/components/WithId.vue new file mode 100644 index 000000000000..2c8667696213 --- /dev/null +++ b/packages/integrations/vue/test/fixtures/basics/src/components/WithId.vue @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/packages/integrations/vue/test/fixtures/basics/src/pages/index.astro b/packages/integrations/vue/test/fixtures/basics/src/pages/index.astro index b2f292d1057c..162b326c8324 100644 --- a/packages/integrations/vue/test/fixtures/basics/src/pages/index.astro +++ b/packages/integrations/vue/test/fixtures/basics/src/pages/index.astro @@ -1,6 +1,7 @@ --- import Bar from '../components/Foo.vue'; import Parent from '../components/Parent.astro'; +import WithId from '../components/WithId.vue'; --- @@ -10,5 +11,7 @@ import Parent from '../components/Parent.astro'; + +