Структура node_modules із символічними посиланнями

інформація

У цій статті описано лише структуру node_modules pnpm за відсутності пакунків з прямими залежностями. Для складнішого сценарію залежностей з прямими залежностями дивись як вирішуються проблеми з прямими залежностями.

У pnpm компонування node_modules використовує символічні посилання для створення вкладеної структури залежностей.

Кожен файл кожного пакунка всередині node_modules є жорстким посиланням на сховище з адресованим вмістом. Скажімо, ви встановили foo@1.0.0, який залежить від bar@1.0.0. pnpm створить жорстке посилання обох пакунків на node_modules, як показано нижче:

node_modules
└── .pnpm
    ├── bar@1.0.0
    │   └── node_modules
    │       └── bar
    │           ├── index.js     -> <store>/001
    │           └── package.json -> <store>/002
    └── foo@1.0.0
        └── node_modules
            └── foo
                ├── index.js     -> <store>/003
                └── package.json -> <store>/004

Це єдині «справжні» файли у node_modules. Після того, як усіх пакунків буде створено жорсткі посилання з node_modules, буде створено символічні посилання для побудови структури вкладеного графа залежностей.

Як ви могли помітити, обидва пакунки мають жорсткі посилання на підтеку всередині теки node_modules (foo@1.0.0/node_modules/foo). Це потрібно для того щоб:

  1. дозволити пакункам імпортувати самих себе. foo повинен мати можливість require('foo/package.json') або import * as package from "foo/package.json".
  2. уникання циклічних символічних посилань. Залежності пакунків розміщуються у тій самій теці, у якій знаходяться залежні пакунки. Для Node.js не має значення, чи знаходяться залежності всередині node_modules пакунка, чи у будь-яких інших node_modules у батьківських теках.

Наступним етапом інсталяції є створення символьних посилань на залежності. bar буде повʼязано з текою foo@1.0.0/node_modules:

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

Далі обробляються прямі залежності. foo буде приєднано до кореневої теки node_modules, оскільки foo є залежністю проєкту:

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

Це дуже простий приклад. Однак макет збереже цю структуру незалежно від кількості залежностей і глибини графа залежностей.

Додамо qar@2.0.0 як залежність bar та foo. Ось так виглядатиме нова структура:

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

Як ви можете бачити, хоча графік став глибшим (foo > bar > qar), глибина тек у файловій системі залишилася незмінною.

This layout might look weird at first glance, but it is completely compatible with Node's module resolution algorithm. Під час перетворення модулів Node ігнорує символічні посилання, тому коли bar вимагається з foo@1.0.0/node_modules/foo/index.js, Node не використовує bar за адресою foo@1.0.0/node_modules/bar, натомість bar перетвориться на його справжнє місцезнаходження (bar@1.0.0/node_modules/bar). Як наслідок, bar також може розвʼязувати свої залежності, які знаходяться у bar@1.0.0/node_modules.

Великим бонусом такої схеми є те, що доступними є лише ті пакунки, які дійсно є у залежностях. Зі сплющеною структурою node_modules всі підняті пакунки є доступними. Щоб дізнатися більше про те, чому це перевага, перегляньте "суворість pnpm допомагає уникнути прикрих помилок"

Unfortunately, many packages in the ecosystem are broken. They use dependencies that are not listed in their package.json. Щоб мінімізувати кількість проблем, з якими стикаються нові користувачі, pnpm стандартно підіймає всі залежності у node_modules/.pnpm/node_modules. Щоб вимкнути цей підйом, встановіть hoist у false.