@pagepocket/single-file-unit 0.8.6 → 0.9.0
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/single-file/embed-resources.d.ts +1 -1
- package/dist/single-file/embed-resources.js +5 -5
- package/dist/single-file/inline-path-url.d.ts +1 -1
- package/dist/single-file/inline-path-url.js +3 -3
- package/dist/single-file/oversize-behavior.js +8 -4
- package/dist/single-file/read-snapshot.d.ts +1 -1
- package/dist/single-file/read-snapshot.js +10 -10
- package/dist/single-file/rewrite-index-html.d.ts +2 -2
- package/dist/single-file/rewrite-index-html.js +19 -16
- package/dist/single-file-unit.js +1 -3
- package/dist/utils/decode-utf8-or-null.d.ts +1 -1
- package/dist/utils/decode-utf8-or-null.js +1 -1
- package/dist/utils/decode-utf8-or-undefined.d.ts +1 -0
- package/dist/utils/decode-utf8-or-undefined.js +5 -0
- package/dist/utils/oversize.d.ts +1 -1
- package/dist/utils/oversize.js +1 -1
- package/package.json +6 -5
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { rewriteCssText } from "@pagepocket/lib";
|
|
2
2
|
import { bytesToBase64 } from "@pagepocket/shared";
|
|
3
|
-
import {
|
|
3
|
+
import { decodeUtf8OrUndefined } from "../utils/decode-utf8-or-undefined.js";
|
|
4
4
|
import { shouldSkipCssValue } from "../utils/should-skip-css-value.js";
|
|
5
5
|
export const embedResourcesPathData = async (options) => {
|
|
6
6
|
const { resourcesPath, bytesByPath, maxInlineBytes, oversizeBehavior, inlinePathUrl } = options;
|
|
@@ -16,17 +16,17 @@ export const embedResourcesPathData = async (options) => {
|
|
|
16
16
|
// If the embedded resource is CSS, inline its url()/@import dependencies so file://
|
|
17
17
|
// replay doesn't attempt to load /... from disk.
|
|
18
18
|
if (item.mimeType === "text/css") {
|
|
19
|
-
const cssText =
|
|
20
|
-
if (cssText
|
|
19
|
+
const cssText = decodeUtf8OrUndefined(bytes);
|
|
20
|
+
if (cssText) {
|
|
21
21
|
const rewritten = await rewriteCssText({
|
|
22
22
|
cssText,
|
|
23
23
|
cssUrl: item.path,
|
|
24
24
|
resolveUrl: async (absolute) => {
|
|
25
25
|
if (shouldSkipCssValue(absolute)) {
|
|
26
|
-
return
|
|
26
|
+
return undefined;
|
|
27
27
|
}
|
|
28
28
|
if (!absolute.startsWith("/")) {
|
|
29
|
-
return
|
|
29
|
+
return undefined;
|
|
30
30
|
}
|
|
31
31
|
return inlinePathUrl(absolute, {
|
|
32
32
|
kind: "css",
|
|
@@ -6,4 +6,4 @@ export declare const createInlinePathUrl: (options: {
|
|
|
6
6
|
bytesByPath: Map<string, Uint8Array>;
|
|
7
7
|
maxInlineBytes: number;
|
|
8
8
|
oversizeBehavior: (mimeType?: string) => "error" | "placeholder";
|
|
9
|
-
}) => (raw: string, context: InlinePathUrlContext) => string |
|
|
9
|
+
}) => (raw: string, context: InlinePathUrlContext) => string | undefined;
|
|
@@ -6,11 +6,11 @@ export const createInlinePathUrl = (options) => {
|
|
|
6
6
|
return (raw, context) => {
|
|
7
7
|
const value = String(raw || "").trim();
|
|
8
8
|
if (!value.startsWith("/")) {
|
|
9
|
-
return
|
|
9
|
+
return undefined;
|
|
10
10
|
}
|
|
11
11
|
const bytes = bytesByPath.get(value);
|
|
12
12
|
if (!bytes) {
|
|
13
|
-
return
|
|
13
|
+
return undefined;
|
|
14
14
|
}
|
|
15
15
|
const inferredMime = lookupMimeTypeFromPath(value);
|
|
16
16
|
if (bytes.byteLength > maxInlineBytes) {
|
|
@@ -24,7 +24,7 @@ export const createInlinePathUrl = (options) => {
|
|
|
24
24
|
if (inferredMime.startsWith("video/") || inferredMime.startsWith("audio/")) {
|
|
25
25
|
return placeholderSvgDataUrl(`media omitted: ${value}`);
|
|
26
26
|
}
|
|
27
|
-
return
|
|
27
|
+
return undefined;
|
|
28
28
|
}
|
|
29
29
|
return toDataUrlBase64(inferredMime, bytes);
|
|
30
30
|
};
|
|
@@ -2,14 +2,18 @@ import { parseResourceTypeFromMime } from "../utils/oversize.js";
|
|
|
2
2
|
export const createOversizeBehavior = (oversize) => {
|
|
3
3
|
return (mimeType) => {
|
|
4
4
|
const type = parseResourceTypeFromMime(mimeType);
|
|
5
|
-
if (type === "script")
|
|
5
|
+
if (type === "script") {
|
|
6
6
|
return oversize.script;
|
|
7
|
-
|
|
7
|
+
}
|
|
8
|
+
if (type === "stylesheet") {
|
|
8
9
|
return oversize.stylesheet;
|
|
9
|
-
|
|
10
|
+
}
|
|
11
|
+
if (type === "image") {
|
|
10
12
|
return oversize.image;
|
|
11
|
-
|
|
13
|
+
}
|
|
14
|
+
if (type === "media") {
|
|
12
15
|
return oversize.media;
|
|
16
|
+
}
|
|
13
17
|
return "placeholder";
|
|
14
18
|
};
|
|
15
19
|
};
|
|
@@ -19,6 +19,6 @@ export type ApiSnapshot = {
|
|
|
19
19
|
records: unknown[];
|
|
20
20
|
};
|
|
21
21
|
export declare const readIndexHtml: (bytesByPath: Map<string, Uint8Array>) => string;
|
|
22
|
-
export declare const readOptionalApiSnapshot: (bytesByPath: Map<string, Uint8Array>) => ApiSnapshot |
|
|
22
|
+
export declare const readOptionalApiSnapshot: (bytesByPath: Map<string, Uint8Array>) => ApiSnapshot | undefined;
|
|
23
23
|
export declare const readResourcesPathSnapshot: (bytesByPath: Map<string, Uint8Array>) => ResourcesPathSnapshot;
|
|
24
24
|
export {};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { decodeUtf8OrUndefined } from "../utils/decode-utf8-or-undefined.js";
|
|
2
2
|
export const readIndexHtml = (bytesByPath) => {
|
|
3
3
|
const indexPath = "/index.html";
|
|
4
4
|
const indexBytes = bytesByPath.get(indexPath);
|
|
5
5
|
if (!indexBytes) {
|
|
6
6
|
throw new Error(`SingleFilePlugin requires ${indexPath}`);
|
|
7
7
|
}
|
|
8
|
-
const indexHtml =
|
|
9
|
-
if (indexHtml
|
|
8
|
+
const indexHtml = decodeUtf8OrUndefined(indexBytes);
|
|
9
|
+
if (!indexHtml) {
|
|
10
10
|
throw new Error("SingleFilePlugin failed to decode index.html as utf-8");
|
|
11
11
|
}
|
|
12
12
|
return indexHtml;
|
|
@@ -14,15 +14,15 @@ export const readIndexHtml = (bytesByPath) => {
|
|
|
14
14
|
export const readOptionalApiSnapshot = (bytesByPath) => {
|
|
15
15
|
const apiPath = "/api.json";
|
|
16
16
|
const apiBytes = bytesByPath.get(apiPath);
|
|
17
|
-
const apiJsonText = apiBytes ?
|
|
17
|
+
const apiJsonText = apiBytes ? decodeUtf8OrUndefined(apiBytes) : undefined;
|
|
18
18
|
if (!apiJsonText) {
|
|
19
|
-
return
|
|
19
|
+
return undefined;
|
|
20
20
|
}
|
|
21
21
|
try {
|
|
22
22
|
return JSON.parse(apiJsonText);
|
|
23
23
|
}
|
|
24
24
|
catch {
|
|
25
|
-
return
|
|
25
|
+
return undefined;
|
|
26
26
|
}
|
|
27
27
|
};
|
|
28
28
|
export const readResourcesPathSnapshot = (bytesByPath) => {
|
|
@@ -31,16 +31,16 @@ export const readResourcesPathSnapshot = (bytesByPath) => {
|
|
|
31
31
|
if (!bytes) {
|
|
32
32
|
throw new Error(`SingleFilePlugin requires ${resourcesPathPath}`);
|
|
33
33
|
}
|
|
34
|
-
const text =
|
|
35
|
-
if (text
|
|
34
|
+
const text = decodeUtf8OrUndefined(bytes);
|
|
35
|
+
if (!text) {
|
|
36
36
|
throw new Error("SingleFilePlugin failed to decode resources_path.json as utf-8");
|
|
37
37
|
}
|
|
38
|
-
let parsed
|
|
38
|
+
let parsed;
|
|
39
39
|
try {
|
|
40
40
|
parsed = JSON.parse(text);
|
|
41
41
|
}
|
|
42
42
|
catch {
|
|
43
|
-
parsed =
|
|
43
|
+
parsed = undefined;
|
|
44
44
|
}
|
|
45
45
|
if (!parsed || parsed.version !== "1.0" || !Array.isArray(parsed.items)) {
|
|
46
46
|
throw new Error("SingleFilePlugin got invalid resources_path.json");
|
|
@@ -3,11 +3,11 @@ export declare const rewriteIndexHtmlToSingleFile: (options: {
|
|
|
3
3
|
indexHtml: string;
|
|
4
4
|
bytesByPath: Map<string, Uint8Array>;
|
|
5
5
|
resourcesPath: ResourcesPathSnapshot;
|
|
6
|
-
apiSnapshot: ApiSnapshot |
|
|
6
|
+
apiSnapshot: ApiSnapshot | undefined;
|
|
7
7
|
maxInlineBytes: number;
|
|
8
8
|
oversizeBehavior: (mimeType?: string) => "error" | "placeholder";
|
|
9
9
|
inlinePathUrl: (raw: string, context: {
|
|
10
10
|
kind: "attr" | "css";
|
|
11
11
|
selector: string;
|
|
12
|
-
}) => string |
|
|
12
|
+
}) => string | undefined;
|
|
13
13
|
}) => Promise<string>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { rewriteCssText } from "@pagepocket/lib";
|
|
2
2
|
import { lookupMimeTypeFromPath } from "@pagepocket/shared";
|
|
3
3
|
import * as cheerio from "cheerio";
|
|
4
|
-
import {
|
|
4
|
+
import { decodeUtf8OrUndefined } from "../utils/decode-utf8-or-undefined.js";
|
|
5
5
|
import { placeholderSvgDataUrl } from "../utils/placeholder-svg.js";
|
|
6
6
|
import { shouldSkipCssValue } from "../utils/should-skip-css-value.js";
|
|
7
7
|
import { toDataUrlBase64 } from "../utils/to-data-url.js";
|
|
@@ -19,7 +19,7 @@ export const rewriteIndexHtmlToSingleFile = async (options) => {
|
|
|
19
19
|
const toSnapshotLocalPath = (absoluteOrPath) => {
|
|
20
20
|
const raw = String(absoluteOrPath || "").trim();
|
|
21
21
|
if (!raw) {
|
|
22
|
-
return
|
|
22
|
+
return undefined;
|
|
23
23
|
}
|
|
24
24
|
if (raw.startsWith("/")) {
|
|
25
25
|
return raw;
|
|
@@ -27,13 +27,13 @@ export const rewriteIndexHtmlToSingleFile = async (options) => {
|
|
|
27
27
|
try {
|
|
28
28
|
const parsed = new URL(raw);
|
|
29
29
|
if (parsed.origin !== CSS_BASE_URL) {
|
|
30
|
-
return
|
|
30
|
+
return undefined;
|
|
31
31
|
}
|
|
32
32
|
const withSearch = `${parsed.pathname}${parsed.search || ""}`;
|
|
33
|
-
return withSearch || parsed.pathname ||
|
|
33
|
+
return withSearch || parsed.pathname || undefined;
|
|
34
34
|
}
|
|
35
35
|
catch {
|
|
36
|
-
return
|
|
36
|
+
return undefined;
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
39
|
const rewriteCssFileToDataUrl = async (cssPath, cssBytes) => {
|
|
@@ -44,8 +44,8 @@ export const rewriteIndexHtmlToSingleFile = async (options) => {
|
|
|
44
44
|
}
|
|
45
45
|
return placeholderSvgDataUrl(`stylesheet omitted: ${cssPath}`);
|
|
46
46
|
}
|
|
47
|
-
const cssText =
|
|
48
|
-
if (cssText
|
|
47
|
+
const cssText = decodeUtf8OrUndefined(cssBytes);
|
|
48
|
+
if (!cssText) {
|
|
49
49
|
return toDataUrlBase64("text/css", cssBytes);
|
|
50
50
|
}
|
|
51
51
|
const rewritten = await rewriteCssText({
|
|
@@ -53,18 +53,18 @@ export const rewriteIndexHtmlToSingleFile = async (options) => {
|
|
|
53
53
|
cssUrl: `${CSS_BASE_URL}${cssPath}`,
|
|
54
54
|
resolveUrl: async (absolute) => {
|
|
55
55
|
if (shouldSkipCssValue(absolute)) {
|
|
56
|
-
return
|
|
56
|
+
return undefined;
|
|
57
57
|
}
|
|
58
58
|
const snapshotPath = toSnapshotLocalPath(absolute);
|
|
59
59
|
if (!snapshotPath) {
|
|
60
|
-
return
|
|
60
|
+
return undefined;
|
|
61
61
|
}
|
|
62
62
|
const mime = lookupMimeTypeFromPath(snapshotPath);
|
|
63
63
|
const behavior = oversizeBehavior(mime);
|
|
64
64
|
const bytes = bytesByPath.get(snapshotPath) ??
|
|
65
65
|
bytesByPath.get(snapshotPath.split("?")[0] || snapshotPath);
|
|
66
66
|
if (!bytes) {
|
|
67
|
-
return
|
|
67
|
+
return undefined;
|
|
68
68
|
}
|
|
69
69
|
if (bytes.byteLength > maxInlineBytes) {
|
|
70
70
|
if (behavior === "error") {
|
|
@@ -76,7 +76,7 @@ export const rewriteIndexHtmlToSingleFile = async (options) => {
|
|
|
76
76
|
if (mime.startsWith("video/") || mime.startsWith("audio/")) {
|
|
77
77
|
return placeholderSvgDataUrl(`media omitted: ${snapshotPath}`);
|
|
78
78
|
}
|
|
79
|
-
return
|
|
79
|
+
return undefined;
|
|
80
80
|
}
|
|
81
81
|
return toDataUrlBase64(mime, bytes);
|
|
82
82
|
}
|
|
@@ -137,8 +137,9 @@ export const rewriteIndexHtmlToSingleFile = async (options) => {
|
|
|
137
137
|
.split(",")
|
|
138
138
|
.map((part) => {
|
|
139
139
|
const trimmed = part.trim();
|
|
140
|
-
if (!trimmed)
|
|
140
|
+
if (!trimmed) {
|
|
141
141
|
return trimmed;
|
|
142
|
+
}
|
|
142
143
|
const pieces = trimmed.split(/\s+/, 2);
|
|
143
144
|
const url = pieces[0] || "";
|
|
144
145
|
const descriptor = pieces[1];
|
|
@@ -152,14 +153,16 @@ export const rewriteIndexHtmlToSingleFile = async (options) => {
|
|
|
152
153
|
};
|
|
153
154
|
$("img[srcset], source[srcset]").each((_index, element) => {
|
|
154
155
|
const value = $(element).attr("srcset");
|
|
155
|
-
if (!value)
|
|
156
|
+
if (!value) {
|
|
156
157
|
return;
|
|
158
|
+
}
|
|
157
159
|
$(element).attr("srcset", rewriteSrcsetInline(value));
|
|
158
160
|
});
|
|
159
161
|
$("link[imagesrcset]").each((_index, element) => {
|
|
160
162
|
const value = $(element).attr("imagesrcset");
|
|
161
|
-
if (!value)
|
|
163
|
+
if (!value) {
|
|
162
164
|
return;
|
|
165
|
+
}
|
|
163
166
|
$(element).attr("imagesrcset", rewriteSrcsetInline(value));
|
|
164
167
|
});
|
|
165
168
|
const inlineStyleText = async (cssText) => {
|
|
@@ -171,11 +174,11 @@ export const rewriteIndexHtmlToSingleFile = async (options) => {
|
|
|
171
174
|
cssUrl: `${CSS_BASE_URL}/`,
|
|
172
175
|
resolveUrl: async (absolute) => {
|
|
173
176
|
if (shouldSkipCssValue(absolute)) {
|
|
174
|
-
return
|
|
177
|
+
return undefined;
|
|
175
178
|
}
|
|
176
179
|
const snapshotPath = toSnapshotLocalPath(absolute);
|
|
177
180
|
if (!snapshotPath) {
|
|
178
|
-
return
|
|
181
|
+
return undefined;
|
|
179
182
|
}
|
|
180
183
|
return inlinePathUrl(snapshotPath, { kind: "css", selector: "css" });
|
|
181
184
|
}
|
package/dist/single-file-unit.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Unit } from "@pagepocket/lib";
|
|
2
|
+
import { buildSingleFileFiles } from "./single-file/build-single-file-files.js";
|
|
2
3
|
export class SingleFileUnit extends Unit {
|
|
3
4
|
constructor(options) {
|
|
4
5
|
super();
|
|
@@ -11,9 +12,6 @@ export class SingleFileUnit extends Unit {
|
|
|
11
12
|
if (!files) {
|
|
12
13
|
throw new Error("SingleFileUnit requires ctx.value.files");
|
|
13
14
|
}
|
|
14
|
-
// Single-file transformation is pure with respect to artifacts.
|
|
15
|
-
// We keep implementation local to this package; it consumes and produces files@1.
|
|
16
|
-
const { buildSingleFileFiles } = await import("./single-file/build-single-file-files.js");
|
|
17
15
|
const nextFiles = await buildSingleFileFiles({ files, options: this.options });
|
|
18
16
|
return { files: nextFiles };
|
|
19
17
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const decodeUtf8OrNull: (bytes: Uint8Array) => string |
|
|
1
|
+
export declare const decodeUtf8OrNull: (bytes: Uint8Array) => string | undefined;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const decodeUtf8OrUndefined: (bytes: Uint8Array) => string | undefined;
|
package/dist/utils/oversize.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ declare const defaultOversize: {
|
|
|
6
6
|
};
|
|
7
7
|
export type OversizeResourceType = keyof typeof defaultOversize;
|
|
8
8
|
export type OversizeBehavior = (typeof defaultOversize)[OversizeResourceType];
|
|
9
|
-
export declare const parseResourceTypeFromMime: (mimeType?: string) => OversizeResourceType |
|
|
9
|
+
export declare const parseResourceTypeFromMime: (mimeType?: string) => OversizeResourceType | undefined;
|
|
10
10
|
export declare const defaultOversizeBehavior: {
|
|
11
11
|
readonly script: "error";
|
|
12
12
|
readonly stylesheet: "error";
|
package/dist/utils/oversize.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pagepocket/single-file-unit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "PagePocket plugin: emit strong-offline single-file index.html",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -17,15 +17,16 @@
|
|
|
17
17
|
"license": "ISC",
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"cheerio": "^1.1.2",
|
|
20
|
-
"@pagepocket/lib": "0.
|
|
21
|
-
"@pagepocket/contracts": "0.
|
|
22
|
-
"@pagepocket/shared": "0.
|
|
20
|
+
"@pagepocket/lib": "0.9.0",
|
|
21
|
+
"@pagepocket/contracts": "0.9.0",
|
|
22
|
+
"@pagepocket/shared": "0.9.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
+
"tsx": "^4.21.0",
|
|
25
26
|
"typescript": "^5.4.5"
|
|
26
27
|
},
|
|
27
28
|
"scripts": {
|
|
28
29
|
"build": "tsc -p tsconfig.json",
|
|
29
|
-
"test": "
|
|
30
|
+
"test": "tsx --test specs/*.spec.ts"
|
|
30
31
|
}
|
|
31
32
|
}
|