diff --git a/.changeset/twelve-mails-drive.md b/.changeset/twelve-mails-drive.md
new file mode 100644
index 000000000000..b8ff5d6e58f4
--- /dev/null
+++ b/.changeset/twelve-mails-drive.md
@@ -0,0 +1,5 @@
+---
+'@astrojs/svelte': patch
+---
+
+Adds experimental support for Svelte 5
diff --git a/packages/integrations/svelte/README.md b/packages/integrations/svelte/README.md
index 5df61fab09fc..25513790bae7 100644
--- a/packages/integrations/svelte/README.md
+++ b/packages/integrations/svelte/README.md
@@ -1,6 +1,6 @@
# @astrojs/svelte 🧡
-This **[Astro integration][astro-integration]** enables server-side rendering and client-side hydration for your [Svelte](https://svelte.dev/) components.
+This **[Astro integration][astro-integration]** enables server-side rendering and client-side hydration for your [Svelte](https://svelte.dev/) components. It supports Svelte 3, 4, and 5 (experimental).
## Installation
diff --git a/packages/integrations/svelte/client-v5.js b/packages/integrations/svelte/client-v5.js
new file mode 100644
index 000000000000..b3d2e1964eb8
--- /dev/null
+++ b/packages/integrations/svelte/client-v5.js
@@ -0,0 +1,43 @@
+import { mount } from 'svelte';
+
+export default (element) => {
+ return async (Component, props, slotted) => {
+ if (!element.hasAttribute('ssr')) return;
+
+ let children = undefined;
+ let $$slots = undefined;
+ for (const [key, value] of Object.entries(slotted)) {
+ if (key === 'default') {
+ children = createSlotDefinition(key, value);
+ } else {
+ $$slots ??= {};
+ $$slots[key] = createSlotDefinition(key, value);
+ }
+ }
+
+ const [, destroy] = mount(Component, {
+ target: element,
+ props: {
+ ...props,
+ children,
+ $$slots,
+ },
+ });
+
+ element.addEventListener('astro:unmount', () => destroy(), { once: true });
+ };
+};
+
+function createSlotDefinition(key, children) {
+ /**
+ * @param {Comment} $$anchor A comment node for slots in Svelte 5
+ */
+ return ($$anchor, _$$slotProps) => {
+ const parent = $$anchor.parentNode;
+ const el = document.createElement('div');
+ el.innerHTML = `${children}`;
+ parent.insertBefore(el.children[0], $$anchor);
+ };
+}
diff --git a/packages/integrations/svelte/package.json b/packages/integrations/svelte/package.json
index 8cf1c9d872ed..65c8522f9182 100644
--- a/packages/integrations/svelte/package.json
+++ b/packages/integrations/svelte/package.json
@@ -24,13 +24,17 @@
"./editor": "./dist/editor.cjs",
"./*": "./*",
"./client.js": "./client.js",
+ "./client-v5.js": "./client-v5.js",
"./server.js": "./server.js",
+ "./server-v5.js": "./server-v5.js",
"./package.json": "./package.json"
},
"files": [
"dist",
"client.js",
- "server.js"
+ "client-v5.js",
+ "server.js",
+ "server-v5.js"
],
"scripts": {
"build": "astro-scripts build \"src/index.ts\" && astro-scripts build \"src/editor.cts\" --force-cjs --no-clean-dist && tsc",
@@ -38,7 +42,7 @@
"dev": "astro-scripts dev \"src/**/*.ts\""
},
"dependencies": {
- "@sveltejs/vite-plugin-svelte": "^2.4.5",
+ "@sveltejs/vite-plugin-svelte": "^2.5.2",
"svelte2tsx": "^0.6.20"
},
"devDependencies": {
@@ -49,7 +53,7 @@
},
"peerDependencies": {
"astro": "^3.0.0",
- "svelte": "^3.55.0 || ^4.0.0"
+ "svelte": "^3.55.0 || ^4.0.0 || ^5.0.0-next.1"
},
"engines": {
"node": ">=18.14.1"
diff --git a/packages/integrations/svelte/server-v5.js b/packages/integrations/svelte/server-v5.js
new file mode 100644
index 000000000000..105b843fb1bf
--- /dev/null
+++ b/packages/integrations/svelte/server-v5.js
@@ -0,0 +1,42 @@
+import { render } from 'svelte/server';
+
+function check(Component) {
+ // Svelte 5 generated components always accept these two props
+ const str = Component.toString();
+ return str.includes('$$payload') && str.includes('$$props');
+}
+
+function needsHydration(metadata) {
+ // Adjust how this is hydrated only when the version of Astro supports `astroStaticSlot`
+ return metadata.astroStaticSlot ? !!metadata.hydrate : true;
+}
+
+async function renderToStaticMarkup(Component, props, slotted, metadata) {
+ const tagName = needsHydration(metadata) ? 'astro-slot' : 'astro-static-slot';
+
+ let children = undefined;
+ let $$slots = undefined;
+ for (const [key, value] of Object.entries(slotted)) {
+ if (key === 'default') {
+ children = () => `<${tagName}>${value}${tagName}>`;
+ } else {
+ $$slots ??= {};
+ $$slots[key] = () => `<${tagName} name="${key}">${value}${tagName}>`;
+ }
+ }
+
+ const { html } = render(Component, {
+ props: {
+ ...props,
+ children,
+ $$slots,
+ },
+ });
+ return { html };
+}
+
+export default {
+ check,
+ renderToStaticMarkup,
+ supportsAstroStaticSlot: true,
+};
diff --git a/packages/integrations/svelte/src/index.ts b/packages/integrations/svelte/src/index.ts
index a9d4f37c9ac6..5348f4c9340d 100644
--- a/packages/integrations/svelte/src/index.ts
+++ b/packages/integrations/svelte/src/index.ts
@@ -1,14 +1,17 @@
import type { Options } from '@sveltejs/vite-plugin-svelte';
import { svelte, vitePreprocess } from '@sveltejs/vite-plugin-svelte';
+import { VERSION } from 'svelte/compiler';
import type { AstroIntegration, AstroRenderer } from 'astro';
import { fileURLToPath } from 'node:url';
import type { UserConfig } from 'vite';
+const isSvelte5 = Number.parseInt(VERSION.split('.').at(0)!) >= 5;
+
function getRenderer(): AstroRenderer {
return {
name: '@astrojs/svelte',
- clientEntrypoint: '@astrojs/svelte/client.js',
- serverEntrypoint: '@astrojs/svelte/server.js',
+ clientEntrypoint: isSvelte5 ? '@astrojs/svelte/client-v5.js' : '@astrojs/svelte/client.js',
+ serverEntrypoint: isSvelte5 ? '@astrojs/svelte/server-v5.js' : '@astrojs/svelte/server.js',
};
}
@@ -37,9 +40,15 @@ async function getViteConfiguration({
}: ViteConfigurationArgs): Promise {
const defaultOptions: Partial = {
emitCss: true,
- compilerOptions: { dev: isDev, hydratable: true },
+ compilerOptions: { dev: isDev },
};
+ // `hydratable` does not need to be set in Svelte 5 as it's always hydratable by default
+ if (!isSvelte5) {
+ // @ts-ignore ignore Partial type above
+ defaultOptions.compilerOptions.hydratable = true;
+ }
+
// Disable hot mode during the build
if (!isDev) {
defaultOptions.hot = false;
@@ -69,8 +78,8 @@ async function getViteConfiguration({
return {
optimizeDeps: {
- include: ['@astrojs/svelte/client.js'],
- exclude: ['@astrojs/svelte/server.js'],
+ include: [isSvelte5 ? '@astrojs/svelte/client-v5.js' : '@astrojs/svelte/client.js'],
+ exclude: [isSvelte5 ? '@astrojs/svelte/server-v5.js' : '@astrojs/svelte/server.js'],
},
plugins: [svelte(resolvedOptions)],
};
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0147967d9d47..37616480c6b2 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -4578,8 +4578,8 @@ importers:
packages/integrations/svelte:
dependencies:
'@sveltejs/vite-plugin-svelte':
- specifier: ^2.4.5
- version: 2.4.6(svelte@4.2.2)(vite@4.5.0)
+ specifier: ^2.5.2
+ version: 2.5.2(svelte@4.2.2)(vite@4.5.0)
svelte2tsx:
specifier: ^0.6.20
version: 0.6.23(svelte@4.2.2)(typescript@5.1.6)
@@ -8237,7 +8237,7 @@ packages:
string.prototype.matchall: 4.0.10
dev: false
- /@sveltejs/vite-plugin-svelte-inspector@1.0.4(@sveltejs/vite-plugin-svelte@2.4.6)(svelte@4.2.2)(vite@4.5.0):
+ /@sveltejs/vite-plugin-svelte-inspector@1.0.4(@sveltejs/vite-plugin-svelte@2.5.2)(svelte@4.2.2)(vite@4.5.0):
resolution: {integrity: sha512-zjiuZ3yydBtwpF3bj0kQNV0YXe+iKE545QGZVTaylW3eAzFr+pJ/cwK8lZEaRp4JtaJXhD5DyWAV4AxLh6DgaQ==}
engines: {node: ^14.18.0 || >= 16}
peerDependencies:
@@ -8248,7 +8248,7 @@ packages:
vite:
optional: true
dependencies:
- '@sveltejs/vite-plugin-svelte': 2.4.6(svelte@4.2.2)(vite@4.5.0)
+ '@sveltejs/vite-plugin-svelte': 2.5.2(svelte@4.2.2)(vite@4.5.0)
debug: 4.3.4(supports-color@8.1.1)
svelte: 4.2.2
vite: 4.5.0(@types/node@18.18.6)(sass@1.69.4)
@@ -8256,17 +8256,17 @@ packages:
- supports-color
dev: false
- /@sveltejs/vite-plugin-svelte@2.4.6(svelte@4.2.2)(vite@4.5.0):
- resolution: {integrity: sha512-zO79p0+DZnXPnF0ltIigWDx/ux7Ni+HRaFOw720Qeivc1azFUrJxTl0OryXVibYNx1hCboGia1NRV3x8RNv4cA==}
+ /@sveltejs/vite-plugin-svelte@2.5.2(svelte@4.2.2)(vite@4.5.0):
+ resolution: {integrity: sha512-Dfy0Rbl+IctOVfJvWGxrX/3m6vxPLH8o0x+8FA5QEyMUQMo4kGOVIojjryU7YomBAexOTAuYf1RT7809yDziaA==}
engines: {node: ^14.18.0 || >= 16}
peerDependencies:
- svelte: ^3.54.0 || ^4.0.0
+ svelte: ^3.54.0 || ^4.0.0 || ^5.0.0-next.0
vite: ^4.0.0
peerDependenciesMeta:
vite:
optional: true
dependencies:
- '@sveltejs/vite-plugin-svelte-inspector': 1.0.4(@sveltejs/vite-plugin-svelte@2.4.6)(svelte@4.2.2)(vite@4.5.0)
+ '@sveltejs/vite-plugin-svelte-inspector': 1.0.4(@sveltejs/vite-plugin-svelte@2.5.2)(svelte@4.2.2)(vite@4.5.0)
debug: 4.3.4(supports-color@8.1.1)
deepmerge: 4.3.1
kleur: 4.1.5