@hyperframes/core 0.8.208 → 0.8.209

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,11 @@
1
1
  import * as fs from "node:fs";
2
- import { randomBytes } from "node:crypto";
2
+ import { createHash, randomBytes } from "node:crypto";
3
3
  import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
4
4
  import { realpath } from "./safePath.js";
5
+ import { renameWithRetry } from "./renameWithRetry.js";
5
6
  // Codes a volume without hard links (FAT, exFAT, some network shares) answers link() with.
6
7
  // EISDIR: libuv maps Windows ERROR_INVALID_FUNCTION (FAT/exFAT refusing a link) to it; nodejs/node#65817.
7
8
  const NO_HARD_LINKS = new Set(["EPERM", "ENOTSUP", "EOPNOTSUPP", "EXDEV", "EISDIR"]);
8
- // Windows refuses a rename while another process briefly holds the target open.
9
- const BUSY_RENAME = new Set(["EPERM", "EBUSY", "EACCES"]);
10
- const RENAME_RETRY_DELAYS_MS = [10, 20, 30, 40];
11
9
  const MAX_LINK_HOPS = 40;
12
10
  const TEMP_NAME_TRIES = 3;
13
11
  const TEMP_SUFFIX = /\.hf[0-9a-f]{6}\.tmp$/;
@@ -18,10 +16,25 @@ export function atomicTempPath(filePath) {
18
16
  export function isAtomicTempPath(path) {
19
17
  return TEMP_SUFFIX.test(path);
20
18
  }
19
+ /** With HF_WRITE_TRACE naming a file, one JSON line per replaced .html: its hash, size and caller. */
20
+ function traceWrite(filePath, content) {
21
+ const out = process.env.HF_WRITE_TRACE;
22
+ if (!out || !filePath.endsWith(".html"))
23
+ return;
24
+ const text = typeof content === "string" ? content : Buffer.from(content).toString("utf8");
25
+ const hash = createHash("sha256").update(text).digest("hex").slice(0, 8);
26
+ const stack = (new Error().stack ?? "")
27
+ .split("\n")
28
+ .slice(2, 7)
29
+ .map((line) => line.trim());
30
+ const file = basename(filePath);
31
+ fs.appendFileSync(out, `${JSON.stringify({ t: Date.now(), kind: "write", file, hash, size: text.length, stack })}\n`);
32
+ }
21
33
  /** Replace a file only after the complete sibling temp file is written. No mode: the default one. */
22
34
  export function replaceFileAtomically(filePath, content, mode, operations = fs) {
35
+ traceWrite(filePath, content);
23
36
  // Node fs.rename uses libuv uv_fs_rename; win32 calls MoveFileExW with MOVEFILE_REPLACE_EXISTING.
24
- publishSibling(filePath, content, mode, operations, (tempPath) => renameWithRetry(operations, tempPath, filePath));
37
+ publishSibling(filePath, content, mode, operations, (tempPath) => renameWithRetry(tempPath, filePath, { rename: operations.renameSync }));
25
38
  }
26
39
  /** Create a file that must not exist yet (EEXIST otherwise); readers never see it empty or partial. */
27
40
  export function createFileAtomically(filePath, content, operations = fs) {
@@ -133,20 +146,6 @@ function writeTempSibling(filePath, content, mode, operations) {
133
146
  }
134
147
  }
135
148
  }
136
- function renameWithRetry(operations, from, to) {
137
- for (let attempt = 0;; attempt++) {
138
- try {
139
- operations.renameSync(from, to);
140
- return;
141
- }
142
- catch (error) {
143
- const delay = RENAME_RETRY_DELAYS_MS[attempt];
144
- if (delay === undefined || !BUSY_RENAME.has(errorCode(error)))
145
- throw error;
146
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, delay);
147
- }
148
- }
149
- }
150
149
  function errorCode(error) {
151
150
  return String(error?.code);
152
151
  }
@@ -36,4 +36,6 @@ export declare function wrapInlineScriptWithErrorBoundary(source: string, errorL
36
36
  * table goes through `jsonScriptLiteral` — see there for why.
37
37
  */
38
38
  export declare function buildVariablesByCompScript(variablesByComp: Record<string, Record<string, unknown>>): string | null;
39
+ /** Writes the table in <head>, so it exists before the composition scripts, which wait for web fonts. */
40
+ export declare function emitVariablesByCompScript(document: Document, variablesByComp: Record<string, Record<string, unknown>>): void;
39
41
  export {};
@@ -654,3 +654,12 @@ export function buildVariablesByCompScript(variablesByComp) {
654
654
  const json = jsonScriptLiteral(variablesByComp);
655
655
  return `window.__hfVariablesByComp = Object.assign({}, window.__hfVariablesByComp || {}, ${json});`;
656
656
  }
657
+ /** Writes the table in <head>, so it exists before the composition scripts, which wait for web fonts. */
658
+ export function emitVariablesByCompScript(document, variablesByComp) {
659
+ const text = buildVariablesByCompScript(variablesByComp);
660
+ if (!text || !document.head)
661
+ return;
662
+ const script = document.createElement("script");
663
+ script.textContent = text;
664
+ document.head.appendChild(script);
665
+ }
@@ -21,7 +21,7 @@ import { rewriteSrcsetUrls } from "./srcset.js";
21
21
  import { compileHtml } from "./htmlCompiler.js";
22
22
  import { RUNTIME_BOOTSTRAP_ATTR, escapeInlineScriptSource, insertRuntimeTag, parseHTMLContent, stripEmbeddedRuntimeScripts, } from "./htmlDocument.js";
23
23
  // rewriteSubCompPaths functions are used by inlineSubCompositions (shared module)
24
- import { buildVariablesByCompScript, dedupeFontFaceRules, hoistFontFaceRules, scopeCssToComposition, wrapInlineScriptWithErrorBoundary, scopedModulePrelude, wrapScopedCompositionScript, } from "./compositionScoping.js";
24
+ import { emitVariablesByCompScript, dedupeFontFaceRules, hoistFontFaceRules, scopeCssToComposition, wrapInlineScriptWithErrorBoundary, scopedModulePrelude, wrapScopedCompositionScript, } from "./compositionScoping.js";
25
25
  import { validateHyperframeHtmlContract } from "./staticGuard.js";
26
26
  import { getHyperframeRuntimeScript } from "../generated/runtime-inline.js";
27
27
  import { readDeclaredDefaults } from "../runtime/getVariables.js";
@@ -1158,13 +1158,7 @@ async function bundleProject(projectDir, options) {
1158
1158
  pushRun(styleRuns, undefined, css);
1159
1159
  for (const chunk of compScriptChunks)
1160
1160
  pushRun(scriptRuns, undefined, chunk);
1161
- const variablesByCompScript = buildVariablesByCompScript(compVariablesByComp);
1162
- if (variablesByCompScript) {
1163
- if (scriptRuns[0] && !scriptRuns[0].scene)
1164
- scriptRuns[0].chunks.unshift(variablesByCompScript);
1165
- else
1166
- scriptRuns.unshift({ chunks: [variablesByCompScript] });
1167
- }
1161
+ emitVariablesByCompScript(document, compVariablesByComp);
1168
1162
  for (const { scene, chunks } of styleRuns) {
1169
1163
  const join = scene ? joinCssHoistingImports : (css) => css.join("\n\n");
1170
1164
  for (const style of styleElementsFor(document, chunks, join)) {
@@ -9,6 +9,10 @@ export declare function parseHTMLContent(html: string): Document;
9
9
  export declare function stripEmbeddedRuntimeScripts(html: string): string;
10
10
  export declare function isRuntimeFileUrl(src: string | null | undefined): boolean;
11
11
  export declare function escapeInlineScriptSource(source: string): string;
12
+ export declare function mapStartTagsAndStyles(html: string, map: {
13
+ tag: (tag: string) => string;
14
+ style: (css: string) => string;
15
+ }): string;
12
16
  export declare function findStartTags(html: string, name: string): number[];
13
17
  export declare function hasCompositionOutsideTemplates(html: string): boolean;
14
18
  /** Insert markup before the document's own `</head>`, `</body>` or `</template>`; null if it has none. */
@@ -199,6 +199,28 @@ function findDocumentTag(html, tag) {
199
199
  }
200
200
  return -1;
201
201
  }
202
+ export function mapStartTagsAndStyles(html, map) {
203
+ const lowered = lowerAscii(html);
204
+ let out = "";
205
+ let cursor = 0;
206
+ for (const open of markupStarts(lowered)) {
207
+ if (open < cursor || !/[a-z]/.test(lowered.charAt(open + 1)))
208
+ continue;
209
+ const end = findTagEnd(lowered, open + 1);
210
+ if (end === -1)
211
+ break;
212
+ out += html.slice(cursor, open) + map.tag(html.slice(open, end + 1));
213
+ cursor = end + 1;
214
+ if (!isTagAt(lowered, open, "<style"))
215
+ continue;
216
+ const close = findRawTextClose(lowered, "style", cursor);
217
+ if (close === -1)
218
+ continue;
219
+ out += map.style(html.slice(cursor, close));
220
+ cursor = close;
221
+ }
222
+ return out + html.slice(cursor);
223
+ }
202
224
  export function findStartTags(html, name) {
203
225
  const lowered = lowerAscii(html);
204
226
  const token = `<${lowerAscii(name)}`;
@@ -9,7 +9,7 @@ export { addScenePartsManifest } from "./scenePartsManifest.js";
9
9
  export { AFTER_FONTS_SCRIPT_TYPE, compositionStyle, cssStyleMergeKey, isAlwaysAppliedStyle, deferScriptsUntilFonts, headStyleRuns, inlineScriptRuns, styleElementsFor, type CompositionStyle, type InlineScriptRun, } from "./scriptRuns.js";
10
10
  export { isJavaScriptType } from "./compositionAssembly.js";
11
11
  export { validateHyperframeHtmlContract, type HyperframeStaticFailureReason, type HyperframeStaticGuardResult, } from "./staticGuard.js";
12
- export { buildVariablesByCompScript, dedupeFontFaceRules, scopeCssToComposition, wrapScopedCompositionScript, } from "./compositionScoping.js";
12
+ export { buildVariablesByCompScript, emitVariablesByCompScript, dedupeFontFaceRules, scopeCssToComposition, wrapScopedCompositionScript, } from "./compositionScoping.js";
13
13
  export { ensureExternalLinkTag, inlineSubCompositions, type InlineSubCompositionsOptions, type InlineSubCompositionsResult, } from "./inlineSubCompositions.js";
14
14
  export { extractStandaloneEntryFromIndex, isTemplateEntry } from "./standaloneEntry.js";
15
15
  export { checkSubCompositionUsability, type ParsableDocumentLike, type SubCompositionValidity, type SubCompositionValidityReason, } from "./subCompositionValidity.js";
@@ -17,7 +17,7 @@ export { isJavaScriptType } from "./compositionAssembly.js";
17
17
  // Static guard
18
18
  export { validateHyperframeHtmlContract, } from "./staticGuard.js";
19
19
  // Composition isolation helpers
20
- export { buildVariablesByCompScript, dedupeFontFaceRules, scopeCssToComposition, wrapScopedCompositionScript, } from "./compositionScoping.js";
20
+ export { buildVariablesByCompScript, emitVariablesByCompScript, dedupeFontFaceRules, scopeCssToComposition, wrapScopedCompositionScript, } from "./compositionScoping.js";
21
21
  // Sub-composition inlining (shared between bundler and producer)
22
22
  export { ensureExternalLinkTag, inlineSubCompositions, } from "./inlineSubCompositions.js";
23
23
  export { extractStandaloneEntryFromIndex, isTemplateEntry } from "./standaloneEntry.js";