Skip to content

Instantly share code, notes, and snippets.

@devijvers
Last active August 29, 2015 14:06
Show Gist options
  • Select an option

  • Save devijvers/d01c177944b2223de8c1 to your computer and use it in GitHub Desktop.

Select an option

Save devijvers/d01c177944b2223de8c1 to your computer and use it in GitHub Desktop.
required(): hack the node.js require cache for fun and profit
require("coffee-script/register");
required = require("./required.coffee");
_ = require("lodash");
// require("underscore") will now work in every module
required("underscore", _);
# License: MIT
# Copyright Steven Devijver, 2014
#
# required(): hack the node.js require cache
#
# By design not suitable for production environments.
# Use at your own risk.
# This script has only been tested with global modules.
#
# Example usage:
#
# // main.js
# require("coffee-script/register");
# required = require("./required.coffee");
# _ = require("lodash");
#
# // require("underscore") will now work in every module
# required("underscore", _);
_module = require("module")
_ = require("lodash")
path = require("path")
fs = require("fs")
# ##### HELPER FUNCTIONS #####
exists = (location) ->
try
fs.statSync location
true
catch error
false
mkdirs = (dirs..., last) ->
flattened = _.flatten([dirs, [last]])
if last
location = path.join.apply this, flattened
else
location = path.join.apply this, dirs
if !exists location
mkdirs dirs
fs.mkdirSync location
location
# Keep require.cache handy
# This cache is used by node.js to store loaded modules.
# We're going to add 'fake' yet real-looking modules.
cache = require.cache
# Get hold of the root module, that is the main script.
# It always has id '.'
getRootModule = ->
key = _.findKey(cache, (module) -> "." == module.id)
cache[key]
rootModule = getRootModule()
# We need the directory where the main script resides
basedir = path.dirname rootModule.filename
# When you require() a module node.js will check if the
# corresponding file exists, even after the module has
# already been loaded in the cache.
# Since we'll add our entries to the cache we still
# need to provide a directory with the module name and
# a index.js file within that directory if we want to
# mislead node.js in returning our module from the cache.
# First let's create a cache directory that will hold
# module directories for us. We don't want to pollute
# node_modules/. We'll create node_modules/required_cache
# in the directory where the main script resides.
cacheDir = mkdirs basedir, "node_modules", 'required_cache'
# Next we have to tell node that it needs to look in the
# required_cache directory when looking for global modules.
# There is no API to do this, the only vector left is hacking
# which unavoidably means trouble.
# When node starts it reads the NODE_PATH environment variable
# and adds directories specified there to the global path array.
# However, node ONLY does that at startup. By the time
# required.coffee is being loaded it's too late to add global paths.
# That's to say, too late unless you don't mind screwing around
# with the module system of the VM under your feet.
# First we add the node_modules/required_cache directory
# to the NODE_PATH entry in the process.env object.
process.env['NODE_PATH'] += path.delimiter + cacheDir
# Now we re-launch the _initPaths() function in lib/module.js.
# The node developers never intended this function to be executed
# after startup. Problem?
_module._initPaths()
# It turns out, no. All it does is update the paths array that
# node uses to hunt for global modules. Still, don't use this
# in a production environment.
# The actual required() function. Pass in a module name and
# the exports you want to expose and you're done.
required = (module, exports) ->
# Create the node_modules/required_cache/<module> directory
moduleCacheDir = mkdirs cacheDir, module
# Touch the node_modules/required_cache/<module>/index.js file
cacheFile = path.join moduleCacheDir, "index.js"
fd = fs.openSync cacheFile, 'a+', 666
fs.writeSync fd, ""
fs.closeSync fd
# Add the exports to require.cache in the format node expects.
cache[cacheFile] =
id: module
filename: cacheFile
exports: exports
parent: rootModule
exports
module.exports = required
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment