From 39841d9ffb75a80a54d672e9f17b01281384de23 Mon Sep 17 00:00:00 2001 From: Artemis Mendrinos Date: Wed, 26 Aug 2026 09:55:56 +0800 Subject: [PATCH] fix: resolve `#standard-fonts/*` to one file under every condition The node ESM build loads the standard font metrics lazily through `createRequire`, so the require condition is the only one ever taken at runtime. Bundlers and file tracers walk `js/pdfkit.node.mjs` as ESM and resolve the same specifiers under the import condition, landing on the `.mjs` twins instead. Anything that packages a traced dependency set ships the modules pdfkit never loads and omits the ones it does, so the first `new PDFDocument()` throws `Cannot find module`. Nothing reachable at runtime uses the import condition here: the browser builds register their fonts through `registerStdFonts` and never reference `#standard-fonts`, and both node builds arrive via `require`. Point the internal mapping at the CommonJS files so every resolver agrees with Node. The public `./standard-fonts/*` export is untouched, so consumers importing a standard font by name still get the ESM build. --- CHANGELOG.md | 1 + package.json | 5 +---- tests/package-resolution.mjs | 24 ++++++++++++++++++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b27cdb0..a2611873 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Unreleased +- Fix bundlers and file tracers packing the ESM copies of the standard font metrics instead of the CommonJS ones the Node build actually loads, which left `Cannot find module` errors for every standard font at runtime, by resolving the internal `#standard-fonts/*` mapping to a single file under all conditions - Fix `doc.file()` throwing when the same in-memory attachment is embedded twice under one name, because the creation and modified dates the deduplication check compares are absent for sources that are not read from disk ### [v0.20.1] - 2026-08-23 diff --git a/package.json b/package.json index 5d4dd8c9..fcf2dc27 100644 --- a/package.json +++ b/package.json @@ -154,10 +154,7 @@ "node": "./lib/stream/node.js", "default": "./lib/stream/browser.js" }, - "#standard-fonts/*": { - "require": "./js/standard-fonts/*.cjs", - "default": "./js/standard-fonts/*.mjs" - } + "#standard-fonts/*": "./js/standard-fonts/*.cjs" }, "engine": [ "node >= v20.0.0" diff --git a/tests/package-resolution.mjs b/tests/package-resolution.mjs index 1f3b5a49..8887976a 100644 --- a/tests/package-resolution.mjs +++ b/tests/package-resolution.mjs @@ -5,6 +5,7 @@ import PDFDocument, { LineWrapper, registerFile } from 'pdfkit'; import { toBytes } from 'pdfkit/output'; const require = createRequire(import.meta.url); +const packageJson = require('../package.json'); assert.equal( import.meta.resolve('pdfkit'), @@ -40,6 +41,29 @@ assert.equal( false, ); +// The node ESM build reaches the standard fonts through createRequire, so the +// require condition is the only one ever taken at runtime. Bundlers and file +// tracers walk this same file as ESM and resolve `#standard-fonts/*` under the +// import condition instead: if the two point at different files, the tracer packs +// the modules that are never loaded, omits the ones that are, and the bundle +// throws `Cannot find module` on the first document. Keep every standard font +// resolving to the one file the runtime uses, under both conditions. +const standardFonts = Object.keys(packageJson.exports) + .filter((entry) => entry.startsWith('./standard-fonts/')) + .map((entry) => entry.slice('./standard-fonts/'.length)); + +assert.equal(standardFonts.length, 14); + +for (const font of standardFonts) { + const expected = new URL(`../js/standard-fonts/${font}.cjs`, import.meta.url) + .href; + assert.equal(import.meta.resolve(`#standard-fonts/${font}`), expected); + assert.equal( + require.resolve(`#standard-fonts/${font}`), + fileURLToPath(expected), + ); +} + const fileDocument = new PDFDocument(); const fileOutput = toBytes(fileDocument); fileDocument.font(