mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-10 22:20:14 +00:00
fix(cli): honor direct entries in keyframe shots (#2217)
* fix(cli): honor direct entries in keyframe shots * fix(core): rebase entry-authored asset paths for direct-entry bundling
This commit is contained in:
@@ -1,12 +1,31 @@
|
|||||||
|
import { mkdtempSync, mkdirSync, writeFileSync } from "node:fs";
|
||||||
|
import { tmpdir } from "node:os";
|
||||||
|
import { join } from "node:path";
|
||||||
import { beforeAll, describe, expect, it } from "vitest";
|
import { beforeAll, describe, expect, it } from "vitest";
|
||||||
import { ensureDOMParser } from "../utils/dom.js";
|
import { ensureDOMParser } from "../utils/dom.js";
|
||||||
import { collectShotSelectors, surfaceComposition } from "./keyframes.js";
|
import { collectShotSelectors, resolveScope, surfaceComposition } from "./keyframes.js";
|
||||||
|
|
||||||
beforeAll(() => ensureDOMParser());
|
beforeAll(() => ensureDOMParser());
|
||||||
|
|
||||||
const wrap = (script: string) =>
|
const wrap = (script: string) =>
|
||||||
`<!doctype html><html><body><div id="root" data-composition-id="main" data-duration="4"><div id="dot" class="clip"></div></div><script>${script}</script></body></html>`;
|
`<!doctype html><html><body><div id="root" data-composition-id="main" data-duration="4"><div id="dot" class="clip"></div></div><script>${script}</script></body></html>`;
|
||||||
|
|
||||||
|
describe("keyframes direct composition scope", () => {
|
||||||
|
it("keeps the project root and passes the nested HTML entry to --shot", () => {
|
||||||
|
const projectDir = mkdtempSync(join(tmpdir(), "hf-keyframes-target-"));
|
||||||
|
const compositionsDir = join(projectDir, "compositions");
|
||||||
|
mkdirSync(compositionsDir);
|
||||||
|
writeFileSync(join(projectDir, "index.html"), wrap(""));
|
||||||
|
const scenePath = join(compositionsDir, "scene.html");
|
||||||
|
writeFileSync(scenePath, wrap(""));
|
||||||
|
|
||||||
|
const scope = resolveScope({ target: scenePath });
|
||||||
|
|
||||||
|
expect(scope.projectDir).toBe(projectDir);
|
||||||
|
expect(scope.entryFile).toBe("compositions/scene.html");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("keyframes multi-stroke traces", () => {
|
describe("keyframes multi-stroke traces", () => {
|
||||||
it("composites ≥2 position strokes on one element into a single trace", () => {
|
it("composites ≥2 position strokes on one element into a single trace", () => {
|
||||||
const html = wrap(`
|
const html = wrap(`
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { defineCommand } from "citty";
|
import { defineCommand } from "citty";
|
||||||
import { existsSync, readFileSync, statSync } from "node:fs";
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
||||||
import { resolve, dirname, basename } from "node:path";
|
import { resolve, dirname, basename, join, relative, sep } from "node:path";
|
||||||
import { parseGsapScript, type GsapAnimation } from "@hyperframes/core/gsap-parser";
|
import { parseGsapScript, type GsapAnimation } from "@hyperframes/core/gsap-parser";
|
||||||
import type { Example } from "./_examples.js";
|
import type { Example } from "./_examples.js";
|
||||||
import { c } from "../ui/colors.js";
|
import { c } from "../ui/colors.js";
|
||||||
@@ -710,6 +710,7 @@ async function runOnionShot(
|
|||||||
comps: SurfacedComposition[],
|
comps: SurfacedComposition[],
|
||||||
allComps: SurfacedComposition[],
|
allComps: SurfacedComposition[],
|
||||||
projectDir: string | undefined,
|
projectDir: string | undefined,
|
||||||
|
entryFile: string | undefined,
|
||||||
args: ShotArgs & { selector?: string },
|
args: ShotArgs & { selector?: string },
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const { captureMotionPathShot } = await import("./motionShot.js");
|
const { captureMotionPathShot } = await import("./motionShot.js");
|
||||||
@@ -722,32 +723,34 @@ async function runOnionShot(
|
|||||||
console.log(c.dim(guardError));
|
console.log(c.dim(guardError));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const saved = await captureMotionPathShot(
|
const saved = await captureMotionPathShot(projectDir!, requests, resolve(args.shot!), {
|
||||||
projectDir!,
|
...onionShotOptions(args),
|
||||||
requests,
|
entryFile,
|
||||||
resolve(args.shot!),
|
});
|
||||||
onionShotOptions(args),
|
|
||||||
);
|
|
||||||
printOnionShotSaved(saved, requests.length);
|
printOnionShotSaved(saved, requests.length);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve the command target (a project dir or a single .html) into surfaced
|
// Resolve the command target (a project dir or a single .html) into surfaced
|
||||||
// compositions, applying the optional --selector filter.
|
// compositions, applying the optional --selector filter.
|
||||||
function resolveScope(args: { target?: string; selector?: string }): {
|
export function resolveScope(args: { target?: string; selector?: string }): {
|
||||||
comps: SurfacedComposition[];
|
comps: SurfacedComposition[];
|
||||||
allComps: SurfacedComposition[];
|
allComps: SurfacedComposition[];
|
||||||
projectName: string;
|
projectName: string;
|
||||||
projectDir: string | undefined;
|
projectDir: string | undefined;
|
||||||
|
entryFile: string | undefined;
|
||||||
} {
|
} {
|
||||||
const raw = args.target?.trim();
|
const raw = args.target?.trim();
|
||||||
let comps: SurfacedComposition[];
|
let comps: SurfacedComposition[];
|
||||||
let projectName: string;
|
let projectName: string;
|
||||||
let projectDir: string | undefined;
|
let projectDir: string | undefined;
|
||||||
|
let entryFile: string | undefined;
|
||||||
if (raw && raw.endsWith(".html") && existsSync(raw) && statSync(raw).isFile()) {
|
if (raw && raw.endsWith(".html") && existsSync(raw) && statSync(raw).isFile()) {
|
||||||
comps = [surfaceComposition(readFileSync(raw, "utf-8"), basename(raw), raw)];
|
const entryPath = resolve(raw);
|
||||||
projectName = basename(raw);
|
comps = [surfaceComposition(readFileSync(entryPath, "utf-8"), basename(entryPath), entryPath)];
|
||||||
projectDir = dirname(raw);
|
projectName = basename(entryPath);
|
||||||
|
projectDir = findProjectRoot(entryPath);
|
||||||
|
entryFile = relative(projectDir, entryPath).split(sep).join("/");
|
||||||
} else {
|
} else {
|
||||||
const project = resolveProject(raw);
|
const project = resolveProject(raw);
|
||||||
comps = collectCompositions(project.indexPath);
|
comps = collectCompositions(project.indexPath);
|
||||||
@@ -777,7 +780,19 @@ function resolveScope(args: { target?: string; selector?: string }): {
|
|||||||
cmp.anime.length > 0,
|
cmp.anime.length > 0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return { comps, allComps, projectName, projectDir };
|
return { comps, allComps, projectName, projectDir, entryFile };
|
||||||
|
}
|
||||||
|
|
||||||
|
function findProjectRoot(entryPath: string): string {
|
||||||
|
const entryDir = dirname(entryPath);
|
||||||
|
let candidate = entryDir;
|
||||||
|
for (;;) {
|
||||||
|
if (existsSync(join(candidate, "index.html"))) return candidate;
|
||||||
|
if (existsSync(join(candidate, ".git"))) return entryDir;
|
||||||
|
const parent = dirname(candidate);
|
||||||
|
if (parent === candidate) return entryDir;
|
||||||
|
candidate = parent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isEmptyComposition(cmp: SurfacedComposition): boolean {
|
function isEmptyComposition(cmp: SurfacedComposition): boolean {
|
||||||
@@ -917,12 +932,12 @@ function createKeyframesCommand(options: Partial<KeyframesCommandOptions> = {})
|
|||||||
);
|
);
|
||||||
console.log();
|
console.log();
|
||||||
}
|
}
|
||||||
const { comps: rawComps, allComps, projectName, projectDir } = resolveScope(args);
|
const { comps: rawComps, allComps, projectName, projectDir, entryFile } = resolveScope(args);
|
||||||
const comps = filterCompositionsByRuntime(rawComps, runtime);
|
const comps = filterCompositionsByRuntime(rawComps, runtime);
|
||||||
|
|
||||||
// --shot: 3D onion-skin self-verify screenshot. Returns true when the command
|
// --shot: 3D onion-skin self-verify screenshot. Returns true when the command
|
||||||
// should stop (guard failure) so run() stays small.
|
// should stop (guard failure) so run() stays small.
|
||||||
if (args.shot && (await runOnionShot(comps, allComps, projectDir, args))) return;
|
if (args.shot && (await runOnionShot(comps, allComps, projectDir, entryFile, args))) return;
|
||||||
|
|
||||||
if (args.json) {
|
if (args.json) {
|
||||||
console.log(
|
console.log(
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ interface ScopeResolution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ShotOptions {
|
export interface ShotOptions {
|
||||||
|
/** Project-relative HTML entry to render. Defaults to `index.html`. */
|
||||||
|
entryFile?: string;
|
||||||
/** Equal-time samples across the (windowed) timeline. Default 9. */
|
/** Equal-time samples across the (windowed) timeline. Default 9. */
|
||||||
samples?: number;
|
samples?: number;
|
||||||
/** "path" = ghosts at real positions + path; "strip" = filmstrip by time. */
|
/** "path" = ghosts at real positions + path; "strip" = filmstrip by time. */
|
||||||
@@ -619,7 +621,7 @@ export async function captureMotionPathShot(
|
|||||||
const { serveStaticProjectHtml } = await import("../utils/staticProjectServer.js");
|
const { serveStaticProjectHtml } = await import("../utils/staticProjectServer.js");
|
||||||
const { bundleToSingleHtml } = await import("@hyperframes/core/compiler");
|
const { bundleToSingleHtml } = await import("@hyperframes/core/compiler");
|
||||||
|
|
||||||
const html = await bundleToSingleHtml(projectDir);
|
const html = await bundleToSingleHtml(projectDir, { entryFile: opts.entryFile });
|
||||||
const server = await serveStaticProjectHtml(
|
const server = await serveStaticProjectHtml(
|
||||||
projectDir,
|
projectDir,
|
||||||
html,
|
html,
|
||||||
|
|||||||
@@ -52,6 +52,49 @@ function tryCreateSymlink(target: string, path: string, type: "dir" | "file"): b
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("bundleToSingleHtml", () => {
|
describe("bundleToSingleHtml", () => {
|
||||||
|
it("bundles a direct composition entry with paths relative to its file", async () => {
|
||||||
|
const dir = makeTempProject({
|
||||||
|
"index.html": "<html><body>wrong entry</body></html>",
|
||||||
|
"compositions/scene.html": `<!doctype html><html><head><link rel="stylesheet" href="scene.css"></head><body>
|
||||||
|
<div data-composition-id="scene" data-width="320" data-height="180">direct scene</div>
|
||||||
|
</body></html>`,
|
||||||
|
"compositions/scene.css": ".direct-scene { color: rgb(1, 2, 3); }",
|
||||||
|
});
|
||||||
|
|
||||||
|
const bundled = await bundleToSingleHtml(dir, { entryFile: "compositions/scene.html" });
|
||||||
|
|
||||||
|
expect(bundled).toContain("direct scene");
|
||||||
|
expect(bundled).not.toContain("wrong entry");
|
||||||
|
expect(bundled).toContain(".direct-scene { color: rgb(1, 2, 3); }");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rebases direct-entry authored asset paths before inlining", async () => {
|
||||||
|
const spriteSvg = '<svg xmlns="http://www.w3.org/2000/svg"><circle r="4"/></svg>';
|
||||||
|
const bgSvg = '<svg xmlns="http://www.w3.org/2000/svg"><rect width="8" height="8"/></svg>';
|
||||||
|
const dir = makeTempProject({
|
||||||
|
"index.html": "<html><body>wrong entry</body></html>",
|
||||||
|
"compositions/scene.html": `<!doctype html><html><head>
|
||||||
|
<style>.scene { background-image: url("./bg.svg"); }</style>
|
||||||
|
</head><body>
|
||||||
|
<div class="scene" data-composition-id="scene" data-width="320" data-height="180" data-start="0" data-duration="1">
|
||||||
|
<img src="./sprite.svg">
|
||||||
|
</div>
|
||||||
|
<script>window.__timelines = window.__timelines || {}; window.__timelines.scene = {}</script>
|
||||||
|
</body></html>`,
|
||||||
|
"compositions/sprite.svg": spriteSvg,
|
||||||
|
"compositions/bg.svg": bgSvg,
|
||||||
|
});
|
||||||
|
|
||||||
|
const bundled = await bundleToSingleHtml(dir, { entryFile: "compositions/scene.html" });
|
||||||
|
const spriteDataUrl = `data:image/svg+xml;base64,${Buffer.from(spriteSvg).toString("base64")}`;
|
||||||
|
const bgDataUrl = `data:image/svg+xml;base64,${Buffer.from(bgSvg).toString("base64")}`;
|
||||||
|
|
||||||
|
expect(bundled).toContain(`src="${spriteDataUrl}"`);
|
||||||
|
expect(bundled).toContain(`url("${bgDataUrl}")`);
|
||||||
|
expect(bundled).not.toContain("./sprite.svg");
|
||||||
|
expect(bundled).not.toContain("./bg.svg");
|
||||||
|
});
|
||||||
|
|
||||||
it("does not merge author scripts into the runtime bootstrap placeholder", async () => {
|
it("does not merge author scripts into the runtime bootstrap placeholder", async () => {
|
||||||
const dir = makeTempProject({
|
const dir = makeTempProject({
|
||||||
"index.html": `<!doctype html>
|
"index.html": `<!doctype html>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ export { FLATTENED_INNER_ROOT_STRIP_ATTRS } from "../runtime/flattenedRoot";
|
|||||||
import { parseHostVariableValues } from "../runtime/getVariables";
|
import { parseHostVariableValues } from "../runtime/getVariables";
|
||||||
import { cssVariableName } from "../tokenSlug";
|
import { cssVariableName } from "../tokenSlug";
|
||||||
import { readFileSync, existsSync } from "fs";
|
import { readFileSync, existsSync } from "fs";
|
||||||
import { join, resolve, relative, dirname, isAbsolute, sep } from "path";
|
import { resolve, relative, dirname, isAbsolute, sep } from "path";
|
||||||
import { CSS_URL_RE, isNonRelativeUrl } from "./assetPaths.js";
|
import { CSS_URL_RE, isNonRelativeUrl } from "./assetPaths.js";
|
||||||
import { transformSync } from "esbuild";
|
import { transformSync } from "esbuild";
|
||||||
import { compileHtml, type MediaDurationProber } from "./htmlCompiler";
|
import { compileHtml, type MediaDurationProber } from "./htmlCompiler";
|
||||||
@@ -133,6 +133,89 @@ function rebaseCssUrls(css: string, cssFileDir: string, projectDir: string): str
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function rebaseRelativePath(urlValue: string, fromDir: string, toDir: string): string {
|
||||||
|
const { basePath, suffix } = splitUrlSuffix(urlValue.trim());
|
||||||
|
if (!basePath) return urlValue;
|
||||||
|
const absolutePath = resolve(fromDir, basePath);
|
||||||
|
const rebased = relative(resolve(toDir), absolutePath).split(sep).join("/");
|
||||||
|
return appendSuffixToUrl(rebased, suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
function rebaseSrcsetPaths(srcsetValue: string, fromDir: string, toDir: string): string {
|
||||||
|
if (!srcsetValue) return srcsetValue;
|
||||||
|
return srcsetValue
|
||||||
|
.split(",")
|
||||||
|
.map((rawCandidate) => {
|
||||||
|
const candidate = rawCandidate.trim();
|
||||||
|
if (!candidate) return candidate;
|
||||||
|
const parts = candidate.split(/\s+/);
|
||||||
|
const first = parts[0] ?? "";
|
||||||
|
if (parts.length === 0 || !isRelativeUrl(first)) return candidate;
|
||||||
|
parts[0] = rebaseRelativePath(first, fromDir, toDir);
|
||||||
|
return parts.join(" ");
|
||||||
|
})
|
||||||
|
.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
function rebaseColorGradingLutPath(value: string, fromDir: string, toDir: string): string {
|
||||||
|
if (!value.trim().startsWith("{")) return value;
|
||||||
|
let parsed: unknown;
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(value);
|
||||||
|
} catch {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return value;
|
||||||
|
|
||||||
|
const lut = Reflect.get(parsed, "lut");
|
||||||
|
if (typeof lut === "string") {
|
||||||
|
if (!isRelativeUrl(lut)) return value;
|
||||||
|
Reflect.set(parsed, "lut", rebaseRelativePath(lut, fromDir, toDir));
|
||||||
|
return JSON.stringify(parsed);
|
||||||
|
}
|
||||||
|
if (typeof lut !== "object" || lut === null || Array.isArray(lut)) return value;
|
||||||
|
const lutSrc = Reflect.get(lut, "src");
|
||||||
|
if (typeof lutSrc !== "string" || !isRelativeUrl(lutSrc)) return value;
|
||||||
|
Reflect.set(lut, "src", rebaseRelativePath(lutSrc, fromDir, toDir));
|
||||||
|
return JSON.stringify(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
function rebaseEntryAuthoredAssetPaths(
|
||||||
|
document: Document,
|
||||||
|
sourceDir: string,
|
||||||
|
projectDir: string,
|
||||||
|
): void {
|
||||||
|
for (const styleEl of [...document.querySelectorAll("style")]) {
|
||||||
|
styleEl.textContent = rebaseCssUrls(styleEl.textContent || "", sourceDir, projectDir);
|
||||||
|
}
|
||||||
|
for (const el of [...document.querySelectorAll("[style]")]) {
|
||||||
|
const styleAttr = el.getAttribute("style");
|
||||||
|
if (styleAttr) el.setAttribute("style", rebaseCssUrls(styleAttr, sourceDir, projectDir));
|
||||||
|
}
|
||||||
|
for (const el of [...document.querySelectorAll("[src], [href], [poster], [xlink\\:href]")]) {
|
||||||
|
if (el.tagName === "LINK" && (el.getAttribute("rel") || "").toLowerCase() === "stylesheet")
|
||||||
|
continue;
|
||||||
|
if (el.tagName === "SCRIPT" && el.hasAttribute("src")) continue;
|
||||||
|
for (const attr of ["src", "href", "poster", "xlink:href"] as const) {
|
||||||
|
const value = el.getAttribute(attr);
|
||||||
|
if (!value || !isRelativeUrl(value)) continue;
|
||||||
|
el.setAttribute(attr, rebaseRelativePath(value, sourceDir, projectDir));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const el of [...document.querySelectorAll("[srcset]")]) {
|
||||||
|
const srcset = el.getAttribute("srcset");
|
||||||
|
if (srcset) el.setAttribute("srcset", rebaseSrcsetPaths(srcset, sourceDir, projectDir));
|
||||||
|
}
|
||||||
|
for (const el of [...document.querySelectorAll(`[${HF_COLOR_GRADING_ATTR}]`)]) {
|
||||||
|
const value = el.getAttribute(HF_COLOR_GRADING_ATTR);
|
||||||
|
if (value)
|
||||||
|
el.setAttribute(
|
||||||
|
HF_COLOR_GRADING_ATTR,
|
||||||
|
rebaseColorGradingLutPath(value, sourceDir, projectDir),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function inlineCssFile(
|
function inlineCssFile(
|
||||||
css: string,
|
css: string,
|
||||||
cssFileDir: string,
|
cssFileDir: string,
|
||||||
@@ -581,6 +664,8 @@ function stripJsCommentsParserSafe(source: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface BundleOptions {
|
export interface BundleOptions {
|
||||||
|
/** Project-relative HTML entry to bundle. Defaults to `index.html`. */
|
||||||
|
entryFile?: string;
|
||||||
/** Optional media duration prober (e.g., ffprobe). If omitted, media durations are not resolved. */
|
/** Optional media duration prober (e.g., ffprobe). If omitted, media durations are not resolved. */
|
||||||
probeMediaDuration?: MediaDurationProber;
|
probeMediaDuration?: MediaDurationProber;
|
||||||
/**
|
/**
|
||||||
@@ -692,11 +777,19 @@ export async function bundleToSingleHtml(
|
|||||||
projectDir: string,
|
projectDir: string,
|
||||||
options?: BundleOptions,
|
options?: BundleOptions,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const indexPath = join(projectDir, "index.html");
|
const entryFile = options?.entryFile ?? "index.html";
|
||||||
if (!existsSync(indexPath)) throw new Error("index.html not found in project directory");
|
const indexPath = resolveWithinProject(projectDir, entryFile);
|
||||||
|
if (!indexPath || !existsSync(indexPath)) {
|
||||||
|
throw new Error(`${entryFile} not found in project directory`);
|
||||||
|
}
|
||||||
|
const sourceDir = dirname(indexPath);
|
||||||
|
const resolveEntryPath = (relativePath: string): string | null => {
|
||||||
|
const resolved = resolve(sourceDir, relativePath);
|
||||||
|
return isSafePath(projectDir, resolved) ? resolved : null;
|
||||||
|
};
|
||||||
|
|
||||||
const rawHtml = readFileSync(indexPath, "utf-8");
|
const rawHtml = readFileSync(indexPath, "utf-8");
|
||||||
const compiled = await compileHtml(rawHtml, projectDir, options?.probeMediaDuration);
|
const compiled = await compileHtml(rawHtml, sourceDir, options?.probeMediaDuration);
|
||||||
|
|
||||||
const staticGuard = await validateHyperframeHtmlContract(compiled);
|
const staticGuard = await validateHyperframeHtmlContract(compiled);
|
||||||
if (!staticGuard.isValid) {
|
if (!staticGuard.isValid) {
|
||||||
@@ -708,13 +801,17 @@ export async function bundleToSingleHtml(
|
|||||||
const withInterceptor = injectInterceptor(compiled, options?.runtime ?? "inline");
|
const withInterceptor = injectInterceptor(compiled, options?.runtime ?? "inline");
|
||||||
const document = parseHTMLContent(withInterceptor);
|
const document = parseHTMLContent(withInterceptor);
|
||||||
|
|
||||||
|
if (resolve(sourceDir) !== resolve(projectDir)) {
|
||||||
|
rebaseEntryAuthoredAssetPaths(document, sourceDir, projectDir);
|
||||||
|
}
|
||||||
|
|
||||||
// Inline local CSS
|
// Inline local CSS
|
||||||
const localCssChunks: string[] = [];
|
const localCssChunks: string[] = [];
|
||||||
let cssAnchorPlaced = false;
|
let cssAnchorPlaced = false;
|
||||||
for (const el of [...document.querySelectorAll('link[rel="stylesheet"]')]) {
|
for (const el of [...document.querySelectorAll('link[rel="stylesheet"]')]) {
|
||||||
const href = el.getAttribute("href");
|
const href = el.getAttribute("href");
|
||||||
if (!href || !isRelativeUrl(href)) continue;
|
if (!href || !isRelativeUrl(href)) continue;
|
||||||
const cssPath = resolveWithinProject(projectDir, href);
|
const cssPath = resolveEntryPath(href);
|
||||||
if (!cssPath) continue;
|
if (!cssPath) continue;
|
||||||
const css = safeReadFile(cssPath);
|
const css = safeReadFile(cssPath);
|
||||||
if (css == null) continue;
|
if (css == null) continue;
|
||||||
@@ -746,7 +843,7 @@ export async function bundleToSingleHtml(
|
|||||||
for (const el of [...document.querySelectorAll("script[src]")]) {
|
for (const el of [...document.querySelectorAll("script[src]")]) {
|
||||||
const src = el.getAttribute("src");
|
const src = el.getAttribute("src");
|
||||||
if (!src || !isRelativeUrl(src)) continue;
|
if (!src || !isRelativeUrl(src)) continue;
|
||||||
const jsPath = resolveWithinProject(projectDir, src);
|
const jsPath = resolveEntryPath(src);
|
||||||
const js = jsPath ? safeReadFile(jsPath) : null;
|
const js = jsPath ? safeReadFile(jsPath) : null;
|
||||||
if (js == null) continue;
|
if (js == null) continue;
|
||||||
localJsChunks.push(js);
|
localJsChunks.push(js);
|
||||||
@@ -781,7 +878,7 @@ export async function bundleToSingleHtml(
|
|||||||
const subCompResult = inlineSubCompositions(document, subCompositionHosts, {
|
const subCompResult = inlineSubCompositions(document, subCompositionHosts, {
|
||||||
resolveHtml: (srcPath: string) => {
|
resolveHtml: (srcPath: string) => {
|
||||||
if (!isRelativeUrl(srcPath)) return null;
|
if (!isRelativeUrl(srcPath)) return null;
|
||||||
const compPath = resolveWithinProject(projectDir, srcPath);
|
const compPath = resolveEntryPath(srcPath);
|
||||||
return compPath ? safeReadFile(compPath) : null;
|
return compPath ? safeReadFile(compPath) : null;
|
||||||
},
|
},
|
||||||
parseHtml: parseHTMLContent,
|
parseHtml: parseHTMLContent,
|
||||||
@@ -814,7 +911,7 @@ export async function bundleToSingleHtml(
|
|||||||
if (seenCompScriptSrcs.has(extSrc)) continue;
|
if (seenCompScriptSrcs.has(extSrc)) continue;
|
||||||
seenCompScriptSrcs.add(extSrc);
|
seenCompScriptSrcs.add(extSrc);
|
||||||
if (isRelativeUrl(extSrc)) {
|
if (isRelativeUrl(extSrc)) {
|
||||||
const jsPath = resolveWithinProject(projectDir, extSrc);
|
const jsPath = resolveEntryPath(extSrc);
|
||||||
const js = jsPath ? safeReadFile(jsPath) : null;
|
const js = jsPath ? safeReadFile(jsPath) : null;
|
||||||
if (js != null) {
|
if (js != null) {
|
||||||
compScriptChunks.push(js);
|
compScriptChunks.push(js);
|
||||||
|
|||||||
Reference in New Issue
Block a user