Adapted from the original article.
Disclosure: I maintain CompatLab, the source of these measurements. An AI agent drafted this article and checked its claims against the linked reports.
By CompatLab · October 5, 2026 · Updated October 13, 2026
Express loaded in all four runtime profiles. Preact’s root did too, while its server entries exposed missing prerequisites and uncertain results. 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.
The observations below come from CompatLab, an open-source loading-test harness. Each report identifies an exact npm artifact, dependency snapshot and set of runtime images. The cases show how optional peers, wildcard exports and a shared process can limit the conclusion you draw from a compatibility result.
Four packages, with the reports attached
Correction, October 12, 2026
The original article called Preact and the Convex agent “Mixed results.” Their retained reports are now inconclusive. The classifier distinguishes missing optional peers and failures after an earlier failed load in the same batch. In particular, the Convex /test results were not confirmed in fresh processes and do not establish a runtime-specific defect. The links below use the October 11 classifications of the same October 5 executions. Raw evidence and observation dates are unchanged; this was not a rescan.
These packages were tested on October 5, 2026. Each report 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.
October 5 observations with their October 11 classifications
| 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 · 13 missing peer · 2 unconfirmed · 1 failure without diagnostics | Inconclusive |
| zod@4.6.5 | 8 passed | 72 passed · wildcard omitted | Inconclusive |
| @convex-dev/agent@0.7.3 | 8 passed | 24 passed · 6 missing peer · 8 unconfirmed · 2 failures without diagnostics | Inconclusive |
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 server entries need context
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 non-passing entries were preact/compat/server and preact/compat/server.browser.
Thirteen subpath checks 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 remaining three checks are in Bun: one import failure has no captured diagnostic, and two later batch failures have no fresh-process confirmation. Their causes cannot all be attributed to the missing peer. The current overall result is inconclusive.
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. These observations describe this dependency snapshot; they do 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.
Does Zod load in Bun and Deno when the overall result is inconclusive?
In a separate execution on October 12, 2026 at 21:26 UTC, Zod 4.6.7 again passed all 80 planned loading operations. That includes the root import in Bun 1.4.2 and Deno 2.9.7, plus the root and explicit subpaths in Node.js 24.21.0 and 26.10.0. The unexpanded ./v4/locales/* pattern keeps overall coverage incomplete. It is not a recorded Bun or Deno loading failure.
Match your actual import before using the result: zod, zod/mini and zod/v4/core are different planned entries. A wildcard locale import needs its own check. In your application's pinned dependency installation, load the same path and test a representative valid input, invalid input, transform and locale you depend on. Those schema behaviors were not exercised by this report. These observations use Linux amd64/glibc with install scripts and execution networking disabled.
The Zod 4.6.7 version summary separates the selected checks from omitted exports. Check the artifact version before applying a result to a dependency upgrade.
Different error messages do not confirm an independent failure
In @convex-dev/agent 0.7.3, all root checks passed. Each subpath batch passed three entries and recorded two failures. For /react, Node and Deno identify a missing optional React peer; Bun captured no error message. The later /test entry failed in the same process and was not independently confirmed in any of the eight runtime/mode combinations. Those entries are now inconclusive.
Historical ESM batch errors for @convex-dev/agent/test; all unconfirmed
| Runtime | Recorded batch error, not an independently confirmed cause |
|---|---|
| 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. |
These messages remain useful investigation clues, but a shared batch’s earlier failure can affect later loads. They cannot establish how this entry behaves in a fresh process or which runtime is more compatible. Test the documented entry with its required tooling and dependencies in isolation before drawing that conclusion. The package root passed; its application behavior was not tested.
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.
Apply the evidence to your application
Before using a loading result in a migration decision, write down the exact package version, import path and loading mode your application uses. Compare those with the observed entries and the omitted coverage. A pass for the package root does not establish whether an untested server adapter or locale import loads.
Reproduce the relevant import in your application's pinned installation, including its optional peers and native prerequisites. Then run a test of the behavior you need: a middleware request for Express, server rendering for Preact, or representative valid and invalid inputs for Zod. The loading reports above did not exercise those behaviors.
Keep the observation date, runtime versions and dependency identity with your test notes. A later classification can change the interpretation without running the package again; a new execution can use a different dependency snapshot even when the top-level package version stays the same. Those are different reasons for a result to change.
For a failed subpath in a shared batch, test that entry in a fresh process before attributing the failure to the runtime. For an omitted export, record the question as unanswered until you test it. Those two steps avoid treating uncertain evidence as a compatibility defect.
Top comments (0)