Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-cf-frontmatter-scan-html-namespace.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/cloudflare': patch
---

Fixes `.astro` files failing with `No matching export in "html:..." for import "default"` when default-imported from a `.ts` file
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ export function astroFrontmatterScanPlugin(): ESBuildPlugin {
return {
name: 'astro-frontmatter-scan',
setup(build) {
build.onLoad({ filter: /\.astro$/ }, async (args) => {
// 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');

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import cloudflare from '@astrojs/cloudflare';
import { defineConfig } from 'astro/config';

export default defineConfig({
adapter: cloudflare(),
output: 'server',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
const message = 'hello from inner';
---

<span>{message}</span>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
import Inner from './Inner.astro';
---

<div>
<Inner />
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// 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';

export const components = { Inner, Outer };
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
import { components } from '../lib/ui';

const Outer = components.Outer;
---

<html>
<body>
<Outer />
</body>
</html>
70 changes: 70 additions & 0 deletions packages/integrations/cloudflare/test/ts-astro-import.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { rmSync } from 'node:fs';
import { describe, before, it } from 'node:test';
import { Writable } from 'node:stream';
import { loadFixture } from './_test-utils.js';
import assert from 'node:assert/strict';
import { fileURLToPath } from 'node:url';
import { AstroLogger } from '../../../astro/dist/core/logger/core.js';

describe('ts file default-importing an .astro component', () => {
/** @type {import('../../../astro/test/test-utils').Fixture} */
let fixture;
const logs = [];

before(async () => {
fixture = await loadFixture({
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.
const viteCacheDir = new URL('./node_modules/.vite/', fixture.config.root);
rmSync(fileURLToPath(viteCacheDir), { recursive: true, force: true });

await fixture.build({
vite: { logLevel: 'error' },
logger: new AstroLogger({
level: 'error',
destination: new Writable({
objectMode: true,
write(event, _, callback) {
logs.push(event);
callback();
},
}),
}),
});
});

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"
const noMatchingExportLog = logs.find(
(log) =>
log.message &&
log.message.includes('No matching export') &&
log.message.includes('html:') &&
log.message.includes('for import "default"'),
);

assert.ok(
!noMatchingExportLog,
`Should not see "No matching export in 'html:...' for import 'default'" message, but got: ${noMatchingExportLog?.message}`,
);
});

it('should complete dependency scanning successfully', async () => {
const dependencyScanFailedLog = logs.find(
(log) => log.message && log.message.includes('Failed to run dependency scan'),
);

assert.ok(
!dependencyScanFailedLog,
`Should not see "Failed to run dependency scan" message, but got: ${dependencyScanFailedLog?.message}`,
);
});
});
Loading