Представьте кодовую базу внешнего интерфейса как единый репозиторий, состоящий из 3 подприложений,
- приложение маркетинговых страниц, созданное с помощью NextJS,
- библиотека пользовательского интерфейса, в которой находится система дизайна, используемая всеми приложениями
- продукт/основное приложение, созданное с помощью Creact React App.
- Внутренняя общая библиотека, используемая в приложениях. Мы бы назвали это
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
.
Но когда мы думали о развертывании в рабочей среде, возникли следующие проблемы.
- Несоответствие сборки при разработке и на производственных платформах типа Netlify.
- Время сборки всех приложений заняло более 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.