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

  1. приложение маркетинговых страниц, созданное с помощью NextJS,
  2. библиотека пользовательского интерфейса, в которой находится система дизайна, используемая всеми приложениями
  3. продукт/основное приложение, созданное с помощью Creact React App.
  4. Внутренняя общая библиотека, используемая в приложениях. Мы бы назвали это shared-lib

При подключении этих приложений использовалось рабочее пространство Yarn, так что один yarn install извлекает все необходимые зависимости, требуемые всеми различными приложениями и библиотеками. Каждое приложение представляет собой пакет NodeJS и имеет собственные внутренние команды сценария для развертывания.

Затем эти скрипты объединяются в корне package.json

репозиторий/пакеты/ui-lib

{ 
    "name": "ui-lib", 
    ..., 
    "scripts":{ 
        "build": "build-storybook -s public" 
    }, 
    ... 
}

repo/package.json

{ 
    "private": true, 
    "workspaces": [ "packages/*" ], 
    "scripts": { 
        "build:ui_lib": "yarn workspace ui_lib build", 
     ... 
    } 
}

Процесс сборки разработки

Предполагается, что библиотека пользовательского интерфейса, созданная с помощью Storybook, будет совместно использоваться как приложением marketing-pages, так и приложением main-app. Все визуальные элементы/страницы, используемые вспомогательными приложениями, размещены и разработаны в этом пакете.

Получение этой библиотеки для использования в качестве зависимости как в приложении marketing-pages, так и в приложении main-app было непростым делом, потому что main-app было создано с помощью приложения Create React, а marketing app было построено с помощью NextJS, оба из которых по умолчанию имеют предопределенный способ поиска зависимостей.

Create React App – это подход к созданию приложений ReactJS, не требующий настройки. По умолчанию единственный способ получить зависимость от реагирующего приложения — это установить его из npm. Это то, из чего был создан main-app.

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

Используя рабочие пространства Yarn, все пакеты приложений, т. е. (ui-lib, marketing-pages, main-app), были автоматически связаны символическими ссылками в корневом каталоге node_modules. Это означает, что на них можно легко ссылаться в разных пакетах.

Проблема заключалась в том, чтобы получить настройку приложения Create React для main-app, чтобы скомпилировать компоненты React, предоставленные ui-lib, без необходимости извлечения. React App Rewired сделал это возможным. Он предоставляет расширение внутренней конфигурации Webpack, используемой приложением Create React, чтобы его можно было расширить.

React App Rewired позволяет создавать простые расширения, но при этом по умолчанию используется Create React App для всей тяжелой работы. Хотя есть и другие варианты, на мой взгляд, это наименее инвазивный подход.

Приложение Create React должно знать, где искать, чтобы найти все каталоги, которые необходимо скомпилировать. (По умолчанию используется каталог src приложения)

Поскольку структура папок корневого репозитория выглядит примерно так

├── node_modules 
├── .git 
├── packages 
| ├── ui-lib 
| | └── node_modules 
| | └── src 
| | └── package.json 
| └── main-app 
| | └── node_modules 
| | └── public 
| | └── src 
| | └── config-overrides.js 
| | └── package.json 
| └── marketing-app 
| | └── node_modules 
| | └── pages 
| | └── next.config.js 
| | └── package.json 
| └── shared-app 
| | └── src 
| | └── package.json 
├── .gitignore 
└── package.json

реализация основного приложения _круглосуточно.

const path = require('path'); 
... 
function compileLinkNodeModules(config, env) { 
  config.module.rules[2].oneOf[1].include = [     
     path.resolve(__dirname, "src"), 
     path.resolve(__dirname, "../shared-lib"),  
     path.resolve(__dirname, "../ui-lib") 
  ]; 
  return config; 
} 
module.exports = function(config, env){ 
    ... 
    config = compileLinkNodeModules(config, env); 
    ... 
    return config 
}

Становится очевидным, где находятся пути к ui-lib и shared-app, и поэтому они должным образом учитываются в файле config-overrides.js.

При этом запуск yarn start в каталоге main-app работает без проблем. Кроме того, компоненты из кодовой базы ui-lib можно получить в main-app следующим образом.

import React from 'react'; 
import AccountingPage from 'ui-lib/src/pages/AccountingPage'; 
...

Что касается marketing-app, у NextJS тоже были похожие проблемы. Это потребовало использования некоторых плагинов NextJS для решения проблемы.

repo/packages/marketing-pages/next.config.json

const withTM = require("next-plugin-transpile-modules"); 
const withFonts = require("next-fonts"); 
const path = require("path"); 
module.exports = withFonts( 
   withTM({ 
      transpileModules: [ 
         "ui-lib", "shared-lib", 
         path.resolve(__dirname, "../ui-lib") 
      ], 
      ... 
      webpack(config, options) { 
         return config; 
      } 
   }) 
);

Проблема производственной сборки:

Приведенная выше настройка проекта позволяет очень легко настроить среду разработки за считанные минуты. Для настройки требуется только одна команда — yarn install.

Но когда мы думали о развертывании в рабочей среде, возникли следующие проблемы.

  1. Несоответствие сборки при разработке и на производственных платформах типа Netlify.
  2. Время сборки всех приложений заняло более 17 минут. Это сделало развертывание небольших изменений очень болезненным и увеличило время, необходимое для исправления ошибок.

Первоначальным решением первой проблемы было использование Gitlab Pipelines и сборка приложений в контейнере Docker. Полученная сборка, сгруппированная по папкам, затем фиксируется в репозитории развертывания, которое затем подхватывается Netlify.

Решение.

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

Предлагаемое решение состояло в том, чтобы получить все подприложения как подмодули Git. Это означало, что каждое подприложение было репозиторием git и могло быть развернуто изолированно. Как только возникали проблемы в конкретном приложении, оно немедленно решалось изолированно и развертывалось.

Но тогда и приложение marketing pages, и приложение main-app не удалось построить изолированно

Проблема интересная. В случае marketing-pages пакеты shared-lib и ui-lib больше не были связаны в пакете node_modules. Это то, что Yarn Workspaces решает по умолчанию.

И ui-lib, и shared-lib также должны были быть подмодулями git. Поскольку git-репозиторий можно установить как зависимости, package.json для marketing-app был обновлен.

repo/packages/marketing-app

{ 
    "name": "marketing-app", 
    ..., 
    "dependencies":{ 
       "ui-lib": "<the repository link of the ui lib>#<the last git tag>", 
       "shared-lib": "<the repository link of the shared lib>#<the last git tag>" 
     } 
}

Гарантируя использование определенного тега git вместо конкретной ветки, развертывание становится явным действием. Чтобы получить последние изменения из кодовой базы ui-lib, тег git нужно было обновить до новой версии. Это позволяет экспериментировать, не беспокоясь о регрессиях или критических изменениях как в marketing-pages, так и в main-app в результате новых коммитов, введенных в кодовые базы ui-lib или shared-lib.

Эти изменения были внесены непосредственно в ветку master как marketing app, так и main_app. Ветвь develop для marketing-pages и main-app — это то, на что затем ссылается корневая кодовая база mono repo, содержащая все подмодули.

Требуется промежуточная ветвь staging как для marketing-pages, так и для main-app, чтобы получать изменения из ветви develop в ветвь master, а также гарантировать, что dependencies в ветви master никогда не переползет на ветвь develop и наоборот.

Кроме того, поскольку в ветке master, в которой находится main-app, поиск фактического местоположения компонентов jsx необходимо обновить в config-overrides.js

repo/packages/main-app(master)

const path = require('path'); ... function compileLinkNodeModules(config, env) { config.module.rules[2].oneOf[1].include = [ path.resolve(__dirname, "src"), path.resolve(__dirname, "node_modules/shared-lib") path.resolve(__dirname, "node_modules/ui-lib") ]; return config; } ...

Примечание. Имя должно совпадать с именем пакета в файле packge.json.

package.json для кодовой базы main-app будет выглядеть следующим образом как для ветви develop, так и для ветви master.

repo/packages/main-app/package.json(develop)

{ 
    "name": "main-app", 
    ..., 
    "dependencies":{ 
        "ui-lib": "*", 
        "shared-lib": "*" 
    } 
}

Причина этого проста. В ветке develop работа, скорее всего, будет происходить в корневом репозитории, в котором находятся подмодули. Поскольку в этом репозитории есть Yarn Workspace, обрабатывающая все зависимости, все, что было разрешено, необходимо, поэтому необходимо явно использовать * как для ui-lib, так и для shared-lib.

repo/packages/main-app/package.json(master)

{ 
    "name": "main-app", 
    ..., 
    "dependencies":{ 
        "ui-lib": "<the repository link of the ui lib>#<the last git tag>", 
        "shared-lib": "<the repository link of the shared lib>#<the last git tag>" 
    } 
}

Первоначально опубликовано на beeola.me.