Основное внимание в этой статье уделяется обеспечению плавной работы по коду в монорепозитории TypeScript без каких-либо сюрпризов, а это означает, что Перейти к определению в среде IDE будет работать после свежего клона, без необходимости сборки проектов. заранее. Предлагается решение с псевдонимами путей и обсуждаются два решения, каждое со своими преимуществами и недостатками, для сборки пакетов.

Вы можете найти полные примеры и многое другое в следующем репо:



NiGhTTraX / ts-monorepo
Пример TypeScript monorepo с Lerna Использование функции« Перейти к определению
в IDE работает без необходимости… github.com»



Если вы хотите добавить в смесь create-react-app, NextJS, ts-node или другие инструменты, ознакомьтесь с моей последующей статьей Как заставить монорепозицию TypeScript хорошо сочетаться с другими инструментами.

Прежде всего, давайте настроим монорепозиторий. Вы можете использовать Lerna или рабочие области пряжи для создания структуры монорепозитория и символической ссылки на пакеты в соответствии с зависимостями между ними. Во второй части этой статьи Lerna будет использоваться для управления сборкой пакетов в правильном порядке, но при этом не требуется, чтобы вы использовали Lerna для управления монорепозиторием, поэтому вы можете выбрать любой инструмент, который вам больше нравится. настраивать.

Возьмем следующий пример, который показывает простой монорепозиторий с 2 ​​пакетами, foo и bar:.

.
├── package.json
├── packages
│   ├── foo
│   │   ├── package.json
│   │   ├── src
│   │   │   └── index.ts
│   │   ├── tsconfig.build.json
│   │   └── tsconfig.json
│   └── bar
│       ├── package.json
│       ├── src
│       │   └── index.ts
│       ├── tsconfig.build.json
│       └── tsconfig.json
├── tsconfig.build.json
└── tsconfig.json

Пакет foo экспортирует простую константу, а пакет bar импортирует ее. Направление зависимости (bar - ›foo) сделано намеренно, чтобы гарантировать, что мы создаем пакеты в правильном порядке (foo, затем bar), а не только в алфавитном порядке.

Обратите внимание на pkg.main конфигурацию в package.json пакета, которая указывает на папку сборки, а не на исходную папку. Когда другой пакет в монорепозитории пытается импортировать этот, или когда вы пытаетесь перейти в IDE из импорта этого пакета, папка сборки должна быть там и быть актуальной, чтобы все работало правильно. Это имеет много недостатков, включая необходимость построения монорепозитория после клонирования и необходимость его перестраивания при каждом изменении кода. Более того, IDE перейдет к выходу JS, а не к источнику TS. Давайте посмотрим, как мы можем указать как IDE, так и компилятор TS вместо этого переходить к источнику монорепозитория.

Корневой каталог проекта tsconfig.build.json - это ваша типичная конфигурация TS, которая будет использоваться в качестве основы для других конфигураций. «Обычный» tsconfig.json будет содержать настройки, необходимые для обеспечения плавной навигации в среде IDE. Конфиги должны быть отдельными, потому что настройки, необходимые для навигации по коду, должны быть отключены при сборке.

Псевдонимы пути

tsconfig.json - это конфигурация, которая будет выбираться IDE по умолчанию, и ее роль заключается в сопоставлении абсолютных имен пакетов с их исходным кодом в монорепозитории:

paths является здесь волшебным соусом: он сообщает компилятору TypeScript, что всякий раз, когда модуль пытается импортировать другой модуль из монорепозитория, он должен разрешить его из папки packages. В частности, он должен указывать на папку src этого пакета, потому что это папка, которая будет скомпилирована при публикации.

Конфигурация paths должна содержать одну запись для каждого пакета или, если у них есть общий префикс, такой как организация, глобус, который соответствует им всем.

Теперь для каждого пакета мы можем настроить другой tsconfig.json, расширяющий корневой пакет:

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

На приведенном выше GIF-изображении мы можем видеть, что даже после нового клона (задача очистки проверяет, не загрязнено ли рабочее дерево), щелчок Go to definition при импорте правильно переходит к исходному коду этого пакета. При таком подходе не возникает сюрпризов, когда кто-то клонирует проект и пытается по нему перемещаться. Это также означает, что когда кто-то обновит код, не будет никаких сюрпризов, поскольку сервер TS, встроенный в IDE, просто получит изменения, и вам не придется перестраивать проект (для воссоздания dist папок, требуемых другими решениями) .

У каждого пакета также будет tsconfig.build.json, который будет использоваться для публикации. Этот конфиг отличается от основного тем, что:

  • опуская compilerOptions.paths настройки, чтобы компилятор TypeScript смотрел в node_modules, а не в монорепозицию,
  • объявление outDir, который должен относиться к каждому пакету.

К сожалению, параметры include и outDir нельзя поднять в корневую конфигурацию, потому что они разрешаются относительно конфигурации, в которой они находятся.



Теперь нам нужно настроить правильные package.json сценарии для компиляции каждого пакета:

Здесь важны следующие моменты:

  • указание зависимостей между пакетами monorepo с помощью dependencies или devDependencies (см. примечание ниже) и
  • сообщая компилятору tsc использовать нашу tsconfig.build.json конфигурацию с помощью флага -p или --project.

Если мы попытаемся собрать bar пакет (который зависит от foo пакета) в новом состоянии (нигде нет dist папок), он потерпит неудачу и будет жаловаться, что не может найти foo, даже если Лерна связала его в своей папке node_modules. Это потому, что package.json foo определяет свою точку входа через поле main, чтобы указать на dist/index. Когда компилятор TypeScript видит import '@nighttrax/foo' в bar пакете, он будет искать bar/node_modules/@nighttrax/foo/dist/index.js и, поскольку foo не был собран (следовательно, в нем нет dist/ папки), завершится ошибка.

Если поле main указывает на исходный исходный код, все будет работать без каких-либо сюрпризов. Импорт пакета будет разрешен в символическую ссылку, которую lerna создает в node_modules и которая указывает на исходный код в монорепозитории.

Мы можем решить эту проблему двумя способами:

  1. Используйте lerna run, чтобы собрать все пакеты одновременно, и полагайтесь на него, собирая пакеты в правильном порядке, просматривая зависимости каждого пакета.
  2. Воспользуйтесь функцией ссылки на проекты, представленной в TS 3.0.

Лерна Беги

lerna run <script> будет запускать <script> в каждом пакете, для которого этот сценарий определен в егоpackage.json. Он также будет запускать их в топологическом порядке, что означает, что если пакет bar зависит от пакета foo, то <script> bar будет запущен до foo. Этот заказ создается путем просмотра dependencies и devDependencies в package.json каждого пакета.

Обычно перед публикацией используется сценарий prepublishOnly для запуска npm run build. Если вам нравится этот подход (и вы должны, потому что он упрощает публикацию), вы должны знать о некоторых предостережениях.

Во-первых, при публикации одного пакета (с независимым управлением версиями) lerna не будет запускать prepublishOnly скрипт для своих зависимостей. Это означает, что пакет, который вы пытаетесь опубликовать, может не найти сборки своих зависимостей, что приведет к ошибке сборки.

Кроме того, в то время как lerna run соблюдает топологический порядок пакетов, lerna publish этого не делает или, по крайней мере, не вычисляет тот же порядок.



Обходной путь для обоих этих предупреждений - не полагаться на prepublishOnly сценарий каждого пакета, а вместо этого построить все пакеты перед публикацией любого из них. Мы можем сделать это, просто вызвав lerna run build перед lerna publish, который запустит build скрипт каждого скрипта.

Если вы часто сталкиваетесь с проблемой публикации ограниченного набора пакетов или просто хотите иметь возможность легко отлаживать сборку, то следующее решение может быть более подходящим для вас.

Ссылки на проекты

Использование ссылок на проекты дает то же самое, что и lerna run - построение проектов в правильном порядке, - но также позволяет нам создавать по одному пакету за раз.

Здесь мы модифицируем предыдущий tsconfig.build.json, чтобы активировать ссылки на проекты, установив composite: true и указав, от каких других пакетов monorepo зависит этот конкретный пакет. Конфигурация references - это массив путей, и здесь мы вручную указываем путь к tsconfig.build.json зависимых пакетов. Каждый пакет должен установить composite: true, даже если это всего лишь листовой узел в ссылочном графе, иначе tsc будет жаловаться. rootDir необходимо для создания правильной структуры папок в dist/, иначе TypeScript может определить корневой каталог выше по иерархии папок и вывести ненужные вложенные папки.

Теперь, если мы изменим compile скрипт в package.json на запуск tsc -b, мы сможем создать единый пакет, и TypeScript создаст для нас любые зависимости проекта. Нам также необходимо обновить сценарий clean, чтобы удалить кеш сборки для tsc -b, иначе мы можем столкнуться с ситуацией, когда, если dist/ папка пакета удалена, но сам пакет не содержит изменений с прошлого раза, tsc -b будет не перестраивать его.

lerna run будет работать, как и раньше, поэтому главное преимущество этого решения состоит в том, что оно позволяет нам отлаживать сборки пакетов, не беспокоясь о других пакетах.

К сожалению, мы не можем поднять настройки references до корневой конфигурации, поэтому в конечном итоге мы объявляем одинаковые зависимости между пакетами как в package.json, так и в tsconfig.build.json для каждого пакета.



Так что у нас это. Хотя у обоих решений есть некоторые незначительные недостатки, я думаю, что мы все еще можем съесть свой торт и съесть его - хотя, возможно, торт меньшего размера, чем нам хотелось бы. Вы можете проверить полный пример в следующем репо:



Мне любопытно узнать, есть ли лучшие решения, особенно те, которые уменьшают дублирование, когда дело доходит до объявления зависимостей. В репозитории microsoft / TypeScript есть открытая проблема с автоматическим выводом ссылок на проекты из конфигураций lerna, но, похоже, в этом нет никакого прогресса.



Если у вас есть какие-либо вопросы или предложения, оставьте их в разделе комментариев или откройте вопрос GitHub в репо!

Спасибо за чтение, и если вам понравилась эта статья, то, возможно, вам понравится читать другие мои статьи или проверять некоторые из моих других проектов с открытым исходным кодом: