@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.
@@ -7,5 +7,5 @@ export declare const embedResourcesPathData: (options: {
7
7
  inlinePathUrl: (raw: string, context: {
8
8
  kind: "attr" | "css";
9
9
  selector: string;
10
- }) => string | null;
10
+ }) => string | undefined;
11
11
  }) => Promise<void>;
@@ -1,6 +1,6 @@
1
1
  import { rewriteCssText } from "@pagepocket/lib";
2
2
  import { bytesToBase64 } from "@pagepocket/shared";
3
- import { decodeUtf8OrNull } from "../utils/decode-utf8-or-null.js";
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 = decodeUtf8OrNull(bytes);
20
- if (cssText !== null) {
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 null;
26
+ return undefined;
27
27
  }
28
28
  if (!absolute.startsWith("/")) {
29
- return null;
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 | null;
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 null;
9
+ return undefined;
10
10
  }
11
11
  const bytes = bytesByPath.get(value);
12
12
  if (!bytes) {
13
- return null;
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 null;
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
- if (type === "stylesheet")
7
+ }
8
+ if (type === "stylesheet") {
8
9
  return oversize.stylesheet;
9
- if (type === "image")
10
+ }
11
+ if (type === "image") {
10
12
  return oversize.image;
11
- if (type === "media")
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 | null;
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 { decodeUtf8OrNull } from "../utils/decode-utf8-or-null.js";
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 = decodeUtf8OrNull(indexBytes);
9
- if (indexHtml === null) {
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 ? decodeUtf8OrNull(apiBytes) : null;
17
+ const apiJsonText = apiBytes ? decodeUtf8OrUndefined(apiBytes) : undefined;
18
18
  if (!apiJsonText) {
19
- return null;
19
+ return undefined;
20
20
  }
21
21
  try {
22
22
  return JSON.parse(apiJsonText);
23
23
  }
24
24
  catch {
25
- return null;
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 = decodeUtf8OrNull(bytes);
35
- if (text === null) {
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 = null;
38
+ let parsed;
39
39
  try {
40
40
  parsed = JSON.parse(text);
41
41
  }
42
42
  catch {
43
- parsed = null;
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 | null;
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 | null;
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 { decodeUtf8OrNull } from "../utils/decode-utf8-or-null.js";
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 null;
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 null;
30
+ return undefined;
31
31
  }
32
32
  const withSearch = `${parsed.pathname}${parsed.search || ""}`;
33
- return withSearch || parsed.pathname || null;
33
+ return withSearch || parsed.pathname || undefined;
34
34
  }
35
35
  catch {
36
- return null;
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 = decodeUtf8OrNull(cssBytes);
48
- if (cssText === null) {
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 null;
56
+ return undefined;
57
57
  }
58
58
  const snapshotPath = toSnapshotLocalPath(absolute);
59
59
  if (!snapshotPath) {
60
- return null;
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 null;
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 null;
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 null;
177
+ return undefined;
175
178
  }
176
179
  const snapshotPath = toSnapshotLocalPath(absolute);
177
180
  if (!snapshotPath) {
178
- return null;
181
+ return undefined;
179
182
  }
180
183
  return inlinePathUrl(snapshotPath, { kind: "css", selector: "css" });
181
184
  }
@@ -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 | null;
1
+ export declare const decodeUtf8OrNull: (bytes: Uint8Array) => string | undefined;
@@ -1,5 +1,5 @@
1
1
  import { decodeUtf8Lenient } from "@pagepocket/shared";
2
2
  export const decodeUtf8OrNull = (bytes) => {
3
3
  const decoded = decodeUtf8Lenient(bytes);
4
- return decoded ? decoded : null;
4
+ return decoded ? decoded : undefined;
5
5
  };
@@ -0,0 +1 @@
1
+ export declare const decodeUtf8OrUndefined: (bytes: Uint8Array) => string | undefined;
@@ -0,0 +1,5 @@
1
+ import { decodeUtf8Lenient } from "@pagepocket/shared";
2
+ export const decodeUtf8OrUndefined = (bytes) => {
3
+ const decoded = decodeUtf8Lenient(bytes);
4
+ return decoded ? decoded : undefined;
5
+ };
@@ -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 | null;
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";
@@ -20,6 +20,6 @@ export const parseResourceTypeFromMime = (mimeType) => {
20
20
  if (mime.startsWith("video/") || mime.startsWith("audio/")) {
21
21
  return "media";
22
22
  }
23
- return null;
23
+ return undefined;
24
24
  };
25
25
  export const defaultOversizeBehavior = defaultOversize;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pagepocket/single-file-unit",
3
- "version": "0.8.6",
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.8.6",
21
- "@pagepocket/contracts": "0.8.6",
22
- "@pagepocket/shared": "0.8.6"
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": "node -e \"process.exit(0)\""
30
+ "test": "tsx --test specs/*.spec.ts"
30
31
  }
31
32
  }