Summary
Every vendored font asset is eagerly reachable from the module graph, so a consumer that only ever
reads PDFs still bundles ~2.79 MiB of font binaries it can never execute. On Cloudflare Workers,
where the free-plan limit is 3 MB gzipped for the entire Worker, this is the difference between the
package being adoptable and not.
Measurement
documents.js@1.100.1 (which depends on pdf-codec@2.2.32) installed clean, single entry point
importing only pdfToMarkdown, bundled with esbuild:
esbuild entry.mjs --bundle --minify --format=esm --platform=browser --target=es2023
|
minified |
gzipped |
| as published |
3.83 MiB |
2.17 MiB |
with dist/assets/*.js stubbed to empty Uint8Array exports |
1113 KiB |
300 KiB |
Per-module attribution from esbuild's metafile:
1052 KiB pdf-codec/dist/assets/stix-two-math-font.js
430 KiB pdf-codec/dist/assets/carlito-bolditalic.js
375 KiB pdf-codec/dist/assets/carlito-italic.js
374 KiB pdf-codec/dist/assets/carlito-bold.js
358 KiB pdf-codec/dist/assets/carlito-regular.js
57 KiB pdf-codec/dist/assets/caladea-bolditalic.js
56 KiB pdf-codec/dist/assets/caladea-italic.js
55 KiB pdf-codec/dist/assets/caladea-bold.js
54 KiB pdf-codec/dist/assets/caladea-regular.js
50 KiB pdf-codec/dist/afm-widths-*.js
--------
2790 KiB fonts + metrics
1063 KiB all actual code
So ~72% of the bundle is font data, on a path that never writes a PDF.
Cause
dist/font-registry.js and dist/math-font.js are the two modules that reference the assets, and
both import them eagerly at module scope. Because they sit on the same graph as the read path, the
assets survive tree-shaking even though sideEffects: false is set correctly and the consumer
imports one read-only function.
Deep-importing does not help — I tried routing around the barrel and got a byte-identical 2.17 MiB,
because the write path and read path share a module.
Ask
Make the font assets severable from the read path, by whichever of these fits the design best:
- Lazy asset imports —
await import() the face inside the resolver that needs it, so a
caller that never renders text never pulls the bytes. Changes the affected functions to async,
which may be unacceptable; noting it for completeness rather than recommending it.
- A parse-only entry point — e.g.
pdf-codec/read, whose graph excludes font-registry and
math-font entirely. Cheapest option, no API change for existing callers.
- Assets as a separate optional package —
@pdf-codec/fonts or similar, resolved through an
injectable font provider. Most invasive, but also makes "bring your own faces" a first-class
feature rather than a special case.
Option 2 alone gets a read-only consumer from 2.17 MiB to ~300 KiB gzipped, which is the outcome
that matters here.
Why it matters
A PDF-to-text/Markdown extraction path on Cloudflare Workers is a natural use of this package, and
300 KiB is a comfortable fit in that budget while 2.17 MiB is not. Reproducible entirely from the
published npm packages — no private code involved.
Summary
Every vendored font asset is eagerly reachable from the module graph, so a consumer that only ever
reads PDFs still bundles ~2.79 MiB of font binaries it can never execute. On Cloudflare Workers,
where the free-plan limit is 3 MB gzipped for the entire Worker, this is the difference between the
package being adoptable and not.
Measurement
documents.js@1.100.1(which depends onpdf-codec@2.2.32) installed clean, single entry pointimporting only
pdfToMarkdown, bundled with esbuild:dist/assets/*.jsstubbed to emptyUint8ArrayexportsPer-module attribution from esbuild's metafile:
So ~72% of the bundle is font data, on a path that never writes a PDF.
Cause
dist/font-registry.jsanddist/math-font.jsare the two modules that reference the assets, andboth import them eagerly at module scope. Because they sit on the same graph as the read path, the
assets survive tree-shaking even though
sideEffects: falseis set correctly and the consumerimports one read-only function.
Deep-importing does not help — I tried routing around the barrel and got a byte-identical 2.17 MiB,
because the write path and read path share a module.
Ask
Make the font assets severable from the read path, by whichever of these fits the design best:
await import()the face inside the resolver that needs it, so acaller that never renders text never pulls the bytes. Changes the affected functions to async,
which may be unacceptable; noting it for completeness rather than recommending it.
pdf-codec/read, whose graph excludesfont-registryandmath-fontentirely. Cheapest option, no API change for existing callers.@pdf-codec/fontsor similar, resolved through aninjectable font provider. Most invasive, but also makes "bring your own faces" a first-class
feature rather than a special case.
Option 2 alone gets a read-only consumer from 2.17 MiB to ~300 KiB gzipped, which is the outcome
that matters here.
Why it matters
A PDF-to-text/Markdown extraction path on Cloudflare Workers is a natural use of this package, and
300 KiB is a comfortable fit in that budget while 2.17 MiB is not. Reproducible entirely from the
published npm packages — no private code involved.