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)

---  ---  <div>Card component</div> 

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

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

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

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"; ---  <Layout title="Welcome to Astro.">     <main>         <h1>Привет, мир! Это монорепо</h1>         <p>Глобальные стили подключены из пакета `helpers`.</p>         <Card />     </main> </Layout> 

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

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

Создаем стили: 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; ---  <!doctype html> <html lang="en">     <head>         <meta charset="UTF-8" />         <meta name="description" content="Astro description" />         <meta name="viewport" content="width=device-width" />         <link rel="icon" type="image/svg+xml" href="/favicon.svg" />         <title>{title}</title>     </head>     <body>         <slot />     </body> </html> 

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

--- interface Props {     title?: string;     text?: string; }  const { title = "Card title", text = "Card text" } = Astro.props; ---  <div class="card">     <p class="title">{title}</p>     <p class="text">{text}</p> </div>  <style>     .card {         background-color: var(--color-theme-pale);         color: var(--color-theme-intense);         padding: 24px 16px;         border-radius: 8px;         display: flex;         flex-direction: column;         gap: 8px;     }      .title {         font-weight: 700;     } </style>

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

--- import Layout from "@/layouts/Layout.astro";  // components import Card from "@my-blog/design-components/components/Card.astro"; ---  <Layout title="Welcome to Astro.">     <main>         <h1>Привет, мир! Это монорепо</h1>         <p>Глобальные стили подключены из пакета `helpers`.</p>         <Card             title="Компонент Card подключен из пакета `design-components`"             text="Тексты и переменные для темы оформления подключены из пакета `astrojs`."         />     </main> </Layout>

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

Создание и подключение пакета 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; ---  <!doctype html> <html lang="en">     <head>         <meta charset="UTF-8" />         <meta name="description" content="Astro description" />         <meta name="viewport" content="width=device-width" />         <link rel="icon" type="image/svg+xml" href="/favicon.svg" />         <title>{title}</title>     </head>     <body>         <slot />     </body> </html> 

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

Выводы

После всех этих действий у нас 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"     }   } }


ссылка на оригинал статьи https://habr.com/ru/articles/833684/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *