Предисловие

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

Несомненно: горячее обновление эталонного модуля

Что такое «ссылочный модуль hot update»: когда вы разрабатываете проект в монорепозитории, вы ссылаетесь на код в подпакетах, изменяете файлы исходного кода других подпакетов и автоматически обнаруживаете его в текущем проекте и поддерживаете горячие обновления, чтобы вы не нужно пересобирать продукт этого подпакета, а затем вручную обновлять страницу.

Давайте рассмотрим элегантное решение для компромисса с нескольких точек зрения, и прежде чем читать, мы предполагаем, что у читателя уже есть опытный разработчик основы веб-пакета или фреймворка.

Решение

Включает Tranpile
Почему бы не посмотреть?
Во-первых, почему нельзя запустить подпакет в режиме просмотра, а потом он может автоматически пересобрать продукт с внесенными изменениями?

  1. Во-первых, есть задержка во времени сборки. Если это tsc, если это rollup, то время, необходимое для восстановления одного файла, становится еще более невыносимым; Во-вторых, для больших подпакетных SDK самый быстрый TSC близок к 10 с, не говоря уже о свертывании. Конечно, можно еще сказать, что проблема времени сборки решается с помощью esbuild, но esbuild bundle — это один файл, однофайловые изменения имеется в виду общий релоад, а не локальный хмр, что также вызовет уничтожение общего рефреша основного проекта, представьте, что вы открываете всплывающий Modalкомпонент и открываете его в следующий раз (здесь не говорить о однофайловом перенос идеи unjs, а не в центре внимания этой статьи).
  2. Во-вторых, изменения продукта не могут быть обнаружены. Поскольку подпакет связан в node_modules, общая сторона фреймворка будет исключена и не будет прослушиваться, поэтому, даже если продукт изменится, страница не будет обновляться в горячем режиме.

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

Разделение правил

Поскольку код нашего подпакета — TS, мы должны позволить всем ресурсам ts войти в нашу область переноса, поэтому нам нужно разделить правила сопоставления .js и .ts resource, все .tsrules не установлены exclude, а .jsrules по-прежнему exclude исключены node_modules, например:

// js
{
  test: /\.js$/,
  exclude: /node_modules/
  use: [
    // transpiler ...
  ]
}

// ts
{
  test: /\.{ts|tsx|jsx)$/
  use: [
    // transpiler ...
  ]
}

Таким образом, ресурсы стороннего .tspackage войдут в область переноса, конечно, есть более подробные практики, такие как вынесение решения о функции при исключении, а не исключение @scope/*. в пределах определенного файла .

Перепозиционирование

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

Решение состоит в том, чтобы сначала найти расположение srcисходного кода этих подпакетов, а затем переместить ссылку на пакет на его ширину исходного кода alias. Как найти? Здесь рекомендуется использовать нижний слой @manypkg/get-packages самого популярного инструмента отправки пакетов монорепозитория changesets следующим образом:

import { getPackages } from '@manypkg/get-packages'
import { join } from 'path'

const collectAllProjectsEntryAlias = async () => {
  const workspaces = await getPackages(
    // ↓ Project root directory, usually obtained through process.cwd()
    projectRootPath
  );
  return workspaces.packages.reduce(
    (obj, pkg) => {
      const name = pkg.packageJson?.name;
      if (name) {
        obj[name] = join(pkg.dir, 'src');
      }
      return obj;
    },
    {},
  );
}

Это всего лишь простая логика, в зависимости от сценария вам также может понадобиться нормализовать поля в вашем дочернем пакете package.json, а также определить наличие папки и ожидать ли вы использовать версию пакета npm или версию локальная версия.

После этого вам просто нужно объединить их в псевдоним.

Напомним, что в прошлом мы использовали "main": "dist/index.js" для экспорта контента, а теперь делаем это с помощью alias:

{
  alias: {
    'some-pkg': '/path/to/some-pkg/src'
  }
}

Используя его исходный код some-pkg/src/index.ts, экспорт и транспиляцию в TS, вы можете добиться плавного горячего обновления!

Поддержка типов TypeScript

Несомненно, прямое позиционирование на src не может распознать изменение типа, мы можем напрямую использовать tsconfig.josn#compilerOptions.paths для идентификации, пример следующий:

{
  "compilerOptions": {
    "paths": {
      "some-pkg/*": ["../packages/some-pkg/*"],
    }
  }
}

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

На данный момент наша проблема с горячим обновлением полностью решена 😎, но неужели все так просто?

Убедитесь, что один экземпляр

Базовая логика для мультиэкземпляров
В монорепозитории легко упустить важный факт:

  • При чтении подпакетов его зависимости ищутся в его каталоге в качестве отправной точки.

Это означает, что если вы используете react для разработки повторно используемых компонентов, в настоящее время, согласно спецификации, вы должны сделать единственного человека, который использует ваш пакет компонентов только react, не писать reactв dependencies, а писать devDependencies, потому что вы разработали его локально для его использования. , и напишите его, чтобы подсказать человеку, который установил ваш пакет: peerDependencies

{
  "devDependencies": {
    "react": "^18.0.0"
  },
  "peerDependencies": {
    "react": "^18.0.0"
  }
}

Но в монорепо может отличаться от использования пакетов npm, будет только установка dependencies, дочерние пакеты devDependencies также являются локальными для нас! Это заставит компоненты пакета react использовать те же компоненты, что и в основном приложении react! Вызывает сбои приложения с несколькими хуками.

Это фатальная ошибка для дубликатов react, которая приводит к сбою приложения. Еще один классический случай, который не является фатальным: две копии antd, что приведет к тому, что всплывающая подсказка Message не будет стоять в очереди.

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

Решение дилеммы

Это также то, что я называю дилеммой peerDependencies, которую мы ранее обсуждали в системе PNPM Monorepo:

В этой статье есть два решения:

  1. во-первых, сохранить уникальность экземпляра за счет унифицированного позиционирования alias по отношению к основному приложению;
  2. Во-вторых, поднять их все до общей картины, чтобы оба нашли один и тот же экземпляр вверху. Любое решение не имеет автоматической масштабируемости и связано с затратами ручного труда.

Есть ли автоматизированное шелковистое решение?

Да, пока все в пределах оговоренных норм. Компромисс здесь заключается в том, что мы можем автоматически считывать peerDependencies поля других подпакетов package.json в том же репозитории, а затем пытаться найти их все в нашем основном application.package.json.

Вот два ключевых слова:

  • Автоматическое считывание: принцип такой же, как и в описанном выше методе отключения обновления, и вы можете использовать инструмент рабочей области, чтобы прочитать, peerDependencies какие из этих подпакетов package.json доступны.package.
  • Попробуйте позиционировать: некоторые зависимости от основного приложения должны быть, например react, а некоторые неважные, главное приложение не может использовать зависимость, наше основное приложение вообще не нужно устанавливать, поэтому заключается в том, чтобы попытаться позиционировать, если он не может быть позиционирован, он будет использовать подпакет, а если он будет найден, он будет использовать основное приложение, чтобы оставаться уникальным. Метод здесь должен быть requre.resolve() или resolve(), простой пример таков:
import resolve from 'resolve'
import { dirname } from 'path'

const alias = {}

try {
  const depPkgDir = dirname(resolve.sync(
    `${depName}/package.json`, 
    { basedir: projectRootPath }
  ))
  alias[depName] = depPkgDir
} catch {}

Необходимо найти, чтобы предотвратить многоэкземплярные зависимости peerDependencies должны быть в разделе Есть ли он? Правильно, должно быть, пока спецификация разработки.

Таким образом, мы можем решить проблему нескольких экземпляров. Оглядываясь назад, можно сказать, что до тех пор, пока разработчик может записывать зависимости в формальной спецификации разработки FE в соответствии с формальной спецификацией разработки FE, peerDependencies она может загружаться автоматически, что не является соглашением в соответствии с отраслевой спецификацией, а является базовой возможностью.

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

  • react
  • react-router
  • react-router-dom

Конечно, для внутренних сценариев разработки, чтобы полностью избежать разработчиков, не стандартизирующих разработку подпакетов, не пишите peerDependencies, вы можете вести список ключевых зависимостей, обычно это UI-библиотеки или зависимости, которые могут быть у каждого проекта, например antd/ arco-design и т. д., а затем навсегда переместите эти ключевые зависимости на сторону фреймворка.

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

Краткое содержание

Мы упростили разработку собственного проекта в монорепозитории, решив горячие обновления и важные проблемы с несколькими экземплярами.

Углубляясь дальше, нам вообще не нужно, чтобы разработчики все это понимали, эта логика должна идти с черным ящиком на стороне фреймворка, и автоматически открываться в монорепозитории, когда распознается, что проект распознан, потому что все это « само собой разумеющееся» для разработчиков.

С точки зрения широты, каковы другие шелковистые методы монорепозитория, такие как использование pnpm changesets, какие шелковистые методы на стороне фреймворка, такие как автоматический polyfill, лучшие современные стратегии сборки по умолчанию и т. д., потому что они не входят в объем темы этой статьи, предоставив читателям возможность исследовать и изучать их самостоятельно.

Выше.