Oasis's Cloud

一个人的首要责任,就是要有雄心。雄心是一种高尚的激情,它可以采取多种合理的形式。
—— 《一个数学家的辩白》

Node.js 的 preserve-symlinks


Node.js 在 6.5.0 版本中支持了 preserveSymlink 选项。通过 preserveSymlink 选项,开发人员可以在 处理文件链接的时候,具有更多的选择。在默认情况下,node.js 默认情况下会解析链接文件,并尝试访问链接所指的 目标文件或目录,这可能会导致 Node.js 提示模块无法解析的错误。

例如在 demo 目录中 b.js 是一个软链,index.js 文件中 require 了 b.js 文件,b.js 文件中通过相对路径的方式 require 了 c.js 文件。 通过 node index.js 运行代码,会出现 Error: Cannot find module './c.js' 的错误提示。

demo 目录结构

├── a.js
├── b.js -> ../../../b.js
├── c.js
└── index.js
b.js 文件内容
// b.js 文件内容,b.js 的目标文件位于 ../../../b.js

// 通过相对路径引入 c.js
// 在不使用 preserve-symlinks 选项时,require('./c.js') 相当于 require('../../../c.js')
// 使用 preserve-symlinks 选项时,require('./c.js') 仍然相对于 './' 目录查找
const c = require('./c.js')
console.log(c)
exports.module = c

运行错误

❯ node index.js 
internal/modules/cjs/loader.js:905
  throw err;
  ^

Error: Cannot find module './c.js'

通过 node --preserve-symlinks index.js 可以让代码正常运行。

❯ node --preserve-symlinks index.js                
{ module: 'This is C' }
{ module: { module: 'This is C' } }

在第一次运行代码时,node.js 会解析 b.js 软链接所指向的目标文件的路径,而不是 b.js 这个软链接所在的目录。 所以会出现找不到 c.js 的情况,因为 c.js 和 b.js 软链接是同一个目录。

第二次运行代码是,node.js 会按照 b.js 软链接的目录来查找 c.js 文件,因为 b.js 软链接和 c.js 文件在同一个目录下,所以通过require('./c.js')是可以定位到的。

了解了 Node.js 如何对文件链接进行处理后,在来看 pnpm ,pnpm 的实现采用了文件链接,借助文件链接来达到节约磁盘空间的目的。 通过 pnpm 安装后的 node_modules 结构如下:

node_modules
├── foo -> ./.pnpm/foo@1.0.0/node_modules/foo
└── .pnpm
    ├── bar@1.0.0
    │   └── node_modules
    │       └── bar -> <store>/bar
    └── foo@1.0.0
        └── node_modules
            ├── foo -> <store>/foo
            └── bar -> ../../bar@1.0.0/node_modules/bar

在这个示例中,foo 依赖的 bar 实际指安装了一次,通过链接的方式进行复用。<store>是存储链接的目录,可以通过 .npmrc 文件中的 virtual-store-dir 修改。

由于 pnpm 的实现采用了链接,所以它提供了 node-linkersymlink 对安装时所采用的链接形式进行控制,除了 pnpm 外,TypeScript 和 rollup 等提供了 preserveSymlinks 选项。

如果对代码仓库进行 pnpm 的支持,遇到 Error: Cannot find module 类的错误,可以尝试修改 TypeScript 或 rollup 的相关配置。