require-falafel
v1.0.13
Published
Use falafel to rewrite require'd modules in node.
Downloads
5
Readme
Require-Falafel
Use falafel to rewrite require'd modules in node.
Hook into node's require command to rewrite the module's AST (in memory) before loading.
Author: Olli Jones
License: MIT
npm install require-falafel --save
Introduction
Falafel uses Acorn to transform a JavaScript AST. Require-falafel hooks into node's require method in order to transform the AST of modules as they're loaded.
For example, if we have a library "lib-7" which makes web requests with an unhelpful user-agent string:
const fetch = require("node-fetch");
module.exports = {
get: function() {
fetch("https://www.example.com", {
headers: { "User-Agent": "lib-7" },
})
}
}
This code snippet contains lots of AST nodes including 4 Literals. We can see these Literal nodes will be:
{
"type": "Literal",
"value": "node-fetch"
}
{
"type": "Literal",
"value": "https://www.example.com"
}
{
"type": "Literal",
"value": "User-Agent"
}
{
"type": "Literal",
"value": "lib-7"
}
Falafel provides node.source()
and node.update(s)
methods, so if we wanted
to replace the "lib-7"
literal, to provide a better user-agent string for
tracking calls to the API from our new client, we can write a transform method
like:
const transform = function (node) {
// only change the right literal:
if (node.type == "Literal" && node.value == "lib-7") {
// node.source() will be "lib-7"
let source = node.source();
// we can update the source code (this is quoted JavaScript):
source = `${source} + "-github.com/ojj11/require-falafel"`;
// then we can update the AST node:
node.update(source);
}
}
We can use this method along with RequireFalafel.INCLUDE_NODE_MODULES
, to
make an instance of RequireFalafel
that we can apply to a block of code that
includes the require
statements for lib-7
that we want to transform:
// wrap our code with a transformation before requiring the library:
const RequireFalafel = require("require-falafel");
const requireFalafel = new RequireFalafel(
RequireFalafel.INCLUDE_NODE_MODULES,
transform);
requireFalafel.applyForBlock(function() {
// require-ing inside .applyForBlock will swap the internals of require to
// apply falafel as the library is loaded:
const lib7 = require("lib-7");
// calling the library will use "lib-7-github.com/ojj11/require-falafel" as the
// user-agent now.
lib7.get();
});
Whilst this example is contrived, it is hopefully relatively straight-forward to see how this library could be used to instrument code as it's loaded in a test pass, or to enable certain developer flags in a development or staging environment.
API documentation
Load require-falafel:
const RequireFalafel = require("require-falafel");
new RequireFalafel(typeOfReplacement, transformer)
Creates a new transformation for require
methods, given a typeOfReplacement
and transform
method, for example:
const requireFalafel = new RequireFalafel(
RequireFalafel.INCLUDE_NODE_MODULES,
function (node) {
if (node.type == "Literal" && node.value == "lib-7") {
let source = node.source();
source = `${source} + "-github.com/ojj11/require-falafel"`;
node.update(source);
}
});
typeOfReplacement should be one of:
['/absolute/paths/to/files.js', require.resolve('module-names')]
will transform only the absolute paths specified in an array. Userequire.resolve
to get the absolute paths of libraries.RequireFalafel.INCLUDE_NO_NODE_MODULES
, will transform./
and../
imports, but will not transform any npm modules in node_modules folders. Only available in node v11+.RequireFalafel.INCLUDE_FIRST_LEVEL_NODE_MODULES
same as above, but will transform only the top level of node_modules that are imported from./
or../
code. Only available in node v11+.RequireFalafel.INCLUDE_NODE_MODULES
the most liberal, will transform all require calls, even those inside node_modules
requireFalafel.applyForBlock(block)
Runs the given block
, replacing require
calls to load code transformed
through the falafel transformer given on construction.
At the end of the block, resets the require
method and clears any loaded
modules from node
's cache, so that a fresh require
will get the correct
module.
For example:
requireFalafel.applyForBlock(function() {
const module2 = require("module2");
// module2 will be transformed and loaded
});
const module2 = require("module2");
// module2 will be re-loaded, but not transformed because it's outside of the
// block
This method will work async functions, but the require hook will continue to
apply until await
is called:
// always immediately await:
await requireFalafel.applyForBlock(async function() {
const module2 = require("module2");
// module2 will be transformed and loaded
});
const module2 = require("module2");
// module2 will be re-loaded, but not transformed because `await` has already
// been called on `applyForBlock`
requireFalafel.replaceRequire()
For cases where supplying a block won't work, use replaceRequire
, like so:
requireFalafel.replaceRequire();
const transformedModule2 = require("module2");
// module2 will be transformed and loaded
requireFalafel.restoreRequire();
const untransformedModule2 = require("module2");
// module2 will be re-loaded, but not transformed because we restored the
// require method
requireFalafel.restoreRequire()
Opposite of requireFalafel.replaceRequire()
- restores the normal operation of
require
.