expose-loader
A webpack loader that exposes a module's exports on window, self, or global for legacy, non-module code to consume.
Repository Health
Technical Analysis
expose-loader is a webpack loader maintained under the webpack-contrib umbrella that bridges modern, module-based JavaScript with legacy code expecting globals. When a build includes a script tag, a browser extension, or a third-party bundle that reaches for window.$ or globalThis.MyLib instead of importing a module, expose-loader rewrites the module at build time so its exports (in whole or via a named export) are assigned onto the global object under a chosen name.
It supports two usage styles: an inline query-string syntax (expose-loader?exposes=$,jQuery!jquery) for one-off cases, and a configuration-based module.rules entry for repeatable setups where a test targets a specific module (commonly resolved via Node’s require.resolve) and an exposes option lists one or more global names, optionally scoped to a single exported member via moduleLocalName, with an override flag to control whether an existing global value may be overwritten.
As of the current major version the project is explicitly marked deprecated in its own README: assigning a module to the global scope generally needs no loader at all — either a one-line globalThis.$ = ... assignment in code you own, or, for third-party dependencies you cannot edit, a small custom webpack plugin hooking NormalModule’s processResult (the README documents both alternatives, plus a link to webpack’s own expose-global example implementing the same idea). The loader itself still works and continues to receive maintenance, just not new features, so it remains a pragmatic option for existing configs or cases where editing a webpack plugin isn’t practical.
What You Get
- Inline and configuration-based syntaxes for exposing a module, so teams can choose a one-off query-string expose or a reusable
module.rulesentry. - Support for exposing an entire module or just a named export via
moduleLocalName, including nested global paths like_.map. - An
overrideoption that gates whether an expose is allowed to replace an existing value already present on the global object, with a development-mode error thrown when it isn’t set and a collision occurs. - A small
getGlobalThisruntime helper bundled into the output for environments without a nativeglobalThis, falling back throughwindow,self, andglobal. - JSON-schema-validated loader options (
src/options.json) enforced via webpack’s owngetOptions/validatepipeline, so misconfiguredexposesvalues fail fast with a clear error. - A documented migration path off the loader entirely for webpack users who no longer need it, including a copy-pasteable
ExposeGlobalPluginexample.
Common Use Cases
- Exposing jQuery or Lodash/Underscore as
$,_, or_.mapso legacy plugins, third-party widgets, or inline<script>tags loaded outside the bundle can find them onwindow. - Making a library available to the browser DevTools console for debugging in development builds without importing it manually.
- Bridging a bundle built with webpack into an environment (a CMS theme, a browser extension content script, an embed snippet) that still expects certain globals to exist.
- Migrating a legacy codebase incrementally onto webpack while keeping older non-module scripts functional during the transition.
Under The Hood
Architecture
The loader is a single default export (src/index.js) registered by webpack per matched module. It reads and JSON-schema-validates its options via this.getOptions(schema) against src/options.json, normalizes the exposes option (string, object, or array forms) through getExposes/resolveExposes in src/utils.js, and then emits replacement JavaScript as a template string: a require() of the original module rewritten to a relative, context-safe path via contextify/stringifyRequest, followed by one assignment statement per requested global name, walking and creating any intermediate nested objects (e.g. _.map) and optionally guarding against overwriting an existing global. The final module.exports = ___EXPOSE_LOADER_IMPORT___ line ensures normal module consumers still get the same export as before the loader ran.
Tech Stack
Written in modern JavaScript, transpiled with Babel (@babel/cli, @babel/preset-env) to a dist/cjs.js entry point for CommonJS consumption; its only runtime coupling is to webpack’s loader-context API (this.getOptions, this._module, this.async, this.utils.contextify) rather than any external npm dependency — the package ships with zero runtime dependencies, only a webpack peer dependency, keeping its footprint minimal. Linting runs through ESLint’s flat config plus Prettier, cspell for spelling, and es-check to confirm the bundled runtime helper stays ES5-compatible for older targets.
Code Quality
Tests run under Jest against a real webpack compiler (test/helpers/compile.js builds a getCompiler harness), exercising the loader end-to-end across dozens of fixture modules covering CommonJS, CommonJS2, and ES module shapes, nested global paths, overrides, and side-effect handling, with results checked against stored Jest snapshots for module source, execution output, and compiler warnings/errors. A separate validate-options.test.js snapshot suite specifically covers the options schema’s rejection paths. Combined with npm audit, commit linting via commitlint, and a GitHub Actions workflow running lint and coverage, this reflects a well-tested, actively maintained pattern typical of webpack’s official loader ecosystem.
What Makes It Unique
Unlike most webpack loaders, which transform source into new modules for further bundling, expose-loader’s job is explicitly a side-effecting bridge out of the module system, and the project’s own README candidly documents its emerging obsolescence, walking through exactly how webpack’s NormalModule compilation hooks let you replicate its behavior in a few lines of custom plugin code. That transparency about its own replaceability, backed with a working reference implementation, is unusual for a widely-used utility package and reflects webpack-contrib’s broader push to shrink the loader ecosystem down to what genuinely still needs to be a loader.
Used by 4 apps in this directory
Bitwarden Server
Password Manager · Security
Self-hosted, open-source password management backend with zero-knowledge encryption and enterprise-grade identity services
Craft CMS
CMS
A developer-first PHP CMS with clean-slate content modeling, auto-generated GraphQL API, and a four-tier edition system that scales from solo projects to enterprise deployments.
Grafana
Monitoring · Analytics
The open-source observability platform that unifies metrics, logs, and traces from any data source into dynamic, queryable dashboards.
Zulip
Team Chat
Topic-based team chat that brings the structure of email threads to real-time messaging, so distributed teams never lose context across hundreds of concurrent conversations.