Skip to content

Commit

Permalink
Support Svelte 5 (experimental) (#9098)
Browse files Browse the repository at this point in the history
Co-authored-by: Nate Moore <[email protected]>
  • Loading branch information
bluwy and natemoo-re committed Nov 22, 2023
1 parent cbc53d1 commit cc6293a
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 9 deletions.
5 changes: 5 additions & 0 deletions .changeset/twelve-mails-drive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/svelte': patch
---

Adds experimental support for Svelte 5
2 changes: 1 addition & 1 deletion packages/integrations/svelte/README.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
43 changes: 43 additions & 0 deletions packages/integrations/svelte/client-v5.js
Original file line number Diff line number Diff line change
@@ -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 = `<astro-slot${
key === 'default' ? '' : ` name="${key}"`
}>${children}</astro-slot>`;
parent.insertBefore(el.children[0], $$anchor);
};
}
10 changes: 7 additions & 3 deletions packages/integrations/svelte/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,25 @@
"./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",
"build:ci": "astro-scripts build \"src/**/*.ts\" && astro-scripts build \"src/editor.cts\" --force-cjs --no-clean-dist",
"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": {
Expand All @@ -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"
Expand Down
42 changes: 42 additions & 0 deletions packages/integrations/svelte/server-v5.js
Original file line number Diff line number Diff line change
@@ -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,
};
19 changes: 14 additions & 5 deletions packages/integrations/svelte/src/index.ts
Original file line number Diff line number Diff line change
@@ -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',
};
}

Expand Down Expand Up @@ -37,9 +40,15 @@ async function getViteConfiguration({
}: ViteConfigurationArgs): Promise<UserConfig> {
const defaultOptions: Partial<Options> = {
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;
Expand Down Expand Up @@ -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)],
};
Expand Down

0 comments on commit cc6293a

Please sign in to comment.