@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.
- package/dist/atomicFile.js +18 -19
- package/dist/compiler/compositionScoping.d.ts +2 -0
- package/dist/compiler/compositionScoping.js +9 -0
- package/dist/compiler/htmlBundler.js +2 -8
- package/dist/compiler/htmlDocument.d.ts +4 -0
- package/dist/compiler/htmlDocument.js +22 -0
- package/dist/compiler/index.d.ts +1 -1
- package/dist/compiler/index.js +1 -1
- package/dist/generated/runtime-inline.js +1 -1
- package/dist/hyperframe.runtime.iife.js +64 -64
- package/dist/removeScratch.d.ts +5 -0
- package/dist/removeScratch.js +23 -0
- package/dist/renameWithRetry.d.ts +16 -0
- package/dist/renameWithRetry.js +81 -0
- package/dist/runtime/applyVariableBindings.d.ts +6 -4
- package/dist/runtime/applyVariableBindings.js +23 -11
- package/dist/runtime/getVariables.js +5 -4
- package/dist/runtime/variableScope.d.ts +2 -0
- package/dist/runtime/variableScope.js +9 -3
- package/package.json +12 -4
package/dist/atomicFile.js
CHANGED
|
@@ -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(
|
|
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 {
|
|
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
|
-
|
|
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)}`;
|
package/dist/compiler/index.d.ts
CHANGED
|
@@ -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";
|
package/dist/compiler/index.js
CHANGED
|
@@ -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";
|