babel原理分析-babel-register addHook

前言

阅读本文时希望您对babel-register有一定了解,如果还有不了解的可以阅读之前的文章传送门

在之前的文章中已经简单介绍了babel-register的功能

那么babel如何给require加上钩子,使得在node环境下实现动态编译的呢(静态编译:统一babel。动态编译:js编译到这行的时候进行编译)

原理分析

注:本文参考代码为babel-0.7.0-beta版本代码。

其实在上文babel-register中可以看到,node环境下babel的编译,是通过一个require上addHook的解决方法,那么这个hook是怎么挂载到require上的呢

首先想到的是node官版有没有提供原生的方法处理,官版确实提供了一个require.extensions的方法,可惜已经废弃了,moudle模块也没有所谓addhook的办法。那就只能安静点看pirate的实现了

深入pirate的源码时,我们却发现实际pirate这个npm包并没有做什么功能

核心代码不超过100行,如下


const Module = module.constructor.length > 1
  ? module.constructor
  : BuiltinModule;

export function addHook(hook, opts = {}) { // eslint-disable-line import/prefer-default-export
  let reverted = false;
  const loaders = [];
  const oldLoaders = [];
  let exts;

  const originalJSLoader = Module._extensions['.js'];

  const matcher = opts.matcher || null;
  const ignoreNodeModules = opts.ignoreNodeModules !== false;
  exts = opts.extensions || opts.exts || opts.extension || opts.ext || ['.js'];
  if (!Array.isArray(exts)) exts = [exts];

  exts.forEach((ext) => {
    if (typeof ext !== 'string') throw new TypeError(`Invalid Extension: ${ext}`);
    const oldLoader = Module._extensions[ext] || originalJSLoader;
    oldLoaders[ext] = oldLoader;

    loaders[ext] = Module._extensions[ext] = function newLoader(mod, filename) {
      let compile;
      if (!reverted) {
        if (shouldCompile(filename, exts, matcher, ignoreNodeModules)) {
          compile = mod._compile;
          mod._compile = function _compile(code) {
            mod._compile = compile;
            const newCode = hook(code, filename);
            if (typeof newCode !== 'string') {
              throw new Error(HOOK_RETURNED_NOTHING_ERROR_MESSAGE);
            }

            return mod._compile(newCode, filename);
          };
        }
      }

      oldLoader(mod, filename);
    };
  });
  return function revert() {
    if (reverted) return;
    reverted = true;

    exts.forEach((ext) => {
      if (Module._extensions[ext] === loaders[ext]) {
        Module._extensions[ext] = oldLoaders[ext];
      }
    });
  };
}

看起来这个代码做的事很简单就是在给原生moudle方法上不断地挂载moudle._extension['.js/.es6/.jsx']之类的处理func,始终没有看到执行时机。

于是简单写了一个demo


console.log('naturelessTT')

debugger;

require('require.js')

babel-node index.js --inspect-brk运行去看这个hook的执行时机以及call stack。

实际是require文件时,io读取文件后会通过moudle.load的方法加载文件,然后依次执行_extension里挂载的方法

真相大白,但是令人惊讶的是0.7之前的版本并没有引入pirate这个包,看了0.6.26版本后,emmmm,babel大佬使用了官版已经标记为废弃的require.extensions。

当然虽然是废弃的,但是node这个模块已经锁死,所以babel大佬还在肆无忌惮的用,当然0.7已经做了修正


Deprecated 

In the past, this list has been used to load non-JavaScript modules into Node.js by compiling them on-demand. However, in practice, there are much better ways to do this, such as loading modules via some other Node.js program, or compiling them to JavaScript ahead of time.

Since the module system is locked, this feature will probably never go away. However, it may have subtle bugs and complexities that are best left untouched.

Note that the number of file system operations that the module system has to perform in order to resolve a require(...) statement to a filename scales linearly with the number of registered extensions.

In other words, adding extensions slows down the module loader and should be discouraged.

natureless
116 声望4 粉丝