Структура 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 -> <store>/bar
    │           ├── index.js
    │           └── package.json
    └── foo@1.0.0
        └── node_modules
            └── foo -> <store>/foo
                ├── index.js
                └── package.json
Це єдині «справжні» файли у 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>/bar
    └── foo@1.0.0
        └── node_modules
            ├── foo -> <store>/foo
            └── 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>/bar
    └── foo@1.0.0
        └── node_modules
            ├── foo -> <store>/foo
            └── 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>/bar
    │       └── qar -> ../../qar@2.0.0/node_modules/qar
    ├── foo@1.0.0
    │   └── node_modules
    │       ├── foo -> <store>/foo
    │       ├── bar -> ../../bar@1.0.0/node_modules/bar
    │       └── qar -> ../../qar@2.0.0/node_modules/qar
      └── qar@2.0.0
        └── node_modules
            └── qar -> <store>/qar
Як ви можете бачити, хоча графік став глибшим (foo > bar > qar), глибина тек у файловій системі залишилася незмінною.
Таке компонування може здатися дивним на перший погляд, але воно повністю сумісне з алгоритмом резолюції модулів Node! Під час перетворення модулів 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 допомагає уникнути дурних помилок»