Do npm packages work on Node.js, Bun and Deno? We built a tool to find out
By CompatLab ·
Express loaded in all four runtime profiles. Preact’s root did too, but two of its subpaths failed. Zod passed 80 loading checks and still received an inconclusive result. Each finding answers a different part of the compatibility question.
You have a Node.js application and want to try Bun or Deno. Before changing the start command, you need to know whether your dependencies will load. A package’s README may name Node versions, but it rarely covers the exact package release, import style and alternative runtime you intend to use.
We built CompatLab to collect that evidence. You select an exact published npm version, and we check its entry points across pinned Node.js, Bun and Deno runtimes. You can inspect the loading failures, coverage and environment in a public report. The engine is open source.
Four packages, with the reports attached
We reviewed these reports on October 5, 2026. Each uses Linux amd64 with glibc, Node.js 24.21.0, Node.js 26.10.0, Bun 1.4.2 and Deno 2.9.7. Install scripts stay disabled. Within each report, the runtimes read the same prepared dependency files.
| Package and report | Root checks | Subpath checks | Overall result |
|---|---|---|---|
| express@5.2.1 | 8 passed | None planned | Passed |
| preact@11.0.0 | 8 passed | 96 passed · 16 failed | Mixed results |
| zod@4.6.5 | 8 passed | 72 passed · wildcard omitted | Inconclusive |
| @convex-dev/agent@0.7.3 | 8 passed | 24 passed · 16 failed | Mixed results |
Eight root checks means one ESM import and one CommonJS require for each of the four runtime profiles. Counts measure loading operations, not distinct features. These selected examples are not a representative sample of npm and do not rank the runtimes. Reports also record exact image and dependency identities; a matching version number alone does not make two scans identical.
Express loads; your HTTP application still needs tests
In the Express 5.2.1 report, import and require passed on both Node.js versions, Bun and Deno. The planner selected no explicit executable subpaths, so the result covers those eight root operations.
That gives you a concrete first answer about npm package compatibility: this release resolved and evaluated in those environments. We did not start an Express server, send requests, exercise middleware or connect to a database. Before migrating an application, run its integration tests under the runtime you plan to deploy.
Preact’s missing peer explains a mixed result
Preact 11.0.0 passed its root checks across the matrix. Each runtime and loading mode then passed 12 of 14 subpath checks. The two failures were preact/compat/server and preact/compat/server.browser.
The retained errors identify a missing preact-render-to-string dependency. Preact’s published manifest declares it as an optional peer. The consumer workspace for this report installed Preact alone. A project that uses those server-rendering entry points needs to account for that peer.
The useful next step is to install the intended peer in your application and test its server-rendering path. We have not performed that follow-up in this report, so we cannot claim it resolves the full workflow. The observed failure affects this dependency snapshot on all four profiles; it does not establish a Bun-only or Deno-only defect.
Zod passes its checks, but the wildcard remains untested
Zod 4.6.5 passed both root modes and nine explicit subpaths per mode on all four profiles: 80 passed loading operations and zero recorded failures. CompatLab still labels the report inconclusive because the published export map also contains ./v4/locales/*.
The current planner checks explicit executable exports and records wildcard patterns as omitted coverage. We cannot claim complete coverage of that package’s exports from these observations. If your application imports a particular locale, include that import in your own tests. Passing the observed entries and covering all public entries are separate requirements.
The same failed subpath can produce different runtime errors
In @convex-dev/agent 0.7.3, all root checks passed. Each subpath batch passed three entries and failed two. The /react entry lacked React in this snapshot. The /test entry exposed a different distinction:
| Runtime | Recorded failure |
|---|---|
| Node.js 24.21.0 and 26.10.0 | Type stripping under node_modules is unsupported. |
| Bun 1.4.2 | import.meta.glob is not a function |
| Deno 2.9.7 | Type stripping under node_modules is unsupported. |
Bun reached a call to import.meta.glob; Node and Deno reported a TypeScript loading restriction first. Vite documents glob imports as a Vite-specific feature. This points toward a test-tooling assumption that a plain runtime loading check does not supply. It is not enough evidence to call the package’s application entry point broken, or to promise that changing runtimes fixes the test entry.
How we test Node, Bun and Deno compatibility
We prepare the published npm artifact with a pinned npm installer, retain its lockfile, then mount the same sealed dependency snapshot into each runtime’s sandbox. We disable lifecycle scripts during installation and networking during loading. The runtime profiles have bounded time, memory and output, with gVisor providing the execution boundary.
Each root mode starts in a fresh sandbox. Subpaths run in ordered batches and share that batch’s module cache and globals. Reports expose those choices alongside omitted entries, failures and exact runtime images. Read the methodology for the full scope and limits.
Import style deserves its own check. Node’s conditional exports let a package choose different entry files for import and require. A package name and a runtime name leave out that part of the experiment. Native binaries, optional peers and installation prerequisites can also change the result.
Deno npm compatibility also depends on the execution profile. Our Deno checks use an existing npm-prepared node_modules tree and -A inside the OS sandbox. They do not test Deno’s default permission prompts or compare its installer with npm. Consult Deno’s npm guide for the setup your application uses. Bun’s Node.js compatibility documentation describes its supported APIs; our reports add observations for an exact package.
Check the package and import path you use
If you arrived asking “does this npm package work with Bun?”, start with its exact version and loading mode. Search for it on CompatLab, open a completed report or request a scan, then inspect the root result and the subpath your application imports. Check the omitted coverage before treating an all-green row as a complete answer.
Keep the report URL with your migration notes. It identifies a recorded observation, while future scans can use different dependencies or runtime images. You can download the report JSON and lockfile. Local replay needs the source-built CLI, a qualified Linux/runsc host and the exact runtime images; hosted images currently require an operator to supply them. The report’s replay instructions explain those prerequisites.
We want examples that expose a useful difference: an entry that loads on one runtime and fails on another, a missing peer, or an export our planner should handle better. Share the package version, import path and report in the project’s issue tracker. Include the behavior you expected. That gives us something we can investigate.