diff --git a/.changeset/rich-taxis-hear.md b/.changeset/rich-taxis-hear.md
new file mode 100644
index 0000000000..af6aa1e41f
--- /dev/null
+++ b/.changeset/rich-taxis-hear.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+feat: include CSS in `
` when `css: 'injected'`
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
index d8ceb45372..6685f75cf3 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
@@ -332,22 +332,16 @@ export function client_component(source, analysis, options) {
}
}
- const append_styles =
- analysis.inject_styles && analysis.css.ast
- ? () =>
- component_block.body.push(
- b.stmt(
- b.call(
- '$.append_styles',
- b.id('$$anchor'),
- b.literal(analysis.css.hash),
- b.literal(render_stylesheet(source, analysis, options).code)
- )
- )
- )
- : () => {};
+ if (analysis.css.ast !== null && analysis.inject_styles) {
+ const hash = b.literal(analysis.css.hash);
+ const code = b.literal(render_stylesheet(analysis.source, analysis, options).code);
- append_styles();
+ state.hoisted.push(b.const('$$css', b.object([b.init('hash', hash), b.init('code', code)])));
+
+ component_block.body.unshift(
+ b.stmt(b.call('$.append_styles', b.id('$$anchor'), b.id('$$css')))
+ );
+ }
const should_inject_context =
analysis.needs_context ||
@@ -423,10 +417,26 @@ export function client_component(source, analysis, options) {
);
if (options.hmr) {
- const accept_fn = b.arrow(
- [b.id('module')],
- b.block([b.stmt(b.call('$.set', b.id('s'), b.member(b.id('module'), b.id('default'))))])
- );
+ const accept_fn_body = [
+ b.stmt(b.call('$.set', b.id('s'), b.member(b.id('module'), b.id('default'))))
+ ];
+
+ if (analysis.css.hash) {
+ // remove existing ``;
+ }
+
return {
- head: payload.head.out || payload.head.title ? payload.head.out + payload.head.title : '',
+ head,
html: payload.out,
body: payload.out
};
diff --git a/packages/svelte/src/internal/server/types.d.ts b/packages/svelte/src/internal/server/types.d.ts
index 2f472c5c78..e6c235147b 100644
--- a/packages/svelte/src/internal/server/types.d.ts
+++ b/packages/svelte/src/internal/server/types.d.ts
@@ -13,6 +13,7 @@ export interface Component {
export interface Payload {
out: string;
+ css: Set<{ hash: string; code: string }>;
head: {
title: string;
out: string;
diff --git a/packages/svelte/tests/server-side-rendering/samples/css-empty/_config.js b/packages/svelte/tests/server-side-rendering/samples/css-empty/_config.js
new file mode 100644
index 0000000000..388d516162
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/css-empty/_config.js
@@ -0,0 +1,4 @@
+import { test } from '../../test';
+
+// Test validates that by default no CSS is rendered on the server
+export default test({});
diff --git a/packages/svelte/tests/server-side-rendering/samples/css-empty/_expected.css b/packages/svelte/tests/server-side-rendering/samples/css-empty/_expected.css
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/packages/svelte/tests/server-side-rendering/samples/css-empty/_expected.html b/packages/svelte/tests/server-side-rendering/samples/css-empty/_expected.html
new file mode 100644
index 0000000000..8ebe1ad73e
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/css-empty/_expected.html
@@ -0,0 +1 @@
+foo
\ No newline at end of file
diff --git a/packages/svelte/tests/server-side-rendering/samples/css-empty/main.svelte b/packages/svelte/tests/server-side-rendering/samples/css-empty/main.svelte
new file mode 100644
index 0000000000..4b85524295
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/css-empty/main.svelte
@@ -0,0 +1,7 @@
+foo
+
+
diff --git a/packages/svelte/tests/server-side-rendering/samples/css/_config.js b/packages/svelte/tests/server-side-rendering/samples/css/_config.js
new file mode 100644
index 0000000000..194b2443f9
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/css/_config.js
@@ -0,0 +1,7 @@
+import { test } from '../../test';
+
+export default test({
+ compileOptions: {
+ css: 'injected'
+ }
+});
diff --git a/packages/svelte/tests/server-side-rendering/samples/css/_expected.css b/packages/svelte/tests/server-side-rendering/samples/css/_expected.css
new file mode 100644
index 0000000000..8882c6ec7e
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/css/_expected.css
@@ -0,0 +1,4 @@
+
+ .foo.svelte-sg04hs {
+ color: red;
+ }
diff --git a/packages/svelte/tests/server-side-rendering/samples/css/_expected.html b/packages/svelte/tests/server-side-rendering/samples/css/_expected.html
new file mode 100644
index 0000000000..8ebe1ad73e
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/css/_expected.html
@@ -0,0 +1 @@
+foo
\ No newline at end of file
diff --git a/packages/svelte/tests/server-side-rendering/samples/css/_expected_head.html b/packages/svelte/tests/server-side-rendering/samples/css/_expected_head.html
new file mode 100644
index 0000000000..7281935646
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/css/_expected_head.html
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/packages/svelte/tests/server-side-rendering/samples/css/main.svelte b/packages/svelte/tests/server-side-rendering/samples/css/main.svelte
new file mode 100644
index 0000000000..4b85524295
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/css/main.svelte
@@ -0,0 +1,7 @@
+foo
+
+
diff --git a/packages/svelte/tests/server-side-rendering/samples/head-html-and-component/_expected-head.html b/packages/svelte/tests/server-side-rendering/samples/head-html-and-component/_expected_head.html
similarity index 100%
rename from packages/svelte/tests/server-side-rendering/samples/head-html-and-component/_expected-head.html
rename to packages/svelte/tests/server-side-rendering/samples/head-html-and-component/_expected_head.html
diff --git a/packages/svelte/tests/server-side-rendering/samples/head-meta-hydrate-duplicate/_expected-head.html b/packages/svelte/tests/server-side-rendering/samples/head-meta-hydrate-duplicate/_expected_head.html
similarity index 100%
rename from packages/svelte/tests/server-side-rendering/samples/head-meta-hydrate-duplicate/_expected-head.html
rename to packages/svelte/tests/server-side-rendering/samples/head-meta-hydrate-duplicate/_expected_head.html
diff --git a/packages/svelte/tests/server-side-rendering/samples/head-multiple-title/_expected-head.html b/packages/svelte/tests/server-side-rendering/samples/head-multiple-title/_expected_head.html
similarity index 100%
rename from packages/svelte/tests/server-side-rendering/samples/head-multiple-title/_expected-head.html
rename to packages/svelte/tests/server-side-rendering/samples/head-multiple-title/_expected_head.html
diff --git a/packages/svelte/tests/server-side-rendering/samples/head-no-duplicates-with-binding/_expected-head.html b/packages/svelte/tests/server-side-rendering/samples/head-no-duplicates-with-binding/_expected_head.html
similarity index 100%
rename from packages/svelte/tests/server-side-rendering/samples/head-no-duplicates-with-binding/_expected-head.html
rename to packages/svelte/tests/server-side-rendering/samples/head-no-duplicates-with-binding/_expected_head.html
diff --git a/packages/svelte/tests/server-side-rendering/samples/head-title/_expected-head.html b/packages/svelte/tests/server-side-rendering/samples/head-title/_expected_head.html
similarity index 100%
rename from packages/svelte/tests/server-side-rendering/samples/head-title/_expected-head.html
rename to packages/svelte/tests/server-side-rendering/samples/head-title/_expected_head.html
diff --git a/packages/svelte/tests/server-side-rendering/test.ts b/packages/svelte/tests/server-side-rendering/test.ts
index 2597f1d2eb..27a48efa83 100644
--- a/packages/svelte/tests/server-side-rendering/test.ts
+++ b/packages/svelte/tests/server-side-rendering/test.ts
@@ -5,6 +5,7 @@
// TODO: happy-dom might be faster but currently replaces quotes which fails assertions
import * as fs from 'node:fs';
+import { assert } from 'vitest';
import { render } from 'svelte/server';
import { compile_directory, should_update_expected, try_read_file } from '../helpers.js';
import { assert_html_equal_with_options } from '../html_equal.js';
@@ -25,7 +26,11 @@ const { test, run } = suite(async (config, test_dir) => {
const rendered = render(Component, { props: config.props || {} });
const { body, head } = rendered;
- fs.writeFileSync(`${test_dir}/_actual.html`, body);
+ fs.writeFileSync(`${test_dir}/_output/rendered.html`, body);
+
+ if (head) {
+ fs.writeFileSync(`${test_dir}/_output/rendered_head.html`, head);
+ }
try {
assert_html_equal_with_options(body, expected_html || '', {
@@ -42,19 +47,17 @@ const { test, run } = suite(async (config, test_dir) => {
}
}
- if (fs.existsSync(`${test_dir}/_expected-head.html`)) {
- fs.writeFileSync(`${test_dir}/_actual-head.html`, head);
-
+ if (fs.existsSync(`${test_dir}/_expected_head.html`)) {
try {
assert_html_equal_with_options(
head,
- fs.readFileSync(`${test_dir}/_expected-head.html`, 'utf-8'),
+ fs.readFileSync(`${test_dir}/_expected_head.html`, 'utf-8'),
{}
);
} catch (error: any) {
if (should_update_expected()) {
- fs.writeFileSync(`${test_dir}/_expected-head.html`, head);
- console.log(`Updated ${test_dir}/_expected-head.html.`);
+ fs.writeFileSync(`${test_dir}/_expected_head.html`, head);
+ console.log(`Updated ${test_dir}/_expected_head.html.`);
error.message += '\n' + `${test_dir}/main.svelte`;
} else {
throw error;
diff --git a/packages/svelte/tests/snapshot/samples/hmr/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/hmr/_expected/client/index.svelte.js
index 6cb6845c1c..5446bb3355 100644
--- a/packages/svelte/tests/snapshot/samples/hmr/_expected/client/index.svelte.js
+++ b/packages/svelte/tests/snapshot/samples/hmr/_expected/client/index.svelte.js
@@ -13,17 +13,17 @@ if (import.meta.hot) {
const s = $.source(Hmr);
const filename = Hmr.filename;
+ const accept = (module) => {
+ $.set(s, module.default);
+ };
+
Hmr = $.hmr(s);
Hmr.filename = filename;
if (import.meta.hot.acceptExports) {
- import.meta.hot.acceptExports(["default"], (module) => {
- $.set(s, module.default);
- });
+ import.meta.hot.acceptExports(["default"], accept);
} else {
- import.meta.hot.accept((module) => {
- $.set(s, module.default);
- });
+ import.meta.hot.accept(accept);
}
}
diff --git a/packages/svelte/tests/sourcemaps/samples/css-injected-map/_config.js b/packages/svelte/tests/sourcemaps/samples/css-injected-map/_config.js
index 845d677bc6..74a2185076 100644
--- a/packages/svelte/tests/sourcemaps/samples/css-injected-map/_config.js
+++ b/packages/svelte/tests/sourcemaps/samples/css-injected-map/_config.js
@@ -30,10 +30,10 @@ export default test({
async test({ assert, code_client }) {
// Check that the css source map embedded in the js is accurate
const match = code_client.match(
- /append_styles\(\$\$anchor, "svelte-.{6}", "(.*?)(?:\\n\/\*# sourceMappingURL=data:(.*?);charset=(.*?);base64,(.*?) \*\/)?"\);/
+ /code: "(.*?)(?:\\n\/\*# sourceMappingURL=data:(.*?);charset=(.*?);base64,(.*?) \*\/)?"/
);
- assert.notEqual(match, null);
+ assert.ok(match);
const [css, mime_type, encoding, css_map_base64] = /** @type {RegExpMatchArray} */ (
match
diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts
index 85349af2c4..c5d5db2f1f 100644
--- a/packages/svelte/types/index.d.ts
+++ b/packages/svelte/types/index.d.ts
@@ -760,8 +760,8 @@ declare module 'svelte/compiler' {
*/
immutable?: boolean;
/**
- * - `'injected'`: styles will be included in the JavaScript class and injected at runtime for the components actually rendered.
- * - `'external'`: the CSS will be returned in the `css` field of the compilation result. Most Svelte bundler plugins will set this to `'external'` and use the CSS that is statically generated for better performance, as it will result in smaller JavaScript bundles and the output can be served as cacheable `.css` files.
+ * - `'injected'`: styles will be included in the `head` when using `render(...)`, and injected into the document (if not already present) when the component mounts. For components compiled as custom elements, styles are injected to the shadow root.
+ * - `'external'`: the CSS will only be returned in the `css` field of the compilation result. Most Svelte bundler plugins will set this to `'external'` and use the CSS that is statically generated for better performance, as it will result in smaller JavaScript bundles and the output can be served as cacheable `.css` files.
* This is always `'injected'` when compiling with `customElement` mode.
*/
css?: 'injected' | 'external';
@@ -2568,8 +2568,8 @@ declare module 'svelte/types/compiler/interfaces' {
*/
immutable?: boolean;
/**
- * - `'injected'`: styles will be included in the JavaScript class and injected at runtime for the components actually rendered.
- * - `'external'`: the CSS will be returned in the `css` field of the compilation result. Most Svelte bundler plugins will set this to `'external'` and use the CSS that is statically generated for better performance, as it will result in smaller JavaScript bundles and the output can be served as cacheable `.css` files.
+ * - `'injected'`: styles will be included in the `head` when using `render(...)`, and injected into the document (if not already present) when the component mounts. For components compiled as custom elements, styles are injected to the shadow root.
+ * - `'external'`: the CSS will only be returned in the `css` field of the compilation result. Most Svelte bundler plugins will set this to `'external'` and use the CSS that is statically generated for better performance, as it will result in smaller JavaScript bundles and the output can be served as cacheable `.css` files.
* This is always `'injected'` when compiling with `customElement` mode.
*/
css?: 'injected' | 'external';
diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md
index f9befc4c66..af4118eaab 100644
--- a/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md
+++ b/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md
@@ -155,6 +155,8 @@ const result = render(App, {
});
```
+If the `css` compiler option was set to `'injected'`, `