Перейти до основного змісту
Версія: Next

.pnpmfile.mjs

pnpm дозволяє вам підключатися безпосередньо до процесу встановлення за допомогою спеціальних функцій (хуків). Хуки можна оголошувати у файлі з назвою .pnpmfile.mjs (ESM) або .pnpmfile.cjs (CommonJS).

Стандартно .pnpmfile.mjs має бути розташований у тій же теці, що і файл блокування. Наприклад, у робочому просторі зі спільним файлом блокування, .pnpmfile.mjs повинен знаходитись у корені монорепо.

Хуки

TL;DR

Функція хукаПроцесВикористання
hooks.readPackage(pkg, context): pkgВикликається після того, як pnpm проаналізує маніфест пакунків залежностіДозволяє змінювати файл package.json залежності.
hooks.afterAllResolved(lockfile, context): lockfileВикликається після розвʼязання залежностей.Дозволяє змінювати файл блокування.
hooks.beforePacking(pkg): pkgВикликається перед створенням tar-архіву під час пакування/публікаціїДозволяє налаштувати опублікований package.json
resolversВикликається під час виконання резолюції пакунка.Дозволяє реєструвати власні резолвери пакунків.
fetchersВикликається під час отримання пакунка.Дозволяє реєструвати власні фетчери пакунків.

hooks.readPackage(pkg, context): pkg | Promise<pkg>

Дозволяє змінювати package.json залежності після аналізу і до розвʼязання. Ці мутації не зберігаються у файловій системі, проте вони впливають на те, що буде отримано у файлі блокування, а отже, і на те, що буде встановлено.

Зауважте, що вам потрібно буде видалити pnpm-lock.yaml, якщо ви вже розвʼязали залежність, яку ви хочете змінити.

підказка

Якщо вам потрібно внести зміни до package.json, збереженого у файловій системі, вам потрібно скористатися командою pnpm patch і виправити файл package.json. Це може бути корисно, наприклад, якщо ви хочете видалити поле bin у залежності.

Аргументи

  • pkg – маніфест пакунка. Або відповідь з реєстру, або вміст package.json.
  • context — обʼєкт Context для кроку. Метод #log(msg) дозволяє використовувати журнал налагодження для кроку.

Використання

Приклад .pnpmfile.mjs (змінює dependencies залежності):

function readPackage(pkg, context) {
// Перевизначимо маніфест foo@1.x, завантаживши його з реєстру
if (pkg.name === 'foo' && pkg.version.startsWith('1.')) {
// Заміна bar@x.x.x with bar@2.0.0
pkg.dependencies = {
...pkg.dependencies,
bar: '^2.0.0'
}
context.log('bar@1 => bar@2 in dependencies of foo')
}

// Це призведе до того, що всі пакунки, які використовують
// baz@x.x.x, почнуть використовувати baz@1.2.3
if (pkg.dependencies.baz) {
pkg.dependencies.baz = '1.2.3';
}

return pkg
}

export const hooks = {
readPackage
}

Відомі обмеження

Видалення поля scripts з маніфесту залежності за допомогою readPackage не завадить pnpm зібрати залежність. При побудові залежності pnpm зчитує package.json пакунка з архіву пакунка, на який хук не впливає. Щоб проігнорувати збірку пакунка, скористайтеся полем allowBuilds.

hooks.updateConfig(config): config | Promise<config>

Додано у: v10.8.0

Дозволяє змінювати параметри конфігурації, які використовуються pnpm. Цей хук є найбільш корисним у поєднанні з configDependencies, що дозволяє вам ділитися та повторно використовувати налаштування в різних репозиторіях Git.

Наприклад, @pnpm/plugin-better-defaults використовує хук updateConfig для застосування спеціального набору рекомендованих параметрів.

Приклад використання

.pnpmfile.mjs
export const hooks = {
updateConfig (config) {
return Object.assign(config, {
enablePrePostScripts: false,
optimisticRepeatInstall: true,
resolutionMode: 'lowest-direct',
verifyDepsBeforeRun: 'install',
})
}
}

hooks.afterAllResolved(lockfile, context): lockfile | Promise<lockfile>

Дозволяє змінювати вивід файлу блокування перед його серіалізацією.

Аргументи

  • lockfile — Обʼєкт резолюції файлу блокування, який серіалізовано у pnpm-lock.yaml.
  • context — обʼєкт Context для кроку. Метод #log(msg) дозволяє використовувати журнал налагодження для кроку.

Приклад використання

.pnpmfile.mjs
function afterAllResolved(lockfile, context) {
// ...
return lockfile
}

export const hooks = {
afterAllResolved
}

Відомі обмеження

Їх немає — все, що можна зробити з файлом блокування, можна змінити за допомогою цієї функції, і ви навіть можете розширити функціональність файлу блокування.

hooks.beforePacking(pkg): pkg | Promise<pkg>

Додано у: v10.28.0

Дозволяє змінити маніфест package.json перед його пакуванням у tar-архів під час pnpm pack або pnpm publish. Це корисно для налаштування опублікованого пакунка без впливу на вашу локальну розробку package.json.

На відміну від hooks.readPackage, який змінює спосіб розвʼязання залежностей під час встановлення, beforePacking впливає лише на вміст архіву, який публікується.

Аргументи

  • pkg – Об’єкт маніфесту пакунка, який буде включено до опублікованого tar-архіву.

Приклад використання

.pnpmfile.mjs
function beforePacking(pkg) {
// Видалити поля, призначені лише для розробки, з опублікованого пакунка
delete pkg.devDependencies
delete pkg.scripts.test

// Додати метадані публікації
pkg.publishedAt = new Date().toISOString()

// Змінити експорт пакунків для production
if (pkg.name === 'my-package') {
pkg.main = './dist/index.js'
}

return pkg
}

export const hooks = {
beforePacking
}
нотатка

Зміни, зроблені цим хуком, впливають тільки на package.json всередині архіву. Ваш локальний файл package.json залишається незмінним.

hooks.preResolution(options): Promise<void>

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

Аргументи

  • options.existsCurrentLockfile — булеве значення, яке є істинним, якщо файл блокування за адресою node_modules/.pnpm/lock.yaml існує.
  • options.currentLockfile — Обʼєкт файлу блокування з node_modules/.pnpm/lock.yaml.
  • options.existsNonEmptyWantedLockfile — булеве значення, яке є істинним, якщо файл блокування за адресою pnpm-lock.yaml існує.
  • options.wantedLockfile — Обʼєкт файлу блокування з pnpm-lock.yaml.
  • options.lockfileDir — Тека, де знаходиться потрібний файл блокування.
  • options.storeDir — Розташування теки store.
  • options.registries — Зіставлення областей видимості з URL-адресами реєстрів.

hooks.importPackage(destinationDir, options): Promise<string | undefined>

Цей хук дозволяє змінити спосіб запису пакунків до node_modules. Значення, що повертається, є необовʼязковим і вказує, який метод було використано для імпорту залежності, наприклад: clone, hardlink.

Аргументи

  • destinationDir — Тека призначення, куди має бути записано пакунок.
  • options.disableRelinkLocalDirDeps
  • options.filesMap
  • options.force
  • options.resolvedFrom
  • options.keepModulesDir

hooks.fetchers

Видалено у v11.0.0

hooks.fetchers було видалено. Використовуйте натомість фетчери верхнього рівня. Інформацію про новий API дивіться у розділі Власні фетчери.

Пошук

Додано у: v10.16.0

Функції пошуку використовуються з pnpm list та pnpm why через прапорець --find-by.

Приклад:

.pnpmfile.mjs
export const finders = {
react17: (ctx) => {
return ctx.readManifest().peerDependencies?.react === "^17.0.0"
}
}

Використання:

pnpm why --find-by=react17

Дивіться Пошук для отримання додаткової інформації.

Власні резолвери та фетчери

Додано у: v11.0.0

За допомогою власних резолверів та фетчерів можна реалізувати власну логіку резолюції та отримання пакунків для нових схем ідентифікаторів пакунків (наприклад, my-protocol:package-name). Вони реєструються як експорти верхнього рівня в .pnpmfile.cjs:

module.exports = {
resolvers: [customResolver1, customResolver2],
fetchers: [customFetcher1, customFetcher2],
}

Інтерфейси TypeScript

interface CustomResolver {
canResolve?: (wantedDependency: WantedDependency) => boolean | Promise<boolean>
resolve?: (wantedDependency: WantedDependency, opts: ResolveOptions) => ResolveResult | Promise<ResolveResult>
shouldRefreshResolution?: (depPath: string, pkgSnapshot: PackageSnapshot) => boolean | Promise<boolean>
}

interface CustomFetcher {
canFetch?: (pkgId: string, resolution: Resolution) => boolean | Promise<boolean>
fetch?: (cafs: Cafs, resolution: Resolution, opts: FetchOptions, fetchers: Fetchers) => FetchResult | Promise<FetchResult>
}

Власні резолвери

Власні резолвери перетворюють дескриптори пакунків (наприклад, foo@^1.0.0) на резолюції, які зберігаються у файлі блокування.

Інтерфейс резолвера

Власний резолвер — це об’єкт, який може реалізовувати будь-яку комбінацію таких методів:

canResolve(wantedDependency): boolean | Promise<boolean>

Визначає, чи може цей резолвер розвʼязати задану залежність.

Аргументи:

  • wantedDependency — Об'єкт з:
    • alias – Назва пакунка або псевдонім, з яким він зʼявляється в package.json
    • bareSpecifier — Діапазон версій, URL-адреса git, шлях до файлу або інший специфікатор

Повертає: true якщо цей резолвер може обробити пакунок, false – якщо ні. Визначає, чи буде викликано resolve.

resolve(wantedDependency, opts): ResolveResult | Promise<ResolveResult>

Визначає необхідну залежність від конкретних метаданих пакунка та інформації про її визначення.

Аргументи:

  • wantedDependency — Бажана залежність (те саме, що й canResolve)
  • opts — Обʼєкт з:
    • lockfileDir – Тека, що містить файл блокування
    • projectDir — Коренева тека проєкту
    • preferredVersions — Мапа назв пакунків та їх бажаних версій

Повертає: Обʼєкт з:

  • id — Унікальний ідентифікатор пакунка (наприклад, 'custom-pkg@1.0.0')
  • resolution — Метадані резолюції. Це можуть бути:
    • Стандартна резолюція, напр { tarball: 'https://...', integrity: '...' }
    • Власна резолюція: { type: 'custom:cdn', url: '...' }

Власні резолюції можуть оброблятись відпвоідним власним фетчером.

Типи власних резолюцій

Власні резолюції повинні використовувати префікс custom: в полі типу (напр., custom:cdn, custom:artifactory) щоб відрізняти їх від вбудованих типів резолюцій.

shouldRefreshResolution(depPath, pkgSnapshot): boolean | Promise<boolean>

Отримання true запускає процес визначення резолюцій всіх пакунків, пропускаючи оптимізацію "Lockfile is up to date". Це є корисним для реалізації інвалідування кешу на основі часу або іншої власної логіки повторного визначення резолюцій.

Аргументи:

  • depPath — рядок ідентифікатора пакунка (наприклад, lodash@4.17.21)
  • pkgSnapshot — Запис в lockfile для цього пакунка, що забезпечує прямий доступ до резолюцій, залежностей тощо.

Повертає: true для примусового повторного визначення резолюцій, інакше — false.

нотатка

shouldRefreshResolution пропускається під час встановлення з замороженим файлом блокування, оскільки в цьому режимі резолюції не допускаються.

Власні фетчери

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

Інтерфейс фетчера

Власний фетчер — це об’єкт, який може впроваджувати наступні методи:

canFetch(pkgId, resolution): boolean | Promise<boolean>

Визначає, чи може цей фетчер отримати пакунок з вказаною резолюцією.

Аргументи:

  • pkgId — Унікальний ідентифікатор пакунка з етапу резолюції
  • resolution – Обʼєкт резолюції з методу resolve резолвера

Повертає: true якщо цей фетчер може отримати пакунок, false — якщо ні.

fetch(cafs, resolution, opts, fetchers): FetchResult | Promise<FetchResult>

Отримує файли пакунка та повертає метадані отриманого пакунка.

Аргументи:

  • cafs — Інтерфейс файлової системи з адресацією вмісту для зберігання файлів
  • resolution – Об’єкт резолюції (такий самий, як передано до canFetch)
  • opts — Параметри отримання включають:
    • lockfileDir – Тека, що містить файл блокування
    • filesIndexFile — Шлях до індексу файлів
    • onStart — Опціональний зворотний виклик, що спрацьовує на початку отримання
    • onProgress — Опціональний зворотний виклик до progress
  • fetchers — Обʼєкт, що містить стандартні фетчери pnpm для делегування:
    • remoteTarball — Фетчер для отримання віддалених tar-архівів
    • localTarball — Фетчер для отримання локальних tar-архівів
    • gitHostedTarball — Фетчер для отримання tar-архівів з GitHub/GitLab/Bitbucket
    • directory — Фетчер для отримання локальних тек
    • git — Фетчер для git-репозиторіїв

Повертає: Обʼєкт з:

  • filesIndex — Мапа відносних шляхів до файлів та їхніх фізичних розташувань. Для віддалених пакунків це шляхи у сховищі pnpm з адресацією за вмістом (CAFS, content-addressable store). Для локальних пакунків (коли local: true) це абсолютні шляхи до файлів на диску.
  • manifest — Опціонально. Файл package.json з отриманого пакунка. Якщо не вказано, pnpm прочитає його з диску за потреби. Це дозволяє уникнути додаткової операції вводу-виводу і рекомендується, якщо дані маніфесту вже доступні (наприклад, якщо вони були проаналізовані під час отримання).
  • requiresBuild — Булеве значення, яке вказує, чи містить пакунок скрипти збірки, які потрібно виконати. Встановлює значення true, якщо пакунок містить скрипти preinstall, installабо postinstall, або містить файли binding.gyp або .hooks/. Стандартні фетчери визначають це автоматично на основі маніфесту та списку файлів.
  • local — Опціонально. Встановлює значення true, щоб завантажити пакунок безпосередньо з диска без копіювання до сховища pnpm. Коли true, filesIndex має містити абсолютні шляхи до файлів на диску, і pnpm жорстко звʼяже їх з node_modules замість копіювання. Ось так фетчер тек обробляє локальні залежності (наприклад, file:../my-package).
Делегування стандартним фетчерам

Власні фетчери можуть виконувати делегування стандартним фетчерам pnpm за допомогою параметра fetchers.

Приклади використання

Простий власний резолвер

У цьому прикладі показано власний резолвер, який розпізнає пакунки з власного реєстру:

.pnpmfile.cjs
const customResolver = {
// Обробляти лише пакунки з областю дії @company
canResolve: (wantedDependency) => {
return wantedDependency.alias.startsWith('@company/')
},

resolve: async (wantedDependency, opts) => {
// Отримати метадані з власного репозиторію
const response = await fetch(
`https://custom-registry.company.com/${wantedDependency.alias}/${wantedDependency.bareSpecifier}`
)
const metadata = await response.json()

return {
id: `${metadata.name}@${metadata.version}`,
resolution: {
tarball: metadata.tarballUrl,
integrity: metadata.integrity
}
}
}
}

module.exports = {
resolvers: [customResolver]
}
Власний резолвер та фетчер з shouldRefreshResolution

У цьому прикладі показано, як резолвер і фетчер працюють разом із власним типом резолюції та інвалідуванням кешу за часом:

.pnpmfile.cjs
const customResolver = {
canResolve: (wantedDependency) => {
return wantedDependency.alias.startsWith('company-cdn:')
},

resolve: async (wantedDependency, opts) => {
const actualName = wantedDependency.alias.replace('company-cdn:', '')
const version = await fetchVersionFromCompanyCDN(actualName, wantedDependency.bareSpecifier)

return {
id: `company-cdn:${actualName}@${version}`,
resolution: {
type: 'custom:cdn',
cdnUrl: `https://cdn.company.com/packages/${actualName}/${version}.tgz`,
cachedAt: Date.now(), // Custom metadata for shouldRefreshResolution
},
}
},

shouldRefreshResolution: (depPath, pkgSnapshot) => {
// Перевірити власні метадані, що зберігаються у резолюції
const cachedAt = pkgSnapshot.resolution?.cachedAt
if (cachedAt && Date.now() - cachedAt > 24 * 60 * 60 * 1000) {
return true // Re-resolve if cached more than 24 hours ago
}
return false
},
}

const customFetcher = {
canFetch: (pkgId, resolution) => {
return resolution.type === 'custom:cdn'
},

fetch: async (cafs, resolution, opts, fetchers) => {
// Делегуємо стандартному фетчеру tar-архівів pnpm
const tarballResolution = {
tarball: resolution.cdnUrl,
integrity: resolution.integrity,
}

return fetchers.remoteTarball(cafs, tarballResolution, opts)
},
}

module.exports = {
resolvers: [customResolver],
fetchers: [customFetcher],
}
Простий власний фетчер

У цьому прикладі показано власний фетчер, який завантажує певні пакунки з іншого джерела:

.pnpmfile.cjs
const customFetcher = {
canFetch: (pkgId, resolution) => {
return pkgId.startsWith('@company/')
},

fetch: async (cafs, resolution, opts, fetchers) => {
// Делегуємо фетчеру tar-архівів pnpm змінений URL
const tarballResolution = {
tarball: resolution.tarball.replace(
'https://registry.npmjs.org/',
'https://custom-registry.company.com/'
),
integrity: resolution.integrity
}

return fetchers.remoteTarball(cafs, tarballResolution, opts)
}
}

module.exports = {
fetchers: [customFetcher]
}
Власний тип резолюції з резолвером і фетчером

У цьому прикладі показано, як власний резолвер і фетчер працюють разом із власним типом резолюції:

.pnpmfile.cjs
const customResolver = {
canResolve: (wantedDependency) => {
return wantedDependency.alias.startsWith('@internal/')
},

resolve: async (wantedDependency) => {
return {
id: `${wantedDependency.alias}@${wantedDependency.bareSpecifier}`,
resolution: {
type: 'custom:internal-directory',
directory: `/packages/${wantedDependency.alias}/${wantedDependency.bareSpecifier}`
}
}
}
}

const customFetcher = {
canFetch: (pkgId, resolution) => {
return resolution.type === 'custom:internal-directory'
},

fetch: async (cafs, resolution, opts, fetchers) => {
// Делегуємо фетчеру тек pnpm локальні пакунки
const directoryResolution = {
type: 'directory',
directory: resolution.directory
}

return fetchers.directory(cafs, directoryResolution, opts)
}
}

module.exports = {
resolvers: [customResolver],
fetchers: [customFetcher]
}

Пріоритет та порядок

Коли зареєстровано кілька резолверів, вони перевіряються по порядку. Для розвʼязання буде використано перший резолвер, для якого canResolve повертає true. Те саме стосується і фетчерів: перший фетчер, для якого canFetch повертає true, буде використаний для отримання пакунків.

Спочатку перевіряються власні резолвери, а вже потім — вбудовані резолвери pnpm (npm, git, tarball тощо), що дає вам повний контроль над пошуком пакунків.

Зауваження щодо продуктивності

Функції canResolve(), canFetch() та shouldRefreshResolution() мають бути дешевими перевірками (в ідеалі — синхронними), оскільки вони викликаються для кожної залежності під час розвʼязання.

Повʼязана конфігурація

ignorePnpmfile

  • Стандартно: false
  • Тип: Boolean

Файл pnpmfile буде проігноровано. Корисно використовувати разом з --ignore-scripts, якщо ви хочете переконатися, що жоден скрипт не буде виконано під час встановлення.

pnpmfile

  • Стандартно: ['.pnpmfile.mjs']
  • Тип: path[]
  • Приклад: ['.pnpm/.pnpmfile.mjs']

Розташування локальних файлів pnpmfile.

globalPnpmfile

  • Стандартно: null
  • Тип: path
  • Приклад: ~/.pnpm/global_pnpmfile.mjs

Розташування глобального pnpmfile. Глобальний pnpmfile використовується всіма проєктами під час встановлення.

нотатка

Рекомендується використовувати локальні pnpmfiles. Використовуйте глобальний файл pnpmfile лише у тому випадку, якщо ви використовуєте pnpm у проєктах, які не використовують pnpm як основний менеджер пакунків.