From b895113a0ae347ecd81bd8866ae2d816ea20836b Mon Sep 17 00:00:00 2001 From: Alexander Niebuhr Date: Tue, 14 Nov 2023 07:53:14 +0100 Subject: [PATCH 01/16] fix(assets): bundling regression for specific config on non-Node runtimes (#9087) --- .changeset/red-houses-explode.md | 5 +++++ packages/astro/src/core/build/plugins/plugin-prerender.ts | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 .changeset/red-houses-explode.md diff --git a/.changeset/red-houses-explode.md b/.changeset/red-houses-explode.md new file mode 100644 index 000000000000..d355327f34a6 --- /dev/null +++ b/.changeset/red-houses-explode.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes the regression which broke bundling of image service for pre-rendered pages, which was introduced by [#8854](https://github.com/withastro/astro/pull/8854) diff --git a/packages/astro/src/core/build/plugins/plugin-prerender.ts b/packages/astro/src/core/build/plugins/plugin-prerender.ts index d3d5305e4f43..0584f15a6b59 100644 --- a/packages/astro/src/core/build/plugins/plugin-prerender.ts +++ b/packages/astro/src/core/build/plugins/plugin-prerender.ts @@ -12,7 +12,11 @@ function vitePluginPrerender(opts: StaticBuildOptions, internals: BuildInternals outputOptions(outputOptions) { extendManualChunks(outputOptions, { - before(id, meta) { + after(id, meta) { + // Split the Astro runtime into a separate chunk for readability + if (id.includes('astro/dist/runtime')) { + return 'astro'; + } const pageInfo = internals.pagesByViteID.get(id); if (pageInfo) { // prerendered pages should be split into their own chunk From 84312f24f8af2098b0831cf2361ea3d37761d3d3 Mon Sep 17 00:00:00 2001 From: Rishi Raj Jain Date: Tue, 14 Nov 2023 15:09:37 +0530 Subject: [PATCH 02/16] fix: Query params trigger the trailingSlash error in preview mode (#9045) --- .changeset/tidy-peas-accept.md | 5 +++++ packages/astro/src/core/preview/vite-plugin-astro-preview.ts | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 .changeset/tidy-peas-accept.md diff --git a/.changeset/tidy-peas-accept.md b/.changeset/tidy-peas-accept.md new file mode 100644 index 000000000000..fa580f073449 --- /dev/null +++ b/.changeset/tidy-peas-accept.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes preview server `trailingSlash` handling for request URLs with query strings diff --git a/packages/astro/src/core/preview/vite-plugin-astro-preview.ts b/packages/astro/src/core/preview/vite-plugin-astro-preview.ts index 9faabe29b21e..b07b88b7ad99 100644 --- a/packages/astro/src/core/preview/vite-plugin-astro-preview.ts +++ b/packages/astro/src/core/preview/vite-plugin-astro-preview.ts @@ -24,7 +24,8 @@ export function vitePluginAstroPreview(settings: AstroSettings): Plugin { return; } - const pathname = stripBase(req.url!, base); + const strippedPathname = stripBase(req.url!, base); + const pathname = new URL(strippedPathname, 'https://a.b').pathname const isRoot = pathname === '/'; // Validate trailingSlash From 6f5de8dfba021cacc8f58140671a98bcbb0936a3 Mon Sep 17 00:00:00 2001 From: bluwy Date: Tue, 14 Nov 2023 09:41:17 +0000 Subject: [PATCH 03/16] [ci] format --- packages/astro/src/core/preview/vite-plugin-astro-preview.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/astro/src/core/preview/vite-plugin-astro-preview.ts b/packages/astro/src/core/preview/vite-plugin-astro-preview.ts index b07b88b7ad99..7f9979275607 100644 --- a/packages/astro/src/core/preview/vite-plugin-astro-preview.ts +++ b/packages/astro/src/core/preview/vite-plugin-astro-preview.ts @@ -25,7 +25,7 @@ export function vitePluginAstroPreview(settings: AstroSettings): Plugin { } const strippedPathname = stripBase(req.url!, base); - const pathname = new URL(strippedPathname, 'https://a.b').pathname + const pathname = new URL(strippedPathname, 'https://a.b').pathname; const isRoot = pathname === '/'; // Validate trailingSlash From 4537ecf0d060f89cb8c000338a7fc5f4197a88c8 Mon Sep 17 00:00:00 2001 From: Bjorn Lu Date: Tue, 14 Nov 2023 23:00:17 +0800 Subject: [PATCH 04/16] Refactor shikiji syntax highlighting code (#9083) --- .changeset/fast-zebras-divide.md | 5 + .changeset/mighty-zebras-clap.md | 6 + packages/astro/components/Code.astro | 60 +------- packages/astro/src/core/errors/dev/vite.ts | 2 +- packages/astro/src/core/shiki.ts | 39 ++--- .../markdoc/src/extensions/shiki.ts | 105 +------------- packages/markdown/remark/src/index.ts | 1 + packages/markdown/remark/src/remark-shiki.ts | 113 +-------------- packages/markdown/remark/src/shiki.ts | 135 ++++++++++++++++++ .../remark/test/{shiki.js => shiki.test.js} | 25 +++- 10 files changed, 198 insertions(+), 293 deletions(-) create mode 100644 .changeset/fast-zebras-divide.md create mode 100644 .changeset/mighty-zebras-clap.md create mode 100644 packages/markdown/remark/src/shiki.ts rename packages/markdown/remark/test/{shiki.js => shiki.test.js} (52%) diff --git a/.changeset/fast-zebras-divide.md b/.changeset/fast-zebras-divide.md new file mode 100644 index 000000000000..54652164d4c4 --- /dev/null +++ b/.changeset/fast-zebras-divide.md @@ -0,0 +1,5 @@ +--- +'@astrojs/markdown-remark': minor +--- + +Exports `createShikiHighlighter` for low-level syntax highlighting usage diff --git a/.changeset/mighty-zebras-clap.md b/.changeset/mighty-zebras-clap.md new file mode 100644 index 000000000000..ab05e497a93c --- /dev/null +++ b/.changeset/mighty-zebras-clap.md @@ -0,0 +1,6 @@ +--- +'@astrojs/markdoc': patch +'astro': patch +--- + +Uses new `createShikiHighlighter` API from `@astrojs/markdown-remark` to avoid code duplication diff --git a/packages/astro/components/Code.astro b/packages/astro/components/Code.astro index b1d21fd9ea3d..506a3ed3c0d1 100644 --- a/packages/astro/components/Code.astro +++ b/packages/astro/components/Code.astro @@ -10,8 +10,7 @@ import type { ThemeRegistration, ThemeRegistrationRaw, } from 'shikiji'; -import { visit } from 'unist-util-visit'; -import { getCachedHighlighter, replaceCssVariables } from '../dist/core/shiki.js'; +import { getCachedHighlighter } from '../dist/core/shiki.js'; interface Props { /** The code to highlight. Required. */ @@ -94,60 +93,13 @@ if (typeof lang === 'object') { const highlighter = await getCachedHighlighter({ langs: [lang], - themes: Object.values(experimentalThemes).length ? Object.values(experimentalThemes) : [theme], + theme, + experimentalThemes, + wrap, }); -const themeOptions = Object.values(experimentalThemes).length - ? { themes: experimentalThemes } - : { theme }; -const html = highlighter.codeToHtml(code, { - lang: typeof lang === 'string' ? lang : lang.name, - ...themeOptions, - transforms: { - pre(node) { - // Swap to `code` tag if inline - if (inline) { - node.tagName = 'code'; - } - - // Cast to string as shikiji will always pass them as strings instead of any other types - const classValue = (node.properties.class as string) ?? ''; - const styleValue = (node.properties.style as string) ?? ''; - - // Replace "shiki" class naming with "astro-code" - node.properties.class = classValue.replace(/shiki/g, 'astro-code'); - - // Handle code wrapping - // if wrap=null, do nothing. - if (wrap === false) { - node.properties.style = styleValue + '; overflow-x: auto;'; - } else if (wrap === true) { - node.properties.style = - styleValue + '; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;'; - } - }, - code(node) { - if (inline) { - return node.children[0] as typeof node; - } - }, - root(node) { - if (Object.values(experimentalThemes).length) { - return; - } - - // theme.id for shiki -> shikiji compat - const themeName = typeof theme === 'string' ? theme : theme.name; - if (themeName === 'css-variables') { - // Replace special color tokens to CSS variables - visit(node as any, 'element', (child) => { - if (child.properties?.style) { - child.properties.style = replaceCssVariables(child.properties.style); - } - }); - } - }, - }, +const html = highlighter.highlight(code, typeof lang === 'string' ? lang : lang.name, { + inline, }); --- diff --git a/packages/astro/src/core/errors/dev/vite.ts b/packages/astro/src/core/errors/dev/vite.ts index 40551a185eba..b20b8ae1c67d 100644 --- a/packages/astro/src/core/errors/dev/vite.ts +++ b/packages/astro/src/core/errors/dev/vite.ts @@ -2,8 +2,8 @@ import * as fs from 'node:fs'; import { fileURLToPath } from 'node:url'; import { codeToHtml } from 'shikiji'; import type { ErrorPayload } from 'vite'; +import { replaceCssVariables } from '@astrojs/markdown-remark'; import type { ModuleLoader } from '../../module-loader/index.js'; -import { replaceCssVariables } from '../../shiki.js'; import { FailedToLoadModuleSSR, InvalidGlob, MdxIntegrationMissingError } from '../errors-data.js'; import { AstroError, type ErrorWithMetadata } from '../errors.js'; import { createSafeError } from '../utils.js'; diff --git a/packages/astro/src/core/shiki.ts b/packages/astro/src/core/shiki.ts index 3bfa0379d9cf..a36cc78afe38 100644 --- a/packages/astro/src/core/shiki.ts +++ b/packages/astro/src/core/shiki.ts @@ -1,36 +1,13 @@ -import { getHighlighter, type Highlighter } from 'shikiji'; +import { + createShikiHighlighter, + type ShikiHighlighter, + type ShikiConfig, +} from '@astrojs/markdown-remark'; -type HighlighterOptions = NonNullable[0]>; - -const ASTRO_COLOR_REPLACEMENTS: Record = { - '#000001': 'var(--astro-code-color-text)', - '#000002': 'var(--astro-code-color-background)', - '#000004': 'var(--astro-code-token-constant)', - '#000005': 'var(--astro-code-token-string)', - '#000006': 'var(--astro-code-token-comment)', - '#000007': 'var(--astro-code-token-keyword)', - '#000008': 'var(--astro-code-token-parameter)', - '#000009': 'var(--astro-code-token-function)', - '#000010': 'var(--astro-code-token-string-expression)', - '#000011': 'var(--astro-code-token-punctuation)', - '#000012': 'var(--astro-code-token-link)', -}; -const COLOR_REPLACEMENT_REGEX = new RegExp( - `(${Object.keys(ASTRO_COLOR_REPLACEMENTS).join('|')})`, - 'g' -); - -// Caches Promise for reuse when the same theme and langs are provided +// Caches Promise for reuse when the same theme and langs are provided const cachedHighlighters = new Map(); -/** - * shiki -> shikiji compat as we need to manually replace it - */ -export function replaceCssVariables(str: string) { - return str.replace(COLOR_REPLACEMENT_REGEX, (match) => ASTRO_COLOR_REPLACEMENTS[match] || match); -} - -export function getCachedHighlighter(opts: HighlighterOptions): Promise { +export function getCachedHighlighter(opts: ShikiConfig): Promise { // Always sort keys before stringifying to make sure objects match regardless of parameter ordering const key = JSON.stringify(opts, Object.keys(opts).sort()); @@ -39,7 +16,7 @@ export function getCachedHighlighter(opts: HighlighterOptions): Promise = { - '#000001': 'var(--astro-code-color-text)', - '#000002': 'var(--astro-code-color-background)', - '#000004': 'var(--astro-code-token-constant)', - '#000005': 'var(--astro-code-token-string)', - '#000006': 'var(--astro-code-token-comment)', - '#000007': 'var(--astro-code-token-keyword)', - '#000008': 'var(--astro-code-token-parameter)', - '#000009': 'var(--astro-code-token-function)', - '#000010': 'var(--astro-code-token-string-expression)', - '#000011': 'var(--astro-code-token-punctuation)', - '#000012': 'var(--astro-code-token-link)', -}; -const COLOR_REPLACEMENT_REGEX = new RegExp( - `(${Object.keys(ASTRO_COLOR_REPLACEMENTS).join('|')})`, - 'g' -); - -const PRE_SELECTOR = /
([\+|\-])/g;
-const INLINE_STYLE_SELECTOR = /style="(.*?)"/;
-const INLINE_STYLE_SELECTOR_GLOBAL = /style="(.*?)"/g;
-
-/**
- * Note: cache only needed for dev server reloads, internal test suites, and manual calls to `Markdoc.transform` by the user.
- * Otherwise, `shiki()` is only called once per build, NOT once per page, so a cache isn't needed!
- */
-const highlighterCache = new Map();
-
-export default async function shiki({
-	langs = [],
-	theme = 'github-dark',
-	wrap = false,
-}: ShikiConfig = {}): Promise {
-	const cacheId = typeof theme === 'string' ? theme : theme.name || '';
-	let highlighter = highlighterCache.get(cacheId)!;
-	if (!highlighter) {
-		highlighter = await getHighlighter({
-			langs: langs.length ? langs : Object.keys(bundledLanguages),
-			themes: [theme],
-		});
-		highlighterCache.set(cacheId, highlighter);
-	}
+export default async function shiki(config?: ShikiConfig): Promise {
+	const highlighter = await createShikiHighlighter(config);
 
 	return {
 		nodes: {
 			fence: {
 				attributes: Markdoc.nodes.fence.attributes!,
 				transform({ attributes }) {
-					let lang: string;
-
-					if (typeof attributes.language === 'string') {
-						const langExists = highlighter
-							.getLoadedLanguages()
-							.includes(attributes.language as any);
-						if (langExists) {
-							lang = attributes.language;
-						} else {
-							console.warn(
-								`[Shiki highlighter] The language "${attributes.language}" doesn't exist, falling back to plaintext.`
-							);
-							lang = 'plaintext';
-						}
-					} else {
-						lang = 'plaintext';
-					}
-
-					let html = highlighter.codeToHtml(attributes.content, { lang, theme });
-
-					// Q: Could these regexes match on a user's inputted code blocks?
-					// A: Nope! All rendered HTML is properly escaped.
-					// Ex. If a user typed `$2'
-						);
-					}
-
-					if (wrap === false) {
-						html = html.replace(INLINE_STYLE_SELECTOR, 'style="$1; overflow-x: auto;"');
-					} else if (wrap === true) {
-						html = html.replace(
-							INLINE_STYLE_SELECTOR,
-							'style="$1; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;"'
-						);
-					}
-
-					// theme.id for shiki -> shikiji compat
-					const themeName = typeof theme === 'string' ? theme : theme.name;
-					if (themeName === 'css-variables') {
-						html = html.replace(INLINE_STYLE_SELECTOR_GLOBAL, (m) => replaceCssVariables(m));
-					}
+					const lang = typeof attributes.language === 'string' ? attributes.language : 'plaintext';
+					const html = highlighter.highlight(attributes.content, lang);
 
 					// Use `unescapeHTML` to return `HTMLString` for Astro renderer to inline as HTML
 					return unescapeHTML(html) as any;
@@ -110,10 +22,3 @@ export default async function shiki({
 		},
 	};
 }
-
-/**
- * shiki -> shikiji compat as we need to manually replace it
- */
-function replaceCssVariables(str: string) {
-	return str.replace(COLOR_REPLACEMENT_REGEX, (match) => ASTRO_COLOR_REPLACEMENTS[match] || match);
-}
diff --git a/packages/markdown/remark/src/index.ts b/packages/markdown/remark/src/index.ts
index 61f97072bf34..a60ab88c0ef5 100644
--- a/packages/markdown/remark/src/index.ts
+++ b/packages/markdown/remark/src/index.ts
@@ -32,6 +32,7 @@ export { rehypeHeadingIds } from './rehype-collect-headings.js';
 export { remarkCollectImages } from './remark-collect-images.js';
 export { remarkPrism } from './remark-prism.js';
 export { remarkShiki } from './remark-shiki.js';
+export { createShikiHighlighter, replaceCssVariables, type ShikiHighlighter } from './shiki.js';
 export * from './types.js';
 
 export const markdownConfigDefaults: Omit, 'drafts'> = {
diff --git a/packages/markdown/remark/src/remark-shiki.ts b/packages/markdown/remark/src/remark-shiki.ts
index 4eaae5ff2529..8c6e93242baa 100644
--- a/packages/markdown/remark/src/remark-shiki.ts
+++ b/packages/markdown/remark/src/remark-shiki.ts
@@ -1,109 +1,17 @@
-import { bundledLanguages, getHighlighter, type Highlighter } from 'shikiji';
 import { visit } from 'unist-util-visit';
 import type { RemarkPlugin, ShikiConfig } from './types.js';
+import { createShikiHighlighter, type ShikiHighlighter } from './shiki.js';
 
-const ASTRO_COLOR_REPLACEMENTS: Record = {
-	'#000001': 'var(--astro-code-color-text)',
-	'#000002': 'var(--astro-code-color-background)',
-	'#000004': 'var(--astro-code-token-constant)',
-	'#000005': 'var(--astro-code-token-string)',
-	'#000006': 'var(--astro-code-token-comment)',
-	'#000007': 'var(--astro-code-token-keyword)',
-	'#000008': 'var(--astro-code-token-parameter)',
-	'#000009': 'var(--astro-code-token-function)',
-	'#000010': 'var(--astro-code-token-string-expression)',
-	'#000011': 'var(--astro-code-token-punctuation)',
-	'#000012': 'var(--astro-code-token-link)',
-};
-const COLOR_REPLACEMENT_REGEX = new RegExp(
-	`(${Object.keys(ASTRO_COLOR_REPLACEMENTS).join('|')})`,
-	'g'
-);
-
-/**
- * getHighlighter() is the most expensive step of Shiki. Instead of calling it on every page,
- * cache it here as much as possible. Make sure that your highlighters can be cached, state-free.
- * We make this async, so that multiple calls to parse markdown still share the same highlighter.
- */
-const highlighterCacheAsync = new Map>();
-
-export function remarkShiki({
-	langs = [],
-	theme = 'github-dark',
-	experimentalThemes = {},
-	wrap = false,
-}: ShikiConfig = {}): ReturnType {
-	const themes = experimentalThemes;
-
-	const cacheId =
-		Object.values(themes)
-			.map((t) => (typeof t === 'string' ? t : t.name ?? ''))
-			.join(',') +
-		(typeof theme === 'string' ? theme : theme.name ?? '') +
-		langs.map((l) => l.name ?? (l as any).id).join(',');
-
-	let highlighterAsync = highlighterCacheAsync.get(cacheId);
-	if (!highlighterAsync) {
-		highlighterAsync = getHighlighter({
-			langs: langs.length ? langs : Object.keys(bundledLanguages),
-			themes: Object.values(themes).length ? Object.values(themes) : [theme],
-		});
-		highlighterCacheAsync.set(cacheId, highlighterAsync);
-	}
+export function remarkShiki(config?: ShikiConfig): ReturnType {
+	let highlighterAsync: Promise | undefined;
 
 	return async (tree: any) => {
-		const highlighter = await highlighterAsync!;
+		highlighterAsync ??= createShikiHighlighter(config);
+		const highlighter = await highlighterAsync;
 
 		visit(tree, 'code', (node) => {
-			let lang: string;
-
-			if (typeof node.lang === 'string') {
-				const langExists = highlighter.getLoadedLanguages().includes(node.lang);
-				if (langExists) {
-					lang = node.lang;
-				} else {
-					// eslint-disable-next-line no-console
-					console.warn(`The language "${node.lang}" doesn't exist, falling back to plaintext.`);
-					lang = 'plaintext';
-				}
-			} else {
-				lang = 'plaintext';
-			}
-
-			let themeOptions = Object.values(themes).length ? { themes } : { theme };
-			let html = highlighter.codeToHtml(node.value, { ...themeOptions, lang });
-
-			// Q: Couldn't these regexes match on a user's inputted code blocks?
-			// A: Nope! All rendered HTML is properly escaped.
-			// Ex. If a user typed `([\+|\-])/g,
-					'$2'
-				);
-			}
-			// Handle code wrapping
-			// if wrap=null, do nothing.
-			if (wrap === false) {
-				html = html.replace(/style="(.*?)"/, 'style="$1; overflow-x: auto;"');
-			} else if (wrap === true) {
-				html = html.replace(
-					/style="(.*?)"/,
-					'style="$1; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;"'
-				);
-			}
-
-			// theme.id for shiki -> shikiji compat
-			const themeName = typeof theme === 'string' ? theme : theme.name;
-			if (themeName === 'css-variables') {
-				html = html.replace(/style="(.*?)"/g, (m) => replaceCssVariables(m));
-			}
+			const lang = typeof node.lang === 'string' ? node.lang : 'plaintext';
+			const html = highlighter.highlight(node.value, lang);
 
 			node.type = 'html';
 			node.value = html;
@@ -111,10 +19,3 @@ export function remarkShiki({
 		});
 	};
 }
-
-/**
- * shiki -> shikiji compat as we need to manually replace it
- */
-function replaceCssVariables(str: string) {
-	return str.replace(COLOR_REPLACEMENT_REGEX, (match) => ASTRO_COLOR_REPLACEMENTS[match] || match);
-}
diff --git a/packages/markdown/remark/src/shiki.ts b/packages/markdown/remark/src/shiki.ts
new file mode 100644
index 000000000000..477ab2184b83
--- /dev/null
+++ b/packages/markdown/remark/src/shiki.ts
@@ -0,0 +1,135 @@
+import { bundledLanguages, getHighlighter } from 'shikiji';
+import { visit } from 'unist-util-visit';
+import type { ShikiConfig } from './types.js';
+
+export interface ShikiHighlighter {
+	highlight(code: string, lang?: string, options?: { inline?: boolean }): string;
+}
+
+const ASTRO_COLOR_REPLACEMENTS: Record = {
+	'#000001': 'var(--astro-code-color-text)',
+	'#000002': 'var(--astro-code-color-background)',
+	'#000004': 'var(--astro-code-token-constant)',
+	'#000005': 'var(--astro-code-token-string)',
+	'#000006': 'var(--astro-code-token-comment)',
+	'#000007': 'var(--astro-code-token-keyword)',
+	'#000008': 'var(--astro-code-token-parameter)',
+	'#000009': 'var(--astro-code-token-function)',
+	'#000010': 'var(--astro-code-token-string-expression)',
+	'#000011': 'var(--astro-code-token-punctuation)',
+	'#000012': 'var(--astro-code-token-link)',
+};
+const COLOR_REPLACEMENT_REGEX = new RegExp(
+	`(${Object.keys(ASTRO_COLOR_REPLACEMENTS).join('|')})`,
+	'g'
+);
+
+export async function createShikiHighlighter({
+	langs = [],
+	theme = 'github-dark',
+	experimentalThemes = {},
+	wrap = false,
+}: ShikiConfig = {}): Promise {
+	const themes = experimentalThemes;
+
+	const highlighter = await getHighlighter({
+		langs: langs.length ? langs : Object.keys(bundledLanguages),
+		themes: Object.values(themes).length ? Object.values(themes) : [theme],
+	});
+
+	const loadedLanguages = highlighter.getLoadedLanguages();
+
+	return {
+		highlight(code, lang = 'plaintext', options) {
+			if (lang !== 'plaintext' && !loadedLanguages.includes(lang)) {
+				// eslint-disable-next-line no-console
+				console.warn(`[Shiki] The language "${lang}" doesn't exist, falling back to "plaintext".`);
+				lang = 'plaintext';
+			}
+
+			const themeOptions = Object.values(themes).length ? { themes } : { theme };
+			const inline = options?.inline ?? false;
+
+			return highlighter.codeToHtml(code, {
+				...themeOptions,
+				lang,
+				transforms: {
+					pre(node) {
+						// Swap to `code` tag if inline
+						if (inline) {
+							node.tagName = 'code';
+						}
+
+						// Cast to string as shikiji will always pass them as strings instead of any other types
+						const classValue = (node.properties.class as string) ?? '';
+						const styleValue = (node.properties.style as string) ?? '';
+
+						// Replace "shiki" class naming with "astro-code"
+						node.properties.class = classValue.replace(/shiki/g, 'astro-code');
+
+						// Handle code wrapping
+						// if wrap=null, do nothing.
+						if (wrap === false) {
+							node.properties.style = styleValue + '; overflow-x: auto;';
+						} else if (wrap === true) {
+							node.properties.style =
+								styleValue + '; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;';
+						}
+					},
+					line(node) {
+						// Add "user-select: none;" for "+"/"-" diff symbols.
+						// Transform `+ something
+						// into      `+ something`
+						if (lang === 'diff') {
+							const innerSpanNode = node.children[0];
+							const innerSpanTextNode =
+								innerSpanNode?.type === 'element' && innerSpanNode.children?.[0];
+
+							if (innerSpanTextNode && innerSpanTextNode.type === 'text') {
+								const start = innerSpanTextNode.value[0];
+								if (start === '+' || start === '-') {
+									innerSpanTextNode.value = innerSpanTextNode.value.slice(1);
+									innerSpanNode.children.unshift({
+										type: 'element',
+										tagName: 'span',
+										properties: { style: 'user-select: none;' },
+										children: [{ type: 'text', value: start }],
+									});
+								}
+							}
+						}
+					},
+					code(node) {
+						if (inline) {
+							return node.children[0] as typeof node;
+						}
+					},
+					root(node) {
+						if (Object.values(experimentalThemes).length) {
+							return;
+						}
+
+						// theme.id for shiki -> shikiji compat
+						const themeName = typeof theme === 'string' ? theme : theme.name;
+						if (themeName === 'css-variables') {
+							// Replace special color tokens to CSS variables
+							visit(node as any, 'element', (child) => {
+								if (child.properties?.style) {
+									child.properties.style = replaceCssVariables(child.properties.style);
+								}
+							});
+						}
+					},
+				},
+			});
+		},
+	};
+}
+
+/**
+ * shiki -> shikiji compat as we need to manually replace it
+ * @internal Exported for error overlay use only
+ */
+export function replaceCssVariables(str: string) {
+	return str.replace(COLOR_REPLACEMENT_REGEX, (match) => ASTRO_COLOR_REPLACEMENTS[match] || match);
+}
diff --git a/packages/markdown/remark/test/shiki.js b/packages/markdown/remark/test/shiki.test.js
similarity index 52%
rename from packages/markdown/remark/test/shiki.js
rename to packages/markdown/remark/test/shiki.test.js
index c7ace6187aee..d6f3e8925928 100644
--- a/packages/markdown/remark/test/shiki.js
+++ b/packages/markdown/remark/test/shiki.test.js
@@ -1,4 +1,4 @@
-import { createMarkdownProcessor } from '../dist/index.js';
+import { createMarkdownProcessor, createShikiHighlighter } from '../dist/index.js';
 import chai from 'chai';
 
 describe('shiki syntax highlighting', () => {
@@ -27,4 +27,27 @@ describe('shiki syntax highlighting', () => {
 		chai.expect(code).to.contain('--shiki-dark-bg:');
 		chai.expect(code).to.contain('github-dark');
 	});
+
+	it('createShikiHighlighter works', async () => {
+		const highlighter = await createShikiHighlighter();
+
+		const html = highlighter.highlight('const foo = "bar";', 'js');
+
+		chai.expect(html).to.contain('astro-code github-dark');
+		chai.expect(html).to.contain('background-color:#24292e;color:#e1e4e8;');
+	});
+
+	it('diff +/- text has user-select: none', async () => {
+		const highlighter = await createShikiHighlighter();
+
+		const html = highlighter.highlight(
+			`\
+- const foo = "bar";
++ const foo = "world";`,
+			'diff'
+		);
+		chai.expect(html).to.contain('user-select: none');
+		chai.expect(html).to.contain('>-');
+		chai.expect(html).to.contain('>+');
+	});
 });

From 0aee43fc16fae51fae329a7078ddb37c018df6f7 Mon Sep 17 00:00:00 2001
From: bluwy 
Date: Tue, 14 Nov 2023 15:01:58 +0000
Subject: [PATCH 05/16] [ci] format

---
 packages/astro/src/core/errors/dev/vite.ts            | 2 +-
 packages/astro/src/core/shiki.ts                      | 2 +-
 packages/integrations/markdoc/src/extensions/shiki.ts | 2 +-
 packages/markdown/remark/src/remark-shiki.ts          | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/packages/astro/src/core/errors/dev/vite.ts b/packages/astro/src/core/errors/dev/vite.ts
index b20b8ae1c67d..b3e49234d603 100644
--- a/packages/astro/src/core/errors/dev/vite.ts
+++ b/packages/astro/src/core/errors/dev/vite.ts
@@ -1,8 +1,8 @@
+import { replaceCssVariables } from '@astrojs/markdown-remark';
 import * as fs from 'node:fs';
 import { fileURLToPath } from 'node:url';
 import { codeToHtml } from 'shikiji';
 import type { ErrorPayload } from 'vite';
-import { replaceCssVariables } from '@astrojs/markdown-remark';
 import type { ModuleLoader } from '../../module-loader/index.js';
 import { FailedToLoadModuleSSR, InvalidGlob, MdxIntegrationMissingError } from '../errors-data.js';
 import { AstroError, type ErrorWithMetadata } from '../errors.js';
diff --git a/packages/astro/src/core/shiki.ts b/packages/astro/src/core/shiki.ts
index a36cc78afe38..83c4d0965807 100644
--- a/packages/astro/src/core/shiki.ts
+++ b/packages/astro/src/core/shiki.ts
@@ -1,7 +1,7 @@
 import {
 	createShikiHighlighter,
-	type ShikiHighlighter,
 	type ShikiConfig,
+	type ShikiHighlighter,
 } from '@astrojs/markdown-remark';
 
 // Caches Promise for reuse when the same theme and langs are provided
diff --git a/packages/integrations/markdoc/src/extensions/shiki.ts b/packages/integrations/markdoc/src/extensions/shiki.ts
index c70ef7f08bf3..3026d8080648 100644
--- a/packages/integrations/markdoc/src/extensions/shiki.ts
+++ b/packages/integrations/markdoc/src/extensions/shiki.ts
@@ -1,5 +1,5 @@
-import Markdoc from '@markdoc/markdoc';
 import { createShikiHighlighter } from '@astrojs/markdown-remark';
+import Markdoc from '@markdoc/markdoc';
 import type { ShikiConfig } from 'astro';
 import { unescapeHTML } from 'astro/runtime/server/index.js';
 import type { AstroMarkdocConfig } from '../config.js';
diff --git a/packages/markdown/remark/src/remark-shiki.ts b/packages/markdown/remark/src/remark-shiki.ts
index 8c6e93242baa..ebf70fbec9b6 100644
--- a/packages/markdown/remark/src/remark-shiki.ts
+++ b/packages/markdown/remark/src/remark-shiki.ts
@@ -1,6 +1,6 @@
 import { visit } from 'unist-util-visit';
-import type { RemarkPlugin, ShikiConfig } from './types.js';
 import { createShikiHighlighter, type ShikiHighlighter } from './shiki.js';
+import type { RemarkPlugin, ShikiConfig } from './types.js';
 
 export function remarkShiki(config?: ShikiConfig): ReturnType {
 	let highlighterAsync: Promise | undefined;

From e63aac94ca8aca96a39785a2f5926827ed18c255 Mon Sep 17 00:00:00 2001
From: "Houston (Bot)" <108291165+astrobot-houston@users.noreply.github.com>
Date: Tue, 14 Nov 2023 07:20:35 -0800
Subject: [PATCH 06/16] [ci] release (#9078)

Co-authored-by: github-actions[bot] 
---
 .changeset/calm-lemons-compare.md           |  5 --
 .changeset/dirty-zoos-fail.md               |  5 --
 .changeset/famous-hats-teach.md             |  5 --
 .changeset/fast-zebras-divide.md            |  5 --
 .changeset/mighty-zebras-clap.md            |  6 --
 .changeset/new-pets-fail.md                 |  5 --
 .changeset/red-houses-explode.md            |  5 --
 .changeset/six-chefs-flash.md               |  5 --
 .changeset/tidy-peas-accept.md              |  5 --
 examples/basics/package.json                |  2 +-
 examples/blog/package.json                  |  4 +-
 examples/component/package.json             |  2 +-
 examples/framework-alpine/package.json      |  2 +-
 examples/framework-lit/package.json         |  2 +-
 examples/framework-multiple/package.json    |  2 +-
 examples/framework-preact/package.json      |  2 +-
 examples/framework-react/package.json       |  2 +-
 examples/framework-solid/package.json       |  2 +-
 examples/framework-svelte/package.json      |  2 +-
 examples/framework-vue/package.json         |  2 +-
 examples/hackernews/package.json            |  2 +-
 examples/integration/package.json           |  2 +-
 examples/middleware/package.json            |  2 +-
 examples/minimal/package.json               |  2 +-
 examples/non-html-pages/package.json        |  2 +-
 examples/portfolio/package.json             |  2 +-
 examples/ssr/package.json                   |  2 +-
 examples/view-transitions/package.json      |  2 +-
 examples/with-markdoc/package.json          |  4 +-
 examples/with-markdown-plugins/package.json |  4 +-
 examples/with-markdown-shiki/package.json   |  2 +-
 examples/with-mdx/package.json              |  4 +-
 examples/with-nanostores/package.json       |  2 +-
 examples/with-tailwindcss/package.json      |  4 +-
 examples/with-vite-plugin-pwa/package.json  |  2 +-
 examples/with-vitest/package.json           |  2 +-
 packages/astro/CHANGELOG.md                 | 23 ++++++++
 packages/astro/package.json                 |  2 +-
 packages/integrations/markdoc/CHANGELOG.md  |  6 ++
 packages/integrations/markdoc/package.json  |  2 +-
 packages/integrations/mdx/CHANGELOG.md      |  7 +++
 packages/integrations/mdx/package.json      |  2 +-
 packages/markdown/remark/CHANGELOG.md       |  6 ++
 packages/markdown/remark/package.json       |  2 +-
 pnpm-lock.yaml                              | 64 ++++++++++-----------
 45 files changed, 110 insertions(+), 114 deletions(-)
 delete mode 100644 .changeset/calm-lemons-compare.md
 delete mode 100644 .changeset/dirty-zoos-fail.md
 delete mode 100644 .changeset/famous-hats-teach.md
 delete mode 100644 .changeset/fast-zebras-divide.md
 delete mode 100644 .changeset/mighty-zebras-clap.md
 delete mode 100644 .changeset/new-pets-fail.md
 delete mode 100644 .changeset/red-houses-explode.md
 delete mode 100644 .changeset/six-chefs-flash.md
 delete mode 100644 .changeset/tidy-peas-accept.md

diff --git a/.changeset/calm-lemons-compare.md b/.changeset/calm-lemons-compare.md
deleted file mode 100644
index d441d65e144e..000000000000
--- a/.changeset/calm-lemons-compare.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'astro': patch
----
-
-When redirecting to the default root locale, Astro middleare should take into consideration the value of `trailingSlash`
diff --git a/.changeset/dirty-zoos-fail.md b/.changeset/dirty-zoos-fail.md
deleted file mode 100644
index 2572092a72ea..000000000000
--- a/.changeset/dirty-zoos-fail.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'astro': patch
----
-
-Fixes display of debug messages when using the `--verbose` flag
diff --git a/.changeset/famous-hats-teach.md b/.changeset/famous-hats-teach.md
deleted file mode 100644
index c3ffb7755181..000000000000
--- a/.changeset/famous-hats-teach.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'astro': patch
----
-
-Fix Passthrough image service generating multiple images with the same content in certain cases
diff --git a/.changeset/fast-zebras-divide.md b/.changeset/fast-zebras-divide.md
deleted file mode 100644
index 54652164d4c4..000000000000
--- a/.changeset/fast-zebras-divide.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@astrojs/markdown-remark': minor
----
-
-Exports `createShikiHighlighter` for low-level syntax highlighting usage
diff --git a/.changeset/mighty-zebras-clap.md b/.changeset/mighty-zebras-clap.md
deleted file mode 100644
index ab05e497a93c..000000000000
--- a/.changeset/mighty-zebras-clap.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-'@astrojs/markdoc': patch
-'astro': patch
----
-
-Uses new `createShikiHighlighter` API from `@astrojs/markdown-remark` to avoid code duplication
diff --git a/.changeset/new-pets-fail.md b/.changeset/new-pets-fail.md
deleted file mode 100644
index ed791a6a5710..000000000000
--- a/.changeset/new-pets-fail.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'astro': patch
----
-
-Supports `formmethod` and `formaction` for form overrides
diff --git a/.changeset/red-houses-explode.md b/.changeset/red-houses-explode.md
deleted file mode 100644
index d355327f34a6..000000000000
--- a/.changeset/red-houses-explode.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'astro': patch
----
-
-Fixes the regression which broke bundling of image service for pre-rendered pages, which was introduced by [#8854](https://github.com/withastro/astro/pull/8854)
diff --git a/.changeset/six-chefs-flash.md b/.changeset/six-chefs-flash.md
deleted file mode 100644
index 3de93b983741..000000000000
--- a/.changeset/six-chefs-flash.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'astro': patch
----
-
-Add a new settings panel to the dev overlay
diff --git a/.changeset/tidy-peas-accept.md b/.changeset/tidy-peas-accept.md
deleted file mode 100644
index fa580f073449..000000000000
--- a/.changeset/tidy-peas-accept.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'astro': patch
----
-
-Fixes preview server `trailingSlash` handling for request URLs with query strings
diff --git a/examples/basics/package.json b/examples/basics/package.json
index 904e00bb637e..a45c4bb8609d 100644
--- a/examples/basics/package.json
+++ b/examples/basics/package.json
@@ -11,6 +11,6 @@
     "astro": "astro"
   },
   "dependencies": {
-    "astro": "^3.5.3"
+    "astro": "^3.5.4"
   }
 }
diff --git a/examples/blog/package.json b/examples/blog/package.json
index 4819ada61b0a..818a563c3dd3 100644
--- a/examples/blog/package.json
+++ b/examples/blog/package.json
@@ -11,9 +11,9 @@
     "astro": "astro"
   },
   "dependencies": {
-    "@astrojs/mdx": "^1.1.4",
+    "@astrojs/mdx": "^1.1.5",
     "@astrojs/rss": "^3.0.0",
     "@astrojs/sitemap": "^3.0.3",
-    "astro": "^3.5.3"
+    "astro": "^3.5.4"
   }
 }
diff --git a/examples/component/package.json b/examples/component/package.json
index fc92e6588b1a..61ffc785f6c7 100644
--- a/examples/component/package.json
+++ b/examples/component/package.json
@@ -15,7 +15,7 @@
   ],
   "scripts": {},
   "devDependencies": {
-    "astro": "^3.5.3"
+    "astro": "^3.5.4"
   },
   "peerDependencies": {
     "astro": "^3.0.0"
diff --git a/examples/framework-alpine/package.json b/examples/framework-alpine/package.json
index 7304bd353c6a..91db63ef8d61 100644
--- a/examples/framework-alpine/package.json
+++ b/examples/framework-alpine/package.json
@@ -14,6 +14,6 @@
     "@astrojs/alpinejs": "^0.3.1",
     "@types/alpinejs": "^3.7.2",
     "alpinejs": "^3.12.3",
-    "astro": "^3.5.3"
+    "astro": "^3.5.4"
   }
 }
diff --git a/examples/framework-lit/package.json b/examples/framework-lit/package.json
index 63e02889b12f..429947f0c21e 100644
--- a/examples/framework-lit/package.json
+++ b/examples/framework-lit/package.json
@@ -13,7 +13,7 @@
   "dependencies": {
     "@astrojs/lit": "^3.0.3",
     "@webcomponents/template-shadowroot": "^0.2.1",
-    "astro": "^3.5.3",
+    "astro": "^3.5.4",
     "lit": "^2.8.0"
   }
 }
diff --git a/examples/framework-multiple/package.json b/examples/framework-multiple/package.json
index a39fc8514a2b..e2383aa0c696 100644
--- a/examples/framework-multiple/package.json
+++ b/examples/framework-multiple/package.json
@@ -16,7 +16,7 @@
     "@astrojs/solid-js": "^3.0.2",
     "@astrojs/svelte": "^4.0.3",
     "@astrojs/vue": "^3.0.4",
-    "astro": "^3.5.3",
+    "astro": "^3.5.4",
     "preact": "^10.17.1",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
diff --git a/examples/framework-preact/package.json b/examples/framework-preact/package.json
index ec0c6f4004c3..37495defecfb 100644
--- a/examples/framework-preact/package.json
+++ b/examples/framework-preact/package.json
@@ -13,7 +13,7 @@
   "dependencies": {
     "@astrojs/preact": "^3.0.1",
     "@preact/signals": "^1.2.1",
-    "astro": "^3.5.3",
+    "astro": "^3.5.4",
     "preact": "^10.17.1"
   }
 }
diff --git a/examples/framework-react/package.json b/examples/framework-react/package.json
index 53c58a73b5be..5bda39a5b1f4 100644
--- a/examples/framework-react/package.json
+++ b/examples/framework-react/package.json
@@ -14,7 +14,7 @@
     "@astrojs/react": "^3.0.4",
     "@types/react": "^18.2.21",
     "@types/react-dom": "^18.2.7",
-    "astro": "^3.5.3",
+    "astro": "^3.5.4",
     "react": "^18.2.0",
     "react-dom": "^18.2.0"
   }
diff --git a/examples/framework-solid/package.json b/examples/framework-solid/package.json
index 819cc77e60db..bdbbe509cbeb 100644
--- a/examples/framework-solid/package.json
+++ b/examples/framework-solid/package.json
@@ -12,7 +12,7 @@
   },
   "dependencies": {
     "@astrojs/solid-js": "^3.0.2",
-    "astro": "^3.5.3",
+    "astro": "^3.5.4",
     "solid-js": "^1.7.11"
   }
 }
diff --git a/examples/framework-svelte/package.json b/examples/framework-svelte/package.json
index 04386d258ef0..e81c59b88396 100644
--- a/examples/framework-svelte/package.json
+++ b/examples/framework-svelte/package.json
@@ -12,7 +12,7 @@
   },
   "dependencies": {
     "@astrojs/svelte": "^4.0.3",
-    "astro": "^3.5.3",
+    "astro": "^3.5.4",
     "svelte": "^4.2.0"
   }
 }
diff --git a/examples/framework-vue/package.json b/examples/framework-vue/package.json
index 59203105709b..e0599b48bf94 100644
--- a/examples/framework-vue/package.json
+++ b/examples/framework-vue/package.json
@@ -12,7 +12,7 @@
   },
   "dependencies": {
     "@astrojs/vue": "^3.0.4",
-    "astro": "^3.5.3",
+    "astro": "^3.5.4",
     "vue": "^3.3.4"
   }
 }
diff --git a/examples/hackernews/package.json b/examples/hackernews/package.json
index 95e199737481..a06ee5c12db6 100644
--- a/examples/hackernews/package.json
+++ b/examples/hackernews/package.json
@@ -12,6 +12,6 @@
   },
   "dependencies": {
     "@astrojs/node": "^6.0.3",
-    "astro": "^3.5.3"
+    "astro": "^3.5.4"
   }
 }
diff --git a/examples/integration/package.json b/examples/integration/package.json
index edf4162fef76..e3618421286e 100644
--- a/examples/integration/package.json
+++ b/examples/integration/package.json
@@ -15,7 +15,7 @@
   ],
   "scripts": {},
   "devDependencies": {
-    "astro": "^3.5.3"
+    "astro": "^3.5.4"
   },
   "peerDependencies": {
     "astro": "^3.0.0"
diff --git a/examples/middleware/package.json b/examples/middleware/package.json
index b33c9aeab1ad..0fff77bec1f7 100644
--- a/examples/middleware/package.json
+++ b/examples/middleware/package.json
@@ -13,7 +13,7 @@
   },
   "dependencies": {
     "@astrojs/node": "^6.0.3",
-    "astro": "^3.5.3",
+    "astro": "^3.5.4",
     "html-minifier": "^4.0.0"
   }
 }
diff --git a/examples/minimal/package.json b/examples/minimal/package.json
index 425f7fcf645a..273841273d62 100644
--- a/examples/minimal/package.json
+++ b/examples/minimal/package.json
@@ -11,6 +11,6 @@
     "astro": "astro"
   },
   "dependencies": {
-    "astro": "^3.5.3"
+    "astro": "^3.5.4"
   }
 }
diff --git a/examples/non-html-pages/package.json b/examples/non-html-pages/package.json
index 4ed01d69390c..ee311ea5efa4 100644
--- a/examples/non-html-pages/package.json
+++ b/examples/non-html-pages/package.json
@@ -11,6 +11,6 @@
     "astro": "astro"
   },
   "dependencies": {
-    "astro": "^3.5.3"
+    "astro": "^3.5.4"
   }
 }
diff --git a/examples/portfolio/package.json b/examples/portfolio/package.json
index 184213711677..815e227cb6bb 100644
--- a/examples/portfolio/package.json
+++ b/examples/portfolio/package.json
@@ -11,6 +11,6 @@
     "astro": "astro"
   },
   "dependencies": {
-    "astro": "^3.5.3"
+    "astro": "^3.5.4"
   }
 }
diff --git a/examples/ssr/package.json b/examples/ssr/package.json
index fd3f9dc43074..f3df90743855 100644
--- a/examples/ssr/package.json
+++ b/examples/ssr/package.json
@@ -14,7 +14,7 @@
   "dependencies": {
     "@astrojs/node": "^6.0.3",
     "@astrojs/svelte": "^4.0.3",
-    "astro": "^3.5.3",
+    "astro": "^3.5.4",
     "svelte": "^4.2.0"
   }
 }
diff --git a/examples/view-transitions/package.json b/examples/view-transitions/package.json
index 0df2f967b192..492b84de73b3 100644
--- a/examples/view-transitions/package.json
+++ b/examples/view-transitions/package.json
@@ -12,6 +12,6 @@
   "devDependencies": {
     "@astrojs/tailwind": "^5.0.2",
     "@astrojs/node": "^6.0.3",
-    "astro": "^3.5.3"
+    "astro": "^3.5.4"
   }
 }
diff --git a/examples/with-markdoc/package.json b/examples/with-markdoc/package.json
index dc2d0f4a19c2..7056542a12c0 100644
--- a/examples/with-markdoc/package.json
+++ b/examples/with-markdoc/package.json
@@ -11,7 +11,7 @@
     "astro": "astro"
   },
   "dependencies": {
-    "@astrojs/markdoc": "^0.7.1",
-    "astro": "^3.5.3"
+    "@astrojs/markdoc": "^0.7.2",
+    "astro": "^3.5.4"
   }
 }
diff --git a/examples/with-markdown-plugins/package.json b/examples/with-markdown-plugins/package.json
index b51a030201c2..94f2afbc167b 100644
--- a/examples/with-markdown-plugins/package.json
+++ b/examples/with-markdown-plugins/package.json
@@ -11,8 +11,8 @@
     "astro": "astro"
   },
   "dependencies": {
-    "@astrojs/markdown-remark": "^3.4.0",
-    "astro": "^3.5.3",
+    "@astrojs/markdown-remark": "^3.5.0",
+    "astro": "^3.5.4",
     "hast-util-select": "^5.0.5",
     "rehype-autolink-headings": "^6.1.1",
     "rehype-slug": "^5.1.0",
diff --git a/examples/with-markdown-shiki/package.json b/examples/with-markdown-shiki/package.json
index e87aa117c4f3..1b20b750a5f7 100644
--- a/examples/with-markdown-shiki/package.json
+++ b/examples/with-markdown-shiki/package.json
@@ -11,6 +11,6 @@
     "astro": "astro"
   },
   "dependencies": {
-    "astro": "^3.5.3"
+    "astro": "^3.5.4"
   }
 }
diff --git a/examples/with-mdx/package.json b/examples/with-mdx/package.json
index 87dd6e71f111..9426890ce2f3 100644
--- a/examples/with-mdx/package.json
+++ b/examples/with-mdx/package.json
@@ -11,9 +11,9 @@
     "astro": "astro"
   },
   "dependencies": {
-    "@astrojs/mdx": "^1.1.4",
+    "@astrojs/mdx": "^1.1.5",
     "@astrojs/preact": "^3.0.1",
-    "astro": "^3.5.3",
+    "astro": "^3.5.4",
     "preact": "^10.17.1"
   }
 }
diff --git a/examples/with-nanostores/package.json b/examples/with-nanostores/package.json
index a3625b61fec6..61752e864496 100644
--- a/examples/with-nanostores/package.json
+++ b/examples/with-nanostores/package.json
@@ -13,7 +13,7 @@
   "dependencies": {
     "@astrojs/preact": "^3.0.1",
     "@nanostores/preact": "^0.5.0",
-    "astro": "^3.5.3",
+    "astro": "^3.5.4",
     "nanostores": "^0.9.3",
     "preact": "^10.17.1"
   }
diff --git a/examples/with-tailwindcss/package.json b/examples/with-tailwindcss/package.json
index b8b41b427eb3..bcaacaf24ca9 100644
--- a/examples/with-tailwindcss/package.json
+++ b/examples/with-tailwindcss/package.json
@@ -11,10 +11,10 @@
     "astro": "astro"
   },
   "dependencies": {
-    "@astrojs/mdx": "^1.1.4",
+    "@astrojs/mdx": "^1.1.5",
     "@astrojs/tailwind": "^5.0.2",
     "@types/canvas-confetti": "^1.6.0",
-    "astro": "^3.5.3",
+    "astro": "^3.5.4",
     "autoprefixer": "^10.4.15",
     "canvas-confetti": "^1.6.0",
     "postcss": "^8.4.28",
diff --git a/examples/with-vite-plugin-pwa/package.json b/examples/with-vite-plugin-pwa/package.json
index c5870809e670..610c97eec408 100644
--- a/examples/with-vite-plugin-pwa/package.json
+++ b/examples/with-vite-plugin-pwa/package.json
@@ -11,7 +11,7 @@
     "astro": "astro"
   },
   "dependencies": {
-    "astro": "^3.5.3",
+    "astro": "^3.5.4",
     "vite-plugin-pwa": "0.16.4",
     "workbox-window": "^7.0.0"
   }
diff --git a/examples/with-vitest/package.json b/examples/with-vitest/package.json
index 220f2fd798b8..7850755c86b0 100644
--- a/examples/with-vitest/package.json
+++ b/examples/with-vitest/package.json
@@ -12,7 +12,7 @@
     "test": "vitest"
   },
   "dependencies": {
-    "astro": "^3.5.3",
+    "astro": "^3.5.4",
     "vitest": "^0.34.2"
   }
 }
diff --git a/packages/astro/CHANGELOG.md b/packages/astro/CHANGELOG.md
index 7a281aeb029a..8aa69c0a23a4 100644
--- a/packages/astro/CHANGELOG.md
+++ b/packages/astro/CHANGELOG.md
@@ -1,5 +1,28 @@
 # astro
 
+## 3.5.4
+
+### Patch Changes
+
+- [#9085](https://github.com/withastro/astro/pull/9085) [`fc66ecff1`](https://github.com/withastro/astro/commit/fc66ecff18a20dd436026cb8e75bcc8b5ab0e681) Thanks [@ematipico](https://github.com/ematipico)! - When redirecting to the default root locale, Astro middleare should take into consideration the value of `trailingSlash`
+
+- [#9067](https://github.com/withastro/astro/pull/9067) [`c6e449c5b`](https://github.com/withastro/astro/commit/c6e449c5b3e6e994b362b9ce441c8a1a81129f23) Thanks [@danielhajduk](https://github.com/danielhajduk)! - Fixes display of debug messages when using the `--verbose` flag
+
+- [#9075](https://github.com/withastro/astro/pull/9075) [`c5dc8f2ec`](https://github.com/withastro/astro/commit/c5dc8f2ec9c8c1bbbffabed9eeb12d151aefb81e) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Fix Passthrough image service generating multiple images with the same content in certain cases
+
+- [#9083](https://github.com/withastro/astro/pull/9083) [`4537ecf0d`](https://github.com/withastro/astro/commit/4537ecf0d060f89cb8c000338a7fc5f4197a88c8) Thanks [@bluwy](https://github.com/bluwy)! - Uses new `createShikiHighlighter` API from `@astrojs/markdown-remark` to avoid code duplication
+
+- [#9084](https://github.com/withastro/astro/pull/9084) [`045e5ec97`](https://github.com/withastro/astro/commit/045e5ec9793a4ba2e3f0248d734246eb033225e8) Thanks [@matthewp](https://github.com/matthewp)! - Supports `formmethod` and `formaction` for form overrides
+
+- [#9087](https://github.com/withastro/astro/pull/9087) [`b895113a0`](https://github.com/withastro/astro/commit/b895113a0ae347ecd81bd8866ae2d816ea20836b) Thanks [@alexanderniebuhr](https://github.com/alexanderniebuhr)! - Fixes the regression which broke bundling of image service for pre-rendered pages, which was introduced by [#8854](https://github.com/withastro/astro/pull/8854)
+
+- [#9058](https://github.com/withastro/astro/pull/9058) [`5ef89ef33`](https://github.com/withastro/astro/commit/5ef89ef33e0dc4621db947b6889b3c563eb56a78) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Add a new settings panel to the dev overlay
+
+- [#9045](https://github.com/withastro/astro/pull/9045) [`84312f24f`](https://github.com/withastro/astro/commit/84312f24f8af2098b0831cf2361ea3d37761d3d3) Thanks [@rishi-raj-jain](https://github.com/rishi-raj-jain)! - Fixes preview server `trailingSlash` handling for request URLs with query strings
+
+- Updated dependencies [[`4537ecf0d`](https://github.com/withastro/astro/commit/4537ecf0d060f89cb8c000338a7fc5f4197a88c8)]:
+  - @astrojs/markdown-remark@3.5.0
+
 ## 3.5.3
 
 ### Patch Changes
diff --git a/packages/astro/package.json b/packages/astro/package.json
index 3470a589d39e..8c94bc1dce74 100644
--- a/packages/astro/package.json
+++ b/packages/astro/package.json
@@ -1,6 +1,6 @@
 {
   "name": "astro",
-  "version": "3.5.3",
+  "version": "3.5.4",
   "description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.",
   "type": "module",
   "author": "withastro",
diff --git a/packages/integrations/markdoc/CHANGELOG.md b/packages/integrations/markdoc/CHANGELOG.md
index ffa1e266dabd..574607c2859a 100644
--- a/packages/integrations/markdoc/CHANGELOG.md
+++ b/packages/integrations/markdoc/CHANGELOG.md
@@ -1,5 +1,11 @@
 # @astrojs/markdoc
 
+## 0.7.2
+
+### Patch Changes
+
+- [#9083](https://github.com/withastro/astro/pull/9083) [`4537ecf0d`](https://github.com/withastro/astro/commit/4537ecf0d060f89cb8c000338a7fc5f4197a88c8) Thanks [@bluwy](https://github.com/bluwy)! - Uses new `createShikiHighlighter` API from `@astrojs/markdown-remark` to avoid code duplication
+
 ## 0.7.1
 
 ### Patch Changes
diff --git a/packages/integrations/markdoc/package.json b/packages/integrations/markdoc/package.json
index 8432bf1ab3be..18e6aca5ef6d 100644
--- a/packages/integrations/markdoc/package.json
+++ b/packages/integrations/markdoc/package.json
@@ -1,7 +1,7 @@
 {
   "name": "@astrojs/markdoc",
   "description": "Add support for Markdoc in your Astro site",
-  "version": "0.7.1",
+  "version": "0.7.2",
   "type": "module",
   "types": "./dist/index.d.ts",
   "author": "withastro",
diff --git a/packages/integrations/mdx/CHANGELOG.md b/packages/integrations/mdx/CHANGELOG.md
index fabf754e4ba2..80febcb54e0e 100644
--- a/packages/integrations/mdx/CHANGELOG.md
+++ b/packages/integrations/mdx/CHANGELOG.md
@@ -1,5 +1,12 @@
 # @astrojs/mdx
 
+## 1.1.5
+
+### Patch Changes
+
+- Updated dependencies [[`4537ecf0d`](https://github.com/withastro/astro/commit/4537ecf0d060f89cb8c000338a7fc5f4197a88c8)]:
+  - @astrojs/markdown-remark@3.5.0
+
 ## 1.1.4
 
 ### Patch Changes
diff --git a/packages/integrations/mdx/package.json b/packages/integrations/mdx/package.json
index 9ad90c0ad4a9..f9e22d58c8a2 100644
--- a/packages/integrations/mdx/package.json
+++ b/packages/integrations/mdx/package.json
@@ -1,7 +1,7 @@
 {
   "name": "@astrojs/mdx",
   "description": "Add support for MDX pages in your Astro site",
-  "version": "1.1.4",
+  "version": "1.1.5",
   "type": "module",
   "types": "./dist/index.d.ts",
   "author": "withastro",
diff --git a/packages/markdown/remark/CHANGELOG.md b/packages/markdown/remark/CHANGELOG.md
index 13e67c693362..54510d68c426 100644
--- a/packages/markdown/remark/CHANGELOG.md
+++ b/packages/markdown/remark/CHANGELOG.md
@@ -1,5 +1,11 @@
 # @astrojs/markdown-remark
 
+## 3.5.0
+
+### Minor Changes
+
+- [#9083](https://github.com/withastro/astro/pull/9083) [`4537ecf0d`](https://github.com/withastro/astro/commit/4537ecf0d060f89cb8c000338a7fc5f4197a88c8) Thanks [@bluwy](https://github.com/bluwy)! - Exports `createShikiHighlighter` for low-level syntax highlighting usage
+
 ## 3.4.0
 
 ### Minor Changes
diff --git a/packages/markdown/remark/package.json b/packages/markdown/remark/package.json
index 4f5061e4d284..9f248eb32fa2 100644
--- a/packages/markdown/remark/package.json
+++ b/packages/markdown/remark/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@astrojs/markdown-remark",
-  "version": "3.4.0",
+  "version": "3.5.0",
   "type": "module",
   "author": "withastro",
   "license": "MIT",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0ca2231f2cf5..0147967d9d47 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -125,13 +125,13 @@ importers:
   examples/basics:
     dependencies:
       astro:
-        specifier: ^3.5.3
+        specifier: ^3.5.4
         version: link:../../packages/astro
 
   examples/blog:
     dependencies:
       '@astrojs/mdx':
-        specifier: ^1.1.4
+        specifier: ^1.1.5
         version: link:../../packages/integrations/mdx
       '@astrojs/rss':
         specifier: ^3.0.0
@@ -140,13 +140,13 @@ importers:
         specifier: ^3.0.3
         version: link:../../packages/integrations/sitemap
       astro:
-        specifier: ^3.5.3
+        specifier: ^3.5.4
         version: link:../../packages/astro
 
   examples/component:
     devDependencies:
       astro:
-        specifier: ^3.5.3
+        specifier: ^3.5.4
         version: link:../../packages/astro
 
   examples/framework-alpine:
@@ -161,7 +161,7 @@ importers:
         specifier: ^3.12.3
         version: 3.13.2
       astro:
-        specifier: ^3.5.3
+        specifier: ^3.5.4
         version: link:../../packages/astro
 
   examples/framework-lit:
@@ -173,7 +173,7 @@ importers:
         specifier: ^0.2.1
         version: 0.2.1
       astro:
-        specifier: ^3.5.3
+        specifier: ^3.5.4
         version: link:../../packages/astro
       lit:
         specifier: ^2.8.0
@@ -197,7 +197,7 @@ importers:
         specifier: ^3.0.4
         version: link:../../packages/integrations/vue
       astro:
-        specifier: ^3.5.3
+        specifier: ^3.5.4
         version: link:../../packages/astro
       preact:
         specifier: ^10.17.1
@@ -227,7 +227,7 @@ importers:
         specifier: ^1.2.1
         version: 1.2.1(preact@10.18.1)
       astro:
-        specifier: ^3.5.3
+        specifier: ^3.5.4
         version: link:../../packages/astro
       preact:
         specifier: ^10.17.1
@@ -245,7 +245,7 @@ importers:
         specifier: ^18.2.7
         version: 18.2.14
       astro:
-        specifier: ^3.5.3
+        specifier: ^3.5.4
         version: link:../../packages/astro
       react:
         specifier: ^18.2.0
@@ -260,7 +260,7 @@ importers:
         specifier: ^3.0.2
         version: link:../../packages/integrations/solid
       astro:
-        specifier: ^3.5.3
+        specifier: ^3.5.4
         version: link:../../packages/astro
       solid-js:
         specifier: ^1.7.11
@@ -272,7 +272,7 @@ importers:
         specifier: ^4.0.3
         version: link:../../packages/integrations/svelte
       astro:
-        specifier: ^3.5.3
+        specifier: ^3.5.4
         version: link:../../packages/astro
       svelte:
         specifier: ^4.2.0
@@ -284,7 +284,7 @@ importers:
         specifier: ^3.0.4
         version: link:../../packages/integrations/vue
       astro:
-        specifier: ^3.5.3
+        specifier: ^3.5.4
         version: link:../../packages/astro
       vue:
         specifier: ^3.3.4
@@ -296,13 +296,13 @@ importers:
         specifier: ^6.0.3
         version: link:../../packages/integrations/node
       astro:
-        specifier: ^3.5.3
+        specifier: ^3.5.4
         version: link:../../packages/astro
 
   examples/integration:
     devDependencies:
       astro:
-        specifier: ^3.5.3
+        specifier: ^3.5.4
         version: link:../../packages/astro
 
   examples/middleware:
@@ -311,7 +311,7 @@ importers:
         specifier: ^6.0.3
         version: link:../../packages/integrations/node
       astro:
-        specifier: ^3.5.3
+        specifier: ^3.5.4
         version: link:../../packages/astro
       html-minifier:
         specifier: ^4.0.0
@@ -320,19 +320,19 @@ importers:
   examples/minimal:
     dependencies:
       astro:
-        specifier: ^3.5.3
+        specifier: ^3.5.4
         version: link:../../packages/astro
 
   examples/non-html-pages:
     dependencies:
       astro:
-        specifier: ^3.5.3
+        specifier: ^3.5.4
         version: link:../../packages/astro
 
   examples/portfolio:
     dependencies:
       astro:
-        specifier: ^3.5.3
+        specifier: ^3.5.4
         version: link:../../packages/astro
 
   examples/ssr:
@@ -344,7 +344,7 @@ importers:
         specifier: ^4.0.3
         version: link:../../packages/integrations/svelte
       astro:
-        specifier: ^3.5.3
+        specifier: ^3.5.4
         version: link:../../packages/astro
       svelte:
         specifier: ^4.2.0
@@ -359,25 +359,25 @@ importers:
         specifier: ^5.0.2
         version: link:../../packages/integrations/tailwind
       astro:
-        specifier: ^3.5.3
+        specifier: ^3.5.4
         version: link:../../packages/astro
 
   examples/with-markdoc:
     dependencies:
       '@astrojs/markdoc':
-        specifier: ^0.7.1
+        specifier: ^0.7.2
         version: link:../../packages/integrations/markdoc
       astro:
-        specifier: ^3.5.3
+        specifier: ^3.5.4
         version: link:../../packages/astro
 
   examples/with-markdown-plugins:
     dependencies:
       '@astrojs/markdown-remark':
-        specifier: ^3.4.0
+        specifier: ^3.5.0
         version: link:../../packages/markdown/remark
       astro:
-        specifier: ^3.5.3
+        specifier: ^3.5.4
         version: link:../../packages/astro
       hast-util-select:
         specifier: ^5.0.5
@@ -398,19 +398,19 @@ importers:
   examples/with-markdown-shiki:
     dependencies:
       astro:
-        specifier: ^3.5.3
+        specifier: ^3.5.4
         version: link:../../packages/astro
 
   examples/with-mdx:
     dependencies:
       '@astrojs/mdx':
-        specifier: ^1.1.4
+        specifier: ^1.1.5
         version: link:../../packages/integrations/mdx
       '@astrojs/preact':
         specifier: ^3.0.1
         version: link:../../packages/integrations/preact
       astro:
-        specifier: ^3.5.3
+        specifier: ^3.5.4
         version: link:../../packages/astro
       preact:
         specifier: ^10.17.1
@@ -425,7 +425,7 @@ importers:
         specifier: ^0.5.0
         version: 0.5.0(nanostores@0.9.4)(preact@10.18.1)
       astro:
-        specifier: ^3.5.3
+        specifier: ^3.5.4
         version: link:../../packages/astro
       nanostores:
         specifier: ^0.9.3
@@ -437,7 +437,7 @@ importers:
   examples/with-tailwindcss:
     dependencies:
       '@astrojs/mdx':
-        specifier: ^1.1.4
+        specifier: ^1.1.5
         version: link:../../packages/integrations/mdx
       '@astrojs/tailwind':
         specifier: ^5.0.2
@@ -446,7 +446,7 @@ importers:
         specifier: ^1.6.0
         version: 1.6.2
       astro:
-        specifier: ^3.5.3
+        specifier: ^3.5.4
         version: link:../../packages/astro
       autoprefixer:
         specifier: ^10.4.15
@@ -464,7 +464,7 @@ importers:
   examples/with-vite-plugin-pwa:
     dependencies:
       astro:
-        specifier: ^3.5.3
+        specifier: ^3.5.4
         version: link:../../packages/astro
       vite-plugin-pwa:
         specifier: 0.16.4
@@ -476,7 +476,7 @@ importers:
   examples/with-vitest:
     dependencies:
       astro:
-        specifier: ^3.5.3
+        specifier: ^3.5.4
         version: link:../../packages/astro
       vitest:
         specifier: ^0.34.2

From a600c14837fd18c4c4c3330c0195cd47b0b73df9 Mon Sep 17 00:00:00 2001
From: Bjorn Lu 
Date: Wed, 15 Nov 2023 23:40:23 +0800
Subject: [PATCH 07/16] 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

From 6e4dd54cfaf23103b4d171ae979a07c31b197105 Mon Sep 17 00:00:00 2001
From: Bjorn Lu 
Date: Wed, 15 Nov 2023 15:42:17 +0000
Subject: [PATCH 08/16] [ci] format

---
 packages/integrations/svelte/src/index.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/integrations/svelte/src/index.ts b/packages/integrations/svelte/src/index.ts
index 5348f4c9340d..695214223def 100644
--- a/packages/integrations/svelte/src/index.ts
+++ b/packages/integrations/svelte/src/index.ts
@@ -1,8 +1,8 @@
 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 { VERSION } from 'svelte/compiler';
 import type { UserConfig } from 'vite';
 
 const isSvelte5 = Number.parseInt(VERSION.split('.').at(0)!) >= 5;

From 60e8210b0ce5bc512aff72a32322ba7937a411b0 Mon Sep 17 00:00:00 2001
From: Erika <3019731+Princesseuh@users.noreply.github.com>
Date: Wed, 15 Nov 2023 16:50:11 +0100
Subject: [PATCH 09/16] feat(dev-overlay): Hide plugins into a separate menu
 when there's too many enabled (#9102)

---
 .changeset/fresh-garlics-film.md              |   5 +
 .../runtime/client/dev-overlay/entrypoint.ts  | 175 +++++++++++++++++-
 .../src/runtime/client/dev-overlay/overlay.ts |  36 +++-
 .../client/dev-overlay/plugins/settings.ts    |   4 +-
 .../runtime/client/dev-overlay/settings.ts    |   4 +-
 .../client/dev-overlay/ui-library/icons.ts    |   2 +
 .../client/dev-overlay/ui-library/toggle.ts   |   8 +
 7 files changed, 220 insertions(+), 14 deletions(-)
 create mode 100644 .changeset/fresh-garlics-film.md

diff --git a/.changeset/fresh-garlics-film.md b/.changeset/fresh-garlics-film.md
new file mode 100644
index 000000000000..12a20ddf8548
--- /dev/null
+++ b/.changeset/fresh-garlics-film.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+In the dev overlay, when there's too many plugins enabled at once, some of the plugins will now be hidden in a separate sub menu to avoid the bar becoming too long
diff --git a/packages/astro/src/runtime/client/dev-overlay/entrypoint.ts b/packages/astro/src/runtime/client/dev-overlay/entrypoint.ts
index 887449c3772c..65e50c98e03e 100644
--- a/packages/astro/src/runtime/client/dev-overlay/entrypoint.ts
+++ b/packages/astro/src/runtime/client/dev-overlay/entrypoint.ts
@@ -1,6 +1,8 @@
 import type { DevOverlayPlugin as DevOverlayPluginDefinition } from '../../../@types/astro.js';
 import { type AstroDevOverlay, type DevOverlayPlugin } from './overlay.js';
+
 import { settings } from './settings.js';
+import type { Icon } from './ui-library/icons.js';
 
 let overlay: AstroDevOverlay;
 
@@ -17,6 +19,7 @@ document.addEventListener('DOMContentLoaded', async () => {
 		{ DevOverlayTooltip },
 		{ DevOverlayWindow },
 		{ DevOverlayToggle },
+		{ getIconElement, isDefinedIcon },
 	] = await Promise.all([
 		// @ts-expect-error
 		import('astro:dev-overlay'),
@@ -30,6 +33,7 @@ document.addEventListener('DOMContentLoaded', async () => {
 		import('./ui-library/tooltip.js'),
 		import('./ui-library/window.js'),
 		import('./ui-library/toggle.js'),
+		import('./ui-library/icons.js'),
 	]);
 
 	// Register custom elements
@@ -53,6 +57,7 @@ document.addEventListener('DOMContentLoaded', async () => {
 			builtIn: builtIn,
 			active: false,
 			status: 'loading' as const,
+			notification: { state: false },
 			eventTarget: eventTarget,
 		};
 
@@ -66,7 +71,9 @@ document.addEventListener('DOMContentLoaded', async () => {
 				newState = evt.detail.state ?? true;
 			}
 
-			if (settings.config.showPluginNotifications === false) {
+			plugin.notification.state = newState;
+
+			if (settings.config.disablePluginNotification === false) {
 				target.querySelector('.notification')?.toggleAttribute('data-active', newState);
 			}
 		});
@@ -83,11 +90,171 @@ document.addEventListener('DOMContentLoaded', async () => {
 		return plugin;
 	};
 
+	const astromorePlugin = {
+		id: 'astro:more',
+		name: 'More',
+		icon: 'dots-three',
+		init(canvas, eventTarget) {
+			const hiddenPlugins = plugins.filter((p) => !p.builtIn).slice(overlay.customPluginsToShow);
+
+			createDropdown();
+
+			document.addEventListener('astro:after-swap', createDropdown);
+
+			function createDropdown() {
+				const style = document.createElement('style');
+				style.innerHTML = `
+					#dropdown {
+						background: rgba(19, 21, 26, 1);
+						border: 1px solid rgba(52, 56, 65, 1);
+						border-radius: 12px;
+						box-shadow: 0px 0px 0px 0px rgba(19, 21, 26, 0.30), 0px 1px 2px 0px rgba(19, 21, 26, 0.29), 0px 4px 4px 0px rgba(19, 21, 26, 0.26), 0px 10px 6px 0px rgba(19, 21, 26, 0.15), 0px 17px 7px 0px rgba(19, 21, 26, 0.04), 0px 26px 7px 0px rgba(19, 21, 26, 0.01);
+						width: 180px;
+						padding: 8px;
+						z-index: 9999999999;
+					}
+
+					.notification {
+						display: none;
+						position: absolute;
+						top: -4px;
+						right: -6px;
+						width: 8px;
+						height: 8px;
+						border-radius: 9999px;
+						border: 1px solid rgba(19, 21, 26, 1);
+						background: #B33E66;
+					}
+
+					.notification[data-active] {
+						display: block;
+					}
+
+					#dropdown button {
+						border: 0;
+						background: transparent;
+						color: white;
+						font-family: system-ui, sans-serif;
+						font-size: 16px;
+						line-height: 1.2;
+						white-space: nowrap;
+						text-decoration: none;
+						margin: 0;
+						display: flex;
+    				align-items: center;
+						width: 100%;
+						padding: 8px;
+						border-radius: 8px;
+					}
+
+					#dropdown button:hover, #dropdown button:focus-visible {
+						background: rgba(27, 30, 36, 1);
+						cursor: pointer;
+					}
+
+					#dropdown button.active {
+						background: rgba(71, 78, 94, 1);
+					}
+
+					#dropdown .icon {
+						position: relative;
+						height: 24px;
+						width: 24px;
+						margin-right: 0.5em;
+					}
+
+					#dropdown .icon svg {
+						max-height: 100%;
+						max-width: 100%;
+					}
+				`;
+				canvas.append(style);
+
+				const dropdown = document.createElement('div');
+				dropdown.id = 'dropdown';
+
+				for (const plugin of hiddenPlugins) {
+					const buttonContainer = document.createElement('div');
+					buttonContainer.classList.add('item');
+					const button = document.createElement('button');
+					button.setAttribute('data-plugin-id', plugin.id);
+
+					const iconContainer = document.createElement('div');
+					const iconElement = getPluginIcon(plugin.icon);
+					iconContainer.append(iconElement);
+
+					const notification = document.createElement('div');
+					notification.classList.add('notification');
+					iconContainer.append(notification);
+					iconContainer.classList.add('icon');
+
+					button.append(iconContainer);
+					button.append(document.createTextNode(plugin.name));
+
+					button.addEventListener('click', () => {
+						overlay.togglePluginStatus(plugin);
+					});
+					buttonContainer.append(button);
+
+					dropdown.append(buttonContainer);
+
+					eventTarget.addEventListener('plugin-toggled', positionDropdown);
+					window.addEventListener('resize', positionDropdown);
+
+					plugin.eventTarget.addEventListener('toggle-notification', (evt) => {
+						if (!(evt instanceof CustomEvent)) return;
+
+						if (settings.config.disablePluginNotification === false) {
+							notification.toggleAttribute('data-active', evt.detail.state ?? true);
+						}
+
+						eventTarget.dispatchEvent(
+							new CustomEvent('toggle-notification', {
+								detail: {
+									state: hiddenPlugins.some((p) => p.notification.state === true),
+								},
+							})
+						);
+					});
+				}
+
+				canvas.append(dropdown);
+
+				function getPluginIcon(icon: Icon) {
+					if (isDefinedIcon(icon)) {
+						return getIconElement(icon)!;
+					}
+
+					return icon;
+				}
+
+				function positionDropdown() {
+					const moreButtonRect = overlay.shadowRoot
+						.querySelector('[data-plugin-id="astro:more"]')
+						?.getBoundingClientRect();
+					const dropdownRect = dropdown.getBoundingClientRect();
+
+					if (moreButtonRect && dropdownRect) {
+						dropdown.style.position = 'absolute';
+						dropdown.style.top = `${moreButtonRect.top - dropdownRect.height - 12}px`;
+						dropdown.style.left = `${
+							moreButtonRect.left + moreButtonRect.width - dropdownRect.width
+						}px`;
+					}
+				}
+			}
+		},
+	} satisfies DevOverlayPluginDefinition;
+
 	const customPluginsDefinitions = (await loadDevOverlayPlugins()) as DevOverlayPluginDefinition[];
 	const plugins: DevOverlayPlugin[] = [
-		...[astroDevToolPlugin, astroXrayPlugin, astroAuditPlugin, astroSettingsPlugin].map(
-			(pluginDef) => preparePlugin(pluginDef, true)
-		),
+		...[
+			astroDevToolPlugin,
+			astroXrayPlugin,
+			astroAuditPlugin,
+			astroSettingsPlugin,
+			astromorePlugin,
+		].map((pluginDef) => preparePlugin(pluginDef, true)),
 		...customPluginsDefinitions.map((pluginDef) => preparePlugin(pluginDef, false)),
 	];
 
diff --git a/packages/astro/src/runtime/client/dev-overlay/overlay.ts b/packages/astro/src/runtime/client/dev-overlay/overlay.ts
index 70d95726920b..900c3fb0fe60 100644
--- a/packages/astro/src/runtime/client/dev-overlay/overlay.ts
+++ b/packages/astro/src/runtime/client/dev-overlay/overlay.ts
@@ -7,6 +7,9 @@ export type DevOverlayPlugin = DevOverlayPluginDefinition & {
 	builtIn: boolean;
 	active: boolean;
 	status: 'ready' | 'loading' | 'error';
+	notification: {
+		state: boolean;
+	};
 	eventTarget: EventTarget;
 };
 
@@ -20,6 +23,7 @@ export class AstroDevOverlay extends HTMLElement {
 	plugins: DevOverlayPlugin[] = [];
 	HOVER_DELAY = 750;
 	hasBeenInitialized = false;
+	customPluginsToShow = 3;
 
 	constructor() {
 		super();
@@ -164,8 +168,8 @@ export class AstroDevOverlay extends HTMLElement {
 			#dev-bar .item .notification {
 				display: none;
 				position: absolute;
-				top: -2px;
-				right: 0;
+				top: -4px;
+				right: -6px;
 				width: 8px;
 				height: 8px;
 				border-radius: 9999px;
@@ -236,17 +240,27 @@ export class AstroDevOverlay extends HTMLElement {
 			
${this.plugins - .filter((plugin) => plugin.builtIn && plugin.id !== 'astro:settings') + .filter( + (plugin) => plugin.builtIn && !['astro:settings', 'astro:more'].includes(plugin.id) + ) .map((plugin) => this.getPluginTemplate(plugin)) .join('')} ${ this.plugins.filter((plugin) => !plugin.builtIn).length > 0 ? `
${this.plugins .filter((plugin) => !plugin.builtIn) + .slice(0, this.customPluginsToShow) .map((plugin) => this.getPluginTemplate(plugin)) .join('')}` : '' } + ${ + this.plugins.filter((plugin) => !plugin.builtIn).length > this.customPluginsToShow + ? this.getPluginTemplate( + this.plugins.find((plugin) => plugin.builtIn && plugin.id === 'astro:more')! + ) + : '' + }
${this.getPluginTemplate( this.plugins.find((plugin) => plugin.builtIn && plugin.id === 'astro:settings')! @@ -438,9 +452,19 @@ export class AstroDevOverlay extends HTMLElement { } plugin.active = newStatus ?? !plugin.active; - const target = this.shadowRoot.querySelector(`[data-plugin-id="${plugin.id}"]`); - if (!target) return; - target.classList.toggle('active', plugin.active); + const mainBarButton = this.shadowRoot.querySelector(`[data-plugin-id="${plugin.id}"]`); + const moreBarButton = this.getPluginCanvasById('astro:more')?.shadowRoot?.querySelector( + `[data-plugin-id="${plugin.id}"]` + ); + + if (mainBarButton) { + mainBarButton.classList.toggle('active', plugin.active); + } + + if (moreBarButton) { + moreBarButton.classList.toggle('active', plugin.active); + } + pluginCanvas.style.display = plugin.active ? 'block' : 'none'; window.requestAnimationFrame(() => { diff --git a/packages/astro/src/runtime/client/dev-overlay/plugins/settings.ts b/packages/astro/src/runtime/client/dev-overlay/plugins/settings.ts index c4202c5c14e7..e0d3384463ef 100644 --- a/packages/astro/src/runtime/client/dev-overlay/plugins/settings.ts +++ b/packages/astro/src/runtime/client/dev-overlay/plugins/settings.ts @@ -15,10 +15,10 @@ const settingsRows = [ name: 'Disable notifications', description: 'Notification bubbles will not be shown when this is enabled.', input: 'checkbox', - settingKey: 'showPluginNotifications', + settingKey: 'disablePluginNotification', changeEvent: (evt: Event) => { if (evt.currentTarget instanceof HTMLInputElement) { - settings.updateSetting('showPluginNotifications', evt.currentTarget.checked); + settings.updateSetting('disablePluginNotification', evt.currentTarget.checked); } }, }, diff --git a/packages/astro/src/runtime/client/dev-overlay/settings.ts b/packages/astro/src/runtime/client/dev-overlay/settings.ts index a6c086d2c9f9..7ba12f2dbf6f 100644 --- a/packages/astro/src/runtime/client/dev-overlay/settings.ts +++ b/packages/astro/src/runtime/client/dev-overlay/settings.ts @@ -1,10 +1,10 @@ export interface Settings { - showPluginNotifications: boolean; + disablePluginNotification: boolean; verbose: boolean; } export const defaultSettings = { - showPluginNotifications: true, + disablePluginNotification: false, verbose: false, } satisfies Settings; diff --git a/packages/astro/src/runtime/client/dev-overlay/ui-library/icons.ts b/packages/astro/src/runtime/client/dev-overlay/ui-library/icons.ts index 7a02369007e5..d9445e44acf8 100644 --- a/packages/astro/src/runtime/client/dev-overlay/ui-library/icons.ts +++ b/packages/astro/src/runtime/client/dev-overlay/ui-library/icons.ts @@ -31,4 +31,6 @@ const icons = { 'check-circle': '', gear: '', + 'dots-three': + '', } as const; diff --git a/packages/astro/src/runtime/client/dev-overlay/ui-library/toggle.ts b/packages/astro/src/runtime/client/dev-overlay/ui-library/toggle.ts index 5ff21fd1837a..63dcba65e304 100644 --- a/packages/astro/src/runtime/client/dev-overlay/ui-library/toggle.ts +++ b/packages/astro/src/runtime/client/dev-overlay/ui-library/toggle.ts @@ -49,4 +49,12 @@ export class DevOverlayToggle extends HTMLElement { this.input.type = 'checkbox'; this.shadowRoot.append(this.input); } + + get value() { + return this.input.value; + } + + set value(val) { + this.input.value = val; + } } From 536c6c9fd3d65d1a60bbc8b924c5939f27541d41 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Wed, 15 Nov 2023 13:43:57 -0500 Subject: [PATCH 10/16] feat(i18n): apply specific routing logic only to pages (#9091) --- .changeset/empty-turtles-wave.md | 5 ++ packages/astro/src/core/app/index.ts | 3 +- packages/astro/src/core/build/generate.ts | 3 +- packages/astro/src/core/pipeline.ts | 20 +++++++ packages/astro/src/i18n/middleware.ts | 27 ++++++++-- .../src/vite-plugin-astro-server/route.ts | 3 +- .../src/pages/test.json.js | 7 +++ packages/astro/test/i18-routing.test.js | 53 +++++++++++++++++++ 8 files changed, 115 insertions(+), 6 deletions(-) create mode 100644 .changeset/empty-turtles-wave.md create mode 100644 packages/astro/test/fixtures/i18n-routing-prefix-always/src/pages/test.json.js diff --git a/.changeset/empty-turtles-wave.md b/.changeset/empty-turtles-wave.md new file mode 100644 index 000000000000..3605ca3d645d --- /dev/null +++ b/.changeset/empty-turtles-wave.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +The `routingStrategy` `prefix-always` should not force its logic to endpoints. This fixes some regression with `astro:assets` and `@astrojs/rss`. diff --git a/packages/astro/src/core/app/index.ts b/packages/astro/src/core/app/index.ts index 2d5cd16ed6bb..ec799011907b 100644 --- a/packages/astro/src/core/app/index.ts +++ b/packages/astro/src/core/app/index.ts @@ -6,7 +6,7 @@ import type { SSRElement, SSRManifest, } from '../../@types/astro.js'; -import { createI18nMiddleware } from '../../i18n/middleware.js'; +import { createI18nMiddleware, i18nPipelineHook } from '../../i18n/middleware.js'; import type { SinglePageBuiltModule } from '../build/types.js'; import { getSetCookiesFromResponse } from '../cookies/index.js'; import { consoleLogDestination } from '../logger/console.js'; @@ -179,6 +179,7 @@ export class App { } else { this.#pipeline.setMiddlewareFunction(i18nMiddleware); } + this.#pipeline.onBeforeRenderRoute(i18nPipelineHook); } else { if (mod.onRequest) { this.#pipeline.setMiddlewareFunction(mod.onRequest as MiddlewareEndpointHandler); diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts index 6d8f51df2acd..02837cf69cc8 100644 --- a/packages/astro/src/core/build/generate.ts +++ b/packages/astro/src/core/build/generate.ts @@ -30,7 +30,7 @@ import { removeLeadingForwardSlash, removeTrailingForwardSlash, } from '../../core/path.js'; -import { createI18nMiddleware } from '../../i18n/middleware.js'; +import { createI18nMiddleware, i18nPipelineHook } from '../../i18n/middleware.js'; import { runHookBuildGenerated } from '../../integrations/index.js'; import { getOutputDirectory, isServerLikeOutput } from '../../prerender/utils.js'; import { PAGE_SCRIPT_ID } from '../../vite-plugin-scripts/index.js'; @@ -289,6 +289,7 @@ async function generatePage( } else { pipeline.setMiddlewareFunction(i18nMiddleware); } + pipeline.onBeforeRenderRoute(i18nPipelineHook); } else if (onRequest) { pipeline.setMiddlewareFunction(onRequest as MiddlewareEndpointHandler); } diff --git a/packages/astro/src/core/pipeline.ts b/packages/astro/src/core/pipeline.ts index 3d33146c6fa9..bd203b437415 100644 --- a/packages/astro/src/core/pipeline.ts +++ b/packages/astro/src/core/pipeline.ts @@ -15,6 +15,12 @@ type EndpointResultHandler = ( result: Response ) => Promise | Response; +type PipelineHooks = { + before: PipelineHookFunction[]; +}; + +export type PipelineHookFunction = (ctx: RenderContext, mod: ComponentInstance | undefined) => void; + /** * This is the basic class of a pipeline. * @@ -23,6 +29,9 @@ type EndpointResultHandler = ( export class Pipeline { env: Environment; #onRequest?: MiddlewareEndpointHandler; + #hooks: PipelineHooks = { + before: [], + }; /** * The handler accepts the *original* `Request` and result returned by the endpoint. * It must return a `Response`. @@ -75,6 +84,9 @@ export class Pipeline { renderContext: RenderContext, componentInstance: ComponentInstance | undefined ): Promise { + for (const hook of this.#hooks.before) { + hook(renderContext, componentInstance); + } const result = await this.#tryRenderRoute( renderContext, this.env, @@ -158,4 +170,12 @@ export class Pipeline { throw new Error(`Couldn't find route of type [${renderContext.route.type}]`); } } + + /** + * Store a function that will be called before starting the rendering phase. + * @param fn + */ + onBeforeRenderRoute(fn: PipelineHookFunction) { + this.#hooks.before.push(fn); + } } diff --git a/packages/astro/src/i18n/middleware.ts b/packages/astro/src/i18n/middleware.ts index 7d94d79679ea..ff467ecb3131 100644 --- a/packages/astro/src/i18n/middleware.ts +++ b/packages/astro/src/i18n/middleware.ts @@ -1,5 +1,9 @@ import { appendForwardSlash, joinPaths } from '@astrojs/internal-helpers/path'; -import type { MiddlewareEndpointHandler, SSRManifest } from '../@types/astro.js'; +import type { MiddlewareEndpointHandler, RouteData, SSRManifest } from '../@types/astro.js'; +import type { RouteInfo } from '../core/app/types.js'; +import type { PipelineHookFunction } from '../core/pipeline.js'; + +const routeDataSymbol = Symbol.for('astro.routeData'); // Checks if the pathname doesn't have any locale, exception for the defaultLocale, which is ignored on purpose function checkIsLocaleFree(pathname: string, locales: string[]): boolean { @@ -26,9 +30,19 @@ export function createI18nMiddleware( return await next(); } - const { locales, defaultLocale, fallback } = i18n; - const url = context.url; + const routeData = Reflect.get(context.request, routeDataSymbol); + if (routeData) { + // If the route we're processing is not a page, then we ignore it + if ( + (routeData as RouteData).type !== 'page' && + (routeData as RouteData).type !== 'fallback' + ) { + return await next(); + } + } + const url = context.url; + const { locales, defaultLocale, fallback } = i18n; const response = await next(); if (response instanceof Response) { @@ -83,3 +97,10 @@ export function createI18nMiddleware( return response; }; } + +/** + * This pipeline hook attaches a `RouteData` object to the `Request` + */ +export const i18nPipelineHook: PipelineHookFunction = (ctx) => { + Reflect.set(ctx.request, routeDataSymbol, ctx.route); +}; diff --git a/packages/astro/src/vite-plugin-astro-server/route.ts b/packages/astro/src/vite-plugin-astro-server/route.ts index 580ceb0b6468..7468f881907a 100644 --- a/packages/astro/src/vite-plugin-astro-server/route.ts +++ b/packages/astro/src/vite-plugin-astro-server/route.ts @@ -20,7 +20,7 @@ import { import { createRequest } from '../core/request.js'; import { matchAllRoutes } from '../core/routing/index.js'; import { isPage, resolveIdToUrl } from '../core/util.js'; -import { createI18nMiddleware } from '../i18n/middleware.js'; +import { createI18nMiddleware, i18nPipelineHook } from '../i18n/middleware.js'; import { getSortedPreloadedMatches } from '../prerender/routing.js'; import { isServerLikeOutput } from '../prerender/utils.js'; import { PAGE_SCRIPT_ID } from '../vite-plugin-scripts/index.js'; @@ -289,6 +289,7 @@ export async function handleRoute({ } else { pipeline.setMiddlewareFunction(i18Middleware); } + pipeline.onBeforeRenderRoute(i18nPipelineHook); } else if (onRequest) { pipeline.setMiddlewareFunction(onRequest); } diff --git a/packages/astro/test/fixtures/i18n-routing-prefix-always/src/pages/test.json.js b/packages/astro/test/fixtures/i18n-routing-prefix-always/src/pages/test.json.js new file mode 100644 index 000000000000..76f3cd4465de --- /dev/null +++ b/packages/astro/test/fixtures/i18n-routing-prefix-always/src/pages/test.json.js @@ -0,0 +1,7 @@ +export const GET = () => { + return new Response(JSON.stringify({ lorem: 'ipsum' }), { + headers: { + 'content-type': 'application/json', + }, + }); +}; diff --git a/packages/astro/test/i18-routing.test.js b/packages/astro/test/i18-routing.test.js index d9a96ef6eb9f..a7e8b318d371 100644 --- a/packages/astro/test/i18-routing.test.js +++ b/packages/astro/test/i18-routing.test.js @@ -992,3 +992,56 @@ describe('[SSR] i18n routing', () => { }); }); }); + +describe('i18n routing does not break assets and endpoints', () => { + describe('assets', () => { + /** @type {import('./test-utils').Fixture} */ + let fixture; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/core-image-base/', + experimental: { + i18n: { + defaultLocale: 'en', + locales: ['en', 'es'], + }, + }, + base: '/blog', + }); + await fixture.build(); + }); + + it('should render the image', async () => { + const html = await fixture.readFile('/index.html'); + const $ = cheerio.load(html); + const src = $('#local img').attr('src'); + expect(src.length).to.be.greaterThan(0); + expect(src.startsWith('/blog')).to.be.true; + }); + }); + + describe('endpoint', () => { + /** @type {import('./test-utils').Fixture} */ + let fixture; + + let app; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/i18n-routing-prefix-always/', + output: 'server', + adapter: testAdapter(), + }); + await fixture.build(); + app = await fixture.loadTestAdapterApp(); + }); + + it('should return the expected data', async () => { + let request = new Request('http://example.com/new-site/test.json'); + let response = await app.render(request); + expect(response.status).to.equal(200); + expect(await response.text()).includes('lorem'); + }); + }); +}); From eeac2885514ecde558ad39bcee39f78f7b60109e Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Wed, 15 Nov 2023 18:45:38 +0000 Subject: [PATCH 11/16] [ci] format --- packages/astro/src/i18n/middleware.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/astro/src/i18n/middleware.ts b/packages/astro/src/i18n/middleware.ts index ff467ecb3131..854a39b77cda 100644 --- a/packages/astro/src/i18n/middleware.ts +++ b/packages/astro/src/i18n/middleware.ts @@ -1,6 +1,5 @@ import { appendForwardSlash, joinPaths } from '@astrojs/internal-helpers/path'; import type { MiddlewareEndpointHandler, RouteData, SSRManifest } from '../@types/astro.js'; -import type { RouteInfo } from '../core/app/types.js'; import type { PipelineHookFunction } from '../core/pipeline.js'; const routeDataSymbol = Symbol.for('astro.routeData'); From c9487138d6d8fd39c8c8512239b6724cf2b275ff Mon Sep 17 00:00:00 2001 From: pilcrowOnPaper <80624252+pilcrowOnPaper@users.noreply.github.com> Date: Thu, 16 Nov 2023 08:39:41 +0900 Subject: [PATCH 12/16] Cancel response stream when connection closes (#9071) * cancel stream on close * add changeset * add test * Update .changeset/modern-ways-develop.md Co-authored-by: Sarah Rainsberger --------- Co-authored-by: lilnasy <69170106+lilnasy@users.noreply.github.com> Co-authored-by: Sarah Rainsberger --- .changeset/modern-ways-develop.md | 5 + .../integrations/node/src/nodeMiddleware.ts | 12 +- .../node/src/response-iterator.ts | 228 ------------------ .../integrations/node/test/api-route.test.js | 19 ++ .../test/fixtures/api-route/src/pages/hash.ts | 2 +- .../fixtures/api-route/src/pages/streaming.ts | 22 ++ 6 files changed, 55 insertions(+), 233 deletions(-) create mode 100644 .changeset/modern-ways-develop.md delete mode 100644 packages/integrations/node/src/response-iterator.ts create mode 100644 packages/integrations/node/test/fixtures/api-route/src/pages/streaming.ts diff --git a/.changeset/modern-ways-develop.md b/.changeset/modern-ways-develop.md new file mode 100644 index 000000000000..0378abc1aeec --- /dev/null +++ b/.changeset/modern-ways-develop.md @@ -0,0 +1,5 @@ +--- +'@astrojs/node': patch +--- + +Fixes a bug where the response stream would not cancel when the connection closed diff --git a/packages/integrations/node/src/nodeMiddleware.ts b/packages/integrations/node/src/nodeMiddleware.ts index ddaa95deb8bd..7f242809ee0e 100644 --- a/packages/integrations/node/src/nodeMiddleware.ts +++ b/packages/integrations/node/src/nodeMiddleware.ts @@ -1,8 +1,6 @@ import type { NodeApp } from 'astro/app/node'; import type { ServerResponse } from 'node:http'; -import type { Readable } from 'stream'; import { createOutgoingHttpHeaders } from './createOutgoingHttpHeaders.js'; -import { responseIterator } from './response-iterator.js'; import type { ErrorHandlerParams, Options, RequestHandlerParams } from './types.js'; // Disable no-unused-vars to avoid breaking signature change @@ -79,8 +77,14 @@ async function writeWebResponse(app: NodeApp, res: ServerResponse, webResponse: res.writeHead(status, nodeHeaders); if (webResponse.body) { try { - for await (const chunk of responseIterator(webResponse) as unknown as Readable) { - res.write(chunk); + const reader = webResponse.body.getReader(); + res.on("close", () => { + reader.cancel(); + }) + let result = await reader.read(); + while (!result.done) { + res.write(result.value); + result = await reader.read(); } } catch (err: any) { console.error(err?.stack || err?.message || String(err)); diff --git a/packages/integrations/node/src/response-iterator.ts b/packages/integrations/node/src/response-iterator.ts deleted file mode 100644 index b79c3a85345b..000000000000 --- a/packages/integrations/node/src/response-iterator.ts +++ /dev/null @@ -1,228 +0,0 @@ -/** - * Original sources: - * - https://github.com/kmalakoff/response-iterator/blob/master/src/index.ts - * - https://github.com/apollographql/apollo-client/blob/main/src/utilities/common/responseIterator.ts - */ - -import { AstroError } from 'astro/errors'; -import type { ReadableStreamDefaultReadResult } from 'node:stream/web'; -import { Readable as NodeReadableStream } from 'stream'; - -interface NodeStreamIterator { - next(): Promise>; - [Symbol.asyncIterator]?(): AsyncIterator; -} - -interface PromiseIterator { - next(): Promise>; - [Symbol.asyncIterator]?(): AsyncIterator; -} - -interface ReaderIterator { - next(): Promise>; - [Symbol.asyncIterator]?(): AsyncIterator; -} - -const canUseSymbol = typeof Symbol === 'function' && typeof Symbol.for === 'function'; - -const canUseAsyncIteratorSymbol = canUseSymbol && Symbol.asyncIterator; - -function isBuffer(value: any): value is Buffer { - return ( - value?.constructor != null && - typeof value.constructor.isBuffer === 'function' && - value.constructor.isBuffer(value) - ); -} - -function isNodeResponse(value: any): value is Response { - return !!(value as Response).body; -} - -function isReadableStream(value: any): value is ReadableStream { - return !!(value as ReadableStream).getReader; -} - -function isAsyncIterableIterator(value: any): value is AsyncIterableIterator { - return !!( - canUseAsyncIteratorSymbol && (value as AsyncIterableIterator)[Symbol.asyncIterator] - ); -} - -function isStreamableBlob(value: any): value is Blob { - return !!(value as Blob).stream; -} - -function isBlob(value: any): value is Blob { - return !!(value as Blob).arrayBuffer; -} - -function isNodeReadableStream(value: any): value is NodeReadableStream { - return !!(value as NodeReadableStream).pipe; -} - -function readerIterator(reader: ReadableStreamDefaultReader): AsyncIterableIterator { - const iterator: ReaderIterator = { - //@ts-expect-error - next() { - return reader.read(); - }, - }; - - if (canUseAsyncIteratorSymbol) { - iterator[Symbol.asyncIterator] = function (): AsyncIterator { - //@ts-expect-error - return this; - }; - } - - return iterator as AsyncIterableIterator; -} - -function promiseIterator(promise: Promise): AsyncIterableIterator { - let resolved = false; - - const iterator: PromiseIterator = { - next(): Promise> { - if (resolved) - return Promise.resolve({ - value: undefined, - done: true, - }); - resolved = true; - return new Promise(function (resolve, reject) { - promise - .then(function (value) { - resolve({ value: value as unknown as T, done: false }); - }) - .catch(reject); - }); - }, - }; - - if (canUseAsyncIteratorSymbol) { - iterator[Symbol.asyncIterator] = function (): AsyncIterator { - return this; - }; - } - - return iterator as AsyncIterableIterator; -} - -function nodeStreamIterator(stream: NodeReadableStream): AsyncIterableIterator { - let cleanup: (() => void) | null = null; - let error: Error | null = null; - let done = false; - const data: unknown[] = []; - - const waiting: [ - ( - value: - | IteratorResult - | PromiseLike> - ) => void, - (reason?: any) => void, - ][] = []; - - function onData(chunk: any) { - if (error) return; - if (waiting.length) { - const shiftedArr = waiting.shift(); - if (Array.isArray(shiftedArr) && shiftedArr[0]) { - return shiftedArr[0]({ value: chunk, done: false }); - } - } - data.push(chunk); - } - function onError(err: Error) { - error = err; - const all = waiting.slice(); - all.forEach(function (pair) { - pair[1](err); - }); - !cleanup || cleanup(); - } - function onEnd() { - done = true; - const all = waiting.slice(); - all.forEach(function (pair) { - pair[0]({ value: undefined, done: true }); - }); - !cleanup || cleanup(); - } - - cleanup = function () { - cleanup = null; - stream.removeListener('data', onData); - stream.removeListener('error', onError); - stream.removeListener('end', onEnd); - stream.removeListener('finish', onEnd); - stream.removeListener('close', onEnd); - }; - stream.on('data', onData); - stream.on('error', onError); - stream.on('end', onEnd); - stream.on('finish', onEnd); - stream.on('close', onEnd); - - function getNext(): Promise> { - return new Promise(function (resolve, reject) { - if (error) return reject(error); - if (data.length) return resolve({ value: data.shift() as T, done: false }); - if (done) return resolve({ value: undefined, done: true }); - waiting.push([resolve, reject]); - }); - } - - const iterator: NodeStreamIterator = { - next(): Promise> { - return getNext(); - }, - }; - - if (canUseAsyncIteratorSymbol) { - iterator[Symbol.asyncIterator] = function (): AsyncIterator { - return this; - }; - } - - return iterator as AsyncIterableIterator; -} - -function asyncIterator(source: AsyncIterableIterator): AsyncIterableIterator { - const iterator = source[Symbol.asyncIterator](); - return { - next(): Promise> { - return iterator.next(); - }, - [Symbol.asyncIterator](): AsyncIterableIterator { - return this; - }, - }; -} - -export function responseIterator(response: Response | Buffer): AsyncIterableIterator { - let body: unknown = response; - - if (isNodeResponse(response)) body = response.body; - - if (isBuffer(body)) body = NodeReadableStream.from(body); - - if (isAsyncIterableIterator(body)) return asyncIterator(body); - - if (isReadableStream(body)) return readerIterator(body.getReader()); - - // this errors without casting to ReadableStream - // because Blob.stream() returns a NodeJS ReadableStream - if (isStreamableBlob(body)) { - return readerIterator((body.stream() as unknown as ReadableStream).getReader()); - } - - if (isBlob(body)) return promiseIterator(body.arrayBuffer()); - - if (isNodeReadableStream(body)) return nodeStreamIterator(body); - - throw new AstroError( - 'Unknown body type for responseIterator. Please pass a streamable response.' - ); -} diff --git a/packages/integrations/node/test/api-route.test.js b/packages/integrations/node/test/api-route.test.js index c830eee2d6c8..7d9422ab4d0c 100644 --- a/packages/integrations/node/test/api-route.test.js +++ b/packages/integrations/node/test/api-route.test.js @@ -89,4 +89,23 @@ describe('API routes', () => { let [out] = await done; expect(new Uint8Array(out.buffer)).to.deep.equal(expectedDigest); }); + + it('Can bail on streaming', async () => { + const { handler } = await import('./fixtures/api-route/dist/server/entry.mjs'); + let { req, res, done } = createRequestAndResponse({ + url: '/streaming', + }); + + let locals = { cancelledByTheServer: false }; + + handler(req, res, () => {}, locals); + req.send(); + + await new Promise((resolve) => setTimeout(resolve, 500)); + res.emit("close"); + + await done; + + expect(locals).to.deep.include({ cancelledByTheServer: true }); + }); }); diff --git a/packages/integrations/node/test/fixtures/api-route/src/pages/hash.ts b/packages/integrations/node/test/fixtures/api-route/src/pages/hash.ts index fbf44c5478bc..3f1b236de76b 100644 --- a/packages/integrations/node/test/fixtures/api-route/src/pages/hash.ts +++ b/packages/integrations/node/test/fixtures/api-route/src/pages/hash.ts @@ -1,6 +1,6 @@ import crypto from 'node:crypto'; -export async function post({ request }: { request: Request }) { +export async function POST({ request }: { request: Request }) { const hash = crypto.createHash('sha256'); const iterable = request.body as unknown as AsyncIterable; diff --git a/packages/integrations/node/test/fixtures/api-route/src/pages/streaming.ts b/packages/integrations/node/test/fixtures/api-route/src/pages/streaming.ts new file mode 100644 index 000000000000..9ecb884bf89b --- /dev/null +++ b/packages/integrations/node/test/fixtures/api-route/src/pages/streaming.ts @@ -0,0 +1,22 @@ +export const GET = ({ locals }) => { + let sentChunks = 0; + + const readableStream = new ReadableStream({ + async pull(controller) { + if (sentChunks === 3) return controller.close(); + else sentChunks++; + + await new Promise(resolve => setTimeout(resolve, 1000)); + controller.enqueue(new TextEncoder().encode('hello\n')); + }, + cancel() { + locals.cancelledByTheServer = true; + } + }); + + return new Response(readableStream, { + headers: { + "Content-Type": "text/event-stream" + } + }); +} From 1862fb44eb1008e17222130ff74a7a589aacecfd Mon Sep 17 00:00:00 2001 From: pilcrowOnPaper Date: Wed, 15 Nov 2023 23:41:21 +0000 Subject: [PATCH 13/16] [ci] format --- packages/integrations/node/src/nodeMiddleware.ts | 4 ++-- packages/integrations/node/test/api-route.test.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/integrations/node/src/nodeMiddleware.ts b/packages/integrations/node/src/nodeMiddleware.ts index 7f242809ee0e..4fd0a4bc23f5 100644 --- a/packages/integrations/node/src/nodeMiddleware.ts +++ b/packages/integrations/node/src/nodeMiddleware.ts @@ -78,9 +78,9 @@ async function writeWebResponse(app: NodeApp, res: ServerResponse, webResponse: if (webResponse.body) { try { const reader = webResponse.body.getReader(); - res.on("close", () => { + res.on('close', () => { reader.cancel(); - }) + }); let result = await reader.read(); while (!result.done) { res.write(result.value); diff --git a/packages/integrations/node/test/api-route.test.js b/packages/integrations/node/test/api-route.test.js index 7d9422ab4d0c..313819188015 100644 --- a/packages/integrations/node/test/api-route.test.js +++ b/packages/integrations/node/test/api-route.test.js @@ -102,7 +102,7 @@ describe('API routes', () => { req.send(); await new Promise((resolve) => setTimeout(resolve, 500)); - res.emit("close"); + res.emit('close'); await done; From ac5633b8f615fe90ea419e00c5c771d00783a6e2 Mon Sep 17 00:00:00 2001 From: brandonsdebt <124833708+brandonsdebt@users.noreply.github.com> Date: Thu, 16 Nov 2023 14:47:46 +0100 Subject: [PATCH 14/16] Add compatibility with cloudflare node (#8925) --- .changeset/calm-ducks-divide.md | 5 +++++ packages/integrations/react/server.js | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/calm-ducks-divide.md diff --git a/.changeset/calm-ducks-divide.md b/.changeset/calm-ducks-divide.md new file mode 100644 index 000000000000..1cacaaf5a79c --- /dev/null +++ b/.changeset/calm-ducks-divide.md @@ -0,0 +1,5 @@ +--- +"@astrojs/react": patch +--- + +Uses `node:stream` during server rendering for compatibility with Cloudflare diff --git a/packages/integrations/react/server.js b/packages/integrations/react/server.js index 26596289eb8c..05ee66c6a817 100644 --- a/packages/integrations/react/server.js +++ b/packages/integrations/react/server.js @@ -53,7 +53,7 @@ async function check(Component, props, children) { } async function getNodeWritable() { - let nodeStreamBuiltinModuleName = 'stream'; + let nodeStreamBuiltinModuleName = 'node:stream'; let { Writable } = await import(/* @vite-ignore */ nodeStreamBuiltinModuleName); return Writable; } From 8366cd777530e6e0740f8a9c535f3c4382ffb053 Mon Sep 17 00:00:00 2001 From: "Houston (Bot)" <108291165+astrobot-houston@users.noreply.github.com> Date: Thu, 16 Nov 2023 05:50:47 -0800 Subject: [PATCH 15/16] [ci] release (#9107) Co-authored-by: github-actions[bot] --- .changeset/calm-ducks-divide.md | 5 -- .changeset/empty-turtles-wave.md | 5 -- .changeset/fresh-garlics-film.md | 5 -- .changeset/modern-ways-develop.md | 5 -- .changeset/twelve-mails-drive.md | 5 -- examples/basics/package.json | 2 +- examples/blog/package.json | 2 +- examples/component/package.json | 2 +- examples/framework-alpine/package.json | 2 +- examples/framework-lit/package.json | 2 +- examples/framework-multiple/package.json | 6 +- examples/framework-preact/package.json | 2 +- examples/framework-react/package.json | 4 +- examples/framework-solid/package.json | 2 +- examples/framework-svelte/package.json | 4 +- examples/framework-vue/package.json | 2 +- examples/hackernews/package.json | 4 +- examples/integration/package.json | 2 +- examples/middleware/package.json | 4 +- examples/minimal/package.json | 2 +- examples/non-html-pages/package.json | 2 +- examples/portfolio/package.json | 2 +- examples/ssr/package.json | 6 +- examples/view-transitions/package.json | 4 +- examples/with-markdoc/package.json | 2 +- examples/with-markdown-plugins/package.json | 2 +- examples/with-markdown-shiki/package.json | 2 +- examples/with-mdx/package.json | 2 +- examples/with-nanostores/package.json | 2 +- examples/with-tailwindcss/package.json | 2 +- examples/with-vite-plugin-pwa/package.json | 2 +- examples/with-vitest/package.json | 2 +- packages/astro/CHANGELOG.md | 8 +++ packages/astro/package.json | 2 +- packages/integrations/node/CHANGELOG.md | 6 ++ packages/integrations/node/package.json | 2 +- packages/integrations/react/CHANGELOG.md | 6 ++ packages/integrations/react/package.json | 2 +- packages/integrations/svelte/CHANGELOG.md | 6 ++ packages/integrations/svelte/package.json | 2 +- pnpm-lock.yaml | 72 ++++++++++----------- 41 files changed, 102 insertions(+), 101 deletions(-) delete mode 100644 .changeset/calm-ducks-divide.md delete mode 100644 .changeset/empty-turtles-wave.md delete mode 100644 .changeset/fresh-garlics-film.md delete mode 100644 .changeset/modern-ways-develop.md delete mode 100644 .changeset/twelve-mails-drive.md diff --git a/.changeset/calm-ducks-divide.md b/.changeset/calm-ducks-divide.md deleted file mode 100644 index 1cacaaf5a79c..000000000000 --- a/.changeset/calm-ducks-divide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@astrojs/react": patch ---- - -Uses `node:stream` during server rendering for compatibility with Cloudflare diff --git a/.changeset/empty-turtles-wave.md b/.changeset/empty-turtles-wave.md deleted file mode 100644 index 3605ca3d645d..000000000000 --- a/.changeset/empty-turtles-wave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'astro': patch ---- - -The `routingStrategy` `prefix-always` should not force its logic to endpoints. This fixes some regression with `astro:assets` and `@astrojs/rss`. diff --git a/.changeset/fresh-garlics-film.md b/.changeset/fresh-garlics-film.md deleted file mode 100644 index 12a20ddf8548..000000000000 --- a/.changeset/fresh-garlics-film.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'astro': patch ---- - -In the dev overlay, when there's too many plugins enabled at once, some of the plugins will now be hidden in a separate sub menu to avoid the bar becoming too long diff --git a/.changeset/modern-ways-develop.md b/.changeset/modern-ways-develop.md deleted file mode 100644 index 0378abc1aeec..000000000000 --- a/.changeset/modern-ways-develop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@astrojs/node': patch ---- - -Fixes a bug where the response stream would not cancel when the connection closed diff --git a/.changeset/twelve-mails-drive.md b/.changeset/twelve-mails-drive.md deleted file mode 100644 index b8ff5d6e58f4..000000000000 --- a/.changeset/twelve-mails-drive.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@astrojs/svelte': patch ---- - -Adds experimental support for Svelte 5 diff --git a/examples/basics/package.json b/examples/basics/package.json index a45c4bb8609d..1be44bed6e74 100644 --- a/examples/basics/package.json +++ b/examples/basics/package.json @@ -11,6 +11,6 @@ "astro": "astro" }, "dependencies": { - "astro": "^3.5.4" + "astro": "^3.5.5" } } diff --git a/examples/blog/package.json b/examples/blog/package.json index 818a563c3dd3..afee1f0281c6 100644 --- a/examples/blog/package.json +++ b/examples/blog/package.json @@ -14,6 +14,6 @@ "@astrojs/mdx": "^1.1.5", "@astrojs/rss": "^3.0.0", "@astrojs/sitemap": "^3.0.3", - "astro": "^3.5.4" + "astro": "^3.5.5" } } diff --git a/examples/component/package.json b/examples/component/package.json index 61ffc785f6c7..2c781217268b 100644 --- a/examples/component/package.json +++ b/examples/component/package.json @@ -15,7 +15,7 @@ ], "scripts": {}, "devDependencies": { - "astro": "^3.5.4" + "astro": "^3.5.5" }, "peerDependencies": { "astro": "^3.0.0" diff --git a/examples/framework-alpine/package.json b/examples/framework-alpine/package.json index 91db63ef8d61..88c5f32295e4 100644 --- a/examples/framework-alpine/package.json +++ b/examples/framework-alpine/package.json @@ -14,6 +14,6 @@ "@astrojs/alpinejs": "^0.3.1", "@types/alpinejs": "^3.7.2", "alpinejs": "^3.12.3", - "astro": "^3.5.4" + "astro": "^3.5.5" } } diff --git a/examples/framework-lit/package.json b/examples/framework-lit/package.json index 429947f0c21e..97f9a4dcb29c 100644 --- a/examples/framework-lit/package.json +++ b/examples/framework-lit/package.json @@ -13,7 +13,7 @@ "dependencies": { "@astrojs/lit": "^3.0.3", "@webcomponents/template-shadowroot": "^0.2.1", - "astro": "^3.5.4", + "astro": "^3.5.5", "lit": "^2.8.0" } } diff --git a/examples/framework-multiple/package.json b/examples/framework-multiple/package.json index e2383aa0c696..af251b7d8796 100644 --- a/examples/framework-multiple/package.json +++ b/examples/framework-multiple/package.json @@ -12,11 +12,11 @@ }, "dependencies": { "@astrojs/preact": "^3.0.1", - "@astrojs/react": "^3.0.4", + "@astrojs/react": "^3.0.5", "@astrojs/solid-js": "^3.0.2", - "@astrojs/svelte": "^4.0.3", + "@astrojs/svelte": "^4.0.4", "@astrojs/vue": "^3.0.4", - "astro": "^3.5.4", + "astro": "^3.5.5", "preact": "^10.17.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/framework-preact/package.json b/examples/framework-preact/package.json index 37495defecfb..c8059410c45e 100644 --- a/examples/framework-preact/package.json +++ b/examples/framework-preact/package.json @@ -13,7 +13,7 @@ "dependencies": { "@astrojs/preact": "^3.0.1", "@preact/signals": "^1.2.1", - "astro": "^3.5.4", + "astro": "^3.5.5", "preact": "^10.17.1" } } diff --git a/examples/framework-react/package.json b/examples/framework-react/package.json index 5bda39a5b1f4..79b09fc45d4c 100644 --- a/examples/framework-react/package.json +++ b/examples/framework-react/package.json @@ -11,10 +11,10 @@ "astro": "astro" }, "dependencies": { - "@astrojs/react": "^3.0.4", + "@astrojs/react": "^3.0.5", "@types/react": "^18.2.21", "@types/react-dom": "^18.2.7", - "astro": "^3.5.4", + "astro": "^3.5.5", "react": "^18.2.0", "react-dom": "^18.2.0" } diff --git a/examples/framework-solid/package.json b/examples/framework-solid/package.json index bdbbe509cbeb..8b1a9a3157f0 100644 --- a/examples/framework-solid/package.json +++ b/examples/framework-solid/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "@astrojs/solid-js": "^3.0.2", - "astro": "^3.5.4", + "astro": "^3.5.5", "solid-js": "^1.7.11" } } diff --git a/examples/framework-svelte/package.json b/examples/framework-svelte/package.json index e81c59b88396..3badefefbdd6 100644 --- a/examples/framework-svelte/package.json +++ b/examples/framework-svelte/package.json @@ -11,8 +11,8 @@ "astro": "astro" }, "dependencies": { - "@astrojs/svelte": "^4.0.3", - "astro": "^3.5.4", + "@astrojs/svelte": "^4.0.4", + "astro": "^3.5.5", "svelte": "^4.2.0" } } diff --git a/examples/framework-vue/package.json b/examples/framework-vue/package.json index e0599b48bf94..99a6b678f005 100644 --- a/examples/framework-vue/package.json +++ b/examples/framework-vue/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "@astrojs/vue": "^3.0.4", - "astro": "^3.5.4", + "astro": "^3.5.5", "vue": "^3.3.4" } } diff --git a/examples/hackernews/package.json b/examples/hackernews/package.json index a06ee5c12db6..fa19009ab647 100644 --- a/examples/hackernews/package.json +++ b/examples/hackernews/package.json @@ -11,7 +11,7 @@ "astro": "astro" }, "dependencies": { - "@astrojs/node": "^6.0.3", - "astro": "^3.5.4" + "@astrojs/node": "^6.0.4", + "astro": "^3.5.5" } } diff --git a/examples/integration/package.json b/examples/integration/package.json index e3618421286e..262ba7aeb1cb 100644 --- a/examples/integration/package.json +++ b/examples/integration/package.json @@ -15,7 +15,7 @@ ], "scripts": {}, "devDependencies": { - "astro": "^3.5.4" + "astro": "^3.5.5" }, "peerDependencies": { "astro": "^3.0.0" diff --git a/examples/middleware/package.json b/examples/middleware/package.json index 0fff77bec1f7..7c3da323eecd 100644 --- a/examples/middleware/package.json +++ b/examples/middleware/package.json @@ -12,8 +12,8 @@ "server": "node dist/server/entry.mjs" }, "dependencies": { - "@astrojs/node": "^6.0.3", - "astro": "^3.5.4", + "@astrojs/node": "^6.0.4", + "astro": "^3.5.5", "html-minifier": "^4.0.0" } } diff --git a/examples/minimal/package.json b/examples/minimal/package.json index 273841273d62..9577132a2dc1 100644 --- a/examples/minimal/package.json +++ b/examples/minimal/package.json @@ -11,6 +11,6 @@ "astro": "astro" }, "dependencies": { - "astro": "^3.5.4" + "astro": "^3.5.5" } } diff --git a/examples/non-html-pages/package.json b/examples/non-html-pages/package.json index ee311ea5efa4..9b8a6e24814d 100644 --- a/examples/non-html-pages/package.json +++ b/examples/non-html-pages/package.json @@ -11,6 +11,6 @@ "astro": "astro" }, "dependencies": { - "astro": "^3.5.4" + "astro": "^3.5.5" } } diff --git a/examples/portfolio/package.json b/examples/portfolio/package.json index 815e227cb6bb..375bb8ff5bfd 100644 --- a/examples/portfolio/package.json +++ b/examples/portfolio/package.json @@ -11,6 +11,6 @@ "astro": "astro" }, "dependencies": { - "astro": "^3.5.4" + "astro": "^3.5.5" } } diff --git a/examples/ssr/package.json b/examples/ssr/package.json index f3df90743855..04c0d698420e 100644 --- a/examples/ssr/package.json +++ b/examples/ssr/package.json @@ -12,9 +12,9 @@ "server": "node dist/server/entry.mjs" }, "dependencies": { - "@astrojs/node": "^6.0.3", - "@astrojs/svelte": "^4.0.3", - "astro": "^3.5.4", + "@astrojs/node": "^6.0.4", + "@astrojs/svelte": "^4.0.4", + "astro": "^3.5.5", "svelte": "^4.2.0" } } diff --git a/examples/view-transitions/package.json b/examples/view-transitions/package.json index 492b84de73b3..b2904039ce43 100644 --- a/examples/view-transitions/package.json +++ b/examples/view-transitions/package.json @@ -11,7 +11,7 @@ }, "devDependencies": { "@astrojs/tailwind": "^5.0.2", - "@astrojs/node": "^6.0.3", - "astro": "^3.5.4" + "@astrojs/node": "^6.0.4", + "astro": "^3.5.5" } } diff --git a/examples/with-markdoc/package.json b/examples/with-markdoc/package.json index 7056542a12c0..8447aa0cff28 100644 --- a/examples/with-markdoc/package.json +++ b/examples/with-markdoc/package.json @@ -12,6 +12,6 @@ }, "dependencies": { "@astrojs/markdoc": "^0.7.2", - "astro": "^3.5.4" + "astro": "^3.5.5" } } diff --git a/examples/with-markdown-plugins/package.json b/examples/with-markdown-plugins/package.json index 94f2afbc167b..ad51e3f987f1 100644 --- a/examples/with-markdown-plugins/package.json +++ b/examples/with-markdown-plugins/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "@astrojs/markdown-remark": "^3.5.0", - "astro": "^3.5.4", + "astro": "^3.5.5", "hast-util-select": "^5.0.5", "rehype-autolink-headings": "^6.1.1", "rehype-slug": "^5.1.0", diff --git a/examples/with-markdown-shiki/package.json b/examples/with-markdown-shiki/package.json index 1b20b750a5f7..f44759d761d9 100644 --- a/examples/with-markdown-shiki/package.json +++ b/examples/with-markdown-shiki/package.json @@ -11,6 +11,6 @@ "astro": "astro" }, "dependencies": { - "astro": "^3.5.4" + "astro": "^3.5.5" } } diff --git a/examples/with-mdx/package.json b/examples/with-mdx/package.json index 9426890ce2f3..13f77a46eded 100644 --- a/examples/with-mdx/package.json +++ b/examples/with-mdx/package.json @@ -13,7 +13,7 @@ "dependencies": { "@astrojs/mdx": "^1.1.5", "@astrojs/preact": "^3.0.1", - "astro": "^3.5.4", + "astro": "^3.5.5", "preact": "^10.17.1" } } diff --git a/examples/with-nanostores/package.json b/examples/with-nanostores/package.json index 61752e864496..14ae5ab8d611 100644 --- a/examples/with-nanostores/package.json +++ b/examples/with-nanostores/package.json @@ -13,7 +13,7 @@ "dependencies": { "@astrojs/preact": "^3.0.1", "@nanostores/preact": "^0.5.0", - "astro": "^3.5.4", + "astro": "^3.5.5", "nanostores": "^0.9.3", "preact": "^10.17.1" } diff --git a/examples/with-tailwindcss/package.json b/examples/with-tailwindcss/package.json index bcaacaf24ca9..3a3e83dd3e7c 100644 --- a/examples/with-tailwindcss/package.json +++ b/examples/with-tailwindcss/package.json @@ -14,7 +14,7 @@ "@astrojs/mdx": "^1.1.5", "@astrojs/tailwind": "^5.0.2", "@types/canvas-confetti": "^1.6.0", - "astro": "^3.5.4", + "astro": "^3.5.5", "autoprefixer": "^10.4.15", "canvas-confetti": "^1.6.0", "postcss": "^8.4.28", diff --git a/examples/with-vite-plugin-pwa/package.json b/examples/with-vite-plugin-pwa/package.json index 610c97eec408..adc9a8032c2c 100644 --- a/examples/with-vite-plugin-pwa/package.json +++ b/examples/with-vite-plugin-pwa/package.json @@ -11,7 +11,7 @@ "astro": "astro" }, "dependencies": { - "astro": "^3.5.4", + "astro": "^3.5.5", "vite-plugin-pwa": "0.16.4", "workbox-window": "^7.0.0" } diff --git a/examples/with-vitest/package.json b/examples/with-vitest/package.json index 7850755c86b0..5259966bb9e7 100644 --- a/examples/with-vitest/package.json +++ b/examples/with-vitest/package.json @@ -12,7 +12,7 @@ "test": "vitest" }, "dependencies": { - "astro": "^3.5.4", + "astro": "^3.5.5", "vitest": "^0.34.2" } } diff --git a/packages/astro/CHANGELOG.md b/packages/astro/CHANGELOG.md index 8aa69c0a23a4..a3061833944f 100644 --- a/packages/astro/CHANGELOG.md +++ b/packages/astro/CHANGELOG.md @@ -1,5 +1,13 @@ # astro +## 3.5.5 + +### Patch Changes + +- [#9091](https://github.com/withastro/astro/pull/9091) [`536c6c9fd`](https://github.com/withastro/astro/commit/536c6c9fd3d65d1a60bbc8b924c5939f27541d41) Thanks [@ematipico](https://github.com/ematipico)! - The `routingStrategy` `prefix-always` should not force its logic to endpoints. This fixes some regression with `astro:assets` and `@astrojs/rss`. + +- [#9102](https://github.com/withastro/astro/pull/9102) [`60e8210b0`](https://github.com/withastro/astro/commit/60e8210b0ce5bc512aff72a32322ba7937a411b0) Thanks [@Princesseuh](https://github.com/Princesseuh)! - In the dev overlay, when there's too many plugins enabled at once, some of the plugins will now be hidden in a separate sub menu to avoid the bar becoming too long + ## 3.5.4 ### Patch Changes diff --git a/packages/astro/package.json b/packages/astro/package.json index 8c94bc1dce74..1cb40861cddb 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -1,6 +1,6 @@ { "name": "astro", - "version": "3.5.4", + "version": "3.5.5", "description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.", "type": "module", "author": "withastro", diff --git a/packages/integrations/node/CHANGELOG.md b/packages/integrations/node/CHANGELOG.md index 1b319f62dfda..8ef4b80ac473 100644 --- a/packages/integrations/node/CHANGELOG.md +++ b/packages/integrations/node/CHANGELOG.md @@ -1,5 +1,11 @@ # @astrojs/node +## 6.0.4 + +### Patch Changes + +- [#9071](https://github.com/withastro/astro/pull/9071) [`c9487138d`](https://github.com/withastro/astro/commit/c9487138d6d8fd39c8c8512239b6724cf2b275ff) Thanks [@pilcrowOnPaper](https://github.com/pilcrowOnPaper)! - Fixes a bug where the response stream would not cancel when the connection closed + ## 6.0.3 ### Patch Changes diff --git a/packages/integrations/node/package.json b/packages/integrations/node/package.json index 09d26aa4fbc4..b50b3e1757f0 100644 --- a/packages/integrations/node/package.json +++ b/packages/integrations/node/package.json @@ -1,7 +1,7 @@ { "name": "@astrojs/node", "description": "Deploy your site to a Node.js server", - "version": "6.0.3", + "version": "6.0.4", "type": "module", "types": "./dist/index.d.ts", "author": "withastro", diff --git a/packages/integrations/react/CHANGELOG.md b/packages/integrations/react/CHANGELOG.md index b74b2c69648b..5b127b75337e 100644 --- a/packages/integrations/react/CHANGELOG.md +++ b/packages/integrations/react/CHANGELOG.md @@ -1,5 +1,11 @@ # @astrojs/react +## 3.0.5 + +### Patch Changes + +- [#8925](https://github.com/withastro/astro/pull/8925) [`ac5633b8f`](https://github.com/withastro/astro/commit/ac5633b8f615fe90ea419e00c5c771d00783a6e2) Thanks [@brandonsdebt](https://github.com/brandonsdebt)! - Uses `node:stream` during server rendering for compatibility with Cloudflare + ## 3.0.4 ### Patch Changes diff --git a/packages/integrations/react/package.json b/packages/integrations/react/package.json index 074d963e85b1..49d357a946f7 100644 --- a/packages/integrations/react/package.json +++ b/packages/integrations/react/package.json @@ -1,7 +1,7 @@ { "name": "@astrojs/react", "description": "Use React components within Astro", - "version": "3.0.4", + "version": "3.0.5", "type": "module", "types": "./dist/index.d.ts", "author": "withastro", diff --git a/packages/integrations/svelte/CHANGELOG.md b/packages/integrations/svelte/CHANGELOG.md index 38a378c2b528..2681a45c0461 100644 --- a/packages/integrations/svelte/CHANGELOG.md +++ b/packages/integrations/svelte/CHANGELOG.md @@ -1,5 +1,11 @@ # @astrojs/svelte +## 4.0.4 + +### Patch Changes + +- [#9098](https://github.com/withastro/astro/pull/9098) [`a600c1483`](https://github.com/withastro/astro/commit/a600c14837fd18c4c4c3330c0195cd47b0b73df9) Thanks [@bluwy](https://github.com/bluwy)! - Adds experimental support for Svelte 5 + ## 4.0.3 ### Patch Changes diff --git a/packages/integrations/svelte/package.json b/packages/integrations/svelte/package.json index 65c8522f9182..4c7e8bb63486 100644 --- a/packages/integrations/svelte/package.json +++ b/packages/integrations/svelte/package.json @@ -1,6 +1,6 @@ { "name": "@astrojs/svelte", - "version": "4.0.3", + "version": "4.0.4", "description": "Use Svelte components within Astro", "type": "module", "types": "./dist/index.d.ts", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 37616480c6b2..68aed44e57be 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -125,7 +125,7 @@ importers: examples/basics: dependencies: astro: - specifier: ^3.5.4 + specifier: ^3.5.5 version: link:../../packages/astro examples/blog: @@ -140,13 +140,13 @@ importers: specifier: ^3.0.3 version: link:../../packages/integrations/sitemap astro: - specifier: ^3.5.4 + specifier: ^3.5.5 version: link:../../packages/astro examples/component: devDependencies: astro: - specifier: ^3.5.4 + specifier: ^3.5.5 version: link:../../packages/astro examples/framework-alpine: @@ -161,7 +161,7 @@ importers: specifier: ^3.12.3 version: 3.13.2 astro: - specifier: ^3.5.4 + specifier: ^3.5.5 version: link:../../packages/astro examples/framework-lit: @@ -173,7 +173,7 @@ importers: specifier: ^0.2.1 version: 0.2.1 astro: - specifier: ^3.5.4 + specifier: ^3.5.5 version: link:../../packages/astro lit: specifier: ^2.8.0 @@ -185,19 +185,19 @@ importers: specifier: ^3.0.1 version: link:../../packages/integrations/preact '@astrojs/react': - specifier: ^3.0.4 + specifier: ^3.0.5 version: link:../../packages/integrations/react '@astrojs/solid-js': specifier: ^3.0.2 version: link:../../packages/integrations/solid '@astrojs/svelte': - specifier: ^4.0.3 + specifier: ^4.0.4 version: link:../../packages/integrations/svelte '@astrojs/vue': specifier: ^3.0.4 version: link:../../packages/integrations/vue astro: - specifier: ^3.5.4 + specifier: ^3.5.5 version: link:../../packages/astro preact: specifier: ^10.17.1 @@ -227,7 +227,7 @@ importers: specifier: ^1.2.1 version: 1.2.1(preact@10.18.1) astro: - specifier: ^3.5.4 + specifier: ^3.5.5 version: link:../../packages/astro preact: specifier: ^10.17.1 @@ -236,7 +236,7 @@ importers: examples/framework-react: dependencies: '@astrojs/react': - specifier: ^3.0.4 + specifier: ^3.0.5 version: link:../../packages/integrations/react '@types/react': specifier: ^18.2.21 @@ -245,7 +245,7 @@ importers: specifier: ^18.2.7 version: 18.2.14 astro: - specifier: ^3.5.4 + specifier: ^3.5.5 version: link:../../packages/astro react: specifier: ^18.2.0 @@ -260,7 +260,7 @@ importers: specifier: ^3.0.2 version: link:../../packages/integrations/solid astro: - specifier: ^3.5.4 + specifier: ^3.5.5 version: link:../../packages/astro solid-js: specifier: ^1.7.11 @@ -269,10 +269,10 @@ importers: examples/framework-svelte: dependencies: '@astrojs/svelte': - specifier: ^4.0.3 + specifier: ^4.0.4 version: link:../../packages/integrations/svelte astro: - specifier: ^3.5.4 + specifier: ^3.5.5 version: link:../../packages/astro svelte: specifier: ^4.2.0 @@ -284,7 +284,7 @@ importers: specifier: ^3.0.4 version: link:../../packages/integrations/vue astro: - specifier: ^3.5.4 + specifier: ^3.5.5 version: link:../../packages/astro vue: specifier: ^3.3.4 @@ -293,25 +293,25 @@ importers: examples/hackernews: dependencies: '@astrojs/node': - specifier: ^6.0.3 + specifier: ^6.0.4 version: link:../../packages/integrations/node astro: - specifier: ^3.5.4 + specifier: ^3.5.5 version: link:../../packages/astro examples/integration: devDependencies: astro: - specifier: ^3.5.4 + specifier: ^3.5.5 version: link:../../packages/astro examples/middleware: dependencies: '@astrojs/node': - specifier: ^6.0.3 + specifier: ^6.0.4 version: link:../../packages/integrations/node astro: - specifier: ^3.5.4 + specifier: ^3.5.5 version: link:../../packages/astro html-minifier: specifier: ^4.0.0 @@ -320,31 +320,31 @@ importers: examples/minimal: dependencies: astro: - specifier: ^3.5.4 + specifier: ^3.5.5 version: link:../../packages/astro examples/non-html-pages: dependencies: astro: - specifier: ^3.5.4 + specifier: ^3.5.5 version: link:../../packages/astro examples/portfolio: dependencies: astro: - specifier: ^3.5.4 + specifier: ^3.5.5 version: link:../../packages/astro examples/ssr: dependencies: '@astrojs/node': - specifier: ^6.0.3 + specifier: ^6.0.4 version: link:../../packages/integrations/node '@astrojs/svelte': - specifier: ^4.0.3 + specifier: ^4.0.4 version: link:../../packages/integrations/svelte astro: - specifier: ^3.5.4 + specifier: ^3.5.5 version: link:../../packages/astro svelte: specifier: ^4.2.0 @@ -353,13 +353,13 @@ importers: examples/view-transitions: devDependencies: '@astrojs/node': - specifier: ^6.0.3 + specifier: ^6.0.4 version: link:../../packages/integrations/node '@astrojs/tailwind': specifier: ^5.0.2 version: link:../../packages/integrations/tailwind astro: - specifier: ^3.5.4 + specifier: ^3.5.5 version: link:../../packages/astro examples/with-markdoc: @@ -368,7 +368,7 @@ importers: specifier: ^0.7.2 version: link:../../packages/integrations/markdoc astro: - specifier: ^3.5.4 + specifier: ^3.5.5 version: link:../../packages/astro examples/with-markdown-plugins: @@ -377,7 +377,7 @@ importers: specifier: ^3.5.0 version: link:../../packages/markdown/remark astro: - specifier: ^3.5.4 + specifier: ^3.5.5 version: link:../../packages/astro hast-util-select: specifier: ^5.0.5 @@ -398,7 +398,7 @@ importers: examples/with-markdown-shiki: dependencies: astro: - specifier: ^3.5.4 + specifier: ^3.5.5 version: link:../../packages/astro examples/with-mdx: @@ -410,7 +410,7 @@ importers: specifier: ^3.0.1 version: link:../../packages/integrations/preact astro: - specifier: ^3.5.4 + specifier: ^3.5.5 version: link:../../packages/astro preact: specifier: ^10.17.1 @@ -425,7 +425,7 @@ importers: specifier: ^0.5.0 version: 0.5.0(nanostores@0.9.4)(preact@10.18.1) astro: - specifier: ^3.5.4 + specifier: ^3.5.5 version: link:../../packages/astro nanostores: specifier: ^0.9.3 @@ -446,7 +446,7 @@ importers: specifier: ^1.6.0 version: 1.6.2 astro: - specifier: ^3.5.4 + specifier: ^3.5.5 version: link:../../packages/astro autoprefixer: specifier: ^10.4.15 @@ -464,7 +464,7 @@ importers: examples/with-vite-plugin-pwa: dependencies: astro: - specifier: ^3.5.4 + specifier: ^3.5.5 version: link:../../packages/astro vite-plugin-pwa: specifier: 0.16.4 @@ -476,7 +476,7 @@ importers: examples/with-vitest: dependencies: astro: - specifier: ^3.5.4 + specifier: ^3.5.5 version: link:../../packages/astro vitest: specifier: ^0.34.2 From e3dce215a5ea06bcff1b21027e5613e6518c69d4 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Thu, 16 Nov 2023 08:54:10 -0600 Subject: [PATCH 16/16] feat(i18n): add `Astro.currentLocale` (#9101) --- .changeset/quick-toes-peel.md | 5 ++ packages/astro/src/@types/astro.ts | 10 +++ packages/astro/src/core/app/index.ts | 8 ++- packages/astro/src/core/build/generate.ts | 4 +- packages/astro/src/core/endpoint/index.ts | 28 ++++++-- packages/astro/src/core/middleware/index.ts | 2 + packages/astro/src/core/pipeline.ts | 10 +-- packages/astro/src/core/render/context.ts | 24 +++++++ packages/astro/src/core/render/core.ts | 2 + packages/astro/src/core/render/result.ts | 27 +++++++- .../src/vite-plugin-astro-server/route.ts | 7 +- .../src/pages/en/start.astro | 5 ++ .../src/pages/pt/start.astro | 4 ++ .../src/pages/current-locale.astro | 12 ++++ .../i18n-routing/src/pages/dynamic/[id].astro | 19 ++++++ .../i18n-routing/src/pages/pt/start.astro | 4 ++ ...8-routing.test.js => i18n-routing.test.js} | 67 +++++++++++++++++++ 17 files changed, 222 insertions(+), 16 deletions(-) create mode 100644 .changeset/quick-toes-peel.md create mode 100644 packages/astro/test/fixtures/i18n-routing/src/pages/current-locale.astro create mode 100644 packages/astro/test/fixtures/i18n-routing/src/pages/dynamic/[id].astro rename packages/astro/test/{i18-routing.test.js => i18n-routing.test.js} (93%) diff --git a/.changeset/quick-toes-peel.md b/.changeset/quick-toes-peel.md new file mode 100644 index 000000000000..25d5c13c7130 --- /dev/null +++ b/.changeset/quick-toes-peel.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Add a new property `Astro.currentLocale`, available when `i18n` is enabled. diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index f15cf7d09630..6477f738396c 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -2112,6 +2112,11 @@ interface AstroSharedContext< */ preferredLocaleList: string[] | undefined; + + /** + * The current locale computed from the URL of the request. It matches the locales in `i18n.locales`, and returns `undefined` otherwise. + */ + currentLocale: string | undefined; } export interface APIContext< @@ -2241,6 +2246,11 @@ export interface APIContext< * [quality value]: https://developer.mozilla.org/en-US/docs/Glossary/Quality_values */ preferredLocaleList: string[] | undefined; + + /** + * The current locale computed from the URL of the request. It matches the locales in `i18n.locales`, and returns `undefined` otherwise. + */ + currentLocale: string | undefined; } export type EndpointOutput = diff --git a/packages/astro/src/core/app/index.ts b/packages/astro/src/core/app/index.ts index ec799011907b..b297171a4fc4 100644 --- a/packages/astro/src/core/app/index.ts +++ b/packages/astro/src/core/app/index.ts @@ -234,7 +234,9 @@ export class App { status, env: this.#pipeline.env, mod: handler as any, - locales: this.#manifest.i18n ? this.#manifest.i18n.locales : undefined, + locales: this.#manifest.i18n?.locales, + routingStrategy: this.#manifest.i18n?.routingStrategy, + defaultLocale: this.#manifest.i18n?.defaultLocale, }); } else { const pathname = prependForwardSlash(this.removeBase(url.pathname)); @@ -269,7 +271,9 @@ export class App { status, mod, env: this.#pipeline.env, - locales: this.#manifest.i18n ? this.#manifest.i18n.locales : undefined, + locales: this.#manifest.i18n?.locales, + routingStrategy: this.#manifest.i18n?.routingStrategy, + defaultLocale: this.#manifest.i18n?.defaultLocale, }); } } diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts index 02837cf69cc8..20854f779b0e 100644 --- a/packages/astro/src/core/build/generate.ts +++ b/packages/astro/src/core/build/generate.ts @@ -558,7 +558,9 @@ async function generatePath(pathname: string, gopts: GeneratePathOptions, pipeli route: pageData.route, env: pipeline.getEnvironment(), mod, - locales: i18n ? i18n.locales : undefined, + locales: i18n?.locales, + routingStrategy: i18n?.routingStrategy, + defaultLocale: i18n?.defaultLocale, }); let body: string | Uint8Array; diff --git a/packages/astro/src/core/endpoint/index.ts b/packages/astro/src/core/endpoint/index.ts index 33c659dcafd5..80af2358d13b 100644 --- a/packages/astro/src/core/endpoint/index.ts +++ b/packages/astro/src/core/endpoint/index.ts @@ -12,7 +12,11 @@ import { ASTRO_VERSION } from '../constants.js'; import { AstroCookies, attachCookiesToResponse } from '../cookies/index.js'; import { AstroError, AstroErrorData } from '../errors/index.js'; import { callMiddleware } from '../middleware/callMiddleware.js'; -import { computePreferredLocale, computePreferredLocaleList } from '../render/context.js'; +import { + computeCurrentLocale, + computePreferredLocale, + computePreferredLocaleList, +} from '../render/context.js'; import { type Environment, type RenderContext } from '../render/index.js'; const encoder = new TextEncoder(); @@ -27,6 +31,8 @@ type CreateAPIContext = { props: Record; adapterName?: string; locales: string[] | undefined; + routingStrategy: 'prefix-always' | 'prefix-other-locales' | undefined; + defaultLocale: string | undefined; }; /** @@ -41,9 +47,12 @@ export function createAPIContext({ props, adapterName, locales, + routingStrategy, + defaultLocale, }: CreateAPIContext): APIContext { let preferredLocale: string | undefined = undefined; let preferredLocaleList: string[] | undefined = undefined; + let currentLocale: string | undefined = undefined; const context = { cookies: new AstroCookies(request), @@ -83,6 +92,16 @@ export function createAPIContext({ return undefined; }, + get currentLocale(): string | undefined { + if (currentLocale) { + return currentLocale; + } + if (locales) { + currentLocale = computeCurrentLocale(request, locales, routingStrategy, defaultLocale); + } + + return currentLocale; + }, url: new URL(request.url), get clientAddress() { if (clientAddressSymbol in request) { @@ -153,8 +172,7 @@ export async function callEndpoint mod: EndpointHandler, env: Environment, ctx: RenderContext, - onRequest: MiddlewareHandler | undefined, - locales: undefined | string[] + onRequest: MiddlewareHandler | undefined ): Promise { const context = createAPIContext({ request: ctx.request, @@ -162,7 +180,9 @@ export async function callEndpoint props: ctx.props, site: env.site, adapterName: env.adapterName, - locales, + routingStrategy: ctx.routingStrategy, + defaultLocale: ctx.defaultLocale, + locales: ctx.locales, }); let response; diff --git a/packages/astro/src/core/middleware/index.ts b/packages/astro/src/core/middleware/index.ts index 77da30aee2a6..c02761351d3c 100644 --- a/packages/astro/src/core/middleware/index.ts +++ b/packages/astro/src/core/middleware/index.ts @@ -35,6 +35,8 @@ function createContext({ request, params, userDefinedLocales = [] }: CreateConte props: {}, site: undefined, locales: userDefinedLocales, + defaultLocale: undefined, + routingStrategy: undefined, }); } diff --git a/packages/astro/src/core/pipeline.ts b/packages/astro/src/core/pipeline.ts index bd203b437415..87f833ee5cc4 100644 --- a/packages/astro/src/core/pipeline.ts +++ b/packages/astro/src/core/pipeline.ts @@ -128,6 +128,8 @@ export class Pipeline { site: env.site, adapterName: env.adapterName, locales: renderContext.locales, + routingStrategy: renderContext.routingStrategy, + defaultLocale: renderContext.defaultLocale, }); switch (renderContext.route.type) { @@ -158,13 +160,7 @@ export class Pipeline { } } case 'endpoint': { - return await callEndpoint( - mod as any as EndpointHandler, - env, - renderContext, - onRequest, - renderContext.locales - ); + return await callEndpoint(mod as any as EndpointHandler, env, renderContext, onRequest); } default: throw new Error(`Couldn't find route of type [${renderContext.route.type}]`); diff --git a/packages/astro/src/core/render/context.ts b/packages/astro/src/core/render/context.ts index 851c41bc5ab2..0f0bf39b0465 100644 --- a/packages/astro/src/core/render/context.ts +++ b/packages/astro/src/core/render/context.ts @@ -29,6 +29,8 @@ export interface RenderContext { props: Props; locals?: object; locales: string[] | undefined; + defaultLocale: string | undefined; + routingStrategy: 'prefix-always' | 'prefix-other-locales' | undefined; } export type CreateRenderContextArgs = Partial< @@ -60,6 +62,8 @@ export async function createRenderContext( params, props, locales: options.locales, + routingStrategy: options.routingStrategy, + defaultLocale: options.defaultLocale, }; // We define a custom property, so we can check the value passed to locals @@ -208,3 +212,23 @@ export function computePreferredLocaleList(request: Request, locales: string[]) return result; } + +export function computeCurrentLocale( + request: Request, + locales: string[], + routingStrategy: 'prefix-always' | 'prefix-other-locales' | undefined, + defaultLocale: string | undefined +): undefined | string { + const requestUrl = new URL(request.url); + for (const segment of requestUrl.pathname.split('/')) { + for (const locale of locales) { + if (normalizeTheLocale(locale) === normalizeTheLocale(segment)) { + return locale; + } + } + } + if (routingStrategy === 'prefix-other-locales') { + return defaultLocale; + } + return undefined; +} diff --git a/packages/astro/src/core/render/core.ts b/packages/astro/src/core/render/core.ts index da9675f105d1..ed9ea7fdbb50 100644 --- a/packages/astro/src/core/render/core.ts +++ b/packages/astro/src/core/render/core.ts @@ -60,6 +60,8 @@ export async function renderPage({ mod, renderContext, env, cookies }: RenderPag cookies, locals: renderContext.locals ?? {}, locales: renderContext.locales, + defaultLocale: renderContext.defaultLocale, + routingStrategy: renderContext.routingStrategy, }); // TODO: Remove in Astro 4.0 diff --git a/packages/astro/src/core/render/result.ts b/packages/astro/src/core/render/result.ts index 91dc545df4c1..e9c8302a1ed7 100644 --- a/packages/astro/src/core/render/result.ts +++ b/packages/astro/src/core/render/result.ts @@ -12,7 +12,11 @@ import { chunkToString } from '../../runtime/server/render/index.js'; import { AstroCookies } from '../cookies/index.js'; import { AstroError, AstroErrorData } from '../errors/index.js'; import type { Logger } from '../logger/core.js'; -import { computePreferredLocale, computePreferredLocaleList } from './context.js'; +import { + computeCurrentLocale, + computePreferredLocale, + computePreferredLocaleList, +} from './context.js'; const clientAddressSymbol = Symbol.for('astro.clientAddress'); const responseSentSymbol = Symbol.for('astro.responseSent'); @@ -47,6 +51,8 @@ export interface CreateResultArgs { locals: App.Locals; cookies?: AstroCookies; locales: string[] | undefined; + defaultLocale: string | undefined; + routingStrategy: 'prefix-always' | 'prefix-other-locales' | undefined; } function getFunctionExpression(slot: any) { @@ -148,6 +154,7 @@ export function createResult(args: CreateResultArgs): SSRResult { let cookies: AstroCookies | undefined = args.cookies; let preferredLocale: string | undefined = undefined; let preferredLocaleList: string[] | undefined = undefined; + let currentLocale: string | undefined = undefined; // Create the result object that will be passed into the render function. // This object starts here as an empty shell (not yet the result) but then @@ -218,6 +225,24 @@ export function createResult(args: CreateResultArgs): SSRResult { return undefined; }, + get currentLocale(): string | undefined { + if (currentLocale) { + return currentLocale; + } + if (args.locales) { + currentLocale = computeCurrentLocale( + request, + args.locales, + args.routingStrategy, + args.defaultLocale + ); + if (currentLocale) { + return currentLocale; + } + } + + return undefined; + }, params, props, locals, diff --git a/packages/astro/src/vite-plugin-astro-server/route.ts b/packages/astro/src/vite-plugin-astro-server/route.ts index 7468f881907a..48f89db043a3 100644 --- a/packages/astro/src/vite-plugin-astro-server/route.ts +++ b/packages/astro/src/vite-plugin-astro-server/route.ts @@ -215,6 +215,9 @@ export async function handleRoute({ env, mod, route, + locales: manifest.i18n?.locales, + routingStrategy: manifest.i18n?.routingStrategy, + defaultLocale: manifest.i18n?.defaultLocale, }); } else { return handle404Response(origin, incomingRequest, incomingResponse); @@ -271,7 +274,9 @@ export async function handleRoute({ route: options.route, mod, env, - locales: i18n ? i18n.locales : undefined, + locales: i18n?.locales, + routingStrategy: i18n?.routingStrategy, + defaultLocale: i18n?.defaultLocale, }); } diff --git a/packages/astro/test/fixtures/i18n-routing-prefix-always/src/pages/en/start.astro b/packages/astro/test/fixtures/i18n-routing-prefix-always/src/pages/en/start.astro index 990baecd9a8c..92e189636e78 100644 --- a/packages/astro/test/fixtures/i18n-routing-prefix-always/src/pages/en/start.astro +++ b/packages/astro/test/fixtures/i18n-routing-prefix-always/src/pages/en/start.astro @@ -1,8 +1,13 @@ +--- +const currentLocale = Astro.currentLocale; +--- Astro Start +Current Locale: {currentLocale ? currentLocale : "none"} + diff --git a/packages/astro/test/fixtures/i18n-routing-prefix-always/src/pages/pt/start.astro b/packages/astro/test/fixtures/i18n-routing-prefix-always/src/pages/pt/start.astro index 5a4a84c2cf0c..6f82c3790f44 100644 --- a/packages/astro/test/fixtures/i18n-routing-prefix-always/src/pages/pt/start.astro +++ b/packages/astro/test/fixtures/i18n-routing-prefix-always/src/pages/pt/start.astro @@ -1,8 +1,12 @@ +--- +const currentLocale = Astro.currentLocale; +--- Astro Oi essa e start +Current Locale: {currentLocale ? currentLocale : "none"} diff --git a/packages/astro/test/fixtures/i18n-routing/src/pages/current-locale.astro b/packages/astro/test/fixtures/i18n-routing/src/pages/current-locale.astro new file mode 100644 index 000000000000..64af0118bae7 --- /dev/null +++ b/packages/astro/test/fixtures/i18n-routing/src/pages/current-locale.astro @@ -0,0 +1,12 @@ +--- +const currentLocale = Astro.currentLocale; +--- + + + + Astro + + + Current Locale: {currentLocale ? currentLocale : "none"} + + diff --git a/packages/astro/test/fixtures/i18n-routing/src/pages/dynamic/[id].astro b/packages/astro/test/fixtures/i18n-routing/src/pages/dynamic/[id].astro new file mode 100644 index 000000000000..58141fec05ce --- /dev/null +++ b/packages/astro/test/fixtures/i18n-routing/src/pages/dynamic/[id].astro @@ -0,0 +1,19 @@ + +--- +export function getStaticPaths() { + return [ + { id: "lorem" } + ] +} +const currentLocale = Astro.currentLocale; + +--- + + + + Astro + + +Current Locale: {currentLocale ? currentLocale : "none"} + + diff --git a/packages/astro/test/fixtures/i18n-routing/src/pages/pt/start.astro b/packages/astro/test/fixtures/i18n-routing/src/pages/pt/start.astro index 15a63a7b87f5..9a37428ca626 100644 --- a/packages/astro/test/fixtures/i18n-routing/src/pages/pt/start.astro +++ b/packages/astro/test/fixtures/i18n-routing/src/pages/pt/start.astro @@ -1,8 +1,12 @@ +--- +const currentLocale = Astro.currentLocale; +--- Astro Hola +Current Locale: {currentLocale ? currentLocale : "none"} diff --git a/packages/astro/test/i18-routing.test.js b/packages/astro/test/i18n-routing.test.js similarity index 93% rename from packages/astro/test/i18-routing.test.js rename to packages/astro/test/i18n-routing.test.js index a7e8b318d371..f305a5747b26 100644 --- a/packages/astro/test/i18-routing.test.js +++ b/packages/astro/test/i18n-routing.test.js @@ -991,6 +991,73 @@ describe('[SSR] i18n routing', () => { }); }); }); + + describe('current locale', () => { + describe('with [prefix-other-locales]', () => { + /** @type {import('./test-utils').Fixture} */ + let fixture; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/i18n-routing/', + output: 'server', + adapter: testAdapter(), + }); + await fixture.build(); + app = await fixture.loadTestAdapterApp(); + }); + + it('should return the default locale', async () => { + let request = new Request('http://example.com/current-locale', {}); + let response = await app.render(request); + expect(response.status).to.equal(200); + expect(await response.text()).includes('Current Locale: en'); + }); + + it('should return the default locale of the current URL', async () => { + let request = new Request('http://example.com/pt/start', {}); + let response = await app.render(request); + expect(response.status).to.equal(200); + expect(await response.text()).includes('Current Locale: pt'); + }); + + it('should return the default locale when a route is dynamic', async () => { + let request = new Request('http://example.com/dynamic/lorem', {}); + let response = await app.render(request); + expect(response.status).to.equal(200); + expect(await response.text()).includes('Current Locale: en'); + }); + }); + + describe('with [prefix-always]', () => { + /** @type {import('./test-utils').Fixture} */ + let fixture; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/i18n-routing-prefix-always/', + output: 'server', + adapter: testAdapter(), + }); + await fixture.build(); + app = await fixture.loadTestAdapterApp(); + }); + + it('should return the locale of the current URL (en)', async () => { + let request = new Request('http://example.com/en/start', {}); + let response = await app.render(request); + expect(response.status).to.equal(200); + expect(await response.text()).includes('Current Locale: en'); + }); + + it('should return the locale of the current URL (pt)', async () => { + let request = new Request('http://example.com/pt/start', {}); + let response = await app.render(request); + expect(response.status).to.equal(200); + expect(await response.text()).includes('Current Locale: pt'); + }); + }); + }); }); describe('i18n routing does not break assets and endpoints', () => {