Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/ripe-breads-hang.md
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions packages/integrations/svelte/env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
declare module 'astro:svelte:opts' {
export const experimentalAsync: boolean;
}
49 changes: 47 additions & 2 deletions packages/integrations/svelte/src/index.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -17,19 +18,63 @@ export function getContainerRenderer(): ContainerRenderer {
};
}

const VIRTUAL_MODULE_ID = 'astro:svelte:opts';
const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID;

function optionsPlugin(options: Pick<Options, 'configFile'> & { 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: {
optimizeDeps: {
include: ['@astrojs/svelte/client.js'],
exclude: ['@astrojs/svelte/server.js'],
},
plugins: [svelte(options)],
plugins: [
svelte(options),
optionsPlugin({
configFile: options?.configFile,
root: config.root,
}),
],
},
});
},
Expand Down
10 changes: 8 additions & 2 deletions packages/integrations/svelte/src/server.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -55,15 +56,20 @@ async function renderToStaticMarkup(
}));
}

const result = await render(Component, {
const options = {
props: {
...props,
children,
$$slots,
...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 };
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "async-rendering",
"name": "@test/async-rendering",
"type": "module",
"version": "0.0.1",
"scripts": {
Expand Down
Original file line number Diff line number Diff line change
@@ -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()]
});
Original file line number Diff line number Diff line change
@@ -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"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<script lang="ts">
import type { Snippet } from 'svelte';

interface Props {
children?: Snippet
}

let { children }: Props = $props();
let count = $state(0);

function add() {
count += 1;
}

function subtract() {
count -= 1;
}
</script>

<div class="counter">
<button onclick={subtract}>-</button>
<pre>{count}</pre>
<button onclick={add}>+</button>
</div>
<div class="message">
{@render children?.()}
</div>

<style>
.counter {
display: grid;
font-size: 2em;
grid-template-columns: repeat(3, minmax(0, 1fr));
margin-top: 2em;
place-items: center;
}

.message {
text-align: center;
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
import Counter from '../components/Counter.svelte';
---

<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />
<title>Astro</title>
</head>
<body>
<h1>Astro</h1>
<section>
<Counter client:visible>
<h1>Hello, Svelte!</h1>
</Counter>
</section>
</body>
</html>
63 changes: 63 additions & 0 deletions packages/integrations/svelte/test/render-warning.test.js
Original file line number Diff line number Diff line change
@@ -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);
});
});
});
2 changes: 1 addition & 1 deletion packages/integrations/svelte/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"extends": "../../../tsconfig.base.json",
"include": ["src"],
"include": ["src", "env.d.ts"],
"compilerOptions": {
"outDir": "./dist",
"verbatimModuleSyntax": false
Expand Down
12 changes: 12 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading