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 @@
+
+
+
+ {{ id }}