How to Hack Node.js Modules Without Changing Their Source Code

This article explains why and how to hack Node.js modules by manipulating the module cache, require.cache, or the require function itself, providing practical code examples and discussing related internals, pitfalls, and real‑world use cases.

Taobao Frontend Technology
Taobao Frontend Technology
Taobao Frontend Technology
How to Hack Node.js Modules Without Changing Their Source Code

Why Hack?

During business development you often rely on third‑party Node.js modules. Hacking these modules lets you alter specific functionality without modifying the original source, which is useful for special local requirements, urgent temporary fixes, or when direct source changes would be hard to maintain across upgrades.

Expectation

Example:

// a.js
module.exports = function(){
  dosomething();
};
// b.js
module.exports = require('a');
// c.js
console.log(require('b'));

Project c depends on b, which in turn depends on a. We want a to inject injectSomething() when called from c:

// Desired output after hack
function(){
  injectSomething();
  dosomething();
}

Main Methods

Manipulating Module Cache

When module a exports an object, you can require it early in c, modify its properties, and later b will receive the patched object from the cache.

// a.js
module.exports = {
  p
};
// b.js
const a = require('a');
a.p();
// c.js
require('b');

Patch in c:

// c.js
const a = require('a');
let oldp = a.p;
a.p = function(...args){
  injectSomething();
  oldp.apply(this, args);
};
require('b');

Limitation: works only when the exported value is an object and the property is not dynamically loaded.

Modifying require.cache

If the module exports a function, you can replace the cached entry directly.

// a.js (exports a function)
module.exports = function(){
  doSomething();
};
// c.js
const aOld = require('a');
let aId = require.resolve('a');
require.cache[aId] = function(...args){
  injectSomething();
  aOld.apply(this, args);
};
require('b');

Limitation: other code may later modify require.cache, e.g., hot‑reloading.

Proxying require

Override Module.prototype.require to intercept specific modules.

const Module = require('module');
const _require = Module.prototype.require;
Module.prototype.require = function(...args){
  let res = _require.apply(this, args);
  if(args[0] === 'a'){
    // only modify module "a"
    injectSomething();
  }
  return res;
};

Limitation: invasive for the whole process.

Related Internals

Node Startup Process

When executing node a.js, the bootstrap script bootstrap_node.js runs startup(), which loads Module.runMain and ultimately calls Module._load to resolve and cache modules.

// Simplified C++ entry point
int main(){
  node::Start(argc, argv);
  // ...
}

Single Cache Object

Module._load

checks Module._cache first; if absent, it creates a new Module instance, stores it in the cache, and returns its exports. The cache is a singleton for the whole process.

Module._load = function(request, parent, isMain){
  var filename = Module._resolveFilename(request, parent, isMain);
  var cachedModule = Module._cache[filename];
  if(cachedModule){
    return cachedModule.exports;
  }
  var module = new Module(filename, parent);
  Module._cache[filename] = module;
  tryModuleLoad(module, filename);
  return module.exports;
};

Cache vs. require.cache

During module compilation, internalModule.makeRequireFunction sets require.cache = Module._cache, so modifying either object affects the same underlying cache.

// In makeRequireFunction
require.cache = Module._cache;

Where require Lives

In REPL, global.require exists, but in regular files require is injected by Module.prototype._compile and points to Module.prototype.require, which ultimately calls Module._load.

$ node
> global.require
{ [Function: require] ... cache: {} }

Practical Tips

Path Keys

The keys in require.cache are absolute paths resolved via require.resolve, not raw file system paths.

// Absolute path
/Users/kino/.def/def_modules/.builders/@ali/builder-cake-kpm/node_modules/@ali/cake-webpack-config/index.js
// Resolved key
/Users/kino/.def/def_modules/.builders/@ali/builder-cake-kpm/node_modules/.0.16.23@@ali/cake-webpack-config/index.js

Multi‑process Considerations

If your tool spawns child processes, ensure the hacking code runs in the same process as the target module (check process.pid).

console.log(process.pid);

Case Studies

Hijacking Prompt Input

In the DEF development environment, you can override yeoman-generator 's prompt method to auto‑fill answers, enabling batch creation or publishing.

#!/usr/bin/env node
'use strict';
require('shelljs/global');
const path = require('path');
const HOME = process.env.HOME;
const yeomanRouter = require(path.join(HOME, '.def/def_modules/.generators/@ali/generator-abs-router/node_modules/yeoman-generator'));

yeomanRouter.generators.Base.prototype.prompt = function(list, callback){
  let item = list[0];
  let prop = {};
  prop[item.name] = 'kissy-pc'; // auto‑fill
  callback(prop);
};
const defPath = which('def').stdout;
require(defPath);

Modifying Build Process (Webpack Config)

Inject a custom pre‑loader into DEF's webpack configuration by replacing the exported function in @ali/cake-webpack-config via require.cache.

// Simple loader that rewrites a require call
module.exports = function(content){
  return content.replace('require(\'./plugin\')', "require('./localPlugin')");
};
#!/usr/bin/env node
'use strict';
require('shelljs/global');
const path = require('path');
const HOME = process.env.HOME;
const CWD = process.cwd();
const cakeWcPath = path.join(HOME, '.def/def_modules/.builders/@ali/builder-cake-kpm/node_modules/@ali/builder-cake-kpm/node_modules/@ali/cake-webpack-config');
const preLoaderPath = path.join(CWD, 'debug/plugin_compile.js');
const cakeWebpackConfig = require(cakeWcPath);
const requireId = require.resolve(cakeWcPath);
require.cache[requireId].exports = (options) => {
  if (options.callback) {
    let oldCb = options.callback;
    options.callback = function(err, obj){
      obj.module.preLoaders = [{
        'test': /index\.js$/,
        'loader': preLoaderPath
      }];
      oldCb(err, obj);
    };
  }
  cakeWebpackConfig(options);
};
const defPath = which('def').stdout;
require(defPath);

Conclusion

Hacking a Node.js module requires understanding its loading chain and cache. While not always the optimal solution, manipulating require, the module cache, or proxying functions provides a viable workaround for many practical scenarios.

// Hello, and welcome to hacking node.js!
// some descriptions
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

nodejsbackend-developmentmodule-hackingrequire-cache
Taobao Frontend Technology
Written by

Taobao Frontend Technology

The frontend landscape is constantly evolving, with rapid innovations across familiar languages. Like us, your understanding of the frontend is continually refreshed. Join us on Taobao, a vibrant, all‑encompassing platform, to uncover limitless potential.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.