Doing require extensions correctly is essential, because:app
nyc
need it to reliably supply coverage information that takes into account sourcemaps from upstream transforms.First, it's worth remembering what default ".js"
extension does.ide
require.extenstions['.js'] = function (module, filename) {
var content = fs.readFileSync(filename, 'utf8');
module._compile(internalModule.stripBOM(content), filename);
}
Really simple. It reads the source content from the disk, and calls the module._compile
. The default module._compile
is a non-trivial piece of code, for simplicity I will just say it is what actually compiles the code.ui
Now let's install a transform that just appends the code + "bar"
to the end of every file (humor me as I keep things simple).this
This is how you would manually create that hook (in what is now widely accepted as the "right" way).spa
// append-bar.js
var oldHoook = require.extensions['.js']; // 1
require.extensions['.js'] = function (module, file) { // 2
var oldCompile = module._compile; // 3
module._compile = function (code, file) { // 4
code = code + ' + "bar"'; // 5
module._compile = oldCompile; // 6
module._compile(code + ' + "bar"'); // 7
};
oldHook(module, file); // 9
});
Note that this extension never reads from the disk. That is because the first extension in the chain (the system default one) handles loading from disk. If it's not obvious why that's true (it wasn't for me), keep reading.code
The really important takeaway here is that you should be implementing require extensions almost exactly as I have above. There are multiple levels of indirection, and it can be confusing. Libraries like pirates
can simplify the process.orm
Here is what happens when you call require("./foo.js")
ip
// foo.js
module.exports = "foo"
What happens inside require
boils down to this:ci
function pseudoRequire(filename) {
var ext = path.extname(filename); // ".js"
var module = new Module();
require.extensions[ext](module, filename);
}
Now let's step through the sequence of events.rem
require.extensions['.js'](module, './foo.js')
. append-bar
is invoked with (module, './foo.js')
append-bar
stores a reference to module._compile
(line 3), an with its own wrapper function (line 4). module._compile
refers to the append-bar
wrapper function. append-bar
's reference to originalCompile
refers to the actual compile implementation.append-bar
calls it's oldHook
(the default .js
extension) with the modified module and filename (line 9)..js
extension reads in the source (module.exports = "foo"
), and calls module._compile(source, filename)
. module._compile
currently points to the append-bar
wrapper function.+ "bar"
to the source (Line 5). The source is now module.exports = "foo" + "bar"
.module._compile
with it's originalCompile
reference (Line 6). module._compile
now points to the actual compile implementationmodule._compile
is called again (this time pointing to actual, and the source is evaled and we get our result "foobar".Assume we have first added the append-bar
extension from above, followed by another called append-quz
(which is for all purposes identical, except it appends baz
instead.
append-bar
extension (replacing the original hook) append-bar#originalHook
points to the original hook.append-quz
extension append-quz#originalHook
points to the append-bar
hook.require('./foo.js');
append-quz
hook is called with (module, './foo.js')
, it replaces module._compile
with it's wrapper function. append-quz#originalCompile
points to the actual compile module._compile
points to the append-quz
wrapper.append-quz
calls it's originalHook
reference, which is append-bar
.append-bar
replaces module._compile
with it's wrapper. append-bar#originalCompile
points to append-quz
wrapper. module._compile
points to the append-bar
wrapper.module._compile('module.exports = "foo"', './foo.js')
module._compile
points to the append-bar
wrapper, so the source is appended with + "bar"
.append-bar
calls the originalCompile
reference (which is the append-quz
wrapper).append-quz
wrapper does it's appending (so we've now got "foo" + "bar" + "quz"
)append-quz
calls it's originalCompile
reference, which is actual and we get "foobarquz"