@turntrout/subfont 1.7.0 → 1.8.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.
Files changed (151) hide show
  1. package/CLAUDE.md +39 -13
  2. package/README.md +24 -14
  3. package/lib/FontTracerPool.d.ts +38 -0
  4. package/lib/FontTracerPool.d.ts.map +1 -0
  5. package/lib/FontTracerPool.js +230 -217
  6. package/lib/FontTracerPool.js.map +1 -0
  7. package/lib/HeadlessBrowser.d.ts +18 -0
  8. package/lib/HeadlessBrowser.d.ts.map +1 -0
  9. package/lib/HeadlessBrowser.js +216 -210
  10. package/lib/HeadlessBrowser.js.map +1 -0
  11. package/lib/cli.d.ts +3 -0
  12. package/lib/cli.d.ts.map +1 -0
  13. package/lib/cli.js +15 -12
  14. package/lib/cli.js.map +1 -0
  15. package/lib/codepointMaps.d.ts +4 -0
  16. package/lib/codepointMaps.d.ts.map +1 -0
  17. package/lib/codepointMaps.js +99 -0
  18. package/lib/codepointMaps.js.map +1 -0
  19. package/lib/collectFeatureGlyphIds.d.ts +3 -0
  20. package/lib/collectFeatureGlyphIds.d.ts.map +1 -0
  21. package/lib/collectFeatureGlyphIds.js +124 -138
  22. package/lib/collectFeatureGlyphIds.js.map +1 -0
  23. package/lib/collectTextsByPage.d.ts +41 -0
  24. package/lib/collectTextsByPage.d.ts.map +1 -0
  25. package/lib/collectTextsByPage.js +726 -965
  26. package/lib/collectTextsByPage.js.map +1 -0
  27. package/lib/concurrencyLimit.d.ts +3 -0
  28. package/lib/concurrencyLimit.d.ts.map +1 -0
  29. package/lib/concurrencyLimit.js +12 -11
  30. package/lib/concurrencyLimit.js.map +1 -0
  31. package/lib/escapeJsStringLiteral.d.ts +3 -0
  32. package/lib/escapeJsStringLiteral.d.ts.map +1 -0
  33. package/lib/escapeJsStringLiteral.js +7 -6
  34. package/lib/escapeJsStringLiteral.js.map +1 -0
  35. package/lib/extractReferencedCustomPropertyNames.d.ts +3 -0
  36. package/lib/extractReferencedCustomPropertyNames.d.ts.map +1 -0
  37. package/lib/extractReferencedCustomPropertyNames.js +15 -16
  38. package/lib/extractReferencedCustomPropertyNames.js.map +1 -0
  39. package/lib/extractVisibleText.d.ts +7 -0
  40. package/lib/extractVisibleText.d.ts.map +1 -0
  41. package/lib/extractVisibleText.js +110 -119
  42. package/lib/extractVisibleText.js.map +1 -0
  43. package/lib/findCustomPropertyDefinitions.d.ts +8 -0
  44. package/lib/findCustomPropertyDefinitions.d.ts.map +1 -0
  45. package/lib/findCustomPropertyDefinitions.js +41 -48
  46. package/lib/findCustomPropertyDefinitions.js.map +1 -0
  47. package/lib/fontConverter.d.ts +2 -0
  48. package/lib/fontConverter.d.ts.map +1 -0
  49. package/lib/fontConverter.js +40 -21
  50. package/lib/fontConverter.js.map +1 -0
  51. package/lib/fontConverterWorker.d.ts +2 -0
  52. package/lib/fontConverterWorker.d.ts.map +1 -0
  53. package/lib/fontConverterWorker.js +52 -15
  54. package/lib/fontConverterWorker.js.map +1 -0
  55. package/lib/fontFaceHelpers.d.ts +64 -0
  56. package/lib/fontFaceHelpers.d.ts.map +1 -0
  57. package/lib/fontFaceHelpers.js +237 -249
  58. package/lib/fontFaceHelpers.js.map +1 -0
  59. package/lib/fontFeatureHelpers.d.ts +30 -0
  60. package/lib/fontFeatureHelpers.d.ts.map +1 -0
  61. package/lib/fontFeatureHelpers.js +277 -212
  62. package/lib/fontFeatureHelpers.js.map +1 -0
  63. package/lib/fontTracerWorker.d.ts +11 -0
  64. package/lib/fontTracerWorker.d.ts.map +1 -0
  65. package/lib/fontTracerWorker.js +94 -60
  66. package/lib/fontTracerWorker.js.map +1 -0
  67. package/lib/gatherStylesheetsWithPredicates.d.ts +26 -0
  68. package/lib/gatherStylesheetsWithPredicates.d.ts.map +1 -0
  69. package/lib/gatherStylesheetsWithPredicates.js +75 -84
  70. package/lib/gatherStylesheetsWithPredicates.js.map +1 -0
  71. package/lib/getCssRulesByProperty.d.ts +29 -0
  72. package/lib/getCssRulesByProperty.d.ts.map +1 -0
  73. package/lib/getCssRulesByProperty.js +316 -316
  74. package/lib/getCssRulesByProperty.js.map +1 -0
  75. package/lib/getFontInfo.d.ts +11 -0
  76. package/lib/getFontInfo.d.ts.map +1 -0
  77. package/lib/getFontInfo.js +31 -33
  78. package/lib/getFontInfo.js.map +1 -0
  79. package/lib/initialValueByProp.d.ts +3 -0
  80. package/lib/initialValueByProp.d.ts.map +1 -0
  81. package/lib/initialValueByProp.js +20 -17
  82. package/lib/initialValueByProp.js.map +1 -0
  83. package/lib/injectSubsetDefinitions.d.ts +3 -0
  84. package/lib/injectSubsetDefinitions.d.ts.map +1 -0
  85. package/lib/injectSubsetDefinitions.js +55 -59
  86. package/lib/injectSubsetDefinitions.js.map +1 -0
  87. package/lib/normalizeFontPropertyValue.d.ts +3 -0
  88. package/lib/normalizeFontPropertyValue.d.ts.map +1 -0
  89. package/lib/normalizeFontPropertyValue.js +59 -54
  90. package/lib/normalizeFontPropertyValue.js.map +1 -0
  91. package/lib/parseCommandLineOptions.d.ts +9 -0
  92. package/lib/parseCommandLineOptions.d.ts.map +1 -0
  93. package/lib/parseCommandLineOptions.js +145 -149
  94. package/lib/parseCommandLineOptions.js.map +1 -0
  95. package/lib/parseFontVariationSettings.d.ts +3 -0
  96. package/lib/parseFontVariationSettings.d.ts.map +1 -0
  97. package/lib/parseFontVariationSettings.js +38 -36
  98. package/lib/parseFontVariationSettings.js.map +1 -0
  99. package/lib/progress.d.ts +27 -0
  100. package/lib/progress.d.ts.map +1 -0
  101. package/lib/progress.js +51 -54
  102. package/lib/progress.js.map +1 -0
  103. package/lib/sfntCache.d.ts +4 -0
  104. package/lib/sfntCache.d.ts.map +1 -0
  105. package/lib/sfntCache.js +67 -25
  106. package/lib/sfntCache.js.map +1 -0
  107. package/lib/stripLocalTokens.d.ts +3 -0
  108. package/lib/stripLocalTokens.d.ts.map +1 -0
  109. package/lib/stripLocalTokens.js +23 -21
  110. package/lib/stripLocalTokens.js.map +1 -0
  111. package/lib/subfont.d.ts +54 -0
  112. package/lib/subfont.d.ts.map +1 -0
  113. package/lib/subfont.js +531 -629
  114. package/lib/subfont.js.map +1 -0
  115. package/lib/subsetFontWithGlyphs.d.ts +21 -0
  116. package/lib/subsetFontWithGlyphs.d.ts.map +1 -0
  117. package/lib/subsetFontWithGlyphs.js +285 -259
  118. package/lib/subsetFontWithGlyphs.js.map +1 -0
  119. package/lib/subsetFonts.d.ts +55 -0
  120. package/lib/subsetFonts.d.ts.map +1 -0
  121. package/lib/subsetFonts.js +899 -1200
  122. package/lib/subsetFonts.js.map +1 -0
  123. package/lib/subsetGeneration.d.ts +36 -0
  124. package/lib/subsetGeneration.d.ts.map +1 -0
  125. package/lib/subsetGeneration.js +328 -325
  126. package/lib/subsetGeneration.js.map +1 -0
  127. package/lib/types/shared.d.ts +11 -0
  128. package/lib/types/shared.d.ts.map +1 -0
  129. package/lib/types/shared.js +3 -0
  130. package/lib/types/shared.js.map +1 -0
  131. package/lib/unicodeRange.d.ts +3 -0
  132. package/lib/unicodeRange.d.ts.map +1 -0
  133. package/lib/unicodeRange.js +17 -30
  134. package/lib/unicodeRange.js.map +1 -0
  135. package/lib/unquote.d.ts +3 -0
  136. package/lib/unquote.d.ts.map +1 -0
  137. package/lib/unquote.js +18 -25
  138. package/lib/unquote.js.map +1 -0
  139. package/lib/variationAxes.d.ts +33 -0
  140. package/lib/variationAxes.d.ts.map +1 -0
  141. package/lib/variationAxes.js +127 -157
  142. package/lib/variationAxes.js.map +1 -0
  143. package/lib/warnAboutMissingGlyphs.d.ts +43 -0
  144. package/lib/warnAboutMissingGlyphs.d.ts.map +1 -0
  145. package/lib/warnAboutMissingGlyphs.js +139 -147
  146. package/lib/warnAboutMissingGlyphs.js.map +1 -0
  147. package/lib/wasmQueue.d.ts +3 -0
  148. package/lib/wasmQueue.d.ts.map +1 -0
  149. package/lib/wasmQueue.js +13 -10
  150. package/lib/wasmQueue.js.map +1 -0
  151. package/package.json +12 -2
package/CLAUDE.md CHANGED
@@ -7,25 +7,29 @@ subfont is a CLI tool and Node.js library that speeds up initial page paint by a
7
7
  ## Development Commands
8
8
 
9
9
  ```bash
10
- pnpm install # Install dependencies
11
- pnpm test # Run mocha tests + lint
12
- pnpm run lint # ESLint + Prettier check
13
- pnpm run coverage # Run tests with nyc coverage
10
+ pnpm install # Install dependencies
11
+ pnpm run build # Compile TypeScript (src/ lib/)
12
+ pnpm test # Build + run mocha tests + lint
13
+ pnpm run lint # ESLint + Prettier check
14
+ pnpm run typecheck # TypeScript type checking (no emit)
15
+ pnpm run coverage # Run tests with nyc coverage
14
16
  pnpm run check-coverage # Verify coverage thresholds
15
17
  ```
16
18
 
17
19
  ## Code Style
18
20
 
21
+ - **Language**: TypeScript (strict mode, ES2022 target, CommonJS output)
19
22
  - **Formatter**: Prettier with single quotes, trailing commas (es5)
20
- - **Linter**: ESLint via neostandard + eslint-config-prettier
21
- - **Rules**: `prefer-template`, `prefer-const` (destructuring: all)
23
+ - **Linter**: ESLint via neostandard + eslint-config-prettier + typescript-eslint
24
+ - **Rules**: `prefer-template`, `prefer-const` (destructuring: all), `@typescript-eslint/no-explicit-any` (error in .ts files)
22
25
  - **Tests**: Mocha with `unexpected` assertion library (not chai/jest)
23
26
  - No exclusive tests (`describe.only`, `it.only`) — enforced by eslint-plugin-mocha
24
27
 
25
28
  ## Project Structure
26
29
 
27
- - `lib/` — Source code (entry: `lib/subfont.js`, CLI: `lib/cli.js`)
28
- - `test/` — Mocha test files
30
+ - `src/` — TypeScript source code (entry: `src/subfont.ts`, CLI: `src/cli.ts`)
31
+ - `lib/` — Compiled JavaScript output (generated by `tsc`, not checked in)
32
+ - `test/` — Mocha test files (JavaScript, run against compiled `lib/`)
29
33
  - `testdata/` — HTML fixtures and font files for tests
30
34
  - `cases/` — Additional test case data
31
35
 
@@ -34,20 +38,42 @@ pnpm run check-coverage # Verify coverage thresholds
34
38
  - Built on **assetgraph** for HTML/CSS asset graph traversal
35
39
  - Uses **puppeteer-core** for headless browser font tracing
36
40
  - **font-tracer** traces which fonts are used on each page
37
- - **harfbuzzjs** for WOFF2 subsetting (via direct WASM calls in `lib/subsetFontWithGlyphs.js`)
38
- - `lib/subsetFonts.js` — Main orchestration logic
39
- - `lib/FontTracerPool.js` — Manages puppeteer browser pool for parallel tracing
41
+ - **harfbuzzjs** for WOFF2 subsetting (via direct WASM calls in `src/subsetFontWithGlyphs.ts`)
42
+ - `src/collectTextsByPage.ts` — Font text collection and tracing orchestration
43
+ - `src/subsetGeneration.ts` — Subset generation with disk caching
44
+ - `src/FontTracerPool.ts` — Worker thread pool for parallel font tracing
40
45
 
41
46
  ## Testing Notes
42
47
 
43
48
  - Tests have a 5-minute timeout (configured in `.mocharc.yml`)
44
49
  - Tests use `httpception` for HTTP mocking and `unexpected` for assertions
45
50
  - Some tests require puppeteer browser binaries (installed via `pnpm install`)
46
- - Coverage thresholds are enforced via `nyc check-coverage`
51
+ - Coverage thresholds: branches 70%, lines 80%, functions 75%, statements 80%
52
+ - Coverage excludes: `lib/cli.js`, `lib/fontConverterWorker.js`, `lib/fontTracerWorker.js`
47
53
 
48
54
  ## Conventions
49
55
 
50
- - CommonJS modules (`require`/`module.exports`), not ESM
56
+ - TypeScript source in `src/`, compiled to CommonJS in `lib/`
51
57
  - Node.js >= 18 required
52
58
  - Use `const` by default; `let` only when reassignment is needed
53
59
  - Template literals preferred over string concatenation
60
+
61
+ ## Subset-size efficiency improvements
62
+
63
+ Whenever you change anything that affects subset output bytes (new
64
+ `hb_subset_input_set` knob, new `DROP_TABLE_TAGS` entry, new flag, new
65
+ gating heuristic, etc.):
66
+
67
+ 1. Re-run `pnpm run build && node scripts/bench-readme.js` (compares
68
+ against the `subset-font` package upstream Munter/subfont uses) and
69
+ paste the printed Markdown rows into the README's "Upstream subfont
70
+ vs `@turntrout/subfont`" table, replacing the previous numbers.
71
+ 2. Add the new optimization to the README's optimization-techniques table
72
+ (one row per knob: name, one-line explanation, gating condition if any).
73
+ 3. Bump `SUBSET_CACHE_VERSION` in `src/subsetGeneration.ts` if the change
74
+ affects bytes deterministically. Skip the bump if the change is purely
75
+ defensive (e.g. removing a no-op).
76
+ 4. Add a regression test in `test/subsetSizeBenchmarks.js` with a hard
77
+ upper bound on output size for at least one font that exercises the
78
+ new path. Bound values get bumped only after confirming a regression
79
+ isn't a real loss.
package/README.md CHANGED
@@ -2,26 +2,30 @@
2
2
 
3
3
  [![Build Status](https://github.com/alexander-turner/subfont/actions/workflows/ci.yml/badge.svg)](https://github.com/alexander-turner/subfont/actions/workflows/ci.yml)
4
4
 
5
- A faster fork of [subfont](https://github.com/Munter/subfont) that subsets web fonts to only the characters used on your pages. Adds parallel tracing, disk caching, woff2-only output, and always-on variable font instancing. On [`turntrout.com`](https://github.com/alexander-turner/TurnTrout.com) (382 pages, 20+ font variants), switching to this fork cut font subsetting from [111 minutes](https://github.com/alexander-turner/TurnTrout.com/actions/runs/23470135763) to [28 minutes](https://github.com/alexander-turner/TurnTrout.com/actions/runs/23518006824).
5
+ A faster fork of [subfont](https://github.com/Munter/subfont) that subsets web fonts to only the characters used on your pages. Adds parallel tracing, disk caching, woff2-only output, always-on variable font instancing, and is fully written in TypeScript (the upstream is JavaScript). On [`turntrout.com`](https://github.com/alexander-turner/TurnTrout.com) (382 pages, 20+ font variants), switching to this fork cut font subsetting from [111 minutes](https://github.com/alexander-turner/TurnTrout.com/actions/runs/23470135763) to [28 minutes](https://github.com/alexander-turner/TurnTrout.com/actions/runs/23518006824).
6
6
 
7
7
  ### Aggressive woff2 subsetting
8
8
 
9
- subfont produces dramatically smaller font files by stripping data that browsers never use:
9
+ `subfont` produces dramatically smaller font files by stripping data that browsers never use:
10
10
 
11
- | Optimization | Technique |
12
- | --------------------------- | ---------------------------------------------------------------------------------- |
13
- | Hinting removal | Strips TrueType hinting instructions (browsers auto-hint) |
14
- | Name table pruning | Keeps only the 4 IDs browsers read (family, subfamily, full name, PostScript name) |
15
- | Table stripping | Drops DSIG, LTSH, VDMX, hdmx, gasp, PCLT |
16
- | CSS-aware feature filtering | Only collects alternate glyphs for OpenType features actually used in your CSS |
11
+ | Optimization | Technique |
12
+ | ---------------------------- | --------------------------------------------------------------------------------------- |
13
+ | Hinting removal | Strips TrueType hinting instructions (browsers auto-hint) |
14
+ | Name table pruning | Keeps only the 4 IDs browsers read (family, subfamily, full name, PostScript name) |
15
+ | Name lang-ID filter | Keeps only en-US name strings; drops Japanese, Russian, Korean, etc. |
16
+ | Table stripping | Drops `DSIG`, `LTSH`, `VDMX`, `hdmx`, `gasp`, `PCLT` |
17
+ | MATH-table drop (gated) | Drops `MATH` when no math codepoints are used on the page |
18
+ | Color-table drop (gated) | Drops `COLR`/`CPAL`/`SVG `/`CBDT`/`CBLC`/`sbix`/`EBDT`/`EBLC`/`EBSC` when no emoji used |
19
+ | Layout-script filter (gated) | Drops GSUB/GPOS lookups for OpenType scripts the page doesn't render |
20
+ | CSS-aware feature retention | Drops GSUB/GPOS features the page's CSS doesn't reference |
17
21
 
18
- On the [`turntrout.com/design`](https://turntrout.com/design) page, a typical font subset (OpenSans, woff2) is **48-68% smaller** than a naive subset of the same glyphs:
22
+ Reproducible benchmark on `testdata/subsetFonts/OpenSans-400.ttf` (run with `node scripts/bench-readme.js`); "upstream" = the [`subset-font`](https://github.com/papandreou/subset-font) package the original [Munter/subfont](https://github.com/Munter/subfont) uses, woff2-compressed:
19
23
 
20
- | Text sample | Naive subset | subfont | Savings |
21
- | ----------------- | ------------ | ------- | ------- |
22
- | Heading (short) | 2,604 B | 824 B | **68%** |
23
- | Paragraph | 4,052 B | 1,840 B | **55%** |
24
- | Full page charset | 5,268 B | 2,716 B | **48%** |
24
+ | Text sample | Upstream subfont | `@turntrout/subfont` | Savings |
25
+ | ----------------- | ---------------- | -------------------- | ------- |
26
+ | Heading (short) | 2,604 B | 828 B | **68%** |
27
+ | Paragraph | 4,448 B | 2,072 B | **53%** |
28
+ | Full page charset | 9,388 B | 5,500 B | **41%** |
25
29
 
26
30
  ## Install
27
31
 
@@ -79,6 +83,12 @@ subfont path/to/index.html -i --cache
79
83
 
80
84
  Run `subfont --help` for the full list.
81
85
 
86
+ ### Environment variables
87
+
88
+ | Variable | Description |
89
+ | --------------------------- | ------------------------------------------------------------------------------ |
90
+ | `PUPPETEER_EXECUTABLE_PATH` | Path to a Chrome/Chromium binary; skips auto-download when `--dynamic` is used |
91
+
82
92
  To include extra characters in a specific font's subset, add `-subfont-text` to its `@font-face`:
83
93
 
84
94
  ```css
@@ -0,0 +1,38 @@
1
+ interface StylesheetWithPredicates {
2
+ text?: string;
3
+ asset?: {
4
+ text?: string;
5
+ };
6
+ predicates?: Record<string, unknown>;
7
+ }
8
+ interface FontTracerPoolOptions {
9
+ taskTimeoutMs?: number;
10
+ }
11
+ declare class FontTracerPool {
12
+ private _workerPath;
13
+ private _numWorkers;
14
+ private _taskTimeoutMs;
15
+ private _workers;
16
+ private _idle;
17
+ private _pendingTasks;
18
+ private _taskCallbacks;
19
+ private _taskTimers;
20
+ private _taskByWorker;
21
+ private _nextTaskId;
22
+ constructor(numWorkers: number, { taskTimeoutMs }?: FontTracerPoolOptions);
23
+ init(): Promise<void>;
24
+ private _onWorkerError;
25
+ private _clearTaskTimer;
26
+ private _onWorkerMessage;
27
+ private _onWorkerExit;
28
+ private _startTaskTimer;
29
+ private _dispatchPending;
30
+ /**
31
+ * Run fontTracer on the given HTML text + stylesheets in a worker.
32
+ * Returns a promise that resolves to textByProps.
33
+ */
34
+ trace(htmlText: string, stylesheetsWithPredicates: StylesheetWithPredicates[]): Promise<unknown>;
35
+ destroy(): Promise<void>;
36
+ }
37
+ export = FontTracerPool;
38
+ //# sourceMappingURL=FontTracerPool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FontTracerPool.d.ts","sourceRoot":"","sources":["../src/FontTracerPool.ts"],"names":[],"mappings":"AAkBA,UAAU,wBAAwB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAI1B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAqCD,UAAU,qBAAqB;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,cAAM,cAAc;IAClB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,KAAK,CAAW;IACxB,OAAO,CAAC,aAAa,CAAmC;IACxD,OAAO,CAAC,cAAc,CAA6B;IACnD,OAAO,CAAC,WAAW,CAA8B;IACjD,OAAO,CAAC,aAAa,CAAsB;IAC3C,OAAO,CAAC,WAAW,CAAS;gBAG1B,UAAU,EAAE,MAAM,EAClB,EAAE,aAAuC,EAAE,GAAE,qBAA0B;IAcnE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAyC3B,OAAO,CAAC,cAAc;IAgBtB,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,aAAa;IAsCrB,OAAO,CAAC,eAAe;IA2BvB,OAAO,CAAC,gBAAgB;IAsBxB;;;OAGG;IAGH,KAAK,CACH,QAAQ,EAAE,MAAM,EAChB,yBAAyB,EAAE,wBAAwB,EAAE,GAEpD,OAAO,CAAC,OAAO,CAAC;IAsBb,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CA4C/B;AAED,SAAS,cAAc,CAAC"}