fix(core): bundling a project no longer blocks the host's thread on esbuild (#5714)

This commit is contained in:
Miguel Ángel
2026-10-16 21:23:28 +00:00
committed by GitHub
parent 41486b9725
commit b1cf6e3c51
2 changed files with 55 additions and 13 deletions
@@ -0,0 +1,39 @@
// @vitest-environment node
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterAll, expect, it, vi } from "vitest";
import { bundleToSingleHtml } from "./htmlBundler";
const syncCalls = vi.hoisted(() => [] as string[]);
vi.mock("esbuild", async (importOriginal) => {
const actual = await importOriginal<typeof import("esbuild")>();
const blocked = (name: string) => () => {
syncCalls.push(name);
throw new Error(`esbuild.${name} blocks the caller's thread`);
};
return { ...actual, transformSync: blocked("transformSync"), buildSync: blocked("buildSync") };
});
const dir = mkdtempSync(join(tmpdir(), "hf-bundle-esbuild-async-"));
afterAll(() => rmSync(dir, { recursive: true, force: true }));
it("strips script comments without a synchronous esbuild call", async () => {
writeFileSync(
join(dir, "index.html"),
`<!doctype html><html><body>
<div data-composition-id="root" data-width="320" data-height="180"></div>
<script src="local.js"></script>
<script>/* authored block note */ window.__timelines = window.__timelines || {}; window.__timelines.root = {};</script>
</body></html>`,
);
writeFileSync(join(dir, "local.js"), "window.__a = 1; // authored line note");
const bundled = await bundleToSingleHtml(dir);
expect(syncCalls).toEqual([]);
expect(bundled).toContain("window.__a = 1");
expect(bundled).not.toContain("authored line note");
expect(bundled).not.toContain("authored block note");
});
+16 -13
View File
@@ -40,7 +40,7 @@ import {
splitUrlSuffix,
} from "@hyperframes/parsers/asset-paths";
import { CSS_URL_RE, isNonRelativeUrl } from "./assetPaths.js";
import { transformSync } from "esbuild";
import { transform } from "esbuild";
import { compileHtml, type MediaDurationProber } from "./htmlCompiler";
import {
RUNTIME_BOOTSTRAP_ATTR,
@@ -733,10 +733,10 @@ function pushRun<T>(runs: PartRun<T>[], scene: string | undefined, chunk: T): vo
else runs.push({ scene, chunks: [chunk] });
}
function coalesceHeadStylesAndBodyScripts(
async function coalesceHeadStylesAndBodyScripts(
document: Document,
provenance?: BundleOptions["scriptProvenance"],
): void {
): Promise<void> {
const allHeadStyles = [...document.querySelectorAll("head style")];
const isScenePart = (el: Element) => el.hasAttribute(SCENE_PART_ATTR);
for (const run of allHeadStyles.length > 1 ? headStyleRuns(allHeadStyles, isScenePart) : []) {
@@ -756,13 +756,13 @@ function coalesceHeadStylesAndBodyScripts(
const mergedJs = joinJsChunks(members.map((el) => el.textContent || ""));
if (mergedJs && !parsesAsScript(mergedJs)) {
for (const el of members)
el.textContent = inlineScriptSource(el.textContent || "", provenance);
el.textContent = await inlineScriptSource(el.textContent || "", provenance);
continue;
}
for (const el of members) el.remove();
if (!mergedJs) continue;
const inlineScript = document.createElement("script");
inlineScript.textContent = inlineScriptSource(mergedJs, provenance);
inlineScript.textContent = await inlineScriptSource(mergedJs, provenance);
if (anchor) anchor.before(inlineScript);
else document.body.appendChild(inlineScript);
}
@@ -825,13 +825,13 @@ export function parsesAsScript(source: string): boolean {
}
}
function stripJsCommentsParserSafe(
async function stripJsCommentsParserSafe(
source: string,
provenance?: BundleOptions["scriptProvenance"],
): string {
): Promise<string> {
if (!source) return source;
try {
const result = transformSync(source, {
const result = await transform(source, {
loader: "js",
minify: false,
legalComments: "none",
@@ -850,8 +850,11 @@ function stripJsCommentsParserSafe(
}
}
function inlineScriptSource(js: string, provenance?: BundleOptions["scriptProvenance"]): string {
return escapeInlineScriptSource(stripJsCommentsParserSafe(js, provenance));
async function inlineScriptSource(
js: string,
provenance?: BundleOptions["scriptProvenance"],
): Promise<string> {
return escapeInlineScriptSource(await stripJsCommentsParserSafe(js, provenance));
}
export interface BundleScriptProvenance {
@@ -1360,7 +1363,7 @@ async function bundleProject(projectDir: string, options?: BundleOptions): Promi
const script = document.createElement("script");
if (scene) script.setAttribute(SCENE_PART_ATTR, scene);
script.textContent = skipsRunMerge
? inlineScriptSource(text, options?.scriptProvenance)
? await inlineScriptSource(text, options?.scriptProvenance)
: text;
document.body.appendChild(script);
}
@@ -1371,11 +1374,11 @@ async function bundleProject(projectDir: string, options?: BundleOptions): Promi
enforceCompositionPixelSizing(document);
autoHealMissingCompositionIds(document);
coalesceHeadStylesAndBodyScripts(document, options?.scriptProvenance);
await coalesceHeadStylesAndBodyScripts(document, options?.scriptProvenance);
for (const el of document.querySelectorAll(`script[${INLINED_FILE_ATTR}]`)) {
const js = el.textContent ?? "";
el.textContent = escapeInlineScriptSource(
isJavaScriptType(el) ? stripJsCommentsParserSafe(js, options?.scriptProvenance) : js,
isJavaScriptType(el) ? await stripJsCommentsParserSafe(js, options?.scriptProvenance) : js,
);
}
deferScriptsUntilFonts(document, (el) => el.hasAttribute(RUNTIME_BOOTSTRAP_ATTR));