Skip to content

Commit abda0a3

Browse files
authored
Validate external Babel browser entrypoint (#6)
Co-authored-by: daggerok <daggerok@users.noreply.github.com>
1 parent 4a32035 commit abda0a3

1 file changed

Lines changed: 43 additions & 13 deletions

File tree

‎scripts/check-index.ts‎

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,61 @@
11
#!/usr/bin/env bun
22

3-
// Parse the browser's inline TypeScript before publishing. The deployed page
4-
// relies on Babel standalone at runtime, so a missing comma or other syntax
5-
// error would otherwise leave the catalog permanently on its loading state.
3+
// Parse the browser's TypeScript before publishing. The deployed page relies
4+
// on Babel standalone at runtime, so a missing comma or other syntax error
5+
// would otherwise leave the catalog permanently on its loading state.
66

77
declare const Bun: {
88
file(path: string): { text(): Promise<string> };
99
Transpiler: new (options: { loader: string }) => { transform(source: string): string | Promise<string> };
1010
};
1111

12-
type Match = RegExpExecArray & { 1: string };
12+
type BrowserScript = { inline: string } | { src: string };
1313

14+
function attributeValue(attributes: string, name: string): string | null {
15+
const match = new RegExp(`\\b${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s>]+))`, 'i').exec(attributes);
16+
return match ? (match[1] ?? match[2] ?? match[3] ?? '') : null;
17+
}
18+
19+
export function extractBrowserScript(html: string): BrowserScript {
20+
const scripts = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
21+
let match: RegExpExecArray | null;
22+
while ((match = scripts.exec(html))) {
23+
const type = attributeValue(match[1], 'type');
24+
if (type?.toLowerCase() !== 'text/babel') continue;
25+
const src = attributeValue(match[1], 'src');
26+
if (src) return { src };
27+
if (match[2].trim()) return { inline: match[2] };
28+
}
29+
throw new Error('index.html: text/babel browser script not found');
30+
}
31+
32+
// Keep the original helper for callers that specifically want an inline
33+
// script, while allowing the page to move its Babel entrypoint into a local
34+
// .tsx file without making this validation stale.
1435
export function extractInlineBrowserScript(html: string): string {
15-
const match = /<script\s+type=["']text\/babel["'][^>]*>([\s\S]*?)<\/script>/i.exec(html) as Match | null;
16-
if (!match || !match[1].trim()) throw new Error('index.html: inline text/babel script not found');
17-
return match[1];
36+
const script = extractBrowserScript(html);
37+
if ('inline' in script) return script.inline;
38+
throw new Error(`index.html: text/babel script is external (${script.src})`);
1839
}
1940

20-
export async function parseInlineBrowserScript(source: string): Promise<string> {
41+
export async function parseBrowserScript(source: string): Promise<string> {
2142
const transformed = await new Bun.Transpiler({ loader: 'tsx' }).transform(source);
22-
if (!transformed || typeof transformed !== 'string') throw new Error('index.html: inline script did not transpile');
43+
if (!transformed || typeof transformed !== 'string') throw new Error('browser script did not transpile');
2344
return transformed;
2445
}
2546

47+
// Backwards-compatible name for the inline-script parser used by the earlier
48+
// single-file layout.
49+
export const parseInlineBrowserScript = parseBrowserScript;
50+
2651
if ((import.meta as { main?: boolean }).main) {
27-
const html = await Bun.file(new URL('../index.html', import.meta.url).pathname).text();
28-
const source = extractInlineBrowserScript(html);
29-
const transformed = await parseInlineBrowserScript(source);
30-
console.log(`index.html inline script parses (${source.length.toLocaleString()} source chars, ${transformed.length.toLocaleString()} transpiled chars)`);
52+
const indexUrl = new URL('../index.html', import.meta.url);
53+
const html = await Bun.file(indexUrl.pathname).text();
54+
const script = extractBrowserScript(html);
55+
const sourcePath = 'src' in script ? new URL(script.src, indexUrl) : indexUrl;
56+
if (sourcePath.protocol !== 'file:') throw new Error(`browser script must be local: ${sourcePath.href}`);
57+
const source = 'inline' in script ? script.inline : await Bun.file(sourcePath.pathname).text();
58+
const transformed = await parseBrowserScript(source);
59+
const label = 'src' in script ? script.src : 'index.html inline script';
60+
console.log(`${label} parses (${source.length.toLocaleString()} source chars, ${transformed.length.toLocaleString()} transpiled chars)`);
3161
}

0 commit comments

Comments
 (0)