diff --git a/.changeset/fix-preferred-locale-quality-sort.md b/.changeset/fix-preferred-locale-quality-sort.md
new file mode 100644
index 000000000000..a675a66296ad
--- /dev/null
+++ b/.changeset/fix-preferred-locale-quality-sort.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Fixes `Astro.preferredLocale` and `Astro.preferredLocaleList` ignoring `Accept-Language` quality values when they are absent or `0`. An entry without an explicit `q=` now correctly counts as quality `1.0` (per RFC 7231) and an entry with `q=0` is treated as not acceptable, so the highest-quality locale is selected regardless of header order.
diff --git a/.changeset/gold-bugs-glow.md b/.changeset/gold-bugs-glow.md
new file mode 100644
index 000000000000..de81aeffcfa0
--- /dev/null
+++ b/.changeset/gold-bugs-glow.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Fixes a type error when passing an image from a content collection `image()` schema to a component or ``. The schema returned by `image()` was missing the `apng` format, so it no longer matched the type of an imported image.
diff --git a/.changeset/long-tips-care.md b/.changeset/long-tips-care.md
new file mode 100644
index 000000000000..e3e06abaa1e4
--- /dev/null
+++ b/.changeset/long-tips-care.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Fixes `memoryCache()` storing responses that set cookies through `Astro.cookies` or `Astro.session`
diff --git a/.changeset/modern-canyons-obey.md b/.changeset/modern-canyons-obey.md
new file mode 100644
index 000000000000..bf11760bb3ac
--- /dev/null
+++ b/.changeset/modern-canyons-obey.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Fixes `server:defer` crashing the dev server with "undefined is not a function" when a deferred component imports from `astro:i18n`
diff --git a/.changeset/tidy-pears-shake.md b/.changeset/tidy-pears-shake.md
new file mode 100644
index 000000000000..9a2814b5cd99
--- /dev/null
+++ b/.changeset/tidy-pears-shake.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Improves the performance of the Astro CLI in local by enabling Node's module compilation cache.
diff --git a/.changeset/tricky-hornets-take.md b/.changeset/tricky-hornets-take.md
new file mode 100644
index 000000000000..21be81e5bc7a
--- /dev/null
+++ b/.changeset/tricky-hornets-take.md
@@ -0,0 +1,5 @@
+---
+'@astrojs/cloudflare': patch
+---
+
+Fixes dep scanning failure when `.astro` frontmatter contains regex literals with quote characters (e.g. `/"/g`)
diff --git a/README.md b/README.md
index 6a291888555a..c7e692c133c3 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,7 @@ Visit our [official documentation](https://docs.astro.build/).
## Support
-Having trouble? Get help in the official [Astro Discord](https://astro.build/chat).
+Having trouble? Get help in the official [Astro Discord](https://astro.build/chat) or via [GitHub discussions](https://github.com/withastro/community-support/discussions).
## Contributing
diff --git a/packages/astro/bin/astro.mjs b/packages/astro/bin/astro.mjs
index cd36f28080aa..f11bb3889325 100755
--- a/packages/astro/bin/astro.mjs
+++ b/packages/astro/bin/astro.mjs
@@ -1,6 +1,21 @@
#!/usr/bin/env node
'use strict';
+import module from 'node:module';
+
+// In CI writing the cache is (most of the time) harmful, as it'll never get re-used and just slows down the CLI.
+if (!process.env.CI) {
+ try {
+ module.enableCompileCache?.();
+ // Long-running commands like `astro dev` never reach the flush that happens on process exit.
+ setTimeout(() => {
+ try {
+ module.flushCompileCache?.();
+ } catch {}
+ }, 10_000).unref();
+ } catch {}
+}
+
const CI_INSTRUCTIONS = {
NETLIFY: 'https://docs.netlify.com/configure-builds/manage-dependencies/#node-js-and-javascript',
GITHUB_ACTIONS:
diff --git a/packages/astro/src/content/config.ts b/packages/astro/src/content/config.ts
index d50af34e3934..9b05c0f2cb28 100644
--- a/packages/astro/src/content/config.ts
+++ b/packages/astro/src/content/config.ts
@@ -39,6 +39,7 @@ type ImageFunction = () => z.ZodObject<{
zCore.$ZodLiteral<'gif'>,
zCore.$ZodLiteral<'svg'>,
zCore.$ZodLiteral<'avif'>,
+ zCore.$ZodLiteral<'apng'>,
]
>;
}>;
diff --git a/packages/astro/src/core/cache/memory-provider.ts b/packages/astro/src/core/cache/memory-provider.ts
index 7e8af26e71d1..eaa6d3833587 100644
--- a/packages/astro/src/core/cache/memory-provider.ts
+++ b/packages/astro/src/core/cache/memory-provider.ts
@@ -1,4 +1,6 @@
import picomatch from 'picomatch';
+import type { AstroCookies } from '../cookies/cookies.js';
+import { getCookiesFromResponse } from '../cookies/response.js';
import { AstroError } from '../errors/errors.js';
import { CacheQueryConfigConflict } from '../errors/errors-data.js';
import type { CacheProvider, CacheProviderFactory, InvalidateOptions } from './types.js';
@@ -255,8 +257,13 @@ function matchesVary(request: Request, entry: CachedEntry): boolean {
return true;
}
+function hasAtLeastOneCookie(cookies: AstroCookies | undefined): boolean {
+ return cookies ? !cookies.headers().next().done : false;
+}
+
function hasSetCookieHeader(response: Response): boolean {
- return response.headers.has('set-cookie');
+ if (response.headers.has('set-cookie')) return true;
+ return hasAtLeastOneCookie(getCookiesFromResponse(response));
}
function warnSkippedSetCookie(url: URL): void {
diff --git a/packages/astro/src/i18n/utils.ts b/packages/astro/src/i18n/utils.ts
index dc816d2b2759..55a3d380a9d1 100644
--- a/packages/astro/src/i18n/utils.ts
+++ b/packages/astro/src/i18n/utils.ts
@@ -71,10 +71,11 @@ function sortAndFilterLocales(browserLocaleList: BrowserLocale[], locales: Local
return true;
})
.sort((a, b) => {
- if (a.qualityValue && b.qualityValue) {
- return Math.sign(b.qualityValue - a.qualityValue);
- }
- return 0;
+ // An absent `q=` means quality 1.0 (RFC 7231), while a bare `*` defaults
+ // to 0 so it is never ranked above a real locale.
+ const qa = a.locale === '*' ? (a.qualityValue ?? 0) : (a.qualityValue ?? 1);
+ const qb = b.locale === '*' ? (b.qualityValue ?? 0) : (b.qualityValue ?? 1);
+ return qb - qa;
});
}
diff --git a/packages/astro/src/vite-plugin-routes/index.ts b/packages/astro/src/vite-plugin-routes/index.ts
index 5e1603c856c8..18cb1b6baf75 100644
--- a/packages/astro/src/vite-plugin-routes/index.ts
+++ b/packages/astro/src/vite-plugin-routes/index.ts
@@ -186,7 +186,7 @@ export default async function astroPluginRoutes({
});
const code = `
- import { deserializeRouteInfo } from 'astro/app';
+ import { deserializeRouteInfo } from 'astro/app/manifest';
const serializedData = ${JSON.stringify(filteredRoutes)};
const routes = serializedData.map(deserializeRouteInfo);
export { routes };
diff --git a/packages/astro/test/types/schemas.ts b/packages/astro/test/types/schemas.ts
index c15c3b409095..fcf25430c6c0 100644
--- a/packages/astro/test/types/schemas.ts
+++ b/packages/astro/test/types/schemas.ts
@@ -9,6 +9,8 @@ import type { SessionDriverConfigSchema } from '../../dist/core/session/config.j
import type { SessionDriverConfig } from '../../dist/core/session/types.js';
import type { SvgOptimizer } from '../../dist/assets/svg/types.js';
import type { SvgOptimizerSchema } from '../../dist/assets/svg/config.js';
+import type { ImageInputFormat } from '../../dist/assets/types.js';
+import type { SchemaContext } from '../../dist/content/config.js';
describe('fonts', () => {
it('FontFamily type matches FontFamilySchema', () => {
@@ -44,3 +46,10 @@ describe('svgOptimizer', () => {
expectTypeOf>().toEqualTypeOf();
});
});
+
+describe('content image()', () => {
+ it('image() schema format matches ImageInputFormat', () => {
+ type ImageSchema = ReturnType;
+ expectTypeOf['format']>().toEqualTypeOf();
+ });
+});
diff --git a/packages/astro/test/units/cache/app-cache.test.ts b/packages/astro/test/units/cache/app-cache.test.ts
index eeac905f950b..751fafefa40b 100644
--- a/packages/astro/test/units/cache/app-cache.test.ts
+++ b/packages/astro/test/units/cache/app-cache.test.ts
@@ -1,5 +1,6 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
+import sessionMemoryDriver from 'unstorage/drivers/memory';
import memoryProvider from '../../../dist/core/cache/memory-provider.js';
import { createComponent, render, renderHead } from '../../../dist/runtime/server/index.js';
import { createEndpoint, createPage, createTestApp } from '../mocks.ts';
@@ -12,6 +13,14 @@ function createCacheManifestOverrides() {
};
}
+function createSessionCacheManifestOverrides() {
+ return {
+ ...createCacheManifestOverrides(),
+ sessionConfig: { driver: 'memory', cookie: 'astro-session' },
+ sessionDriver: async () => ({ default: () => sessionMemoryDriver() }),
+ };
+}
+
// #region Route factories
function cachedEndpoint() {
@@ -73,6 +82,33 @@ function withCookieEndpoint() {
);
}
+function withAstroCookieEndpoint() {
+ return createEndpoint(
+ {
+ GET: (ctx: APIContext) => {
+ ctx.cache.set({ maxAge: 300, tags: ['astro-cookie'] });
+ ctx.cookies.set('session', 'test', { path: '/', httpOnly: true });
+ return Response.json({ nonce: Math.random() });
+ },
+ },
+ { route: '/with-astro-cookie' },
+ );
+}
+
+function withSessionEndpoint() {
+ return createEndpoint(
+ {
+ GET: async (ctx: APIContext) => {
+ ctx.cache.set({ maxAge: 300, tags: ['session'] });
+ const count = ((await ctx.session!.get('count')) ?? 0) + 1;
+ ctx.session!.set('count', count);
+ return Response.json({ count, nonce: Math.random() });
+ },
+ },
+ { route: '/with-session' },
+ );
+}
+
function invalidateEndpoint() {
return createEndpoint(
{
@@ -269,6 +305,53 @@ describe('context.cache through App pipeline', () => {
assert.notEqual(firstBody.nonce, secondBody.nonce);
});
+ it('does not cache responses that set Astro cookies', async () => {
+ const overrides = createCacheManifestOverrides();
+ const app = createTestApp([withAstroCookieEndpoint()], overrides);
+
+ const first = await app.render(new Request('http://localhost/with-astro-cookie'), {
+ addCookieHeader: true,
+ });
+ assert.equal(first.headers.get('X-Astro-Cache'), null);
+ assert.ok(first.headers.get('Set-Cookie'));
+ const firstBody = await first.json();
+
+ const second = await app.render(new Request('http://localhost/with-astro-cookie'), {
+ addCookieHeader: true,
+ });
+ assert.equal(second.headers.get('X-Astro-Cache'), null);
+ assert.ok(second.headers.get('Set-Cookie'));
+ const secondBody = await second.json();
+
+ assert.notEqual(firstBody.nonce, secondBody.nonce);
+ });
+
+ it('does not cache responses that set an Astro session', async () => {
+ const overrides = createSessionCacheManifestOverrides();
+ const app = createTestApp([withSessionEndpoint()], overrides);
+
+ const first = await app.render(new Request('http://localhost/with-session'), {
+ addCookieHeader: true,
+ });
+ assert.equal(first.headers.get('X-Astro-Cache'), null);
+ const cookie = first.headers.get('Set-Cookie');
+ assert.ok(cookie);
+ const firstBody = await first.json();
+ assert.equal(firstBody.count, 1);
+
+ const second = await app.render(
+ new Request('http://localhost/with-session', {
+ headers: { Cookie: cookie.split(';', 1)[0] },
+ }),
+ { addCookieHeader: true },
+ );
+ assert.equal(second.headers.get('X-Astro-Cache'), null);
+ const secondBody = await second.json();
+
+ assert.equal(secondBody.count, 2);
+ assert.notEqual(firstBody.nonce, secondBody.nonce);
+ });
+
it('normalizes query parameter order (sorting)', async () => {
const overrides = createCacheManifestOverrides();
const app = createTestApp([cachedEndpoint()], overrides);
diff --git a/packages/astro/test/units/i18n/i18n-utils.test.ts b/packages/astro/test/units/i18n/i18n-utils.test.ts
index dfcfa6e49761..3e42b861eb76 100644
--- a/packages/astro/test/units/i18n/i18n-utils.test.ts
+++ b/packages/astro/test/units/i18n/i18n-utils.test.ts
@@ -67,6 +67,20 @@ describe('computePreferredLocale', () => {
assert.equal(computePreferredLocale(req, locales), 'fr');
});
+ it('prefers an implicit q=1 entry over a lower explicit-q entry regardless of header order', () => {
+ const req = new Request('http://example.com/', {
+ headers: { 'Accept-Language': 'en;q=0.7, de' },
+ });
+ assert.equal(computePreferredLocale(req, ['en', 'fr', 'de']), 'de');
+ });
+
+ it('excludes a q=0 entry from winning', () => {
+ const req = new Request('http://example.com/', {
+ headers: { 'Accept-Language': 'de;q=0, en;q=0.5' },
+ });
+ assert.equal(computePreferredLocale(req, ['en', 'fr', 'de']), 'en');
+ });
+
it('returns undefined when no match', () => {
const req = new Request('http://example.com/', {
headers: { 'Accept-Language': 'de,ja' },
diff --git a/packages/astro/test/units/routing/routes-virtual-module.test.ts b/packages/astro/test/units/routing/routes-virtual-module.test.ts
new file mode 100644
index 000000000000..1651d66616fe
--- /dev/null
+++ b/packages/astro/test/units/routing/routes-virtual-module.test.ts
@@ -0,0 +1,50 @@
+import assert from 'node:assert/strict';
+import { describe, it } from 'node:test';
+
+import astroPluginRoutes from '../../../dist/vite-plugin-routes/index.js';
+import { defaultLogger, createBasicSettings } from '../test-utils.ts';
+import { makeRoute, staticPart } from './test-helpers.ts';
+
+describe('astro:routes virtual module', () => {
+ it('imports deserializeRouteInfo from astro/app/manifest to avoid a circular dependency through the barrel', async () => {
+ const settings = await createBasicSettings();
+ const route = makeRoute({
+ segments: [[staticPart('')]],
+ trailingSlash: 'ignore',
+ route: '/',
+ pathname: '/',
+ });
+ const routesList = { routes: [route] };
+
+ const plugin = await astroPluginRoutes({
+ settings,
+ logger: defaultLogger,
+ routesList,
+ command: 'dev',
+ });
+
+ const loadHook = plugin.load;
+ const handler = typeof loadHook === 'function' ? loadHook : loadHook?.handler;
+ assert.ok(handler, 'plugin should have a load handler');
+
+ // Call the handler with a mock `this` providing the environment name.
+ // Only `environment.name` is accessed; cast to satisfy the type checker.
+ const rawResult = handler.call(
+ { environment: { name: 'astro' } } as any,
+ '\0virtual:astro:routes',
+ );
+ const result = await rawResult;
+
+ assert.ok(result, 'load handler should return a result');
+ const code = typeof result === 'string' ? result : result.code;
+
+ assert.ok(
+ code.includes("from 'astro/app/manifest'"),
+ `Generated code should import from 'astro/app/manifest', not from 'astro/app' barrel. Got:\n${code}`,
+ );
+ assert.ok(
+ !code.includes("from 'astro/app';"),
+ `Generated code should NOT import from 'astro/app' barrel (circular dependency). Got:\n${code}`,
+ );
+ });
+});
diff --git a/packages/integrations/cloudflare/src/esbuild-plugin-astro-frontmatter.ts b/packages/integrations/cloudflare/src/esbuild-plugin-astro-frontmatter.ts
deleted file mode 100644
index 4a3e4039d67a..000000000000
--- a/packages/integrations/cloudflare/src/esbuild-plugin-astro-frontmatter.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-import { readFile } from 'node:fs/promises';
-import type { DepOptimizationConfig } from 'vite';
-
-const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---/;
-
-// Matches tokens to skip (strings, template literals, comments) OR a top-level `return`.
-// The first alternative is preserved as-is; only the second is rewritten.
-// Negative lookbehind `(? {
- if (skip !== undefined) return skip;
- return tail.trim() === ';' ? 'throw 0;' : 'throw ';
- });
-}
-
-// Not exposed as a type from Vite, so need to grab this way.
-type ESBuildPlugin = NonNullable<
- NonNullable['plugins']
->[0];
-
-/**
- * An esbuild plugin that extracts frontmatter from .astro files during
- * dependency optimization scanning. This allows Vite to discover imports
- * in the server-side frontmatter code.
- */
-export function astroFrontmatterScanPlugin(): ESBuildPlugin {
- return {
- name: 'astro-frontmatter-scan',
- setup(build) {
- // Scope to the "file" namespace so that .astro files resolved into the
- // "html" namespace (e.g. when a .ts file default-imports a component)
- // fall through to Vite's built-in html-type handler, which appends
- // `export default {}` and avoids "No matching export" errors.
- build.onLoad({ filter: /\.astro$/, namespace: 'file' }, async (args) => {
- try {
- const code = await readFile(args.path, 'utf-8');
-
- // Extract frontmatter content between --- markers
- const frontmatterMatch = FRONTMATTER_RE.exec(code);
- if (frontmatterMatch) {
- // Replace `return` with `throw` to avoid esbuild's "Top-level return" error during scanning.
- // This aligns with Astro's core compiler logic for frontmatter error handling.
- // See: packages/astro/src/vite-plugin-astro/compile.ts
- const contents = replaceTopLevelReturns(frontmatterMatch[1]);
-
- // Append `export default {}` so that default imports of .astro files
- // (e.g. `import MyComponent from './MyComponent.astro'`) resolve correctly
- // during the dep scan. Without this, .astro files loaded in the `html`
- // namespace (when imported from .ts files) would have no default export,
- // causing esbuild to fail with "No matching export for import 'default'".
- return {
- contents: contents + '\nexport default {}',
- loader: 'ts',
- };
- }
- } catch {
- // Ignore read errors
- }
-
- // No frontmatter or read error, return empty with a default export
- return {
- contents: 'export default {}',
- loader: 'ts',
- };
- });
- },
- };
-}
diff --git a/packages/integrations/cloudflare/src/rolldown-plugin-astro-frontmatter.ts b/packages/integrations/cloudflare/src/rolldown-plugin-astro-frontmatter.ts
index 138ca0da2084..38756e822694 100644
--- a/packages/integrations/cloudflare/src/rolldown-plugin-astro-frontmatter.ts
+++ b/packages/integrations/cloudflare/src/rolldown-plugin-astro-frontmatter.ts
@@ -3,27 +3,35 @@ import type { Plugin } from 'vite';
const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---/;
-// Matches tokens to skip (strings, template literals, comments) OR a top-level `return`.
-// The first alternative is preserved as-is; only the second is rewritten.
-// Negative lookbehind `(? {
- if (skip !== undefined) return skip;
- return tail.trim() === ';' ? 'throw 0;' : 'throw ';
+// Matches static import declarations (single-line and multiline).
+// Used to hoist imports to module scope when wrapping frontmatter in a function.
+const IMPORT_STMT_RE = /^\s*import\b[\s\S]*?(?:from\s+['"][^'"]*['"]|['"][^'"]*['"]);?\s*$/gm;
+
+/**
+ * Wraps frontmatter code in an async function so that top-level `return`
+ * statements are valid syntax for the bundler's parser. Static import
+ * declarations are hoisted above the function since they must remain at
+ * module scope for the dep scanner to discover them.
+ */
+export function wrapFrontmatter(code: string): string {
+ const imports: string[] = [];
+ const body = code.replace(IMPORT_STMT_RE, (match) => {
+ imports.push(match.trim());
+ return '';
});
+ return (
+ imports.join('\n') +
+ (imports.length ? '\n' : '') +
+ 'async function __astro__() {\n' +
+ body +
+ '\n}'
+ );
}
/**
* A Rolldown plugin that extracts frontmatter from .astro files during
* dependency optimization scanning. This allows Vite to discover imports
* in the server-side frontmatter code.
- *
- * This is the Rolldown equivalent of the esbuild plugin in
- * `esbuild-plugin-astro-frontmatter.ts`, needed because Vite 8 uses Rolldown
- * for dependency optimization and ignores `optimizeDeps.esbuildOptions`.
*/
export function rolldownAstroFrontmatterScanPlugin(): Plugin {
return {
@@ -42,9 +50,9 @@ export function rolldownAstroFrontmatterScanPlugin(): Plugin {
// Extract frontmatter content between --- markers
const frontmatterMatch = FRONTMATTER_RE.exec(code);
if (frontmatterMatch) {
- // Replace `return` with `throw` to avoid "Top-level return" errors during scanning.
- // This aligns with Astro's core compiler logic for frontmatter error handling.
- const contents = replaceTopLevelReturns(frontmatterMatch[1]);
+ // Wrap the frontmatter in a function so `return` is valid syntax,
+ // and hoist imports to module scope for the dep scanner.
+ const contents = wrapFrontmatter(frontmatterMatch[1]);
// Append `export default {}` so that default imports of .astro files
// resolve correctly during the dep scan.
diff --git a/packages/integrations/cloudflare/test/fixtures/top-level-return/src/pages/index.astro b/packages/integrations/cloudflare/test/fixtures/top-level-return/src/pages/index.astro
index 2912fa3b1a16..4cd9a7f6c3f0 100644
--- a/packages/integrations/cloudflare/test/fixtures/top-level-return/src/pages/index.astro
+++ b/packages/integrations/cloudflare/test/fixtures/top-level-return/src/pages/index.astro
@@ -1,22 +1,20 @@
---
// This import statement is necessary to indicate that this code is an ECMAScript module.
import { guard } from "../lib/index.js"
-// Un-commenting the following lines will trigger the following error:
-// `X [ERROR] No matching export in "../lib/index.ts" for import "throw"`
-// This is because 'return' is replaced with 'throw', and it's highly unlikely
-// that the source library provides an export named 'throw', leading to a name mismatch.
-//
-// import { return as ret } from "../lib/index.js"
-// console.log(ret)
if (guard()) {
return Astro.redirect("/404")
}
-// Bare return (no value) — must not produce invalid `throw ;` syntax during dep scanning
+// Bare return (no value)
const source = "hello";
if (!source) return;
+// Regex literal containing a quote character — must not break dep scanning (#17697)
+function escapeHtml(value) {
+ return value.replace(/"/g, """);
+}
+
---
diff --git a/packages/integrations/cloudflare/test/fixtures/ts-astro-import/src/lib/ui.ts b/packages/integrations/cloudflare/test/fixtures/ts-astro-import/src/lib/ui.ts
index f5e449a43cdd..9787538d2ec7 100644
--- a/packages/integrations/cloudflare/test/fixtures/ts-astro-import/src/lib/ui.ts
+++ b/packages/integrations/cloudflare/test/fixtures/ts-astro-import/src/lib/ui.ts
@@ -1,12 +1,3 @@
-// A .ts file that default-imports .astro components — the same pattern
-// used by @storyblok/astro's virtual:import-storyblok-components.
-//
-// During esbuild dep scanning, these .astro imports land in the "html"
-// namespace. Without `namespace: "file"` on the astro-frontmatter-scan
-// onLoad handler, the plugin intercepts the load and returns only the
-// frontmatter — which has no `export default` — breaking the import with
-// `No matching export in "html:..." for import "default"`. Regression
-// guard for #16203.
import Inner from '../components/Inner.astro';
import Outer from '../components/Outer.astro';
diff --git a/packages/integrations/cloudflare/test/ssr-deps.test.ts b/packages/integrations/cloudflare/test/ssr-deps.test.ts
index b15246e79f13..4629fa8e50d0 100644
--- a/packages/integrations/cloudflare/test/ssr-deps.test.ts
+++ b/packages/integrations/cloudflare/test/ssr-deps.test.ts
@@ -48,7 +48,7 @@ describe('SSR dependencies', () => {
it('should not fail the dep scan when .ts files import .astro components', async () => {
// When a .ts file imports a .astro component with a default import
- // (e.g. `import Duration from './Duration.astro'`), the esbuild scan plugin
+ // (e.g. `import Duration from './Duration.astro'`), the frontmatter scanner
// must provide a default export so the scan doesn't fail with
// "No matching export for import 'default'".
const scanFailedLog = viteMessages.find((msg) =>
diff --git a/packages/integrations/cloudflare/test/top-level-return.test.ts b/packages/integrations/cloudflare/test/top-level-return.test.ts
index 93eee94f9081..0a8aea824d66 100644
--- a/packages/integrations/cloudflare/test/top-level-return.test.ts
+++ b/packages/integrations/cloudflare/test/top-level-return.test.ts
@@ -38,7 +38,7 @@ describe('Top-level Return', () => {
});
});
- it('should avoid esbuild top-level return error by replacing with void', async () => {
+ it('should support top-level return statements during dependency scanning', async () => {
const topLevelReturnErrorLog = logs.find(
(log) =>
log.message &&
diff --git a/packages/integrations/cloudflare/test/ts-astro-import.test.ts b/packages/integrations/cloudflare/test/ts-astro-import.test.ts
index 89e40b43095a..8f09c330828c 100644
--- a/packages/integrations/cloudflare/test/ts-astro-import.test.ts
+++ b/packages/integrations/cloudflare/test/ts-astro-import.test.ts
@@ -15,8 +15,7 @@ describe('ts file default-importing an .astro component', () => {
root: './fixtures/ts-astro-import/',
});
- // Clear the Vite cache so dep optimization runs from scratch
- // and the esbuild scan actually exercises the plugin path under test.
+ // Clear the Vite cache so dependency optimization runs from scratch.
const viteCacheDir = new URL('./node_modules/.vite/', fixture.config.root);
rmSync(fileURLToPath(viteCacheDir), { recursive: true, force: true });
@@ -37,12 +36,7 @@ describe('ts file default-importing an .astro component', () => {
});
it('should not produce "No matching export" error when a .ts module default-imports a .astro component', async () => {
- // Regression test for #16203. Without `namespace: 'file'` on the
- // astro-frontmatter-scan onLoad handler, Vite's dep scanner resolves
- // `.astro` files into the `html` namespace and the plugin still
- // intercepts them, returning only the frontmatter (no `export default`)
- // and producing:
- // No matching export in "html:/.../Component.astro" for import "default"
+ // The frontmatter scanner must provide a default export for `.astro` files.
const noMatchingExportLog = logs.find(
(log) =>
log.message &&
diff --git a/packages/integrations/cloudflare/test/units/wrap-frontmatter.test.ts b/packages/integrations/cloudflare/test/units/wrap-frontmatter.test.ts
new file mode 100644
index 000000000000..cc31f4e77b18
--- /dev/null
+++ b/packages/integrations/cloudflare/test/units/wrap-frontmatter.test.ts
@@ -0,0 +1,91 @@
+import assert from 'node:assert/strict';
+import { describe, it } from 'node:test';
+import { wrapFrontmatter } from '../../dist/rolldown-plugin-astro-frontmatter.js';
+
+describe('wrapFrontmatter', () => {
+ it('hoists a single-line import and wraps the body', () => {
+ const input = 'import { foo } from "pkg";\nconst x = foo();';
+ const result = wrapFrontmatter(input);
+ assert.ok(result.startsWith('import { foo } from "pkg";'));
+ assert.ok(result.includes('async function __astro__()'));
+ assert.ok(result.includes('const x = foo();'));
+ });
+
+ it('hoists multiline imports', () => {
+ const input = [
+ 'import {',
+ ' foo,',
+ ' bar,',
+ '} from "some-package";',
+ 'const x = foo();',
+ ].join('\n');
+ const result = wrapFrontmatter(input);
+ assert.ok(result.includes('import {\n foo,\n bar,\n} from "some-package";'));
+ assert.ok(result.includes('async function __astro__()'));
+ assert.ok(result.includes('const x = foo();'));
+ });
+
+ it('preserves return statements inside the function wrapper', () => {
+ const input = 'if (true) {\n\treturn new Response("Not Allowed");\n}';
+ const result = wrapFrontmatter(input);
+ assert.ok(result.includes('return new Response'));
+ assert.ok(result.includes('async function __astro__()'));
+ });
+
+ it('handles regex literals containing quote characters (#17697)', () => {
+ const input = [
+ 'import { h } from "preact";',
+ 'function escapeHtml(value) {',
+ '\treturn value.replace(/"/g, """);',
+ '}',
+ 'if (Astro.request.method !== "GET") {',
+ '\treturn new Response("Method Not Allowed", { status: 405 });',
+ '}',
+ ].join('\n');
+ const result = wrapFrontmatter(input);
+ // Import should be hoisted
+ assert.ok(result.startsWith('import { h } from "preact";'));
+ // Both returns must be preserved (not rewritten or lost)
+ const returnCount = (result.match(/\breturn\b/g) || []).length;
+ assert.equal(returnCount, 2, 'both return statements should be preserved');
+ // The regex literal must be intact
+ assert.ok(result.includes('/"/g'));
+ });
+
+ it('does not hoist import.meta references', () => {
+ const input = 'import { h } from "preact";\nconst url = import.meta.url;';
+ const result = wrapFrontmatter(input);
+ assert.ok(result.includes('import.meta.url'));
+ // import.meta.url should be inside the function, not hoisted
+ const funcStart = result.indexOf('async function __astro__()');
+ const metaPos = result.indexOf('import.meta.url');
+ assert.ok(metaPos > funcStart, 'import.meta.url should be inside the function body');
+ });
+
+ it('handles side-effect imports', () => {
+ const input = 'import "./styles.css";\nif (true) return;';
+ const result = wrapFrontmatter(input);
+ assert.ok(result.startsWith('import "./styles.css";'));
+ assert.ok(result.includes('async function __astro__()'));
+ });
+
+ it('handles frontmatter with no imports', () => {
+ const input = 'if (true) {\n\treturn;\n}';
+ const result = wrapFrontmatter(input);
+ // Should start directly with the function wrapper
+ assert.ok(result.startsWith('async function __astro__()'));
+ assert.ok(result.includes('return;'));
+ });
+
+ it('handles bare return (no value)', () => {
+ const input = 'import { h } from "preact";\nif (!source) return;';
+ const result = wrapFrontmatter(input);
+ assert.ok(result.includes('return;'));
+ });
+
+ it('handles type imports', () => {
+ const input = 'import type { Foo } from "pkg";\nconst x: Foo = {};';
+ const result = wrapFrontmatter(input);
+ assert.ok(result.startsWith('import type { Foo } from "pkg";'));
+ });
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d7cdd83b8613..386a56276a74 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -226,7 +226,7 @@ importers:
version: 2.29.8(@types/node@22.19.19)
'@earendil-works/pi-ai':
specifier: ^0.83.0
- version: 0.83.0(ws@8.20.1)(zod@4.3.6)
+ version: 0.83.0(supports-color@8.1.1)(ws@8.20.1)(zod@4.3.6)
'@flue/runtime':
specifier: ^2.0.3
version: 2.0.3(typescript@6.0.3)(ws@8.20.1)(zod@4.3.6)
@@ -241,7 +241,7 @@ importers:
version: 0.2.0
eslint:
specifier: ^10.4.0
- version: 10.4.0(jiti@2.6.1)
+ version: 10.4.0(jiti@2.6.1)(supports-color@8.1.1)
eslint-plugin-regexp:
specifier: ^3.1.0
version: 3.1.0(eslint@10.4.0)
@@ -271,7 +271,7 @@ importers:
version: 6.0.3
typescript-eslint:
specifier: ^8.59.1
- version: 8.59.2(eslint@10.4.0)(typescript@6.0.3)
+ version: 8.59.2(eslint@10.4.0)(supports-color@8.1.1)(typescript@6.0.3)
valibot:
specifier: ^1.2.0
version: 1.4.2(typescript@6.0.3)
@@ -5393,7 +5393,7 @@ importers:
version: 4.0.2
'@shikijs/twoslash':
specifier: ^4.0.2
- version: 4.0.2(typescript@6.0.3)
+ version: 4.0.2(supports-color@8.1.1)(typescript@6.0.3)
'@types/estree':
specifier: ^1.0.8
version: 1.0.8
@@ -5648,7 +5648,7 @@ importers:
version: 5.2.0
'@netlify/vite-plugin':
specifier: ^2.12.3
- version: 2.12.3(@azure/identity@4.13.0)(@vercel/functions@3.4.3)(vite@8.2.1)
+ version: 2.12.3(@azure/identity@4.13.0)(@vercel/functions@3.4.3)(supports-color@8.1.1)(vite@8.2.1)
'@vercel/nft':
specifier: ^1.3.2
version: 1.3.2
@@ -5816,7 +5816,7 @@ importers:
version: 5.9.0
express:
specifier: ^5.2.1
- version: 5.2.1
+ version: 5.2.1(supports-color@8.1.1)
fastify:
specifier: ^5.12.0
version: 5.12.0
@@ -6021,7 +6021,7 @@ importers:
version: link:../../internal-helpers
'@preact/preset-vite':
specifier: ^2.10.5
- version: 2.10.5(@babel/core@7.29.0)(preact@10.29.8)(vite@8.2.1)
+ version: 2.10.5(@babel/core@7.29.0)(preact@10.29.8)(supports-color@8.1.1)(vite@8.2.1)
'@preact/signals':
specifier: ^2.8.2
version: 2.8.2(preact@10.29.8)
@@ -6567,7 +6567,7 @@ importers:
version: 8.2.1(@types/node@22.19.19)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.98.0)(tsx@4.22.3)(yaml@2.9.0)
vite-plugin-vue-devtools:
specifier: ^8.1.0
- version: 8.1.0(vite@8.2.1)(vue@3.5.30)
+ version: 8.1.0(supports-color@8.1.1)(vite@8.2.1)(vue@3.5.30)
devDependencies:
astro:
specifier: workspace:*
@@ -6595,7 +6595,7 @@ importers:
version: link:../../../../../astro
vite-svg-loader:
specifier: 5.1.1
- version: 5.1.1(vue@3.5.30)
+ version: 5.1.1(supports-color@8.1.1)(vue@3.5.30)
vue:
specifier: ^3.5.30
version: 3.5.30(typescript@6.0.3)
@@ -6610,7 +6610,7 @@ importers:
version: link:../../../../../astro
vite-svg-loader:
specifier: 5.1.1
- version: 5.1.1(vue@3.5.30)
+ version: 5.1.1(supports-color@8.1.1)(vue@3.5.30)
vue:
specifier: ^3.5.30
version: 3.5.30(typescript@6.0.3)
@@ -6634,7 +6634,7 @@ importers:
version: link:../../../../../astro
vite-svg-loader:
specifier: 5.1.1
- version: 5.1.1(vue@3.5.30)
+ version: 5.1.1(supports-color@8.1.1)(vue@3.5.30)
vue:
specifier: ^3.5.30
version: 3.5.30(typescript@6.0.3)
@@ -6970,7 +6970,7 @@ importers:
version: 11.7.5
ovsx:
specifier: ^0.10.10
- version: 0.10.10
+ version: 0.10.10(supports-color@8.1.1)
tsx:
specifier: ^4.22.0
version: 4.22.3
@@ -17653,7 +17653,7 @@ snapshots:
'@earendil-works/pi-agent-core@0.83.0(ws@8.20.1)(zod@4.3.6)':
dependencies:
- '@earendil-works/pi-ai': 0.83.0(ws@8.20.1)(zod@4.3.6)
+ '@earendil-works/pi-ai': 0.83.0(supports-color@8.1.1)(ws@8.20.1)(zod@4.3.6)
diff: 8.0.4
ignore: 7.0.5
typebox: 1.3.7
@@ -17666,7 +17666,7 @@ snapshots:
- ws
- zod
- '@earendil-works/pi-ai@0.83.0(ws@8.20.1)(zod@4.3.6)':
+ '@earendil-works/pi-ai@0.83.0(supports-color@8.1.1)(ws@8.20.1)(zod@4.3.6)':
dependencies:
'@anthropic-ai/sdk': 0.91.1(zod@4.3.6)
'@aws-sdk/client-bedrock-runtime': 3.1048.0
@@ -17674,7 +17674,7 @@ snapshots:
'@mistralai/mistralai': 2.2.6(@opentelemetry/api@1.9.0)
'@opentelemetry/api': 1.9.0
'@smithy/node-http-handler': 4.7.3
- http-proxy-agent: 7.0.2
+ http-proxy-agent: 7.0.2(supports-color@8.1.1)
https-proxy-agent: 7.0.6
openai: 6.26.0(ws@8.20.1)(zod@4.3.6)
partial-json: 0.1.7
@@ -17957,12 +17957,12 @@ snapshots:
'@eslint-community/eslint-utils@4.9.1(eslint@10.4.0)':
dependencies:
- eslint: 10.4.0(jiti@2.6.1)
+ eslint: 10.4.0(jiti@2.6.1)(supports-color@8.1.1)
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.2': {}
- '@eslint/config-array@0.23.5':
+ '@eslint/config-array@0.23.5(supports-color@8.1.1)':
dependencies:
'@eslint/object-schema': 3.0.5
debug: 4.4.3(supports-color@8.1.1)
@@ -18040,7 +18040,7 @@ snapshots:
'@flue/runtime@2.0.3(typescript@6.0.3)(ws@8.20.1)(zod@4.3.6)':
dependencies:
'@earendil-works/pi-agent-core': 0.83.0(ws@8.20.1)(zod@4.3.6)
- '@earendil-works/pi-ai': 0.83.0(ws@8.20.1)(zod@4.3.6)
+ '@earendil-works/pi-ai': 0.83.0(supports-color@8.1.1)(ws@8.20.1)(zod@4.3.6)
'@hono/node-server': 2.0.4(hono@4.12.18)
'@modelcontextprotocol/client': 2.0.0
'@valibot/to-json-schema': 1.5.0(valibot@1.4.2)
@@ -18536,7 +18536,7 @@ snapshots:
uuid: 13.0.0
write-file-atomic: 5.0.1
- '@netlify/dev@4.18.3(@azure/identity@4.13.0)(@vercel/functions@3.4.3)':
+ '@netlify/dev@4.18.3(@azure/identity@4.13.0)(@vercel/functions@3.4.3)(supports-color@8.1.1)':
dependencies:
'@netlify/ai': 0.4.1
'@netlify/blobs': 10.7.5
@@ -18544,7 +18544,7 @@ snapshots:
'@netlify/database-dev': 0.10.1
'@netlify/dev-utils': 4.4.3
'@netlify/edge-functions-dev': 1.0.17
- '@netlify/functions-dev': 1.2.8
+ '@netlify/functions-dev': 1.2.8(supports-color@8.1.1)
'@netlify/headers': 2.1.8
'@netlify/images': 1.3.7(@azure/identity@4.13.0)(@netlify/blobs@10.7.5)(@vercel/functions@3.4.3)
'@netlify/redirects': 3.1.10
@@ -18616,7 +18616,7 @@ snapshots:
dependencies:
'@netlify/types': 2.6.0
- '@netlify/functions-dev@1.2.8':
+ '@netlify/functions-dev@1.2.8(supports-color@8.1.1)':
dependencies:
'@netlify/blobs': 10.7.5
'@netlify/dev-utils': 4.4.3
@@ -18624,7 +18624,7 @@ snapshots:
'@netlify/zip-it-and-ship-it': 14.5.6
cron-parser: 4.9.0
decache: 4.6.2
- extract-zip: 2.0.1
+ extract-zip: 2.0.1(supports-color@8.1.1)
is-stream: 4.0.1
jwt-decode: 4.0.0
lambda-local: 2.2.0
@@ -18727,9 +18727,9 @@ snapshots:
'@netlify/types@2.6.0': {}
- '@netlify/vite-plugin@2.12.3(@azure/identity@4.13.0)(@vercel/functions@3.4.3)(vite@8.2.1)':
+ '@netlify/vite-plugin@2.12.3(@azure/identity@4.13.0)(@vercel/functions@3.4.3)(supports-color@8.1.1)(vite@8.2.1)':
dependencies:
- '@netlify/dev': 4.18.3(@azure/identity@4.13.0)(@vercel/functions@3.4.3)
+ '@netlify/dev': 4.18.3(@azure/identity@4.13.0)(@vercel/functions@3.4.3)(supports-color@8.1.1)
'@netlify/dev-utils': 4.4.3
dedent: 1.7.1
vite: 8.2.1(@types/node@22.19.19)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.98.0)(tsx@4.22.3)(yaml@2.9.0)
@@ -19082,7 +19082,7 @@ snapshots:
'@poppinss/exception@1.2.3': {}
- '@preact/preset-vite@2.10.5(@babel/core@7.29.0)(preact@10.29.8)(vite@8.2.1)':
+ '@preact/preset-vite@2.10.5(@babel/core@7.29.0)(preact@10.29.8)(supports-color@8.1.1)(vite@8.2.1)':
dependencies:
'@babel/core': 7.29.0
'@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.0)
@@ -19332,11 +19332,11 @@ snapshots:
dependencies:
'@shikijs/types': 4.0.2
- '@shikijs/twoslash@4.0.2(typescript@6.0.3)':
+ '@shikijs/twoslash@4.0.2(supports-color@8.1.1)(typescript@6.0.3)':
dependencies:
'@shikijs/core': 4.0.2
'@shikijs/types': 4.0.2
- twoslash: 0.3.8(typescript@6.0.3)
+ twoslash: 0.3.8(supports-color@8.1.1)(typescript@6.0.3)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
@@ -19785,15 +19785,15 @@ snapshots:
'@types/node': 22.19.19
optional: true
- '@typescript-eslint/eslint-plugin@8.59.2(@typescript-eslint/parser@8.59.2)(eslint@10.4.0)(typescript@6.0.3)':
+ '@typescript-eslint/eslint-plugin@8.59.2(@typescript-eslint/parser@8.59.2)(eslint@10.4.0)(supports-color@8.1.1)(typescript@6.0.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
- '@typescript-eslint/parser': 8.59.2(eslint@10.4.0)(typescript@6.0.3)
+ '@typescript-eslint/parser': 8.59.2(eslint@10.4.0)(supports-color@8.1.1)(typescript@6.0.3)
'@typescript-eslint/scope-manager': 8.59.2
- '@typescript-eslint/type-utils': 8.59.2(eslint@10.4.0)(typescript@6.0.3)
+ '@typescript-eslint/type-utils': 8.59.2(eslint@10.4.0)(supports-color@8.1.1)(typescript@6.0.3)
'@typescript-eslint/utils': 8.59.2(eslint@10.4.0)(typescript@6.0.3)
'@typescript-eslint/visitor-keys': 8.59.2
- eslint: 10.4.0(jiti@2.6.1)
+ eslint: 10.4.0(jiti@2.6.1)(supports-color@8.1.1)
ignore: 7.0.5
natural-compare: 1.4.0
ts-api-utils: 2.5.0(typescript@6.0.3)
@@ -19801,14 +19801,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/parser@8.59.2(eslint@10.4.0)(typescript@6.0.3)':
+ '@typescript-eslint/parser@8.59.2(eslint@10.4.0)(supports-color@8.1.1)(typescript@6.0.3)':
dependencies:
'@typescript-eslint/scope-manager': 8.59.2
'@typescript-eslint/types': 8.59.2
'@typescript-eslint/typescript-estree': 8.59.2(typescript@6.0.3)
'@typescript-eslint/visitor-keys': 8.59.2
debug: 4.4.3(supports-color@8.1.1)
- eslint: 10.4.0(jiti@2.6.1)
+ eslint: 10.4.0(jiti@2.6.1)(supports-color@8.1.1)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
@@ -19844,13 +19844,13 @@ snapshots:
dependencies:
typescript: 6.0.3
- '@typescript-eslint/type-utils@8.59.2(eslint@10.4.0)(typescript@6.0.3)':
+ '@typescript-eslint/type-utils@8.59.2(eslint@10.4.0)(supports-color@8.1.1)(typescript@6.0.3)':
dependencies:
'@typescript-eslint/types': 8.59.2
'@typescript-eslint/typescript-estree': 8.59.2(typescript@6.0.3)
'@typescript-eslint/utils': 8.59.2(eslint@10.4.0)(typescript@6.0.3)
debug: 4.4.3(supports-color@8.1.1)
- eslint: 10.4.0(jiti@2.6.1)
+ eslint: 10.4.0(jiti@2.6.1)(supports-color@8.1.1)
ts-api-utils: 2.5.0(typescript@6.0.3)
typescript: 6.0.3
transitivePeerDependencies:
@@ -19894,7 +19894,7 @@ snapshots:
'@typescript-eslint/scope-manager': 8.59.2
'@typescript-eslint/types': 8.59.2
'@typescript-eslint/typescript-estree': 8.59.2(typescript@6.0.3)
- eslint: 10.4.0(jiti@2.6.1)
+ eslint: 10.4.0(jiti@2.6.1)(supports-color@8.1.1)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
@@ -19904,7 +19904,7 @@ snapshots:
'@typescript-eslint/types': 8.59.2
eslint-visitor-keys: 5.0.1
- '@typescript/vfs@1.6.4(typescript@6.0.3)':
+ '@typescript/vfs@1.6.4(supports-color@8.1.1)(typescript@6.0.3)':
dependencies:
debug: 4.4.3(supports-color@8.1.1)
typescript: 6.0.3
@@ -19913,7 +19913,7 @@ snapshots:
'@typespec/ts-http-runtime@0.3.3':
dependencies:
- http-proxy-agent: 7.0.2
+ http-proxy-agent: 7.0.2(supports-color@8.1.1)
https-proxy-agent: 7.0.6
tslib: 2.8.1
transitivePeerDependencies:
@@ -20159,7 +20159,7 @@ snapshots:
'@vscode/test-electron@2.5.2':
dependencies:
- http-proxy-agent: 7.0.2
+ http-proxy-agent: 7.0.2(supports-color@8.1.1)
https-proxy-agent: 7.0.6
jszip: 3.10.1
ora: 8.2.0
@@ -20169,7 +20169,7 @@ snapshots:
'@vscode/test-electron@3.1.0':
dependencies:
- http-proxy-agent: 7.0.2
+ http-proxy-agent: 7.0.2(supports-color@8.1.1)
https-proxy-agent: 7.0.6
jszip: 3.10.1
ora: 8.2.0
@@ -20247,7 +20247,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@vscode/vsce@3.7.1':
+ '@vscode/vsce@3.7.1(supports-color@8.1.1)':
dependencies:
'@azure/identity': 4.13.0
'@secretlint/node': 10.2.2
@@ -20270,7 +20270,7 @@ snapshots:
minimatch: 3.1.2
parse-semver: 1.1.1
read: 1.0.7
- secretlint: 10.2.2
+ secretlint: 10.2.2(supports-color@8.1.1)
semver: 7.8.5
tmp: 0.2.5
typed-rest-client: 1.8.11
@@ -20770,7 +20770,7 @@ snapshots:
blake3-wasm@2.1.5: {}
- body-parser@2.2.2:
+ body-parser@2.2.2(supports-color@8.1.1):
dependencies:
bytes: 3.1.2
content-type: 1.0.5
@@ -21635,7 +21635,7 @@ snapshots:
'@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0)
'@eslint-community/regexpp': 4.12.2
comment-parser: 1.4.5
- eslint: 10.4.0(jiti@2.6.1)
+ eslint: 10.4.0(jiti@2.6.1)(supports-color@8.1.1)
jsdoc-type-pratt-parser: 7.1.1
refa: 0.12.1
regexp-ast-analysis: 0.7.1
@@ -21652,11 +21652,11 @@ snapshots:
eslint-visitor-keys@5.0.1: {}
- eslint@10.4.0(jiti@2.6.1):
+ eslint@10.4.0(jiti@2.6.1)(supports-color@8.1.1):
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0)
'@eslint-community/regexpp': 4.12.2
- '@eslint/config-array': 0.23.5
+ '@eslint/config-array': 0.23.5(supports-color@8.1.1)
'@eslint/config-helpers': 0.6.0
'@eslint/core': 1.2.1
'@eslint/plugin-kit': 0.7.1
@@ -21790,10 +21790,10 @@ snapshots:
expect-type@1.3.0: {}
- express@5.2.1:
+ express@5.2.1(supports-color@8.1.1):
dependencies:
accepts: 2.0.0
- body-parser: 2.2.2
+ body-parser: 2.2.2(supports-color@8.1.1)
content-disposition: 1.0.1
content-type: 1.0.5
cookie: 0.7.2
@@ -21803,7 +21803,7 @@ snapshots:
encodeurl: 2.0.0
escape-html: 1.0.3
etag: 1.8.1
- finalhandler: 2.1.1
+ finalhandler: 2.1.1(supports-color@8.1.1)
fresh: 2.0.0
http-errors: 2.0.1
merge-descriptors: 2.0.0
@@ -21814,7 +21814,7 @@ snapshots:
proxy-addr: 2.0.7
qs: 6.14.2
range-parser: 1.2.1
- router: 2.2.0
+ router: 2.2.0(supports-color@8.1.1)
send: 1.2.1
serve-static: 2.2.1
statuses: 2.0.2
@@ -21827,7 +21827,7 @@ snapshots:
extendable-error@0.1.7: {}
- extract-zip@2.0.1:
+ extract-zip@2.0.1(supports-color@8.1.1):
dependencies:
debug: 4.4.3(supports-color@8.1.1)
get-stream: 5.2.0
@@ -21970,7 +21970,7 @@ snapshots:
filter-obj@6.1.0: {}
- finalhandler@2.1.1:
+ finalhandler@2.1.1(supports-color@8.1.1):
dependencies:
debug: 4.4.3(supports-color@8.1.1)
encodeurl: 2.0.0
@@ -22506,7 +22506,7 @@ snapshots:
http-parser-js@0.5.10: {}
- http-proxy-agent@7.0.2:
+ http-proxy-agent@7.0.2(supports-color@8.1.1):
dependencies:
agent-base: 7.1.4
debug: 4.4.3(supports-color@8.1.1)
@@ -24040,9 +24040,9 @@ snapshots:
outdent@0.5.0: {}
- ovsx@0.10.10:
+ ovsx@0.10.10(supports-color@8.1.1):
dependencies:
- '@vscode/vsce': 3.7.1
+ '@vscode/vsce': 3.7.1(supports-color@8.1.1)
commander: 6.2.1
follow-redirects: 1.15.11
is-ci: 2.0.0
@@ -25106,7 +25106,7 @@ snapshots:
rosie-skills-freebsd-x64: 0.6.4
rosie-skills-linux-x64: 0.6.4
- router@2.2.0:
+ router@2.2.0(supports-color@8.1.1):
dependencies:
debug: 4.4.3(supports-color@8.1.1)
depd: 2.0.0
@@ -25185,7 +25185,7 @@ snapshots:
scule@1.3.0: {}
- secretlint@10.2.2:
+ secretlint@10.2.2(supports-color@8.1.1):
dependencies:
'@secretlint/config-creator': 10.2.2
'@secretlint/formatter': 10.2.2
@@ -25818,9 +25818,9 @@ snapshots:
twoslash-protocol@0.3.8: {}
- twoslash@0.3.8(typescript@6.0.3):
+ twoslash@0.3.8(supports-color@8.1.1)(typescript@6.0.3):
dependencies:
- '@typescript/vfs': 1.6.4(typescript@6.0.3)
+ '@typescript/vfs': 1.6.4(supports-color@8.1.1)(typescript@6.0.3)
twoslash-protocol: 0.3.8
typescript: 6.0.3
transitivePeerDependencies:
@@ -25867,13 +25867,13 @@ snapshots:
dependencies:
semver: 7.8.5
- typescript-eslint@8.59.2(eslint@10.4.0)(typescript@6.0.3):
+ typescript-eslint@8.59.2(eslint@10.4.0)(supports-color@8.1.1)(typescript@6.0.3):
dependencies:
- '@typescript-eslint/eslint-plugin': 8.59.2(@typescript-eslint/parser@8.59.2)(eslint@10.4.0)(typescript@6.0.3)
- '@typescript-eslint/parser': 8.59.2(eslint@10.4.0)(typescript@6.0.3)
+ '@typescript-eslint/eslint-plugin': 8.59.2(@typescript-eslint/parser@8.59.2)(eslint@10.4.0)(supports-color@8.1.1)(typescript@6.0.3)
+ '@typescript-eslint/parser': 8.59.2(eslint@10.4.0)(supports-color@8.1.1)(typescript@6.0.3)
'@typescript-eslint/typescript-estree': 8.59.2(typescript@6.0.3)
'@typescript-eslint/utils': 8.59.2(eslint@10.4.0)(typescript@6.0.3)
- eslint: 10.4.0(jiti@2.6.1)
+ eslint: 10.4.0(jiti@2.6.1)(supports-color@8.1.1)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
@@ -26110,7 +26110,7 @@ snapshots:
dependencies:
vite: 8.2.1(@types/node@22.19.19)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.98.0)(tsx@4.22.3)(yaml@2.9.0)
- vite-plugin-inspect@11.3.3(vite@8.2.1):
+ vite-plugin-inspect@11.3.3(supports-color@8.1.1)(vite@8.2.1):
dependencies:
ansis: 4.2.0
debug: 4.4.3(supports-color@8.1.1)
@@ -26138,14 +26138,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
- vite-plugin-vue-devtools@8.1.0(vite@8.2.1)(vue@3.5.30):
+ vite-plugin-vue-devtools@8.1.0(supports-color@8.1.1)(vite@8.2.1)(vue@3.5.30):
dependencies:
'@vue/devtools-core': 8.1.0(vue@3.5.30)
'@vue/devtools-kit': 8.1.0
'@vue/devtools-shared': 8.1.0
sirv: 3.0.2
vite: 8.2.1(@types/node@22.19.19)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.98.0)(tsx@4.22.3)(yaml@2.9.0)
- vite-plugin-inspect: 11.3.3(vite@8.2.1)
+ vite-plugin-inspect: 11.3.3(supports-color@8.1.1)(vite@8.2.1)
vite-plugin-vue-inspector: 5.3.2(vite@8.2.1)
transitivePeerDependencies:
- '@nuxt/kit'
@@ -26177,7 +26177,7 @@ snapshots:
stack-trace: 1.0.0-pre2
vite: 8.2.1(@types/node@22.19.19)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.98.0)(tsx@4.22.3)(yaml@2.9.0)
- vite-svg-loader@5.1.1(vue@3.5.30):
+ vite-svg-loader@5.1.1(supports-color@8.1.1)(vue@3.5.30):
dependencies:
debug: 4.4.3(supports-color@8.1.1)
svgo: 3.3.3
diff --git a/reference/optimize-deps.md b/reference/optimize-deps.md
index 80e0cdc74502..0f87b1493880 100644
--- a/reference/optimize-deps.md
+++ b/reference/optimize-deps.md
@@ -77,22 +77,15 @@ The result (`astroPkgsConfig`) is passed to `vitePluginEnvironment`.
### The Cloudflare adapter's `configEnvironment`
-`@astrojs/cloudflare` also implements `configEnvironment`. For the `ssr` environment, it sets an explicit `optimizeDeps.include` list (things that need to be pre-bundled unconditionally). It also registers `astroFrontmatterScanPlugin` as an esbuild plugin in `optimizeDeps.esbuildOptions.plugins`.
+`@astrojs/cloudflare` also implements `configEnvironment`. For its server environments, it sets an explicit `optimizeDeps.include` list (things that need to be pre-bundled unconditionally). It also registers `rolldownAstroFrontmatterScanPlugin` in `optimizeDeps.rolldownOptions.plugins`.
-`astroFrontmatterScanPlugin` (`packages/integrations/cloudflare/src/esbuild-plugin-astro-frontmatter.ts`) handles `.astro` files during the dep scan: it reads the frontmatter (`---` block) and returns it as TypeScript for esbuild to process. This allows esbuild to see imports declared in `.astro` frontmatter and discover their deps.
+`rolldownAstroFrontmatterScanPlugin` (`packages/integrations/cloudflare/src/rolldown-plugin-astro-frontmatter.ts`) handles `.astro` files during the dep scan: it reads the frontmatter (`---` block) and returns it as TypeScript for Rolldown to process. This allows Rolldown to see imports declared in `.astro` frontmatter and discover their deps.
---
## How non-JS files are handled in the scan
-Vite's `esbuildScanPlugin` (inside `vite/dist/node/chunks/config.js`) routes files through different handlers based on type:
-
-- Files matching `htmlTypesRE` (`.html`, `.vue`, `.svelte`, `.astro`, `.imba`) → `html` namespace
-- JS/TS files → loaded directly and scanned for imports
-
-For files in the `html` namespace, `htmlTypeOnLoadCallback` reads the file, looks for `