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

Fixes dev dependency scan failing when a frontmatter line starts with `import.meta` or `import(`
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 '';
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
);
}
});
});
Loading