mrkeyoor.com_
Sat 08 Aug 22:49 UTC
npmWeb Frontendupdated 08 Aug 2026

exenv

exenv is a tiny CommonJS-era feature detector extracted from React's old private ExecutionEnvironment module. Requiring it gives you four booleans: whether a DOM, Worker constructor, event listener API, and screen-backed viewport were present when the file first ran. It does not identify browsers, detect Node.js directly, polyfill missing APIs, or update its answers after import. Its original purpose was keeping packages from reaching into React internals when deciding whether browser-only code was safe during server rendering.

Verdict

Keep it when an older dependency graph already speaks its four-flag API. For new code, direct capability checks or a maintained environment classifier are easier to type, audit, and reason about.

API stability5/5The public surface is four boolean properties and has not changed since version 1.2.0 in 2015; version 1.2.2 still exports the same object shown in the README. That makes existing behavior highly predictable, although the stability comes from inactivity rather than a stated compatibility policy or a maintained release process.
Docs2/5The README clearly lists every property, gives the installation command, and explains why React's private module was extracted. It does not document ESM or TypeScript interop, module-load caching, jsdom test ordering, exact false-positive boundaries, browser script usage, supported runtimes, or any test suite, so users must read the 39-line source for operational details.
Maintenance1/5npm shows version 1.2.2 was published on April 23, 2017, and GitHub reports the last repository push on November 12, 2018. The repository is not archived and has only five open issues and PRs combined, but there are no recent releases, commits, compatibility updates, or bundled type improvements to demonstrate active maintenance.
Ecosystem3/5The npm endpoint recorded 3,569,039 downloads for July 31 through August 6, 2026, which indicates substantial transitive use. GitHub has 233 stars and the package offers no plugin ecosystem, framework integration layer, or extension points. Much of that traffic can come from old dependency trees, so downloads should not be read as evidence of current community investment.

Use it if

  • You maintain an older CommonJS package that already expects React-style canUseDOM and related flags
  • You need one dependency-free guard before touching window, document, addEventListener, or screen
  • You must preserve behavior shared with old packages that depend on exenv rather than redesign their environment checks
Skip it if

Setup reality

Installation is only npm install exenv, and there are no runtime dependencies, peer dependencies, native builds, credentials, or config files. The catch is compatibility rather than installation. The package exposes CommonJS through a UMD wrapper, so modern ESM and TypeScript projects may need a default import interop setting, createRequire, or a local declaration instead of getting a clean named export. It bundles no type definitions. Its four booleans are snapshots created as soon as index.js executes. If a test imports exenv before installing jsdom globals, canUseDOM stays false even after window is added; reset the module cache or install the environment before importing. The reverse also applies if globals disappear. canUseWorkers is independent of canUseDOM and checks only the global Worker name, while event listeners and viewport require a usable DOM first. This is capability detection, not browser detection, and it should not be used to make security decisions. Browser script-tag loading creates window.ExecutionEnvironment, AMD loaders get define(), and Node gets module.exports, but the README documents only the require form. There is no build step or ongoing configuration to fix these limits because version 1.2.2 is effectively frozen.

Patterns

Guard browser-only DOM workguard-dom-access

const { canUseDOM } = require('exenv');

if (canUseDOM) {
  const root = document.getElementById('app');
  root?.classList.add('ready');
}

The flag prevents direct document access during Node.js rendering, but it reflects the globals present when exenv was first required.

Choose a server-rendering fallbackchoose-ssr-fallback

const ExecutionEnvironment = require('exenv');

const viewportWidth = ExecutionEnvironment.canUseDOM
  ? window.innerWidth
  : 1024;

A false value means no usable DOM was detected; it does not prove that the runtime is Node.js or that 1024 is the real client width.

Create a Web Worker only when exposedcheck-workers

const { canUseWorkers } = require('exenv');

const worker = canUseWorkers
  ? new Worker('/search-worker.js')
  : null;

This only checks whether a global named Worker exists. It does not test construction, URL support, Content Security Policy, or Node worker_threads.

Attach a global listener conditionallycheck-event-listeners

const { canUseEventListeners } = require('exenv');

if (canUseEventListeners) {
  window.addEventListener('resize', onResize);
}

The source accepts either addEventListener or old attachEvent, but this snippet intentionally uses the modern API after the guard.

Read screen information safelycheck-viewport

const { canUseViewport } = require('exenv');

const screenSize = canUseViewport
  ? { width: window.screen.width, height: window.screen.height }
  : null;

canUseViewport means window.screen was truthy. It says nothing about visualViewport, layout viewport dimensions, or measurement accuracy.

Load a DOM-dependent module only in the browserdefer-browser-module

const { canUseDOM } = require('exenv');

const editor = canUseDOM
  ? require('./browser-editor')
  : require('./editor-placeholder');

The conditional must surround require itself. Importing the browser-only module at the top level can fail before canUseDOM is checked.

Set up jsdom before importing exenv in a testinstall-jsdom-first

const { JSDOM } = require('jsdom');
const dom = new JSDOM('<!doctype html>');

global.window = dom.window;
global.document = dom.window.document;

const { canUseDOM } = require('exenv');
console.assert(canUseDOM === true);

Import order is part of the behavior because Node caches the computed export. Requiring exenv before these assignments leaves the value false.

Refresh the cached result between CommonJS testsrefresh-test-snapshot

const modulePath = require.resolve('exenv');

delete require.cache[modulePath];
const environment = require('exenv');

console.log(environment.canUseDOM);

Clearing application module caches can have side effects. Prefer installing test globals before imports or isolating each environment in a separate process.

Load the CommonJS package from Node ESMuse-from-esm

import { createRequire } from 'node:module';

const require = createRequire(import.meta.url);
const { canUseDOM } = require('exenv');

There is no native ESM entry or named-export declaration. Some bundlers synthesize imports, but createRequire is the explicit Node.js route.

Describe the export locally in TypeScriptnarrow-local-type

type ExecutionEnvironment = {
  canUseDOM: boolean;
  canUseWorkers: boolean;
  canUseEventListeners: boolean;
  canUseViewport: boolean;
};

const env = require('exenv') as ExecutionEnvironment;

Version 1.2.2 does not bundle TypeScript declarations. Check whether your project already receives community declarations before adding a duplicate module declaration.

Alternatives

PackageRegistryPick it when
can-use-domnpmYou want a single-purpose CommonJS DOM availability check with an even smaller API
browser-or-nodenpmYou need to distinguish browser, Node.js, Web Worker, jsdom, and Deno environments
is-browsernpmYou only need a simple browser boolean and accept another small legacy package