Skip to content

@resvg/resvg-js missing from the browser field breaks browser bundlers since 7.11.0 #421

Description

@wiso

Summary

7.11.0 added @resvg/resvg-js as the Node PNG backend, but did not add a matching
"@resvg/resvg-js": false entry to the browser map in package.json. It is now the only
runtime dependency with a Node-only import site and no browser mapping, so browser bundlers
resolve it for real and fail on the prebuilt native addon.

Reproduction

mkdir jsroot-vite-repro && cd jsroot-vite-repro
npm init -y && npm pkg set type=module
npm i jsroot@7.11.1 && npm i -D vite@^6
mkdir src && echo "import { openFile, draw } from 'jsroot'; export { openFile, draw };" > src/main.js
cat > vite.config.js <<'CFG'
import { defineConfig } from 'vite';
export default defineConfig({ build: { lib: { entry: 'src/main.js', formats: ['es'], fileName: 'main' } } });
CFG
npx vite build

Result (vite 6.4.3, rollup, node 20+, linux x64)

[plugin vite:resolve] Module "fs" has been externalized for browser compatibility,
  imported by ".../node_modules/@resvg/resvg-js/js-binding.js"
  (same for "path" and "child_process")
✓ 377 modules transformed.
✗ Build failed
[commonjs--resolver] node_modules/@resvg/resvg-js-linux-x64-gnu/resvgjs.linux-x64-gnu.node (1:0):
Unexpected character '\u{7f}' (Note that you need plugins to import files that are not JavaScript)

7.10.0 and earlier build cleanly. Bisects to the dependency added in 7.11.0.

Cause

modules/base/BasePainter.mjs (~L893) does import('@resvg/resvg-js') inside if (isNodeJs()).
The runtime guard is invisible to the bundler — the specifier is a static string literal, so
Rollup/Vite resolves and bundles it regardless of reachability. Every other Node-only import
(canvas, jsdom, tmp, xhr2, mathjax, fs) is stubbed via the browser map and
therefore resolves to an empty module; @resvg/resvg-js is not in that map.

Suggested fix

   "browser": {
     "canvas": false,
+    "@resvg/resvg-js": false,
     "jsdom": false,

Verified locally: patching node_modules/jsroot/package.json this way makes the build above
succeed, and the import site resolves to __vite-browser-external, matching how canvas and
jsdom are already handled. Node behaviour is unaffected — the browser field is only read by
browser bundlers.

master (7.11.99) still lacks the entry. A CI check diffing dependencies against browser
keys would prevent the next recurrence:

import json
p = json.load(open('package.json'))
missing = [k for k in p['dependencies'] if k not in p['browser'] and k != 'three']
assert not missing, missing

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions