From a600c14837fd18c4c4c3330c0195cd47b0b73df9 Mon Sep 17 00:00:00 2001 From: Bjorn Lu Date: Wed, 15 Nov 2023 23:40:23 +0800 Subject: [PATCH] Support Svelte 5 (experimental) (#9098) Co-authored-by: Nate Moore --- .changeset/twelve-mails-drive.md | 5 +++ packages/integrations/svelte/README.md | 2 +- packages/integrations/svelte/client-v5.js | 43 +++++++++++++++++++++++ packages/integrations/svelte/package.json | 10 ++++-- packages/integrations/svelte/server-v5.js | 42 ++++++++++++++++++++++ packages/integrations/svelte/src/index.ts | 19 +++++++--- pnpm-lock.yaml | 16 ++++----- 7 files changed, 120 insertions(+), 17 deletions(-) create mode 100644 .changeset/twelve-mails-drive.md create mode 100644 packages/integrations/svelte/client-v5.js create mode 100644 packages/integrations/svelte/server-v5.js 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}`; + } else { + $$slots ??= {}; + $$slots[key] = () => `<${tagName} name="${key}">${value}`; + } + } + + 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