@hyperframes/core 0.8.191 → 0.8.192
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/beats/beatFile.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Persistence for beat data: one JSON file per audio file, matched by the
|
|
2
2
|
// audio's project-relative path. Lives under `beats/` in the project so it
|
|
3
3
|
// survives the audio being removed and re-added.
|
|
4
|
-
import { decodeAuthoredAttribute, scanHtmlOpeningTags, } from "@hyperframes/parsers";
|
|
4
|
+
import { decodeAuthoredAttribute, decodedUrlPath, scanHtmlOpeningTags, } from "@hyperframes/parsers";
|
|
5
5
|
/** Project-relative path of the audio file behind a (possibly absolute) src URL. */
|
|
6
6
|
export function audioRelPathForSrc(src) {
|
|
7
7
|
if (!src)
|
|
@@ -23,16 +23,16 @@ export function audioRelPathForSrc(src) {
|
|
|
23
23
|
after = after.slice(0, queryOrHash);
|
|
24
24
|
if (after.startsWith("comp/"))
|
|
25
25
|
after = after.slice("comp/".length);
|
|
26
|
-
rel = after ?
|
|
26
|
+
rel = after ? decodedUrlPath(after) : null;
|
|
27
27
|
}
|
|
28
28
|
if (!rel) {
|
|
29
29
|
// Fall back to the FULL pathname (not just basename) so two files with the
|
|
30
30
|
// same name in different folders don't collide on one beat file.
|
|
31
31
|
try {
|
|
32
|
-
rel =
|
|
32
|
+
rel = decodedUrlPath(new URL(src, "http://_").pathname);
|
|
33
33
|
}
|
|
34
34
|
catch {
|
|
35
|
-
rel = src;
|
|
35
|
+
rel = decodedUrlPath(src);
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
if (!rel)
|
|
@@ -125,23 +125,42 @@ function rebaseRelativePath(urlValue, fromDir, toDir) {
|
|
|
125
125
|
const rebased = encodeUrlPath(relative(resolve(toDir), absolutePath).split(sep).join("/"));
|
|
126
126
|
return appendSuffixToUrl(rebased, suffix);
|
|
127
127
|
}
|
|
128
|
-
function
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
128
|
+
function rewriteSrcsetUrls(srcset, rewriteUrl) {
|
|
129
|
+
const parts = [];
|
|
130
|
+
let position = 0;
|
|
131
|
+
let written = 0;
|
|
132
|
+
// Preserve URL commas per https://html.spec.whatwg.org/multipage/images.html#parse-a-srcset-attribute
|
|
133
|
+
while (position < srcset.length) {
|
|
134
|
+
while (position < srcset.length && /[ \t\n\r\f,]/.test(srcset.charAt(position)))
|
|
135
|
+
position++;
|
|
136
|
+
const start = position;
|
|
137
|
+
while (position < srcset.length && !/[ \t\n\r\f]/.test(srcset.charAt(position)))
|
|
138
|
+
position++;
|
|
139
|
+
let end = position;
|
|
140
|
+
while (end > start && srcset.charAt(end - 1) === ",")
|
|
141
|
+
end--;
|
|
142
|
+
if (end > start) {
|
|
143
|
+
const url = srcset.slice(start, end);
|
|
144
|
+
const rewritten = rewriteUrl(url);
|
|
145
|
+
if (rewritten !== url) {
|
|
146
|
+
parts.push(srcset.slice(written, start), rewritten);
|
|
147
|
+
written = end;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (end < position)
|
|
151
|
+
continue;
|
|
152
|
+
let inParens = false;
|
|
153
|
+
while (position < srcset.length) {
|
|
154
|
+
const char = srcset.charAt(position++);
|
|
155
|
+
if (char === "(")
|
|
156
|
+
inParens = true;
|
|
157
|
+
else if (char === ")")
|
|
158
|
+
inParens = false;
|
|
159
|
+
else if (char === "," && !inParens)
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return parts.length > 0 ? parts.join("") + srcset.slice(written) : srcset;
|
|
145
164
|
}
|
|
146
165
|
function rebaseColorGradingLutPath(value, fromDir, toDir) {
|
|
147
166
|
if (!value.trim().startsWith("{"))
|
|
@@ -194,7 +213,7 @@ function rebaseEntryAuthoredAssetPaths(document, sourceDir, projectDir) {
|
|
|
194
213
|
for (const el of [...document.querySelectorAll("[srcset]")]) {
|
|
195
214
|
const srcset = el.getAttribute("srcset");
|
|
196
215
|
if (srcset)
|
|
197
|
-
el.setAttribute("srcset",
|
|
216
|
+
el.setAttribute("srcset", rewriteSrcsetUrls(srcset, (url) => isRelativeUrl(url) ? rebaseRelativePath(url, sourceDir, projectDir) : url));
|
|
198
217
|
}
|
|
199
218
|
for (const el of [...document.querySelectorAll(`[${HF_COLOR_GRADING_ATTR}]`)]) {
|
|
200
219
|
const value = el.getAttribute(HF_COLOR_GRADING_ATTR);
|
|
@@ -301,6 +320,8 @@ const INLINE_MIME = {
|
|
|
301
320
|
* exactly where "self-contained" stops being true.
|
|
302
321
|
*/
|
|
303
322
|
const MAX_INLINE_ASSET_BYTES = 2 * 1024 * 1024;
|
|
323
|
+
// An asset is inlined once per reference, so repeats can pass V8's ~2^29-char string limit.
|
|
324
|
+
const MAX_INLINED_BUNDLE_CHARS = 256 * 1024 * 1024;
|
|
304
325
|
function safeStatSize(filePath) {
|
|
305
326
|
try {
|
|
306
327
|
return statSync(filePath).size;
|
|
@@ -313,9 +334,14 @@ function warnAssetTooLargeToInline(assetPath, byteLength) {
|
|
|
313
334
|
const mb = (byteLength / (1024 * 1024)).toFixed(1);
|
|
314
335
|
console.warn(`[HyperFrames] Not inlining "${assetPath}" (${mb} MB exceeds the ${MAX_INLINE_ASSET_BYTES / (1024 * 1024)} MB inline limit). The bundle may not be self-contained.`);
|
|
315
336
|
}
|
|
337
|
+
function warnInlineBudgetExhausted(assetPath) {
|
|
338
|
+
console.warn(`[HyperFrames] Not inlining "${assetPath}" and later assets: the bundle already inlines ${MAX_INLINED_BUNDLE_CHARS / (1024 * 1024)} MB of data URLs. The bundle may not be self-contained.`);
|
|
339
|
+
}
|
|
316
340
|
/** Inlines relative asset URLs as data: URLs, reading each project file once per build. */
|
|
317
341
|
function createAssetInliner(resolveProjectPath, inlineAssets) {
|
|
318
342
|
const assets = new Map();
|
|
343
|
+
let inlinedChars = 0;
|
|
344
|
+
let budgetExhausted = false;
|
|
319
345
|
const readAsset = (filePath, mimeType) => {
|
|
320
346
|
// Size-check before reading: an oversized asset must not be pulled into memory
|
|
321
347
|
// just to be discarded.
|
|
@@ -354,7 +380,16 @@ function createAssetInliner(resolveProjectPath, inlineAssets) {
|
|
|
354
380
|
warnAssetTooLargeToInline(basePath, asset.tooLarge);
|
|
355
381
|
return null;
|
|
356
382
|
}
|
|
357
|
-
|
|
383
|
+
if (asset.dataUrl === null)
|
|
384
|
+
return null;
|
|
385
|
+
if (budgetExhausted || inlinedChars + asset.dataUrl.length > MAX_INLINED_BUNDLE_CHARS) {
|
|
386
|
+
if (!budgetExhausted)
|
|
387
|
+
warnInlineBudgetExhausted(basePath);
|
|
388
|
+
budgetExhausted = true;
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
inlinedChars += asset.dataUrl.length;
|
|
392
|
+
return appendSuffixToUrl(asset.dataUrl, suffix);
|
|
358
393
|
};
|
|
359
394
|
}
|
|
360
395
|
function isExternalSvgFragmentUse(el, attr, urlValue) {
|
|
@@ -414,25 +449,6 @@ function rewriteColorGradingLutWithInlinedAssets(value, inlineAsset) {
|
|
|
414
449
|
Reflect.set(lut, "src", inlined);
|
|
415
450
|
return JSON.stringify(parsed);
|
|
416
451
|
}
|
|
417
|
-
function rewriteSrcsetWithInlinedAssets(srcsetValue, inlineAsset) {
|
|
418
|
-
if (!srcsetValue)
|
|
419
|
-
return srcsetValue;
|
|
420
|
-
return srcsetValue
|
|
421
|
-
.split(",")
|
|
422
|
-
.map((rawCandidate) => {
|
|
423
|
-
const candidate = rawCandidate.trim();
|
|
424
|
-
if (!candidate)
|
|
425
|
-
return candidate;
|
|
426
|
-
const parts = candidate.split(/\s+/);
|
|
427
|
-
if (parts.length === 0)
|
|
428
|
-
return candidate;
|
|
429
|
-
const maybeInlined = inlineAsset(parts[0] ?? "");
|
|
430
|
-
if (maybeInlined)
|
|
431
|
-
parts[0] = maybeInlined;
|
|
432
|
-
return parts.join(" ");
|
|
433
|
-
})
|
|
434
|
-
.join(", ");
|
|
435
|
-
}
|
|
436
452
|
function rewriteCssUrlsWithInlinedAssets(cssText, inlineAsset) {
|
|
437
453
|
if (!cssText)
|
|
438
454
|
return cssText;
|
|
@@ -1228,7 +1244,7 @@ async function bundleProject(projectDir, options) {
|
|
|
1228
1244
|
for (const el of [...document.querySelectorAll("[srcset]")]) {
|
|
1229
1245
|
const srcset = el.getAttribute("srcset");
|
|
1230
1246
|
if (srcset)
|
|
1231
|
-
el.setAttribute("srcset",
|
|
1247
|
+
el.setAttribute("srcset", rewriteSrcsetUrls(srcset, (url) => inlineAsset(url) ?? url));
|
|
1232
1248
|
}
|
|
1233
1249
|
// Before inlining, so postcss reads paths not font bytes.
|
|
1234
1250
|
const liveStyles = [...document.querySelectorAll("style")].filter(isAlwaysAppliedStyle);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
type SpecifierMap = Record<string, string | null>;
|
|
1
2
|
/** The subset of an import map a composition can declare. */
|
|
2
3
|
export interface ImportMap {
|
|
3
|
-
imports?:
|
|
4
|
-
scopes?: Record<string,
|
|
4
|
+
imports?: SpecifierMap;
|
|
5
|
+
scopes?: Record<string, SpecifierMap>;
|
|
5
6
|
}
|
|
6
7
|
/**
|
|
7
8
|
* Parses a mounted composition's import map, rebasing each address the way its `src`/`href`
|
|
@@ -16,3 +17,4 @@ export declare function parseImportMap(json: string, rebase: (url: string) => st
|
|
|
16
17
|
export declare function mergeImportMapsIntoDocument(doc: Document, maps: ImportMap[]): void;
|
|
17
18
|
/** Emits what inlineSubCompositions collected from mounted files' module and import-map scripts. */
|
|
18
19
|
export declare function emitMountedModuleScripts(doc: Document, importMaps: ImportMap[], moduleScripts: string[]): void;
|
|
20
|
+
export {};
|
|
@@ -1,5 +1,14 @@
|
|
|
1
|
+
function isRecord(value) {
|
|
2
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
function isScopeMap(value) {
|
|
5
|
+
return isRecord(value) && Object.values(value).every(isRecord);
|
|
6
|
+
}
|
|
1
7
|
const rebaseEntries = (entries, rebase) => entries &&
|
|
2
|
-
Object.fromEntries(Object.entries(entries).map(([specifier, url]) => [
|
|
8
|
+
Object.fromEntries(Object.entries(entries).map(([specifier, url]) => [
|
|
9
|
+
specifier,
|
|
10
|
+
typeof url === "string" ? rebase(url) : null,
|
|
11
|
+
]));
|
|
3
12
|
/**
|
|
4
13
|
* Parses a mounted composition's import map, rebasing each address the way its `src`/`href`
|
|
5
14
|
* attributes are rebased. Returns null for a map the browser would reject too.
|
|
@@ -12,9 +21,13 @@ export function parseImportMap(json, rebase) {
|
|
|
12
21
|
catch {
|
|
13
22
|
return null;
|
|
14
23
|
}
|
|
15
|
-
if (!parsed
|
|
24
|
+
if (!isRecord(parsed))
|
|
16
25
|
return null;
|
|
17
26
|
const { imports, scopes } = parsed;
|
|
27
|
+
if (imports !== undefined && !isRecord(imports))
|
|
28
|
+
return null;
|
|
29
|
+
if (scopes !== undefined && !isScopeMap(scopes))
|
|
30
|
+
return null;
|
|
18
31
|
return {
|
|
19
32
|
imports: rebaseEntries(imports, rebase),
|
|
20
33
|
scopes: scopes &&
|
|
@@ -26,8 +39,14 @@ export function parseImportMap(json, rebase) {
|
|
|
26
39
|
}
|
|
27
40
|
function mergeEntries(into, from, where) {
|
|
28
41
|
for (const [specifier, url] of Object.entries(from ?? {})) {
|
|
29
|
-
if (!(specifier
|
|
30
|
-
into
|
|
42
|
+
if (!Object.hasOwn(into, specifier)) {
|
|
43
|
+
Object.defineProperty(into, specifier, {
|
|
44
|
+
value: url,
|
|
45
|
+
enumerable: true,
|
|
46
|
+
configurable: true,
|
|
47
|
+
writable: true,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
31
50
|
else if (into[specifier] !== url) {
|
|
32
51
|
console.warn(`[HyperFrames] import map conflict for "${specifier}"${where}: keeping ${into[specifier]}, ignoring ${url}.`);
|
|
33
52
|
}
|
|
@@ -54,7 +73,9 @@ export function mergeImportMapsIntoDocument(doc, maps) {
|
|
|
54
73
|
for (const map of maps) {
|
|
55
74
|
mergeEntries(imports, map.imports, "");
|
|
56
75
|
for (const [prefix, entries] of Object.entries(map.scopes ?? {})) {
|
|
57
|
-
|
|
76
|
+
const scope = (Object.hasOwn(scopes, prefix) ? scopes[prefix] : undefined) ?? {};
|
|
77
|
+
Object.defineProperty(scopes, prefix, { value: scope, enumerable: true });
|
|
78
|
+
mergeEntries(scope, entries, ` in scope ${prefix}`);
|
|
58
79
|
}
|
|
59
80
|
}
|
|
60
81
|
if (!el) {
|