esm-env
Zero-dependency export-conditions library that resolves BROWSER, DEV, and NODE constants at build time instead of runtime.
Repository Health
Technical Analysis
esm-env is a tiny, zero-dependency package that exposes three boolean constants — BROWSER, DEV, and NODE — describing the environment code is running in. Instead of checking typeof window or process.env.NODE_ENV at runtime, it relies on package.json export conditions so bundlers like Vite, Rollup, and webpack (and runtimes like Node.js and Bun) resolve the correct value at build or import time, letting dead code elimination strip the unused branch entirely.
It ships with fallback files for bundlers that don’t set matching conditions, so consumers get a sane default (checking for a browser-like global for BROWSER, inspecting NODE_ENV for DEV) rather than an error. The library backs environment detection inside tools including Vite and SvelteKit, where correctly discriminating between server and client, or dev and prod, at build time avoids bundling unnecessary code paths.
What You Get
- BROWSER constant - a boolean resolved via the
browserexport condition, true when bundled for a browser environment. - DEV constant - a boolean resolved via the
developmentexport condition, true unless the bundler or runtime explicitly sets a production condition. - NODE constant - a boolean resolved via the
nodeexport condition, true when running under Node.js’s own conditional-exports resolution. - Runtime fallbacks -
browser-fallback.jsanddev-fallback.jsinfer a reasonable value for bundlers that don’t set the matching export condition, instead of erroring. - Zero runtime dependencies - the entire package is a handful of one-line ESM files with no dependency tree to audit or bloat a bundle.
Common Use Cases
- Stripping dev-only code from production bundles - libraries wrap debug logging or prop-type checks in
if (DEV)so bundlers dead-code-eliminate them from production builds. - Universal libraries branching on browser vs. server - packages that behave differently in SSR vs. the browser import
BROWSERinstead of feature-detectingwindowat runtime, which fails at build-time static analysis. - Framework internals like Vite and SvelteKit - build tools use
esm-envto select environment-specific behavior at bundle time, keeping the decision static and tree-shakeable. - Verifying bundler configuration across tools - the repo’s own
playgrounds/rollupandplaygrounds/webpackprojects demonstrate configuringexportConditions/conditionNamesso consumers can check their own bundler resolves the intended constant.
Under The Hood
Architecture
esm-env’s architecture is entirely declarative, living in the package.json exports map for packages/esm-env rather than in runtime logic: index.js re-exports three named bindings (BROWSER, DEV, NODE) from three synthetic subpaths (esm-env/browser, esm-env/development, esm-env/node), and each subpath’s exports entry lists condition keys (browser, development, production, node, default) pointing at one-line files (true.js, false.js, browser-fallback.js, dev-fallback.js) that a bundler or runtime resolves at import/build time based on which conditions it declares. There is no dependency injection, shared state, or execution path beyond module resolution — the fallback files exist purely to give a sane default when the consumer’s bundler doesn’t set a matching condition. Extending the core abstraction would mean adding a new condition key and a corresponding fallback file, a well-contained, low-risk change given how few files the whole package comprises.
Tech Stack
The package is plain ESM JavaScript ("type": "module") with zero runtime dependencies, hand-written index.d.ts type declarations and no TypeScript build step, developed inside a pnpm workspace monorepo (esm-env-monorepo) alongside two throwaway test packages, playgrounds/rollup and playgrounds/webpack, which pin @rollup/plugin-node-resolve/rollup and webpack/webpack-cli purely to exercise export-condition resolution against real bundlers. Releases are automated via Changesets and a GitHub Actions workflow that runs pnpm changeset:publish with npm provenance on pushes to main, so there is no custom build pipeline for the published artifact — the source files are the published files.
Code Quality
There is no automated test suite in the repository — no test runner config and no test files were found; correctness of export-condition resolution is instead exercised manually through the two playground packages, which each import DEV and build a bundle to confirm the intended condition resolves. Error handling is minimal by necessity: the dev fallback uses optional chaining to avoid throwing when process is undefined, addressing a real bug noted in the 1.2.2 changelog (a fix for a warning when the environment couldn’t be determined). No linter or formatter configuration is present, and CI is limited to the release workflow rather than a lint or test gate.
API Design
The public API is about as low-friction as it gets: three self-explanatory named exports (BROWSER, DEV, NODE) with zero configuration objects, hooks, or setup calls — consumers import the constant they need and get a boolean. Type declarations ship inline so there’s no separate types package to install, and the README is a single concise page walking through exactly which conditions flag to add for Node, Bun, Vite/Vitest, and webpack. The one added-friction point for a first-time consumer is conceptual rather than syntactic: getting the intended value requires understanding and correctly configuring export conditions in one’s own bundler, a concept the library depends on but doesn’t teach from scratch.
Used by 3 apps in this directory
GitButler
Developer Tools · Devops · AI Development
Git, but better — a modern version control client with stacked branches, parallel workflows, unlimited undo, and first-class support for AI-powered development.
likec4
Developer Tools · Devops
Define your software architecture as code and get always up-to-date, interactive C4 diagrams generated automatically from a DSL.
Windmill
Automation · Developer Tools
Turn scripts into webhooks, workflows, and auto-generated UIs — the fastest self-hostable workflow engine, 13x faster than Airflow.