Структура 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). Це потрібно для того щоб:
- дозволити пакункам імпортувати самих себе.
fooповинен мати можливістьrequire('foo/package.json')абоimport * as package from "foo/package.json". - уникання циклічних символічних посилань. Залежності пакунків розміщуються у тій самій теці, у якій знаходяться залежні пакунки. Для 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.