` component to support languages beyond the primary `lang`
+
+ This allows, for example, highlighting `.vue` files with a `
+
+
+
+ ```
+
+ See the [`` component documentation](https://v6.docs.astro.build/en/guides/syntax-highlighting/#code-) for more details.
+
+- [#15483](https://github.com/withastro/astro/pull/15483) [`7be3308`](https://github.com/withastro/astro/commit/7be3308bf4b1710a3f378ba338d09a8528e01e76) Thanks [@florian-lefebvre](https://github.com/florian-lefebvre)! - Adds `streaming` option to the `createApp()` function in the Adapter API, mirroring the same functionality available when creating a new `App` instance
+
+ An adapter's `createApp()` function now accepts `streaming` (defaults to `true`) as an option. HTML streaming breaks a document into chunks to send over the network and render on the page in order. This normally results in visitors seeing your HTML as fast as possible but factors such as network conditions and waiting for data fetches can block page rendering.
+
+ HTML streaming helps with performance and generally provides a better visitor experience. In most cases, disabling streaming is not recommended.
+
+ However, when you need to disable HTML streaming (e.g. your host only supports non-streamed HTML caching at the CDN level), you can opt out of the default behavior by passing `streaming: false` to `createApp()`:
+
+ ```ts
+ import { createApp } from 'astro/app/entrypoint';
+
+ const app = createApp({ streaming: false });
+ ```
+
+ See more about [the `createApp()` function](https://v6.docs.astro.build/en/reference/adapter-reference/#createapp) in the Adapter API reference.
+
+### Patch Changes
+
+- [#15542](https://github.com/withastro/astro/pull/15542) [`9760404`](https://github.com/withastro/astro/commit/97604040b73ec1d029f5d5a489aa744aaecfd173) Thanks [@rururux](https://github.com/rururux)! - Improves rendering by preserving `hidden="until-found"` value in attribues
+
+- [#15550](https://github.com/withastro/astro/pull/15550) [`58df907`](https://github.com/withastro/astro/commit/58df9072391fbfcd703e8d791ca51b7bedefb730) Thanks [@florian-lefebvre](https://github.com/florian-lefebvre)! - Improves the JSDoc annotations for the `AstroAdapter` type
+
+- [#15507](https://github.com/withastro/astro/pull/15507) [`07f6610`](https://github.com/withastro/astro/commit/07f66101ed2850874c8a49ddf1f1609e6b1339fb) Thanks [@matthewp](https://github.com/matthewp)! - Avoid bundling SSR renderers when only API endpoints are dynamic
+
+- [#15459](https://github.com/withastro/astro/pull/15459) [`a4406b4`](https://github.com/withastro/astro/commit/a4406b4dfbca3756983b82c37d7c74f84d2de096) Thanks [@florian-lefebvre](https://github.com/florian-lefebvre)! - Fixes a case where `context.csp` was logging warnings in development that should be logged in production only
+
+- Updated dependencies [[`84d6efd`](https://github.com/withastro/astro/commit/84d6efd9f1036fdf3c29e9b786b4a96453a607ed)]:
+ - @astrojs/markdown-remark@7.0.0-beta.7
+
## 6.0.0-beta.12
### Major Changes
diff --git a/packages/astro/components/Code.astro b/packages/astro/components/Code.astro
index a84083cebe8a..860ea9ceea2e 100644
--- a/packages/astro/components/Code.astro
+++ b/packages/astro/components/Code.astro
@@ -1,10 +1,18 @@
---
-import { type ThemePresets, createShikiHighlighter } from '@astrojs/markdown-remark';
+import {
+ type ThemePresets,
+ createShikiHighlighter,
+ globalShikiStyleCollector,
+ transformerStyleToClass,
+} from '@astrojs/markdown-remark';
import type { ShikiTransformer, ThemeRegistration, ThemeRegistrationRaw } from 'shiki';
import { bundledLanguages } from 'shiki/langs';
import type { CodeLanguage } from '../dist/types/public/common.js';
import type { HTMLAttributes } from '../types.js';
+// Code.astro always uses Shiki, so import the virtual CSS module
+import 'virtual:astro:shiki-styles.css';
+
interface Props extends Omit', () => {
let html = await fixture.readFile('/no-lang/index.html');
const $ = cheerio.load(html);
assert.equal($('pre').length, 1);
- assert.equal(
- $('pre').attr('style'),
- 'background-color:#24292e;color:#e1e4e8; overflow-x: auto;',
- 'applies default and overflow',
- );
+ // Styles are now class-based - no inline styles
+ assert.ok($('pre').hasClass('astro-code-overflow'), 'has overflow class');
+ assert.ok(!$('pre').attr('style'), 'should have no inline style');
+ assert.ok($('pre').attr('class'), 'has classes for styling');
assert.equal($('pre > code').length, 1);
// test: contains some generated spans
@@ -30,7 +29,10 @@ describe('', () => {
let html = await fixture.readFile('/basic/index.html');
const $ = cheerio.load(html);
assert.equal($('pre').length, 1);
- assert.equal($('pre').attr('class'), 'astro-code github-dark');
+ // Classes now include token style classes and overflow class
+ assert.ok($('pre').hasClass('astro-code'), 'has astro-code class');
+ assert.ok($('pre').hasClass('github-dark'), 'has github-dark theme class');
+ assert.ok($('pre').hasClass('astro-code-overflow'), 'has overflow class');
assert.equal($('pre > code').length, 1);
// test: contains many generated spans
assert.equal($('pre > code span').length >= 6, true);
@@ -40,12 +42,11 @@ describe('', () => {
let html = await fixture.readFile('/custom-theme/index.html');
const $ = cheerio.load(html);
assert.equal($('pre').length, 1);
- assert.equal($('pre').attr('class'), 'astro-code nord');
- assert.equal(
- $('pre').attr('style'),
- 'background-color:#2e3440ff;color:#d8dee9ff; overflow-x: auto;',
- 'applies custom theme',
- );
+ // Classes now include token style classes and overflow class
+ assert.ok($('pre').hasClass('astro-code'), 'has astro-code class');
+ assert.ok($('pre').hasClass('nord'), 'has nord theme class');
+ assert.ok($('pre').hasClass('astro-code-overflow'), 'has overflow class');
+ assert.ok(!$('pre').attr('style'), 'should have no inline style');
});
it('', async () => {
@@ -53,28 +54,28 @@ describe('', () => {
let html = await fixture.readFile('/wrap-true/index.html');
const $ = cheerio.load(html);
assert.equal($('pre').length, 1);
- // test: applies wrap overflow
- assert.equal(
- $('pre').attr('style'),
- 'background-color:#24292e;color:#e1e4e8; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;',
- );
+ // Wrap styles are now classes, not inline
+ assert.ok($('pre').hasClass('astro-code-overflow'), 'has overflow class');
+ assert.ok($('pre').hasClass('astro-code-wrap'), 'has wrap class');
+ assert.ok(!$('pre').attr('style'), 'should have no inline style');
}
{
let html = await fixture.readFile('/wrap-false/index.html');
const $ = cheerio.load(html);
assert.equal($('pre').length, 1);
- // test: applies wrap overflow
- assert.equal(
- $('pre').attr('style'),
- 'background-color:#24292e;color:#e1e4e8; overflow-x: auto;',
- );
+ // Overflow is now a class, not inline
+ assert.ok($('pre').hasClass('astro-code-overflow'), 'has overflow class');
+ assert.ok(!$('pre').hasClass('astro-code-wrap'), 'should NOT have wrap class');
+ assert.ok(!$('pre').attr('style'), 'should have no inline style');
}
{
let html = await fixture.readFile('/wrap-null/index.html');
const $ = cheerio.load(html);
assert.equal($('pre').length, 1);
- // test: applies wrap overflow
- assert.equal($('pre').attr('style'), 'background-color:#24292e;color:#e1e4e8');
+ // When wrap is null, no overflow or wrap classes
+ assert.ok(!$('pre').hasClass('astro-code-overflow'), 'should not have overflow class');
+ assert.ok(!$('pre').hasClass('astro-code-wrap'), 'should not have wrap class');
+ assert.ok(!$('pre').attr('style'), 'should have no inline style');
}
});
@@ -82,20 +83,18 @@ describe('', () => {
let html = await fixture.readFile('/css-theme/index.html');
const $ = cheerio.load(html);
assert.equal($('pre').length, 1);
- assert.equal($('pre').attr('class'), 'astro-code css-variables');
- assert.deepEqual(
- $('pre, pre span')
- .map((_i, f) => (f.attribs ? f.attribs.style : 'no style found'))
- .toArray(),
- [
- 'background-color:var(--astro-code-background);color:var(--astro-code-foreground); overflow-x: auto;',
- 'color:var(--astro-code-token-constant)',
- 'color:var(--astro-code-token-function)',
- 'color:var(--astro-code-foreground)',
- 'color:var(--astro-code-token-string-expression)',
- 'color:var(--astro-code-foreground)',
- ],
- );
+ // Classes now include overflow class
+ assert.ok($('pre').hasClass('astro-code'), 'has astro-code class');
+ assert.ok($('pre').hasClass('css-variables'), 'has css-variables theme class');
+ assert.ok($('pre').hasClass('astro-code-overflow'), 'has overflow class');
+
+ // CSS variables theme still uses inline styles on spans (not transformed by style-to-class)
+ // but pre background/color should be in classes, overflow should be a class
+ assert.ok(!$('pre').attr('style'), 'pre should have no inline style');
+
+ // Spans should have CSS variable colors as inline styles
+ const spans = $('pre span');
+ assert.ok(spans.length > 0, 'should have spans');
});
it(' with custom theme and lang', async () => {
@@ -103,10 +102,9 @@ describe('', () => {
const $ = cheerio.load(html);
assert.equal($('#theme > pre').length, 1);
- assert.equal(
- $('#theme > pre').attr('style'),
- 'background-color:#FDFDFE;color:#4E5377; overflow-x: auto;',
- );
+ // Styles are now class-based - no inline styles
+ assert.ok($('#theme > pre').hasClass('astro-code-overflow'), 'has overflow class');
+ assert.ok(!$('#theme > pre').attr('style'), 'should have no inline style');
assert.equal($('#lang > pre').length, 1);
assert.equal($('#lang > pre > code span').length, 3);
@@ -118,7 +116,8 @@ describe('', () => {
const codeEl = $('.astro-code');
assert.equal(codeEl.prop('tagName'), 'CODE');
- assert.match(codeEl.attr('style'), /background-color:/);
+ // Inline code now uses classes instead of inline styles
+ assert.ok(codeEl.attr('class'), 'should have classes for styling');
assert.equal($('pre').length, 0);
});
diff --git a/packages/astro/test/astro-markdown-shiki-conditional.test.js b/packages/astro/test/astro-markdown-shiki-conditional.test.js
new file mode 100644
index 000000000000..614b99ad4cb1
--- /dev/null
+++ b/packages/astro/test/astro-markdown-shiki-conditional.test.js
@@ -0,0 +1,106 @@
+import assert from 'node:assert/strict';
+import { before, describe, it } from 'node:test';
+import * as cheerio from 'cheerio';
+import { loadFixture } from './test-utils.js';
+
+describe('Markdown Shiki CSS conditional injection', () => {
+ describe('With code blocks', () => {
+ let fixture;
+ let $;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/astro-markdown-shiki-conditional/with-code/',
+ });
+ await fixture.build();
+ const html = await fixture.readFile('/index.html');
+ $ = cheerio.load(html);
+ });
+
+ it('should inject Shiki CSS when code blocks present', () => {
+ const styles = $('style').text();
+ // Check for Shiki class prefix
+ assert.ok(styles.includes('.__a_'), 'Should have Shiki token class definitions');
+ // Check for base utility classes
+ assert.ok(styles.includes('.astro-code-overflow'), 'Should have overflow class');
+ });
+
+ it('should render code blocks with Shiki classes', () => {
+ const codeBlock = $('pre.astro-code');
+ assert.ok(codeBlock.length > 0, 'Should have code blocks with astro-code class');
+
+ const classes = codeBlock.attr('class');
+ assert.ok(classes && classes.includes('__a_'), 'Code block should have Shiki token class');
+ });
+ });
+
+ describe('Without code blocks', () => {
+ let fixture;
+ let $;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/astro-markdown-shiki-conditional/no-code/',
+ });
+ await fixture.build();
+ const html = await fixture.readFile('/index.html');
+ $ = cheerio.load(html);
+ });
+
+ it('should NOT inject Shiki CSS when no code blocks', () => {
+ const styles = $('style').text();
+ // Should not have Shiki classes
+ assert.ok(!styles.includes('.__a_'), 'Should NOT have Shiki token class definitions');
+ assert.ok(!styles.includes('.astro-code-overflow'), 'Should NOT have overflow class');
+ });
+
+ it('should NOT have code blocks', () => {
+ const codeBlocks = $('pre.astro-code');
+ assert.equal(codeBlocks.length, 0, 'Should not have code blocks');
+ });
+
+ it('should still render markdown content', () => {
+ assert.equal($('h1').text(), 'Hello world');
+ assert.ok($('p').length > 0, 'Should have paragraph elements');
+ // Inline code should still be present
+ assert.ok($('code').length > 0, 'Should have inline code elements');
+ });
+ });
+
+ describe('With excluded language', () => {
+ let fixture;
+ let $;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/astro-markdown-shiki-conditional/excluded-lang/',
+ });
+ await fixture.build();
+ const html = await fixture.readFile('/index.html');
+ $ = cheerio.load(html);
+ });
+
+ it('should NOT inject CSS for excluded languages', () => {
+ const styles = $('style').text();
+ // Should not have Shiki classes since mermaid is excluded
+ assert.ok(!styles.includes('.__a_'), 'Should NOT have Shiki token class definitions');
+ assert.ok(!styles.includes('.astro-code-overflow'), 'Should NOT have overflow class');
+ });
+
+ it('should still have the code block element (unstyled)', () => {
+ // The code block should exist but without Shiki styling
+ const codeElements = $('pre code');
+ assert.ok(codeElements.length > 0, 'Should have code block element');
+
+ // Should have language-mermaid class from markdown processing
+ const code = codeElements.first();
+ const className = code.attr('class') || '';
+ assert.ok(className.includes('language-mermaid'), 'Should have language-mermaid class');
+ });
+
+ it('should NOT have astro-code class', () => {
+ const astroCodeBlocks = $('pre.astro-code');
+ assert.equal(astroCodeBlocks.length, 0, 'Should not have astro-code class');
+ });
+ });
+});
diff --git a/packages/astro/test/astro-markdown-shiki.test.js b/packages/astro/test/astro-markdown-shiki.test.js
index 8140b4d5dfee..42417897965c 100644
--- a/packages/astro/test/astro-markdown-shiki.test.js
+++ b/packages/astro/test/astro-markdown-shiki.test.js
@@ -16,23 +16,24 @@ describe('Astro Markdown Shiki', () => {
const html = await fixture.readFile('/index.html');
const $ = cheerio.load(html);
- // There should be no HTML from Prism
- assert.equal($('.token').length, 0);
-
+ // There are 2 code blocks in index.md (yaml and diff)
assert.equal($('pre').length, 2);
- assert.ok($('pre').hasClass('astro-code'));
- assert.equal(
- $('pre').attr().style,
- 'background-color:#24292e;color:#e1e4e8; overflow-x: auto;',
- );
+ const yamlBlock = $('pre').first();
+ assert.ok(yamlBlock.hasClass('astro-code'));
+ // Styles are now class-based - no inline styles
+ assert.ok(yamlBlock.hasClass('astro-code-overflow'), 'has overflow class');
+ assert.ok(!yamlBlock.attr('style'), 'should have no inline style attribute');
});
it('Can render diff syntax with "user-select: none"', async () => {
const html = await fixture.readFile('/index.html');
const $ = cheerio.load(html);
- const diffBlockHtml = $('pre').last().html();
- assert.ok(diffBlockHtml.includes(`+`));
- assert.ok(diffBlockHtml.includes(`-`));
+ // The diff block is the second in index.html
+ const diffBlock = $('pre').eq(1);
+ const diffHtml = $.html(diffBlock);
+ // user-select: none is now a class, not inline style
+ assert.ok(diffHtml.includes(`+`));
+ assert.ok(diffHtml.includes(`-`));
});
});
@@ -51,10 +52,9 @@ describe('Astro Markdown Shiki', () => {
assert.equal($('pre').length, 1);
assert.ok($('pre').hasClass('astro-code'));
- assert.equal(
- $('pre').attr().style,
- 'background-color:#fff;color:#24292e; overflow-x: auto;',
- );
+ // Styles are now class-based - no inline styles
+ assert.ok($('pre').hasClass('astro-code-overflow'), 'has overflow class');
+ assert.ok(!$('pre').attr('style'), 'should have no inline style attribute');
});
});
@@ -70,12 +70,15 @@ describe('Astro Markdown Shiki', () => {
const html = await fixture.readFile('/index.html');
const $ = cheerio.load(html);
- assert.equal($('pre').length, 1);
- assert.ok($('pre').hasClass('astro-code'));
- assert.equal(
- $('pre').attr().style,
- 'background-color:#FDFDFE;color:#4E5377; overflow-x: auto;',
- );
+ // With class-based styles, ALL styles are in CSS classes (no inline styles)
+ assert.ok(!$('pre').attr('style'), 'should have no inline style attribute');
+ assert.ok($('pre').hasClass('astro-code-overflow'), 'has overflow class');
+
+ // Verify styles are in the