Why

Doing require extensions correctly is essential, because:

  1. Users should be able to install multiple extensions in succession, and have them work together.
  2. Coverage tools like nyc need it to reliably supply coverage information that takes into account sourcemaps from upstream transforms.
  3. 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.

  1. The system calls require.extensions['.js'](module, './foo.js').
    This means append-bar is invoked with (module, './foo.js')
  2. 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.
  3. append-bar calls it's oldHook (the default .js extension) with the modified module and filename (line 9).
  4. The default .js extension reads in the source (module.exports = "foo"), and calls module._compile(source, filename).
    Remember module._compile currently points to the append-bar wrapper function.
  5. The append-bar wrapper adds + "bar" to the source (Line 5). The source is now module.exports = "foo" + "bar".
  6. The append-bar wrapper now replaces module._compile with it's originalCompile reference (Line 6).
    module._compile now points to the actual compile implementation
  7. 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.

    1. We install the append-bar extension (replacing the original hook)
      append-bar#originalHook points to the original hook.
    2. We install the append-quz extension
      append-quz#originalHook points to the append-bar hook.
    3. We call require('./foo.js');
    4. 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.
    5. append-quz calls it's originalHook reference, which is append-bar.
    6. 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.
    7. The original extension is called, which loads the source from disk and calls module._compile('module.exports = "foo"', './foo.js')
    8. At this point module._compile points to the append-bar wrapper, so the source is appended with + "bar".
    9. The append-bar calls the originalCompile reference (which is the append-quz wrapper).
    10. The append-quz wrapper does it's appending (so we've now got "foo" + "bar" + "quz")
    11. append-quz calls it's originalCompile reference, which is actual and we get "foobarquz"

How_Require_Extensions_Work的更多相关文章

随机推荐

  1. Docker小白从零入门实战

    环境:Centos 6.9 0.查看是否满足安装需求. 先检查服务器环境,docker要求操作系统CentOS6以上,kernel 版本必须2.6.32-431或更高,即>=CentOS 6.5 ...

  2. PE文件 02 导出表

    0x01  导出表结构  导出表是由数据目录表中的第一个成员DataDirectory[0]指出的: typedef struct _IMAGE_DATA_DIRECTORY { DWORD Virt ...

  3. Alpha冲刺3

    前言 队名:拖鞋旅游队 组长博客:https://www.cnblogs.com/Sulumer/p/9971198.html 作业博客:https://edu.cnblogs.com/campus/ ...

  4. 转-Asynchronous bulk transfer using libusb

    https://falsinsoft.blogspot.jp/2015/02/asynchronous-bulk-transfer-using-libusb.html The 'linusb' is ...

  5. shell脚本实例-内存磁盘使用警告

    1,磁盘使用警告并发送邮件 #!usr/bin/bash #df -Th|grep '/$' 这个是获取内存使用的那一条记录 #后面两句是获取内存的使用率 disk=`df -Th|grep '/$' ...

  6. Java Editplus编译环境配置

    java jdk 安装win10 配置:此电脑--属性--高级系统设置--环境变量--系统变量-->新建--变量名--JAVA_HOME 变量值--浏览目录--jdk安装路径jdk...--&g ...

  7. 20165228 学习基础和C语言基础调查

    ========== 做中学读后感 我依然认为兴趣与自觉性是推动一个人进步的两大因素,他们之间的区别是"兴趣"带来的影响更多是主动性的学习,而"自觉"则是略显被 ...

  8. ANDROID BINDER机制浅析

    Binder是Android上一种IPC机制,重要且较难理解.由于Linux上标准IPC在灵活和可靠性存在一定不足,Google基于OpenBinder的设计和构想实现了Binder. 本文只简单介绍 ...

  9. jQuery的文件引入、入口函数以及js对象和jquery对象之间的互相转换

    JavaScript与jquery的区别 JavaScript是一门编程语言,用来编写客户端浏览器脚本. jQuery是javascript的一个库,包含多个可重用的函数,用来辅助简化javascri ...

  10. Java中的面向对象I

    一.首先来了解一下Java面向对象的五个程序设计方式: 1.万物皆对象 Java以类为基本模块来将问题抽象化,在计算机中解决实际生活中的问题 2.程序为对象的集合,程序中的类通过互发消息来告知彼此要做 ...