Skip to content

feat: add Node ESM build: route the node import condition to a real Node bundle - #1778

Merged
blikblum merged 1 commit into
masterfrom
diegomura/helsinki
Aug 23, 2026
Merged

feat: add Node ESM build: route the node import condition to a real Node bundle#1778
blikblum merged 1 commit into
masterfrom
diegomura/helsinki

Conversation

@diegomura

@diegomura diegomura commented Aug 23, 2026

Copy link
Copy Markdown
Collaborator

Problem

The root export map only defines require under the node condition:

".": {
  "node": { "require": "./js/pdfkit.js" },
  "default": "./js/pdfkit.browser.mjs"
}

When a Node ESM consumer does import PDFDocument from 'pdfkit', the nested node object has no matching key, so resolution falls through to "default" — and modern Node users silently get the browser build. On Node that means:

  • Virtual fs instead of real fs: path-based APIs (doc.image('/path.png'), doc.font('/path.ttf'), doc.file('/path')) fail unless the file was pre-registered with registerFile.
  • fflate's pure-JS zlibSync instead of native zlib.deflateSync for every content stream (a measurable perf regression on large documents).
  • The custom browser Readable shim instead of Node streams.
  • Standard fonts are not self-registering (the browser build expects the consumer to call registerStdFonts).

Nothing guarded this: tests/package-resolution.cjs only exercises require('pdfkit'). (Extra context: react-pdf is converging onto upstream pdfkit and is a pure-ESM consumer, so it currently lands on the browser build in Node.)

Change

Add a Node ESM bundle (js/pdfkit.node.mjs) and wire it into the export map:

".": {
  "node": {
    "import": "./js/pdfkit.node.mjs",
    "require": "./js/pdfkit.js"
  },
  "default": "./js/pdfkit.browser.mjs"
}
  • The ESM bundle is a second output of the existing lib/document.node.js rollup entry, sharing its plugins, externals and babel config. #fs/#zlib/#stream are inlined via the node condition exactly as in the CJS bundle, with fs/zlib/stream external.
  • lib/document.node.js now gets its require from createRequire(import.meta.url) so the lazy standard-font loaders work in both output formats (rollup shims import.meta.url in the CJS output; the AFM data modules are still only loaded when a standard font is actually used).
  • Browsers/bundlers are unaffected: they don't match the node condition and keep resolving to js/pdfkit.browser.mjs via "default".

Tests

New tests/package-resolution.mjs, wired into npm run test:package:

  • Asserts import.meta.resolve('pdfkit') points at js/pdfkit.node.mjs.
  • Proves it's the Node build functionally: loads a font and an image by file path and ends the document cleanly (this fails on the browser build's virtual fs — verified).
  • Proves standard fonts self-register: a default-Helvetica document with text ends cleanly.
  • Proves laziness is kept: after using only Helvetica, no other standard-font data module is in the require cache.

tests/package-resolution.cjs previously used await import('pdfkit') to reach the browser bundle (the misresolution this PR fixes); it now asserts the dynamic import yields the Node ESM build and loads the browser ESM bundle directly by path to keep its coverage.

npm test (481 tests), npm run test:tools, npm run lint and npm run prettier all pass.

@diegomura diegomura changed the title Add Node ESM build: route the node import condition to a real Node bundle feat: add Node ESM build: route the node import condition to a real Node bundle Aug 23, 2026
@diegomura
diegomura requested a review from blikblum August 23, 2026 14:14
@diegomura

Copy link
Copy Markdown
Collaborator Author

After this I can start using pdfkit in react-pdf 😄 @blikblum I'd appreciate your review and re-publishing 🙏🏻 I think that will be big for this lib

@blikblum

Copy link
Copy Markdown
Member

Done. BTW i'm surprised that it will work on ESM in node (due to some dependencies not being ESM friendly)

@blikblum
blikblum merged commit 910c86a into master Aug 23, 2026
3 checks passed
@blikblum
blikblum deleted the diegomura/helsinki branch August 23, 2026 15:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants