Давайте спробуємо створити Monorepo.

pic

https://www.aviator.co/blog/monorepo-a-hands-on-guide-for-managing-repositories-and-microservices/

Не так давно я стикався з проблемою використання однакових компонентів і конфігурацій у кількох проектах одночасно. Спочатку я використовував метод розділення Git Repo для спільних частин, але результат був таким, що інколи спільний код не оновлювався або виникали баги, що вимагало частих оновлень. Це призвело до пошуку рішення, і я натрапив на техніку, яку називають Monorepo. Коли я почав читати про концепцію, я зрозумів, що ця техніка може допомогти вирішити мою проблему. Тому я вирішив написати цей блог, щоб поділитися та зберегти його для себе, а також поділитися з іншими розробниками, щоб вони могли використовувати і розвивати це далі.

Чому Monorepo?

Звісно, все має свої переваги і недоліки, які треба ретельно оцінити перед прийняттям рішення про використання. Тому перед тим, як вирішити, чи варто використовувати Monorepo, важливо ретельно проаналізувати його плюси і мінуси, щоб переконатися, що Monorepo підходить для потреб і розміру проекту, над яким ви працюєте.

Переваги

  1. Обмін кодом між проектами: Ви стикаєтесь з проблемою розділення модулів, які можна використовувати в кількох проектах, і кількість репозиторіїв зростає? Якщо так, то використання Monorepo допоможе зменшити повторювану роботу і збільшити гнучкість у розвитку. Один репозиторій, не потрібно турбуватись про багатократне отримання кодів з різних репозиторіїв для виконання роботи.
  2. Відсутність проблем з отриманням кількох репозиторіїв для запуску одного проекту: Коли код спільний, без Monorepo нам доведеться створювати окремі git-репозиторії для кожної спільної папки. У деяких організаціях використовують різні git-токени для кожного репозиторію (що є хорошою практикою). Використання Monorepo вирішує цю проблему.
  3. Можливість відразу використовувати оновлення в спільному коді: Ви більше не забудете оновити спільний репозиторій, оскільки це буде відбуватись автоматично, коли все знаходиться в одному репозиторії.

Недоліки

  1. Управління версіями може бути складним: Якщо не продумати git flow, ваш git-репозиторій може швидко стати важким для управління.
  2. Більше навантаження на машину, що виконує: Якщо у вас мало оперативної пам'яті або слабкий процесор, система може працювати повільніше через більшу кількість ресурсів і простору, що використовуються.
    3.
    Проблеми інтеграції та розгортання: Коли репозиторій стає складнішим, конфігурація для розгортання також ускладнюється. Потрібно добре спланувати і виділити час для налаштування.

У цьому блозі я налаштовуватиму Monorepo, використовуючи Bun як основу для розробки. Для цього буде створено папку shared для зберігання утиліт і папку myproject, яка буде використовуватися як основний проект.

Попередні вимоги

Для цього блогу буде використовуватися наступний технологічний стек:

Коли все готово, почнемо!

Структура папок

Почнемо зі створення робочого простору, створивши нову папку, яку я назвучу themonorepo. Потім використаємо команду bun init -y, щоб ініціалізувати проект. Після завершення створення структура папок виглядатиме так:

pic

Ініціальна структура папок

Ця команда створить файл index.ts, який в кореневій папці не буде використовуватись. Його можна видалити.

Далі створимо ще одну папку, в якій зберігатимемо спільні утиліти. У цьому блозі вона буде називатися shared.

mkdir shared

Тепер заходимо в папку shared і створюємо ще 2 папки: components і utils.

cd shared  
mkdir components  
mkdir utils

Тепер використовуємо команду bun init -y для кожної з папок:

cd components  
bun init -y  
cd ../utils  
bun init -y

У папці components будуть використовуватися компоненти React, тому може знадобитися змінити tsconfig.json і встановити React, щоб усе працювало коректно (зверніть увагу на версію, адже якщо вона не співпадає з версією в інших проектах, можуть виникнути проблеми).

Якщо у вас є класи утиліт, такі як Tailwind, їх не потрібно буде встановлювати заново, оскільки вони будуть компілюватися під час виконання та повинні вже бути в основному проекті.

bun add react@18  
bun add -D @types/react@18

Нарешті, основний проект, в цьому блозі буде тільки один модуль, який я назву myproject. Створимо його за допомогою такої команди:

bunx create-next-app myproject

Ця команда запитає у нас кілька налаштувань для конфігурації проекту. У цьому блозі ми використовуватимемо значення за замовчуванням.

Тепер наша структура папок буде виглядати так:

themonorepo/  
├── shared/   
│ ├── components/ # Компоненти React  
│ └── utils/ # Утиліти  
├── myproject/ # Основний проект Next.js  
│ ├── pages/   
│ ├── public/   
│ ├── components/   
│ ├── tsconfig.json   
│ ├── package.json   
│ └── next.config.js   
└── package.json # Основний package.json

Налаштування робочого простору

Тепер ми налаштуємо root folder, щоб він знав про всі модулі всередині. Для цього оновимо файл package.json кореневої папки, ось так:

{  
 ...,  
 "workspaces": [  
 "shared/*",  
 "myproject"  
 ]  
}

У цьому випадку ми додали параметр workspaces, який є масивом, що вказує шляхи до підпроектів.

  • "shared/*": Помітимо, що після shared/ є *, оскільки ця папка містить підкаталоги, такі як components/ та utils/
  • "myproject": Основний проект, який знаходиться в папці myproject

Далі створимо scripts, щоб запустити Next.js. Назву скрипта можна вибрати довільно. Коли ми запустимо, будемо використовувати команду bun run <назва скрипта>. У цьому блозі називатимемо скрипт "dev" (але праву частину команди змінювати не можна).

{  
 ...,  
 "workspaces": [  
 "shared/*",  
 "myproject"  
 ],  
 "scripts": {  
 "dev": "bun run --filter myproject dev"  
 }  
}

У цьому прикладі:

  • Параметр --filter використовується для вказівки, який модуль ми хочемо запустити.
  • Після цього додаємо dev, що є скриптом у package.json проекту Next.js в myproject.

Тепер спробуємо запустити:

bun run dev

pic

Підключення утилітних папок до основного проекту

Тепер давайте змінимо назви модулів у shared folders, щоб вони відповідали вимогам Monorepo та щоб ми могли звертатися до цих папок в основному проекті з префіксом “@testmonorepo/*”. Змініть файл відповідно.

# ./shared/utils/package.json  
{  
 "name": "@testmonorepo/utils",  
 ...


# ./shared/components/package.json  
{  
 "name": "@testmonorepo/components",  
 ...  
}

Тепер ми скажемо TypeScript для Next.js, щоб він знав про наші папки, оновивши файл tsconfig.json в myproject наступним чином:

# ./myproject/tsconfig.json  
{  
 ...,  
 "paths": {  
 "@/*": ["./src/*"],  
 "@testmonorepo/utils": ["../shared/utils/*"],  
 "@testmonorepo/components": ["../shared/components/*"]  
 },  
 ...  
}

Тепер спробуємо перевірити, чи правильно налаштували модулі, додавши код до utils та components, а потім викликаючи їх

// ./shared/utils/index.ts  
export const greetUser = (name: string): string => {  
 return `Hello, ${name}!`;  
};
// ./shared/components/container.tsx  
import type { FC, PropsWithChildren } from "react";  

const Container: FC = ({ children }) => {  
 return (  
 <div className="container">{children}</div>  
 );  
};  
export default Container;

Тепер спробуємо додати це в page.tsx:

// ./myproject/src/app/page.tsx   
import Container from "@testmonorepo/components/container";   
import { greetUser } from "@testmonorepo/utils";  

export default function Home() {    
 return <Container>{greetUser("Champ")}</Container>;  
} 

Результат буде таким:

pic

Якщо ви побачите, Tailwind class у components не працює, тому що цей компонент знаходиться поза content у Tailwind config. Це можна виправити двома способами:

  1. Додати Tailwind у модуль components.
  2. Або додати content config безпосередньо в myproject.

У цьому блозі ми виберемо другий спосіб — додамо content в Tailwind config для myproject:

# ./myproject/tailwind.config.ts  
{  
 content: [  
   "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",  
   "./src/components/**/*.{js,ts,jsx,tsx,mdx}",  
   "./src/app/**/*.{js,ts,jsx,tsx,mdx}",  
   "../shared/**/*.{js,ts,jsx,tsx,mdx}"  
 ],  
 ...,  
}

Тепер спробуємо оновити сторінку:

pic

Тепер все правильно відображається.

Висновок

При створенні Monorepo є кілька важливих аспектів, які можуть допомогти зробити ваш проект більш ефективним і уникнути проблем у майбутньому.

Основні моменти

  1. Розділення модулів: Варто чітко проектувати модулі, наприклад, розділяючи код, що використовується повторно (shared utilities) і основний проект, щоб забезпечити їх узгодженість.
  2. Налаштування імен: Використовуйте префікси, щоб позначити, що модуль є частиною Monorepo. Це допоможе зробити проект більш організованим і зрозумілим, оскільки робота з Monorepo ускладнює структуру проекту, тому організація імен має велике значення.
  3. Сумісність версій: Звертайте увагу на використання залежностей з різними версіями в різних модулях, оскільки це може спричинити проблеми з компіляцією або взаємодією між модулями.
  4. Налаштування Tailwind CSS: Якщо використовуєте Tailwind CSS, будьте уважні до неправильних налаштувань у файлі tailwind.config.ts, що може призвести до того, що частини коду не будуть оброблені.
  5. Управління змінами: Коли відбуваються зміни в одному з модулів, можливо, буде потрібно оновити пов'язані модулі, щоб вони були узгоджені, що робить управління Monorepo більш обережним. Це один з основних недоліків роботи з Monorepo.
    6.
    Здоров'я коду: У Monorepo, якщо частина коду має помилку, це часто впливає на весь репозиторій, що може позначитися на багатьох проектах у ньому. Якщо виправлення в одному місці спричиняє проблеми в усіх проектах, важливо приділяти увагу перевірці та підтримці якості коду в усіх модулях.

TailwindCSS 4.0

Для тих, хто використовує Tailwind 4.0, а також додає директорії в основний проект, щоб забезпечити роботу Tailwind у спільних модулях, додайте цей рядок у основний CSS файл:

@import 'tailwindcss';  

@source '../../../shared/**/*.{js,ts,jsx,tsx,mdx}'; // додайте цей рядок  

...

Можете клонувати цей репозиторій для вивчення або тестування.

Посилання

Перекладено з: มาลองทำ Monorepo กัน