diff --git a/.changeset/hot-places-ask.md b/.changeset/hot-places-ask.md new file mode 100644 index 000000000000..9b1c28ef7d36 --- /dev/null +++ b/.changeset/hot-places-ask.md @@ -0,0 +1,5 @@ +--- +'@astrojs/cloudflare': patch +--- + +Fixes dev dependency scan failing when a frontmatter line starts with `import.meta` or `import(` diff --git a/packages/integrations/cloudflare/src/rolldown-plugin-astro-frontmatter.ts b/packages/integrations/cloudflare/src/rolldown-plugin-astro-frontmatter.ts index 38756e822694..9bb7d74577e4 100644 --- a/packages/integrations/cloudflare/src/rolldown-plugin-astro-frontmatter.ts +++ b/packages/integrations/cloudflare/src/rolldown-plugin-astro-frontmatter.ts @@ -7,6 +7,30 @@ const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---/; // 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; +function isImportExpression(code: string): boolean { + let index = code.indexOf('import') + 'import'.length; + while (index < code.length) { + if (/\s/.test(code[index])) { + index++; + continue; + } + if (code.startsWith('/*', index)) { + const commentEnd = code.indexOf('*/', index + 2); + if (commentEnd === -1) return true; + index = commentEnd + 2; + continue; + } + if (code.startsWith('//', index)) { + const lineBreak = code.slice(index + 2).search(/[\r\n]/); + if (lineBreak === -1) return true; + index += lineBreak + 3; + continue; + } + return code[index] === '.' || code[index] === '('; + } + return true; +} + /** * Wraps frontmatter code in an async function so that top-level `return` * statements are valid syntax for the bundler's parser. Static import @@ -16,6 +40,7 @@ const IMPORT_STMT_RE = /^\s*import\b[\s\S]*?(?:from\s+['"][^'"]*['"]|['"][^'"]*[ export function wrapFrontmatter(code: string): string { const imports: string[] = []; const body = code.replace(IMPORT_STMT_RE, (match) => { + if (isImportExpression(match)) return match; imports.push(match.trim()); return ''; }); diff --git a/packages/integrations/cloudflare/test/units/wrap-frontmatter.test.ts b/packages/integrations/cloudflare/test/units/wrap-frontmatter.test.ts index cc31f4e77b18..3a02105e722b 100644 --- a/packages/integrations/cloudflare/test/units/wrap-frontmatter.test.ts +++ b/packages/integrations/cloudflare/test/units/wrap-frontmatter.test.ts @@ -88,4 +88,63 @@ describe('wrapFrontmatter', () => { const result = wrapFrontmatter(input); assert.ok(result.startsWith('import type { Foo } from "pkg";')); }); + + it('does not hoist import.meta at start of line with string literals (#18144)', () => { + const input = [ + 'import { something } from "astro";', + 'const assetBase =', + ' import.meta.env.BASE_URL.replace(/\\/$/, "") + "/assets/dir";', + 'const label = "repro";', + ].join('\n'); + const result = wrapFrontmatter(input); + const funcStart = result.indexOf('async function __astro__()'); + const metaPos = result.indexOf('import.meta.env'); + assert.ok(metaPos > funcStart, 'import.meta.env should be inside the function body'); + assert.ok( + result.includes('const assetBase =\n import.meta.env'), + 'assignment and import.meta expression should stay together', + ); + }); + + it('does not hoist dynamic import() expressions (#18144)', () => { + const input = [ + 'import { something } from "astro";', + 'const mod = await', + ' import("./dynamic-module");', + 'const label = "repro";', + ].join('\n'); + const result = wrapFrontmatter(input); + const funcStart = result.indexOf('async function __astro__()'); + const importCallPos = result.indexOf('import("./dynamic-module")'); + assert.ok(importCallPos > funcStart, 'dynamic import() should be inside the function body'); + assert.ok(result.includes('const label = "repro"'), 'subsequent code should not be swallowed'); + }); + + it('does not hoist import.meta expressions separated by comments (#18144)', () => { + for (const expression of [ + 'import /* comment */.meta.url.replace("file:", "");', + 'import // comment\n .meta.url.replace("file:", "");', + ]) { + const input = ['const url =', ` ${expression}`, 'const label = "repro";'].join('\n'); + const result = wrapFrontmatter(input); + assert.ok( + result.includes(`const url =\n ${expression}`), + 'import.meta expression should stay with its assignment', + ); + } + }); + + it('does not hoist dynamic import() expressions separated by comments (#18144)', () => { + for (const expression of [ + 'import /* comment */ ("./dynamic-module");', + 'import // comment\n ("./dynamic-module");', + ]) { + const input = ['const mod = await', ` ${expression}`, 'const label = "repro";'].join('\n'); + const result = wrapFrontmatter(input); + assert.ok( + result.includes(`const mod = await\n ${expression}`), + 'dynamic import() expression should stay with its assignment', + ); + } + }); });