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.
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._loadchecks 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.jsMulti‑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 descriptionsSigned-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
