Strategies per environment

One resolution and delivery model, consumed four ways: browsers with native ES modules or SystemJS, Node.js through BEE Node and Deno through its import map, with the development updates that work in all of them and the local measurements behind the defaults.

  • Availability: Experimental
  • Evidence: Recorded run
  • Explanation

One model

Every origin of the delivery contract answers the same two things: the modules at their /m/… addresses, and the resolution that says which address delivers each public specifier, at /resolution.json and /importmap.json. An origin is a development server, a Workspace environment or the base of one CDN release (/_r/<release number>).

An environment does not need a delivery of its own. It needs a way to consume those two documents: turn a bare specifier into an address, fetch it from that origin, and run it. That is the only part that changes from one environment to another.

The strategies

Environment How it resolves Which outputs
Browser, native ES modules (default) An import map inline in the page; the eager modules are preloaded target=browser&format=esm
Browser, SystemJS SystemJS 6.15.1, stored with the release as loader.js, reading a systemjs-importmap target=browser&format=system
Node.js BEE Node's resolution adapter reads resolution.json of the base target=node&format=esm
Deno Deno reads importmap.json of the base natively target=node&format=esm, or platform-neutral browser outputs through the browser map

A release chooses its browser loader when it is prepared (loader of the application: esm or system). Node.js and Deno use the backend target of the application, whose outputs and resolution a release stores as well. See Backend targets.

Browsers

Native ES modules are the default. The shell of a release inlines its import map with release-bound addresses and announces the eager modules with <link rel="modulepreload">, so the browser fetches them in parallel. See Native ESM and import maps.

SystemJS is the mode for environments without import maps. The release stores SystemJS as its own loader.js, so no page reaches a third-party origin. See SystemJS.

In both, stylesheets are applied explicitly: the document links them, a widget adopts them in its shadow root. See Who applies a stylesheet.

Node.js

BEE Node loads Beyond modules into Node.js through module hooks. Its resolution adapter is given a base, reads <base>resolution.json?target=node&format=esm once when the process starts, and resolves every bare specifier of a served module through it, scopes included:

Shell
BEE_URL=https://<application host>/_r/12/ BEE_ADAPTER=resolution BEE_CACHE=.beyond/cache \
  node --import @beyond-js/bee-node/register app.mjs
  • A module is fetched only from under the base. A specifier the document does not resolve is an error naming the specifier and the importer; a Node builtin goes to Node.
  • Your own script keeps Node's resolution for your installed packages; the document answers first.
  • Responses are kept for the process. With BEE_CACHE, immutable answers are also kept on disk and reused without a request, which is what a release base answers: a process that starts again with a warm cache makes no request at all. A development server answers no-store, so it is asked on every start.
  • A handshake that fails stops the process, naming the adapter. Nothing falls back to another adapter.

Deno

Deno reads the import map of the base directly:

Shell
deno run --allow-import=<application host> \
  --import-map='https://<application host>/_r/12/importmap.json?target=node&format=esm' app.mjs

Its addresses are relative to the map's URL, so Deno requests every module from the base. Node-target outputs run, node: builtins included; a platform-neutral module of a browser target runs through the browser map (?target=browser&format=esm). Deno needs --allow-import for the delivery host.

During development

A development server answers the same /resolution.json and /importmap.json, computed from the workspace, and format=system as well. The same strategies therefore work against it, with the development options.

The development runtime (@beyond-js/local-2026, a provisional name) applies updates with one mechanism in all four environments: it replaces the internal modules whose hash changed and keeps the state of the others. Each environment only supplies how it imports an update (native import(), or SystemJS's own import in a SystemJS page) and whether it has a document for stylesheets.

  • Where notifications come from. By default the service's event stream, <origin>/events. Any emitter that speaks the same event protocol can replace it, at another URL or as an object in the same process; Workspace is one such emitter. local.hmr.notify(event) delivers a notification by hand. The updates themselves are always requested from the origin.
  • Failures keep the last valid state. A source that does not compile changes nothing and its correction is applied; code that throws when evaluated fails that update and the next one evaluates it again; a stylesheet that is invalid or fails to load keeps the last valid one.
  • A consumer that missed events is stale. After the service restarts, the runtime reports stale and applies nothing more: restart the consumer, or reload the page.

This was executed in Chrome with native ES modules and with SystemJS, in Node.js through BEE Node and in Deno, with the service's own stream, an external emitter and notify().

Measurements

Measured on 2026-09-23 on one development machine under heavy shared load, on a loopback network, with releases prepared by the real pipeline; medians of five runs. In Chrome a 40 ms round trip was emulated on every request. Compare the variants with each other; these are not hosted numbers.

Case, Chrome, cold start Ready, no preload Ready, preload Transferred
React, native ESM 244 ms 147 ms 233.9 kB
React, SystemJS 247 ms 148 ms 453.2 kB
Four widget families, native ESM 429 ms 335 ms 577.4 kB
Four widget families, SystemJS 432 ms 345 ms 981.3 kB
Case, a React server renderer Cold Warm
Node.js 22, BEE Node resolution adapter 89 ms, 5 requests 41 ms, no request
Deno 2.9.7, --import-map 55 ms, 5 requests 19 ms, no request

The defaults follow from them:

  • Preload is on. Announcing the eager modules started a cold page 23–40 % sooner once a round trip exists; on loopback it cost nothing measurable.
  • Native ES modules stay the default. SystemJS was as fast here but transferred 1.7–1.9 times the bytes, because its conversion reprints the minified output.
  • Import maps are inline in the shell: 1.1 to 6.1 kB in these cases, one request fewer than a map loaded with src.
  • Node.js processes that start often use BEE_CACHE: a warm cache starts without a request and in half the time.

Another environment

A new environment is a new consumer of the same two documents, not a new delivery. It needs to read resolution.json or importmap.json of an origin, fetch modules only from that origin, and run ES modules or System.register modules. For development updates it gives the runtime its import function and, where it has one, a document.