AstroJS проекты в monorepo с помощью npm workspaces

image

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

В этом случае полезно иметь монорепу, и я это сделаю без внешних зависимостей только с помощью npm workspaces.


Преимущества monorepo

Эффективность дискового пространства
Устанавливается только одна копия зависимости, общая для нескольких пакетов.

Более быстрая установка
При установке внешних npm-пакетов мы скачиваем только один пакет вместо нескольких копий.

Согласованные версии зависимостей
Все пакеты в npm workspaces используют одну и ту же версию зависимости — больше нет конфликта версий.


Пример проекта

Создадим монорепо для блога:


  • внешняя часть сайта: AstroJS
  • библиотека компонентов: файлы компонентов .astro
  • библиотека вспомогательных элементов: скрипты, типы, стили

Структура проекта, которую мы хотим получить:

/
├── node_modules/
├── packages
│   ├── astrojs (делаем сейчас один, потом его дублируем)
│   ├── design-components
│   └── helpers
├── package.json
└── other files

Потом мы можем добавить несколько пакетов с лендингами на AstroJS, используя уже созданные общие блоки.

Для каждого лендинга/блога есть свой AstroJS проект с переменными только для этого проекта:


  • контент каждого проекта будет в своей папке c AstroJS: /astrojs/src/content;
  • css-переменные для темы оформления каждого лендинга: /astrojs/src/styles/theme.css;
  • тексты и набор ссылок для меню, шапки и подвала: например, /astrojs/src/data/linksFooter.json;
  • favicons: /astrojs/public/favicons/favicon.ico
  • дефолтные картинки для соцсетей: /astrojs/public/images/og-default.png

В каждый проект будем подключать:


  • локальные файлы с переменными
  • общую библиотеку компонентов (получает данные от каждого проекта)
  • общие файлы helpers: стили, типы и js/ts-функции (файлы будут получать данные от каждого проекта)


Создание корневого проекта

Создаем корневую папку нашего проекта:

mkdir root-project
cd root-project

Инициализируем проект:

npm init -y

Открываем проект в редакторе кода. Я использую VS Code:

code .

Редактируем наш корневой package.json для всего проекта и указываем, откуда брать дочерние пакеты:

{
  "name": "my-blog",
  "private": true,
  "workspaces": [
    "packages/*"
  ]
}

Создаем папки для наших пакетов:


  • root-project / packages / astrojs
  • root-project / packages / design-components

Для удобства я сразу создаю файлы .editorconfig и .nvmrc в корневой папке.


Создание пакета с AstroJS

Заходим в папку с AstroJS и устанавливаем сам AstroJS:

cd packages/astrojs
npm create astro@latest

В процессе установки выбираем:


  • установка в текущую папку
  • самый простой проект
  • зависимости установим потом
  • git инициализировать не надо

После установки в папке пакета появляется файл package.json и необходимые для AstroJS файлы.

Редактируем файл package.json:


  • прописываем название пакета, с учетом корневого my-blog
  • выносим все dependencies пакета AstroJS в dependencies корневого проекта

Код package.json astrojs-пакета:

{
  "name": "@my-blog/astrojs",
  "type": "module",
  "version": "0.0.1",
  "scripts": {
    "dev": "astro dev",
    "start": "astro dev",
    "build": "astro check && astro build",
    "preview": "astro preview",
    "astro": "astro",
    "check": "astro check"
  }
}

Код package.json корневого пакета:

{
  "name": "my-blog",
  "private": true,
  "workspaces": [
    "packages/*"
  ],
  "dependencies": {
    "astro": "^4.13.1",
    "typescript": "^5.5.4"
  },
  "devDependencies": {
    "@astrojs/check": "^0.9.1"
  }
}


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

Работаем с папками и файлами в редакторе кода.

Создаем package.json в папке packages/design-components:

{
  "name": "@my-blog/design-components",
  "version": "0.0.1",
  "type": "module",
  "private": true
}

Создаем папку для компонентов components и первый компонент Card.astro (путь от корневого проекта: root-project/packages/design-components/components/Card.astro)

---

---

Card component


Установка пакетов

Возвращаемся в корневой проект.

Устанавливаем зависимости (все локальные и внешние пакеты):

npm install

Результат: added 412 packages, and audited 415 packages in 1m

В корневом проекте появилась папка node_modules:


  • множество внешних проектов
  • папка @my-blog с линками на подпапки: @my-blog/astrojs и @my-blog/design-components

image


Подключение библиотеки компонентов

Из корневого проекта переходим в наш AstroJS пакет и запускаем его:

cd packages/astrojs
npm run dev

В браузере проверяем: http://localhost:4321/ — проект запустился.

Отредактируем tsconfig.json — добавим import aliases:

{
  "extends": "astro/tsconfigs/strict",
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "exclude": ["dist"]
}

Строка "exclude": ["dist"] нужна, потому что последнее время AstroJS проверяет папку .dist при проверке командой astro check.

Отредактируем нашу главную страницу root-project/packages/astrojs/src/pages/index.astro и добавим туда наш компонент из локального пакета

---
import Layout from "@/layouts/Layout.astro";

// components
import Card from "@my-blog/design-components/components/Card.astro";
---


    

Привет, мир! Это монорепо

Глобальные стили подключены из пакета `helpers`.

Поздравляю! Теперь вы умеете работать в двумя локальными пакетами.


Подключение темы оформления сайта

Создаем стили: root-project/packages/astrojs/src/styles/theme.css

:root {
    /* FONTS */
    --font-family-base: "Comic Sans MS";

    /* COLORS */
    --color-theme-pale: #f3e8ff;
    --color-theme-muted: #d8b4fe;
    --color-theme-neutral: #a855f7;
    --color-theme-bold: #7e22ce;
    --color-theme-intense: #581c87;
}

Добавляем файл стилей в root-project/packages/astrojs/src/layouts/Layout.astro:

---
// styles
import "@/styles/theme.css";

interface Props {
    title: string;
}

const { title } = Astro.props;
---



    
        
        
        
        
        {title}
    
    
        
    












Используем эти переменные стилей всего проекта в компоненте из библиотеки root-project/packages/design-components/components/Card.astro и добавим получение props:

---
interface Props {
    title?: string;
    text?: string;
}

const { title = "Card title", text = "Card text" } = Astro.props;
---

{title}

{text}

Заберем в компонент тексты из AstroJS в файле packages/astrojs/src/pages/index.astro:

---
import Layout from "@/layouts/Layout.astro";

// components
import Card from "@my-blog/design-components/components/Card.astro";
---


    

Привет, мир! Это монорепо

Глобальные стили подключены из пакета `helpers`.

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


Создание и подключение пакета helpers

Полезно иметь общие вещи для всех проектов в отдельном пакете.

Создадим пакет в новой папке root-project/packages/helpers.

Добавим файл package.json для нового пакета:

{
  "name": "@my-blog/helpers",
  "version": "0.0.1",
  "type": "module",
  "private": true
}

Создадим общий файл со сбросом стилей root-project/packages/helpers/styles/reset.css:

*,
*::after,
*::before {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

Создадим общий файл глобальных стилей root-project/packages/helpers/styles/global.css:

body {
    min-width: 320px;
    background-color: var(--color-background-page, red);
    color: var(--color-text-page, blue);
    line-height: 24px;
    font-weight: 400;
    font-size: 16px;
    font-family: var(--font-family-base, monospace);
}

Выходим в корневой проект и устанавливаем наш новый пакет:

# мы были в AstroJS проекте, остановим его: CTRL + C
# выходим на уровень корневого проекта:
cd ../..

# теперь мы в корневом проекте, устанавливаем новый локальный пакет:
npm i

Результат: added 1 package, and audited 419 packages in 1s

Возвращаемся в папку пакета AstroJS и запускаем его снова:

cd packages/astrojs
npm run dev

Подключаем файлы стилей из пакета helpers в Layout root-project/packages/astrojs/src/layouts/Layout.astro:

---
// styles
import "@my-blog/helpers/styles/reset.css";
import "@/styles/theme.css";
import "@my-blog/helpers/styles/global.css";

interface Props {
    title: string;
}

const { title } = Astro.props;
---



    
        
        
        
        
        {title}
    
    
        
    

Поздравляю! Теперь ваши компоненты отделены от стилей, которые будут одинаковы для всех проектов.


Выводы

После всех этих действий у нас monorepo для множества проектов и минимум зависимостей.

image

Проверим зависимости на данный момент (только необходимые для самого AstroJS): npm ls

my-blog
├── @astrojs/check@0.9.1
├── @my-blog/astrojs@0.0.1 -> ./packages/astrojs
├── @my-blog/design-components@0.0.1 -> ./packages/design-components
├── @my-blog/helpers@0.0.1 -> ./packages/helpers
├── astro@4.13.1
└── typescript@5.5.4

Для удобства разработки можно настроить AstroJS проекты по моей инструкции.

В этом случае внешние пакеты устанавливаем в корневой проект:


  • stylelint
  • prettier
  • eslint
  • прочие печенюшки.

Если AstroJS проекту понадобятся зависимости типа »@astrojs/react», тоже устанавливаем их в корневой проект.


Часто задаваемые вопросы

ВОПРОС: Нужно ли публиковать наши пакеты в npm?

Публиковать в npm не нужно. Это локальные зависимости, которые живут в вашей же монорепе.

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

Примера такого решения: библиотека UI компонентов Яндекса. Они могли оставить ее как внутренний проект только в своей монорепе, но вынесли в публичный доступ.

ВОПРОС: Что добавляем в git?

В git идет корневой проект: git init.

В проекте внутри git у вас будут все ваши локальные пакеты.

Не забудьте проверить, что у вас уходит в git из пакета с AstroJS: в .gitignore надо запретить

# build output
dist/

# generated types
.astro/

# dependencies
node_modules/

ВОПРОС: какие действия, если устанавливаем уже созданную кем-то монорепу через npm workspaces?

В корневой папке делаем npm install — установятся все зависимости, как внешние, так и локальные.

ВОПРОС: как работать сразу с несколькими пакетами, например, StrapiJS в качестве CMS и AstroJS как внешнюю часть?

В корневом проекте для package.json добавляем команды:

{
  "name": "my-blog",
  "workspaces": [
    "packages/*"
  ],
  {
    "scripts": {
      "build": "npm run build:package-a && npm run build:package-b",
      "build:package-a": "cd packages/package-a && npm run build",
      "build:package-b": "cd packages/package-b && npm run build"
    }
  }
}

© Habrahabr.ru