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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions