Supporting Different JavaScript Runtimes (Node.js, Deno, Bun) with the Same API

@huggingface/transformers automatically detects whether it is running on Node.js, Deno, or Bun at import time and configures filesystem access, caching, and model loading accordingly, exposing a single unified API across all three runtimes.

Transformers.js is engineered to execute unmodified on the three major JavaScript server runtimes while maintaining an identical public interface. Whether you import { pipeline } from a Node.js script, a Deno module, or a Bun file, the library's internal runtime detection layer in src/env.js abstracts away platform-specific globals like process, Deno, and Bun so that downstream code never needs conditional branching.

Runtime Detection at Import Time

The foundation of universal compatibility lies in feature checks that execute immediately when the module loads. In packages/transformers/src/env.js, the library defines boolean flags by probing global objects:

const IS_DENO_RUNTIME = typeof globalThis.Deno !== 'undefined';
const IS_BUN_RUNTIME  = typeof globalThis.Bun  !== 'undefined';
const IS_PROCESS_AVAILABLE = typeof process !== 'undefined';
const IS_NODE_ENV = IS_PROCESS_AVAILABLE && process?.release?.name === 'node' && !IS_DENO_WEB_RUNTIME;

These constants are exported through a frozen apis object, allowing internal modules to query the execution context without re-evaluating the checks:

export const apis = Object.freeze({
    IS_DENO_RUNTIME,
    IS_BUN_RUNTIME,
    IS_NODE_ENV,
    // …other flags
});

The detection logic distinguishes Node.js by verifying process.release.name, Deno by checking globalThis.Deno, and Bun by testing globalThis.Bun. A special case, IS_DENO_WEB_RUNTIME, identifies when Deno operates in a browser-like environment where filesystem APIs are unavailable.

Runtime-Aware Configuration Defaults

Rather than forcing developers to manually configure environment variables, the env singleton in src/env.js uses the detection flags to set sensible defaults for filesystem access, local model loading, and caching strategies:

  • allowLocalModels – Enabled automatically when a filesystem is available (Node.js, Deno CLI, Bun), but disabled in browser or Deno Web environments.
  • useFS – Mirrors filesystem availability across runtimes.
  • useBrowserCache – Activated only when the Web Cache API is present (Deno Web, browsers).
  • useWasmCache – Enabled whenever either filesystem or Cache API storage is available.

The default cache directory (DEFAULT_CACHE_DIR) resolves to a valid path only when IS_FS_AVAILABLE is true, remaining null in ephemeral environments. This conditional setup ensures that Node.js and Bun default to disk-based caching, while Deno running with Web APIs utilizes the Cache API instead.

Unified Public API Design

All runtime-specific complexity is hidden behind the env configuration object. End users interact with a plain JavaScript object regardless of the underlying platform:

import { env, pipeline } from '@huggingface/transformers';

// Disable remote model fetching – works identically on all three runtimes
env.allowRemoteModels = false;

// Set a custom cache directory – effective only where filesystem access exists
env.cacheDir = '/tmp/transformers-cache';

Internally, modules such as src/utils/model-loader.js consult env and apis to decide whether to invoke fs.readFile, fetch, or the Cache API when retrieving model weights. This abstraction guarantees a single source of truth for configuration while permitting runtime-specific optimizations.

Cross-Runtime Module Resolution

Handling ESM and CommonJS differences across runtimes requires a safe wrapper that resolves the library's own directory without breaking on bundlers. The code in src/env.js uses a defensive pattern:

let dirname__ = './';
if (RUNNING_LOCALLY) {
    const _import_meta_url = Object(import.meta).url;
    if (_import_meta_url) {
        dirname__ = path.dirname(path.dirname(url.fileURLToPath(_import_meta_url))); // ESM
    } else if (typeof __dirname !== 'undefined') {
        dirname__ = path.dirname(__dirname); // CommonJS fallback
    }
}

The Object(import.meta).url syntax prevents Webpack and similar bundlers from inlining import.meta in CommonJS builds, while the __dirname fallback supports Node.js CommonJS modules and Bun's dual-mode support. Since Deno exclusively supports ESM, it follows the first branch automatically.

Runtime-Agnostic WASM Caching

The ONNX Runtime backend preloads WebAssembly binaries through a unified caching layer defined in src/backends/utils/cacheWasm.js. The system consults env.useWasmCache, env.useBrowserCache, and env.useFSCache to determine storage strategy:

  • Node.js and Bun cache WASM binaries to the local filesystem.
  • Deno Web and browsers cache via the Cache API.
  • Deno CLI uses filesystem storage like Node.js.

All implementations share the same cache key (env.cacheKey), ensuring that the precompiled binary is reused efficiently regardless of whether the code executes in a server or browser-like context.

Code Examples

Node.js Example

// node-example.mjs
import { env, pipeline } from '@huggingface/transformers';

// Node.js has filesystem access, so local caching works by default
env.cacheDir = './.cache';

const generator = await pipeline('text-generation', 'gpt2');
const output = await generator('Hello world');
console.log(output);

Deno Example


# Run with necessary permissions

deno run --allow-net --allow-read --allow-write deno-example.mjs
// deno-example.mjs
import { env, pipeline } from 'npm:@huggingface/transformers';

// Explicitly allow local models (Deno CLI has FS access)
env.allowLocalModels = true;
env.cacheDir = '/tmp/transformers-cache';

const generator = await pipeline('text-generation', 'gpt2');
console.log(await generator('Deno rocks!'));

Bun Example

// bun-example.mjs
import { env, pipeline } from '@huggingface/transformers';

// Force offline mode and use local model weights
env.allowRemoteModels = false;
env.localModelPath = './models/';

const summarizer = await pipeline('summarization', 'sshleifer/distilbart-cnn-12-6');
console.log(await summarizer('Bun is fast and modern!'));

Summary

  • Automatic detection in src/env.js identifies Node.js, Deno, and Bun by checking process.release.name, globalThis.Deno, and globalThis.Bun.
  • Conditional defaults for allowLocalModels, useFS, and caching methods eliminate manual configuration for each runtime.
  • Unified env API exposes a single configuration interface that works identically across all platforms.
  • Safe module resolution handles both ESM (import.meta.url) and CommonJS (__dirname) without bundler issues.
  • Runtime-agnostic WASM caching stores ONNX binaries via filesystem or Cache API depending on environment capabilities.

Frequently Asked Questions

How does Transformers.js detect which JavaScript runtime is executing the code?

The library performs feature checks at import time in packages/transformers/src/env.js by testing for runtime-specific globals: globalThis.Deno for Deno, globalThis.Bun for Bun, and process.release.name === 'node' for Node.js. These results are stored in the read-only apis object for internal reference.

Can I use the same import statement for Node.js, Deno, and Bun?

Yes. While Deno requires the npm: specifier (e.g., import from 'npm:@huggingface/transformers'), the public API surface—including the env configuration object and pipeline function—remains identical. All three runtimes support the same method signatures and configuration options.

Does local model loading work on all three runtimes?

Local model loading is automatically enabled for Node.js, Bun, and Deno CLI environments where filesystem access is available. However, when Deno runs in a Web-compatible mode without filesystem permissions, or in browser environments, env.allowLocalModels defaults to false to prevent errors.

How is caching handled differently across Node.js, Deno, and Bun?

The library adapts its caching strategy based on runtime capabilities. Node.js and Bun cache WASM binaries and model files to disk using the local filesystem. Deno uses the filesystem when running as a CLI tool, but switches to the Web Cache API when operating in browser-like environments where globalThis.caches is available.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →