Node.js中 --preserve-symlinks 参数的实现方式

之前的文章写了--preserve-symlinks可以解决软链文件包寻找的问题,那么它是如何实现的?

包解析简易流程

这里我们只看 commonjs 模块的加载。

当 require 一个模块时,nodejs会:

  • 计算被引文件的完整路径。
  • new 一个 Module 类,再调用 Module.load 方法执行模块代码返回输出结果。

这里需要注意的是,在计算被引文件的完整路径时,如果被引文件是一个相对路径,那么会以当前文件的路径进行查找。例如 a.js 使用相对路径引用 b.js,那么 b.js 文件的解析是按照 a.js 文件所在的目录来查找的。

--preserve-symlinks

lib/internal/modules/cjs/loader.js

--preserve-symlinks 有关的代码,关键的其实就只有这两行:

1
2
3
4
if (preserveSymlinks && !isMain) {
return rc === 0 && path.resolve(requestPath);
}
return rc === 0 && toRealPath(requestPath);

这部分代码处在“计算被引文件的完整路径”这一过程中。执行到这里时,requestPath 已经是一个完整的路径了。

判断 preserveSymlinks 为 true 时,会将原路径返回。

判断 preserveSymlinks 为 false 时,会调用 toRealPath 方法,返回软链原始文件的路径。

toRealPath 方法的实现非常简单,就是调用了 fs.realpathSync:

1
2
3
4
5
function toRealPath(requestPath) {
return fs.realpathSync(requestPath, {
[internalFS.realpathCacheKey]: realpathCache
});
}

举个栗子

假设我们有这样一个文件结构:

  • folderA
    • a.js
  • folderB
    • b.js -> a.js
    • c.js
  • index.js

folderB/b.js 是 folderA/a.js 的软链。

a.js 中这样引用了 c.js: require('./c.js');

index.js 中引用了 b.js: require('./folderB/b.js');

下面说一下当我们执行 node index.js 时会发生啥。

当我们执行 index.js 时,会去引用 b.js,此时

  • 模块 id 为 ./folderB/b.js
  • 路径即为 index.js 的路径,我们记为 /

拼接后得到 requestPath:/folderB/b.js

之后会进入到我们上面贴的代码处,

  • 如果 preserveSymlinks 为 true,那么会直接返回 /folderB/b.js 作为模块的 filename。
  • 如果 preserveSymlinks 为 false,那么会调用 toRealPath 方法,返回 /folderA/a.js,作为模块的 filename。

接下来,就要引用 c.js 了,模块 id 为 ./c.js,而 path 就不一样了:

  • 如果 preserveSymlinks 为 true,那么 path 是 b.js 所在的目录 /folderB
  • 如果 preserveSymlinks 为 false,那么 path 是 a.js 所在的目录 /folderA

拼接之后得到 /folderB/c.js/folderA/a.js。显然 preserveSymlinks 为 false 的时候就会出现找不到包的情况。