12k
All articles

Начало работы с npm Workspaces

Настройка, команды и ограничения npm workspaces: управляйте монорепо, связывайте соседние пакеты и понимайте, когда добавить Turborepo или Nx.

OpenReplay Team
OpenReplay Team
Начало работы с npm Workspaces

npm workspaces, встроенные в npm начиная с версии 7, позволяют управлять несколькими пакетами в одном репозитории — монорепозитории — из единого корня: одна команда npm install поднимает общие зависимости в единый корневой node_modules и создаёт симлинки на ваши пакеты, благодаря чему межпакетные импорты разрешаются без npm link и без повторной публикации. Если у вас есть приложение и общая библиотека, или библиотека компонентов и сайт с документацией к ней, и вы устали от npm link, копирования кода или жонглирования отдельными репозиториями — это встроенная функция, которая устраняет эти трудности без каких-либо сторонних инструментов. В данном руководстве рассматриваются минимальная конфигурация, точные флаги команд, реальные ограничения и случаи, когда поверх следует добавить оркестратор сборки.

Ключевые выводы

  • npm workspaces входят в состав npm 7+; текущий релиз — npm 11.18.0, проверить свою версию можно командой npm -v.
  • Минимальная настройка состоит из двух файлов: корневой package.json с полями "private": true и "workspaces": ["packages/*"], а также по одному package.json на каждый пакет — после чего единственная команда npm install в корне связывает всё воедино.
  • Чтобы добавить зависимость от соседнего пакета, укажите его по имени с диапазоном "*"; npm создаст симлинк при установке, и изменения в исходном коде сразу станут видны во всех потребителях — без пересборки и повторной публикации.
  • npm workspaces разрешает и связывает зависимости, но не запускает задачи в порядке зависимостей, не кэширует результаты сборки и не вычисляет граф «затронутых» пакетов.
  • Используйте Turborepo или Nx поверх npm workspaces, а не вместо них — npm разрешает и связывает пакеты, а эти инструменты добавляют оркестрацию задач и кэширование.

Как работают npm workspaces?

npm workspaces превращают один репозиторий в монорепозиторий, поднимая общие зависимости в единый корневой node_modules и создавая симлинки на ваши пакеты рядом с ними. При запуске npm install в корне npm сканирует все рабочие пространства, устанавливает сторонние зависимости один раз на верхнем уровне и связывает каждый локальный пакет в node_modules по значению поля name. Если два ваших пакета зависят друг от друга, ссылка разрешается через этот симлинк — npm CLI автоматизирует связывание в рамках npm install и устраняет необходимость вручную запускать npm link.

Те же поле workspaces и модель симлинков используются в Yarn, pnpm и Bun, поэтому концептуальная модель переносится между менеджерами пакетов. Функция появилась в npm 7; любая более новая версия также поддерживается.

Какова минимальная настройка npm workspaces?

Минимальная настройка состоит из двух файлов: корневой package.json, объявляющий расположение пакетов, и по одному package.json на каждый пакет. Создайте следующую структуру:

my-monorepo/
├── package.json          # корень — private, перечисляет workspaces
└── packages/
    ├── utils/
    │   └── package.json   # @myorg/utils
    └── app/
        └── package.json   # @myorg/app

В корневом package.json необходимы два поля:

{
  "name": "my-monorepo",
  "private": true,
  "workspaces": ["packages/*"]
}

"private": true предотвращает случайную публикацию корня, а глоб packages/* указывает npm считать каждую директорию внутри packages/ рабочим пространством. Давайте каждому пакету скоупированное имя вида @myorg/utils, чтобы избежать коллизий в реестре:

{
  "name": "@myorg/utils",
  "version": "1.0.0",
  "main": "dist/index.js"
}

Запустите npm install один раз в корне. Единственный lockfile находится в корне, внутри отдельных пакетов нет node_modules — всё поднимается наверх.

Добавление межпакетной зависимости

Чтобы добавить зависимость от соседнего пакета, укажите его по имени с диапазоном "*"; npm создаст симлинк при установке, и изменения в исходном коде сразу станут видны во всех потребителях. В @myorg/app:

{
  "name": "@myorg/app",
  "dependencies": {
    "@myorg/utils": "*"
  }
}

Снова запустите npm install в корне. npm создаст симлинк из node_modules/@myorg/utils на packages/utils, и вы сможете импортировать его как любой опубликованный модуль:

import { formatDate } from "@myorg/utils";

Поскольку это симлинк, изменения в исходном коде packages/utils отражаются в app без пересборки и повторной публикации — именно в этом преимущество перед npm link. Одна оговорка при работе с разными инструментами: npm не поддерживает протокол версий workspace:, используемый в pnpm и Yarn Berry. Передача спецификатора workspace: приводит к ошибке npm с кодом EUNSUPPORTEDPROTOCOL, поэтому в npm внутренние пакеты указываются по имени и диапазону ("*"), а не через workspace:*.

Повседневные команды

Флаги нередко вызывают путаницу, поскольку единственное и множественное число означают разные вещи. Добавить зависимость в один пакет можно с флагом -w, во все пакеты — с --workspaces; запустить скрипт в одном рабочем пространстве — с -w, а во всех сразу — с --workspaces --if-present, что пропускает пакеты, в которых данный скрипт не определён.

# Установить зависимость в ОДНО рабочее пространство
npm install lodash -w @myorg/app

# Установить dev-зависимость в одно рабочее пространство
npm install -D vitest -w @myorg/utils

# Установить зависимость во ВСЕ рабочие пространства
npm install eslint --workspaces

# Запустить скрипт в ОДНОМ рабочем пространстве
npm run build -w @myorg/utils

# Запустить скрипт во ВСЕХ рабочих пространствах, пропуская те, где он не определён
npm run test --workspaces --if-present

-w — сокращение для --workspace, а --workspaces (или -ws) нацелено на все рабочие пространства. Настройте корневые скрипты один раз, чтобы npm run build разворачивался по всем пакетам:

{
  "scripts": {
    "build": "npm run build --workspaces --if-present",
    "test": "npm run test --workspaces --if-present"
  }
}

Чтобы убедиться, что граф связан корректно, выполните npm ls -ws или сделайте запрос с помощью npm query .workspace.

Ограничения: что npm workspaces не умеет

npm workspaces разрешает и связывает зависимости, но не запускает задачи в порядке зависимостей, не кэширует результаты сборки и не вычисляет граф «затронутых» пакетов. Если ваше приложение импортирует библиотеку, библиотеку необходимо собрать первой — запуск скрипта во всех рабочих пространствах завершится ошибкой, если они зависят друг от друга, поскольку npm не выполняет задачи в топологическом порядке; это улучшение по-прежнему остаётся открытым. Задайте порядок явно или используйте npm-run-all:

{
  "scripts": {
    "build:utils": "npm run build -w @myorg/utils",
    "build:app": "npm run build -w @myorg/app",
    "build": "npm run build:utils && npm run build:app"
  }
}

Ещё два подводных камня:

  • Вложенные node_modules. Когда два пакета требуют несовместимых версий одной и той же зависимости, npm прекращает поднимать её наверх и устанавливает вложенную копию внутри одного из пакетов. Зафиксируйте единую общую версию с помощью поля overrides в корне, чтобы дерево оставалось плоским:

    { "overrides": { "lodash": "^4.17.21" } }
  • Политика install-скриптов ужесточается. В npm v12, релиз которого ожидается в июле 2026 года, параметр allowScripts по умолчанию будет отключён, поэтому npm install больше не будет запускать скрипты зависимостей preinstall, install или postinstall без явного разрешения. Если ваши рабочие пространства используют шаг сборки в postinstall или prepare, заранее предусмотрите его явное разрешение — эти изменения отображаются как предупреждения в npm 11.16.0 и новее, что позволяет подготовиться заблаговременно.

Обратите внимание, что «отсутствие нативной интеграции с React/Vue/Vite» — это описание области применения, а не недостаток: рабочие пространства намеренно не зависят от фреймворка. Создание шаблонов приложений не входит в их задачи.

Когда стоит обратиться к Turborepo или Nx

Используйте Turborepo или Nx поверх npm workspaces, а не вместо них: npm разрешает и связывает ваши пакеты, тогда как эти инструменты добавляют оркестрацию задач, кэширование и сборки на основе графа затронутых пакетов для крупных репозиториев. Это взаимодополняющие уровни.

Задачаnpm workspacesTurborepo / Nx
Установка и связывание пакетовДелегирует npm
Порядок выполнения задач❌ ручные скрипты✅ топологический
Кэширование сборки/тестов✅ локальное + удалённое
«Затронутые» сборки✅ граф на основе изменений

Добавляйте один из этих инструментов, когда упорядоченные скрипты становятся громоздкими, CI пересобирает всё при каждом изменении или вы хотите запускать задачи только для пакетов, затронутых коммитом. Обратите внимание, что современная Lerna теперь основана на Nx — старый совет «npm + Lerna» влился в ту же модель многоуровневого подхода.

npm workspaces покрывает примерно первые 80% потребностей небольшого монорепозитория без каких-либо дополнительных инструментов. Создайте конфигурацию из двух файлов, настройте флаги, упорядочьте сборки и добавляйте оркестратор только тогда, когда узким местом становится конвейер, а не разрешение зависимостей. Используйте активный LTS-релиз Node (Node 20 достиг конца жизненного цикла 30 апреля 2026 года) и убедитесь, что npm -v возвращает версию 7 или новее, прежде чем начинать.

Часто задаваемые вопросы

Нужен ли в npm workspaces отдельный lockfile для каждого пакета или достаточно одного в корне?

npm workspaces создаёт единственный package-lock.json в корне репозитория, а не по одному на каждый пакет. Команда npm install в корне разрешает зависимости всех рабочих пространств совместно и фиксирует их в этом одном lockfile, тогда как в отдельных пакетах нет собственной директории node_modules, поскольку зависимости поднимаются в корень. Именно эта модель с единым lockfile обеспечивает согласованность версий во всех пакетах и является причиной, по которой установку всегда следует запускать из корня.

Почему команда 'npm run build --workspaces' завершается ошибкой, если мои пакеты зависят друг от друга?

Это происходит потому, что npm не запускает скрипты рабочих пространств в топологическом порядке (порядке зависимостей); он запускает их в том порядке, в котором перечислены рабочие пространства, поэтому потребитель может начать сборку раньше, чем будет готова импортируемая им библиотека, что приводит к ошибкам 'cannot find module' или неудачному разрешению зависимостей. Это по-прежнему открытое улучшение npm (issue 4139). Исправьте это, определив явные упорядоченные скрипты, которые сначала собирают библиотеку, или используйте такой инструмент, как npm-run-all, Turborepo или Nx.

Можно ли использовать протокол 'workspace:*' с npm, как в pnpm или Yarn?

Нет. npm не поддерживает протокол версий workspace:, используемый в pnpm и Yarn Berry, и передача спецификатора workspace: приводит к ошибке npm с кодом EUNSUPPORTEDPROTOCOL (задокументировано в npm/cli issue 8845). В npm внутренние пакеты указываются по имени и обычному диапазону, например '@myorg/utils': '*'; npm создаёт симлинки при установке. При миграции репозитория с pnpm или Yarn на npm замените все спецификаторы workspace: на обычные диапазоны.

Нужен ли 'npm link' при использовании workspaces?

Нет. npm workspaces автоматизирует связывание в рамках npm install, создавая симлинки на каждый локальный пакет в корневом node_modules по значению поля name, что устраняет необходимость вручную запускать npm link. Как только пакет указывает соседний пакет в зависимостях с диапазоном '*', единственная команда npm install в корне создаёт симлинк, и изменения в исходном пакете сразу становятся видны во всех потребителях — без пересборки и повторной публикации.

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.