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/orange-boats-refuse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/cloudflare': patch
---

Fixes an issue where `esbuild` would throw a "Top-level return cannot be used inside an ECMAScript module" error during dependency scanning in certain environments.
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,16 @@ export function astroFrontmatterScanPlugin(): ESBuildPlugin {
// Extract frontmatter content between --- markers
const frontmatterMatch = FRONTMATTER_RE.exec(code);
if (frontmatterMatch) {
// Return the frontmatter as TypeScript for import scanning
// 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
//
// Known Limitation: Using regex /\breturn\b/ will incorrectly match
// identifiers like `$return` or aliases like `import { return as ret }`.
const contents = frontmatterMatch[1].replace(/\breturn\b/g, 'throw ');

return {
contents: frontmatterMatch[1],
contents,
loader: 'ts',
};
}
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,12 @@
{
"name": "@test/astro-cloudflare-top-level-return",
"version": "0.0.0",
"private": true,
"scripts": {
"build": "astro build"
},
"dependencies": {
"@astrojs/cloudflare": "workspace:*",
"astro": "workspace:*"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function guard() {
return false;
}

const ret = 0;

export { ret as return }
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
// 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")
}

---

<html>
<head>
<title>Top-level Return Test</title>
</head>
<body>
<h1>Top-level Return Test</h1>
</body>
</html>
62 changes: 62 additions & 0 deletions packages/integrations/cloudflare/test/top-level-return.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
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 { Logger } from '../../../astro/dist/core/logger/core.js';

describe('Top-level Return', () => {
/** @type {import('../../../astro/test/test-utils').Fixture} */
let fixture;
const logs = [];

before(async () => {
fixture = await loadFixture({
root: './fixtures/top-level-return/',
});

// Clear the Vite cache before testing
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 Logger({
level: 'error',
dest: new Writable({
objectMode: true,
write(event, _, callback) {
logs.push(event);
callback();
},
}),
}),
});
});

it('should avoid esbuild top-level return error by replacing with void', async () => {
const topLevelReturnErrorLog = logs.find(
(log) =>
log.message &&
log.message.includes('Top-level return cannot be used inside an ECMAScript module'),
);

assert.ok(
!topLevelReturnErrorLog,
`Should not see "Top-level return cannot be used inside an ECMAScript module" message, but got: ${topLevelReturnErrorLog?.message}`,
);
});

it('should not break JS syntax and 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}`,
);
});
});
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading