How_Require_Extensions_Work
Why
Doing require extensions correctly is essential, because:
- Users should be able to install multiple extensions in succession, and have them work together.
- Coverage tools like
nyc
need it to reliably supply coverage information that takes into account sourcemaps from upstream transforms. - Because non-standard, un-predictable behavior causes hard to solve bugs, and major headaches for project maintainers.
What is a require extension anyways?
First, it's worth remembering what default ".js"
extension does.
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.
Our first custom extension
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 is how you would manually create that hook (in what is now widely accepted as the "right" way).
// 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.
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.
Breakdown with 1 Custom Extension
Here is what happens when you call require("./foo.js")
// foo.js
module.exports = "foo"
What happens inside require
boils down to this:
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.
- The system calls
require.extensions['.js'](module, './foo.js')
.
This meansappend-bar
is invoked with(module, './foo.js')
append-bar
stores a reference tomodule._compile
(line 3), an with its own wrapper function (line 4).
module._compile
refers to theappend-bar
wrapper function.
append-bar
's reference tooriginalCompile
refers to the actual compile implementation.append-bar
calls it'soldHook
(the default.js
extension) with the modified module and filename (line 9).- The default
.js
extension reads in the source (module.exports = "foo"
), and callsmodule._compile(source, filename)
.
Remembermodule._compile
currently points to theappend-bar
wrapper function. - The append-bar wrapper adds
+ "bar"
to the source (Line 5). The source is nowmodule.exports = "foo" + "bar"
. - The append-bar wrapper now replaces
module._compile
with it'soriginalCompile
reference (Line 6).
module._compile
now points to the actual compile implementation module._compile
is called again (this time pointing to actual, and the source is evaled and we get our result "foobar".
Breakdown with 2 Custom Extension
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.
- We install the
append-bar
extension (replacing the original hook)
append-bar#originalHook
points to the original hook. - We install the
append-quz
extension
append-quz#originalHook
points to theappend-bar
hook. - We call
require('./foo.js');
append-quz
hook is called with(module, './foo.js')
, it replacesmodule._compile
with it's wrapper function.
append-quz#originalCompile
points to the actual compile
module._compile
points to theappend-quz
wrapper.append-quz
calls it'soriginalHook
reference, which isappend-bar
.append-bar
replacesmodule._compile
with it's wrapper.
append-bar#originalCompile
points toappend-quz
wrapper.
module._compile
points to theappend-bar
wrapper.- The original extension is called, which loads the source from disk and calls
module._compile('module.exports = "foo"', './foo.js')
- At this point
module._compile
points to theappend-bar
wrapper, so the source is appended with+ "bar"
. - The
append-bar
calls theoriginalCompile
reference (which is theappend-quz
wrapper). - The
append-quz
wrapper does it's appending (so we've now got"foo" + "bar" + "quz"
) append-quz
calls it'soriginalCompile
reference, which is actual and we get"foobarquz"
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
2016-12-20 rabiitmq集群完整安装
2014-12-20 Mysql 批量插入数据的方法
2013-12-20 silverlight 进行本地串口调用的一种可行的解决方法 之silverlight端代码