node-ensure
node-ensure is a compatibility shim for the old require.ensure approach to asynchronous module loading. On Node, it ignores the requested module names and runs your callback on setImmediate, after which ordinary require calls load the modules. When a bundler honors the package's browser map, a second entry delegates the same call to the bundler's own require.ensure implementation so it can create and fetch a separate client bundle. It does not load modules itself, expose a CLI, return a Promise, or provide modern dynamic import behavior.
Do not add node-ensure to new code. Keep it only as a pinned compatibility shim while an old custom bundler still requires its exact protocol, then migrate those split points to import().
Use it if
- You maintain an old isomorphic CommonJS application whose bundler explicitly implements this node-ensure protocol
- You need the exact server fallback expected by dynapack-era code: defer once, then use ordinary require
- You are preserving a package that already publishes a browser replacement compatible with require.ensure
- You are writing new code: native import() is standardized, Promise-based, and understood by current Node releases and bundlers
- You use Browserify: the README explicitly says node-ensure is not compatible with it
- You expect dependency validation or preloading on Node: index.js ignores the module-name array and only calls setImmediate(callback)
- You need maintained tooling, tests, or types: 0.0.0 is the only release, the repo's last push was in 2015, its test script always exits with an error, and it ships no TypeScript declaration
- You expect errors to behave consistently across server and client: the Node callback receives no error, while any client error semantics belong entirely to the bundler-provided require.ensure
Setup reality
npm install node-ensure adds no dependencies, native builds, credentials, peer dependencies, or config files. It also does much less than the name suggests. On Node, ensure(moduleNames, callback) never resolves or checks moduleNames; it simply schedules callback with setImmediate. The require calls must remain literally inside that callback so an old compatible bundler can discover the split point. For browser builds, the package.json browser map replaces index.js with browser.js, which forwards to the require.ensure supplied by your loader. Your bundler must honor that browser map, inject a require function with an ensure property, accept the same array-and-callback arguments, and avoid per-module closure state in its implementation. The README explicitly excludes Browserify and names dynapack as the example, but both the package and that ecosystem come from 2015. The function returns undefined, supplies no Promise, timeout, cancellation, preload signal, chunk naming, retry, or fallback UI. Node errors from a later require happen inside your callback and are not passed as its err argument. There are no shipped types, exports map, ESM entry, tests, or declared Node engine range. If this protocol is already embedded in a legacy build, pin the exact version, test both server and produced browser chunks, and document the bundler dependency. For any new or actively migrated project, replace the split point with import() and let webpack, Vite, Rollup, or your runtime own module loading.
Patterns
Use the basic legacy split-point shapeload-commonjs-modules
const ensure = require('node-ensure');
ensure(['superagent', 'react'], (err) => {
if (err) return handleClientLoadError(err);
const request = require('superagent');
const React = require('react');
startApp({ request, React });
});On Node, the module-name array is ignored and err is undefined. A compatible browser bundler is responsible for interpreting both.
Attach the shim to requireattach-require-ensure
require.ensure = require('node-ensure');
require.ensure(['./feature'], (err) => {
if (err) throw err;
const feature = require('./feature');
feature.run();
});This mutates the local CommonJS require function. It is for bundlers that look specifically for require.ensure syntax, not general Node module loading.
Keep dependency requests statically visiblekeep-static-require
ensure(['./reports/monthly'], (err) => {
if (err) return showLoadFailure(err);
const monthlyReport = require('./reports/monthly');
monthlyReport.render();
});Do not build the require path from a runtime variable. Old bundlers need a literal request to know what belongs in the split bundle.
Treat callback errors as client-loader errorshandle-client-error
ensure(['./account-panel'], (err) => {
if (err) {
showRetryButton(() => openAccountPanel());
return;
}
require('./account-panel').mount();
});The README says Node should never pass an error. Whether browser failures reach err depends on the injected bundler or loader.
Catch a missing module on Nodecatch-node-require-error
ensure(['./optional-feature'], () => {
try {
require('./optional-feature').start();
} catch (error) {
console.error('Feature could not start', error);
}
});A Node require failure is thrown inside the callback; node-ensure does not catch it or convert it into the callback's err argument.
Wrap the callback protocol in a Promisewrap-in-promise
function ensureAsync(modules) {
return new Promise((resolve, reject) => {
ensure(modules, (err) => err ? reject(err) : resolve());
});
}
await ensureAsync(['./editor']);
const editor = require('./editor');The wrapper only changes consumption. On Node it still does not preload or validate modules, and bundler recognition of the indirect call may fail.
Understand the actual Node timingdefer-server-work
console.log('before');
ensure(['anything'], () => console.log('callback'));
console.log('after');
// before, after, callbackThe Node implementation is exactly setImmediate(callback). The dependency list does not influence timing or loading.
Pass context through a require.ensure callpreserve-callback-context
require.ensure = require('node-ensure');
require.ensure.call({ chunk: 'settings' }, ['./settings'], function (err) {
if (!err) require('./settings').open(this.chunk);
});The browser shim forwards this with apply. Whether the injected loader preserves it for the callback is loader-specific, so test the produced bundle.
Add a minimal local TypeScript declarationdeclare-types-locally
declare module 'node-ensure' {
type EnsureCallback = (error?: Error) => void;
function ensure(modules: string[], callback: EnsureCallback): void;
export = ensure;
}The npm package ships no declarations and has no types field. Keep this in your own .d.ts file if migration cannot happen yet.
Mirror the package's browser replacement conventionconfigure-browser-replacement
{
"browser": {
"./server-loader.js": "./browser-loader.js"
}
}node-ensure depends on the bundler honoring package.json browser mappings. This convention alone does not guarantee require.ensure support.
Replace a split point with dynamic importmigrate-dynamic-import
async function openEditor() {
try {
const { createEditor } = await import('./editor.js');
return createEditor();
} catch (error) {
showLoadFailure(error);
}
}This is the preferred new-code shape. Current Node and bundlers understand import() without node-ensure or a custom callback protocol.
Preserve an explicit webpack chunk name during migrationname-webpack-chunk
const panelModule = await import(
/* webpackChunkName: "account-panel" */
'./account-panel.js'
);
panelModule.mount();The comment is webpack-specific. Other bundlers split dynamic imports but use their own output naming controls.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| webpack | npm | You need a maintained bundler that turns dynamic import calls into on-demand chunks |
| vite | npm | You want an ESM-first development and production build around standard dynamic imports |
| @loadable/component | npm | You need React-focused code splitting with loading components and server-rendering support |