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 `