diff --git a/.changeset/ripe-breads-hang.md b/.changeset/ripe-breads-hang.md new file mode 100644 index 000000000000..e1b5a45bc929 --- /dev/null +++ b/.changeset/ripe-breads-hang.md @@ -0,0 +1,5 @@ +--- +'@astrojs/svelte': patch +--- + +Fixes a case where a warning would be shown by svelte when not using `compilerOptions.experimental.async: true` in the svelte config diff --git a/packages/integrations/svelte/env.d.ts b/packages/integrations/svelte/env.d.ts new file mode 100644 index 000000000000..f781a18b4b4c --- /dev/null +++ b/packages/integrations/svelte/env.d.ts @@ -0,0 +1,3 @@ +declare module 'astro:svelte:opts' { + export const experimentalAsync: boolean; +} diff --git a/packages/integrations/svelte/src/index.ts b/packages/integrations/svelte/src/index.ts index 0db02aff3bbc..b28081f343a4 100644 --- a/packages/integrations/svelte/src/index.ts +++ b/packages/integrations/svelte/src/index.ts @@ -1,6 +1,7 @@ import type { Options } from '@sveltejs/vite-plugin-svelte'; import { svelte, vitePreprocess } from '@sveltejs/vite-plugin-svelte'; import type { AstroIntegration, AstroRenderer, ContainerRenderer } from 'astro'; +import type { Plugin } from 'vite'; function getRenderer(): AstroRenderer { return { @@ -17,11 +18,49 @@ export function getContainerRenderer(): ContainerRenderer { }; } +const VIRTUAL_MODULE_ID = 'astro:svelte:opts'; +const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID; + +function optionsPlugin(options: Pick & { root: URL }): Plugin { + let configFile = (options.configFile ?? 'svelte.config.js') || null; + if (configFile && !configFile.startsWith('.')) { + configFile = `./${configFile}`; + } + let resolvedPath = configFile ? new URL(configFile, options.root) : null; + + return { + name: '@astrojs/svelte:opts', + resolveId(id) { + if (id === VIRTUAL_MODULE_ID) { + return RESOLVED_VIRTUAL_MODULE_ID; + } + }, + async load(id) { + if (id !== RESOLVED_VIRTUAL_MODULE_ID) { + return; + } + + // We check if the config file actually exists + const resolved = resolvedPath ? await this.resolve(resolvedPath.href) : null; + + if (resolved) { + return ` + import svelteConfig from ${JSON.stringify(resolvedPath!.href)}; + + export const experimentalAsync = svelteConfig?.compilerOptions?.experimental?.async ?? false; + `; + } + + return `export const experimentalAsync = false;`; + }, + }; +} + export default function svelteIntegration(options?: Options): AstroIntegration { return { name: '@astrojs/svelte', hooks: { - 'astro:config:setup': async ({ updateConfig, addRenderer }) => { + 'astro:config:setup': async ({ config, updateConfig, addRenderer }) => { addRenderer(getRenderer()); updateConfig({ vite: { @@ -29,7 +68,13 @@ export default function svelteIntegration(options?: Options): AstroIntegration { include: ['@astrojs/svelte/client.js'], exclude: ['@astrojs/svelte/server.js'], }, - plugins: [svelte(options)], + plugins: [ + svelte(options), + optionsPlugin({ + configFile: options?.configFile, + root: config.root, + }), + ], }, }); }, diff --git a/packages/integrations/svelte/src/server.ts b/packages/integrations/svelte/src/server.ts index c077b38f17a6..502a5b3c1b7f 100644 --- a/packages/integrations/svelte/src/server.ts +++ b/packages/integrations/svelte/src/server.ts @@ -1,3 +1,4 @@ +import { experimentalAsync } from 'astro:svelte:opts'; import type { AstroComponentMetadata, NamedSSRLoadedRendererValue } from 'astro'; import { createRawSnippet } from 'svelte'; import { render } from 'svelte/server'; @@ -55,7 +56,7 @@ async function renderToStaticMarkup( })); } - const result = await render(Component, { + const options = { props: { ...props, children, @@ -63,7 +64,12 @@ async function renderToStaticMarkup( ...renderProps, }, idPrefix, - }); + }; + + // Svelte is very strict about how render() is called, so we need an intermediate function + const renderComponent = () => render(Component, options); + const result = experimentalAsync ? await renderComponent() : renderComponent(); + return { html: result.body }; } diff --git a/packages/integrations/svelte/test/fixtures/async-rendering/package.json b/packages/integrations/svelte/test/fixtures/async-rendering/package.json index 37405f998d9a..868ce3d74833 100644 --- a/packages/integrations/svelte/test/fixtures/async-rendering/package.json +++ b/packages/integrations/svelte/test/fixtures/async-rendering/package.json @@ -1,5 +1,5 @@ { - "name": "async-rendering", + "name": "@test/async-rendering", "type": "module", "version": "0.0.1", "scripts": { diff --git a/packages/integrations/svelte/test/fixtures/render-warning/astro.config.mjs b/packages/integrations/svelte/test/fixtures/render-warning/astro.config.mjs new file mode 100644 index 000000000000..1d02b15f63f6 --- /dev/null +++ b/packages/integrations/svelte/test/fixtures/render-warning/astro.config.mjs @@ -0,0 +1,9 @@ +// @ts-check +import { defineConfig } from 'astro/config'; + +import svelte from '@astrojs/svelte'; + +// https://astro.build/config +export default defineConfig({ + integrations: [svelte()] +}); \ No newline at end of file diff --git a/packages/integrations/svelte/test/fixtures/render-warning/package.json b/packages/integrations/svelte/test/fixtures/render-warning/package.json new file mode 100644 index 000000000000..e1d8f24ddfe0 --- /dev/null +++ b/packages/integrations/svelte/test/fixtures/render-warning/package.json @@ -0,0 +1,16 @@ +{ + "name": "@test/render-warning", + "type": "module", + "version": "0.0.1", + "scripts": { + "dev": "astro dev", + "build": "astro build", + "preview": "astro preview", + "astro": "astro" + }, + "dependencies": { + "@astrojs/svelte": "^7.1.1", + "astro": "^5.13.10", + "svelte": "^5.39.3" + } +} diff --git a/packages/integrations/svelte/test/fixtures/render-warning/src/components/Counter.svelte b/packages/integrations/svelte/test/fixtures/render-warning/src/components/Counter.svelte new file mode 100644 index 000000000000..a11538645e6c --- /dev/null +++ b/packages/integrations/svelte/test/fixtures/render-warning/src/components/Counter.svelte @@ -0,0 +1,41 @@ + + +
+ +
{count}
+ +
+
+ {@render children?.()} +
+ + diff --git a/packages/integrations/svelte/test/fixtures/render-warning/src/pages/index.astro b/packages/integrations/svelte/test/fixtures/render-warning/src/pages/index.astro new file mode 100644 index 000000000000..2b0cb9f8380c --- /dev/null +++ b/packages/integrations/svelte/test/fixtures/render-warning/src/pages/index.astro @@ -0,0 +1,20 @@ +--- +import Counter from '../components/Counter.svelte'; +--- + + + + + + + Astro + + +

Astro

+
+ +

Hello, Svelte!

+
+
+ + diff --git a/packages/integrations/svelte/test/render-warning.test.js b/packages/integrations/svelte/test/render-warning.test.js new file mode 100644 index 000000000000..8f673c020a0c --- /dev/null +++ b/packages/integrations/svelte/test/render-warning.test.js @@ -0,0 +1,63 @@ +import assert from 'node:assert/strict'; +import { after, before, describe, it } from 'node:test'; +import { loadFixture } from '../../../astro/test/test-utils.js'; + +let fixture; + +describe('Render warning', () => { + before(async () => { + fixture = await loadFixture({ + root: new URL('./fixtures/render-warning/', import.meta.url), + }); + }); + + describe('build', () => { + let originalWarn; + let logs = []; + + before(() => { + originalWarn = console.warn; + console.warn = (message) => { + logs.push(message); + originalWarn(message); + }; + }); + + after(async () => { + console.warn = originalWarn; + logs = []; + }); + + it('does not show any render warning', async () => { + await fixture.build(); + assert.equal(logs.length, 0); + }); + }); + + describe('dev', () => { + /** @type {import('../../../astro/test/test-utils.js').Fixture} */ + let devServer; + let originalWarn; + let logs = []; + + before(async () => { + devServer = await fixture.startDevServer(); + originalWarn = console.warn; + console.warn = (message) => { + logs.push(message); + originalWarn(message); + }; + }); + + after(async () => { + await devServer.stop(); + console.warn = originalWarn; + logs = []; + }); + + it('does not show any render warning', async () => { + await fixture.fetch('/'); + assert.equal(logs.length, 0); + }); + }); +}); diff --git a/packages/integrations/svelte/tsconfig.json b/packages/integrations/svelte/tsconfig.json index 5742d1f6efd2..100f3c93b6d7 100644 --- a/packages/integrations/svelte/tsconfig.json +++ b/packages/integrations/svelte/tsconfig.json @@ -1,6 +1,6 @@ { "extends": "../../../tsconfig.base.json", - "include": ["src"], + "include": ["src", "env.d.ts"], "compilerOptions": { "outDir": "./dist", "verbatimModuleSyntax": false diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5982c6600d19..dfb04a1c47d2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5965,6 +5965,18 @@ importers: specifier: ^5.39.3 version: 5.39.11 + packages/integrations/svelte/test/fixtures/render-warning: + dependencies: + '@astrojs/svelte': + specifier: ^7.1.1 + version: link:../../.. + astro: + specifier: ^5.13.10 + version: link:../../../../../astro + svelte: + specifier: ^5.39.3 + version: 5.39.11 + packages/integrations/vercel: dependencies: '@astrojs/internal-helpers':