Руководство по созданию монорепозитория машинописного текста с использованием рабочих пространств npm и Nx.

В этой статье объясняется, как вы можете иметь несколько проектов typescript в монорепозитории. Я пишу эту статью, потому что мне надоели простые примеры, такие как пакет even или odd, которые даже близко не представляют реальный вариант использования.

Готовую реализацию того, о чем говорится в этой статье, можно найти здесь:



Что такое монорепо

Монорепозиторий — это набор релевантного кода, сгруппированного вместе. Как правило, он включает в себя несколько приложений и библиотек, от которых зависят приложения. Монорепо отслеживается в одном репозитории, отсюда и название.

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

Что мы здесь делаем?

Мы создаем монорепозиторий с тремя пакетами typescript, два из которых являются приложениями, а один — библиотекой, импортируемой обоими этими приложениями.

Мы настроим все это на машинописном языке и сделаем так, чтобы при внесении изменений в импортированную библиотеку автоматически запускалась пересборка приложений, которые ее импортируют.

Пример проблемы

Допустим, у нас есть простое старомодное приложение с интерфейсом и сервером, и оба они зависят от одной и той же библиотеки. Это может быть что-то, используемое для проверки, общие типы доменов или что-то еще.

Если бы серверная часть и внешний интерфейс были в разных репозиториях, нам пришлось бы либо дублировать код для них обоих, что все и их бабушки знают не делать в этот момент, либо нам пришлось бы экспортировать пакет в npm и добавить его как в серверную часть, так и во внешний интерфейс в качестве зависимости, или что-то в этом роде.

Это звучит немного запутанно, и, возможно, вы не хотите публиковать свой пакет в npm, потому что он содержит конфиденциальные данные домена, которые определяют ту самую проблему, которую пытается решить ваше программное обеспечение.

Решение

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

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

Создание монорепозитория с помощью Npm Workspaces и Nx

Используя рабочие пространства npm (представленные в версии 7) и Nx, мы создадим монорепозиторий с двумя приложениями, интерфейсом и сервером, а также общей библиотекой, назначение которой мы определим чуть позже.

Обратите внимание, что при выполнении большинства будущих команд мы будем использовать флаг -w рядом с именем пакета, на который мы хотим повлиять:

npm run -w package-name-here command

Это позволит нам запускать все наши команды из корня монорепозитория без необходимости перехода к какому-либо из пакетов.

1. Создание монорепозитория

Во-первых, давайте создадим новый монорепозиторий под названием HelloWorld. Самый простой способ сделать это — использовать следующую команду npx:

npx create-nx-workspace@latest HelloWorld --preset=npm

Если вам будет предложено следующее:

Need to install the following packages:
  [email protected]

Затем введите y и нажмите Enter.

Вам также может быть предложено следующее:

Enable distributed caching to make your CI faster …
Yes I want faster builds
No

Это выходит за рамки данной статьи, поэтому вам следует ответить «нет» и двигаться дальше.

Вы получите следующее:

Внутри package.json вы увидите раздел под названием workspaces:

Вот как npm узнает, какие пакеты у вас есть в вашем монорепозитории. Строка packages/* сообщает, что все в папке packages является пакетом монорепозитория.

Мы добавим сюда еще одну строку, чтобы наши библиотеки также могли быть обнаружены:

"workspaces": [
  "packages/*",
  "libs/*"
]

Затем создайте пустую папку в корне вашего проекта с именем libs:

Мы добавим библиотеку в эту папку в следующем разделе.

Далее давайте добавим в монорепозиторий два приложения: серверное приложение, использующее NestJS, и внешнее приложение, использующее Angular.

2. Создание бэкенда

Приложение NestJS можно создать и добавить, выполнив следующую команду:

npx nx nest new packages/backend

После того, как это завершится, Nest создаст новый репозиторий git. Обязательно удалите его. Это можно сделать, удалив папку .git в папке packages/backend, что можно сделать с помощью следующей команды:

rm -rf packages/backend/.git

Чтобы запустить бэкенд, вам не нужно переходить к его пакету, вместо этого вы можете сделать следующее:

npm run -w backend start:dev

Где часть -w backend указывает npm запустить команду start:dev в рабочей области backend. Имя backend взято из поля name в файле package.json внутреннего пакета.

На данный момент бэкенд готов.

2.1. Создание интерфейса

Чтобы создать интерфейс с помощью Angular, сначала установите cli (глобально):

npm install -g @angular/cli

затем выполните следующую команду

cd packages
ng new frontend

Вы можете просто спамить через подсказки, так как они не имеют никакого отношения к этой статье.

Вы можете протестировать интерфейсный пакет, используя следующее:

npm run -w frontend start

3. Создание общей библиотеки

Сложность создания монорепозитория с использованием машинописного текста связана с тем, что нам нужно сначала преобразовать код в javascript, сохраняя при этом такие вещи, как типы, исходные карты и т. д., доступными для всех, кто импортирует библиотеку.

Такие фреймворки, как NestJS и Angular, сами компилируются для нас, и мы все равно никуда их не импортируем. А вот с библиотекой другая история.

Короче говоря, чтобы создать общую библиотеку, нам нужно подумать о нескольких вещах:

  • Транспиляция из Typescript в Javascript
  • Сохранение типов без изменений
  • Карты исходного кода для обеспечения легкого доступа к исходному коду с помощью инструментов IDE, таких как «Перейти к ссылкам».
  • Автоматическое перестроение при внесении изменений
  • Потому что любое место, где эта библиотека была импортирована, будет перестроена, если у нее есть собственный режим просмотра.

В качестве примера мы заставим библиотеку представлять собой очень простой регистратор, который окружает заданное сообщение кучей смайликов и записывает его в консоль. Итак, приступим!

Сначала мы создадим библиотеку с помощью команды npm. Мы сделаем это с помощью следующей команды:

npm init -w ./libs/fancy-logger -y

Это создаст файл package.json под libs/fancy-logger со (более или менее) следующим содержимым:

Чтобы позволить нам использовать машинописный текст в этом пакете, нам нужно установить машинописный текст и добавить пару записей в скрипты в этом файле package.json.

Мы начнем с установки машинописного текста с помощью следующей команды:

npm i -w fancy-logger --dev typescript

Напоминание: убедитесь, что вы запускаете эти команды из корня монорепозитория. Вам не нужно переходить к вашему пакету в терминале, так как npm предоставляет способ нацеливания на определенный пакет при выполнении команды.

Установив typescript, замените раздел scripts в package.json fancy-logger.

"scripts": {
  "build": "tsc",
  "build:watch": "tsc -w"
},

Первый скрипт, build, строит библиотеку один раз, а build:watch строит библиотеку один раз, затем следит за любыми изменениями и перестраивает библиотеку.

Еще одна вещь — добавить файл tsconfig.json. Вы можете скопировать и вставить следующее в новый файл под libs/fancy-logger/tsconfig.json:

{
    "compilerOptions": {
        "moduleResolution": "nodenext",
        "module": "commonjs",
        "baseUrl": "./",
        "outDir": "./dist",
        "lib": ["es6"],
        "declaration": true,
        "declarationMap": true,
        "target": "ES2021",
        "sourceMap": true
    }
}

Вот разбивка того, что происходит в файле:

  • moduleResolution: "nodenext" согласно рекомендациям typescript docs
  • module: "commonjs" устанавливает, какую систему модулей будет использовать сгенерированный javascript.
  • baseUrl: "./", чтобы разрешить абсолютный импорт, например src/../.. (документы)
  • outDir: "./dist" — это место для встроенной библиотеки.
  • lib: ["es6"], чтобы включить объявления типов для функций es6, таких как Promise, Set, Map и т. д.
  • declaration: true для создания .d.ts файлов и добавления их в сборку
  • declarationMap: true, чтобы среда IDE могла найти результирующие .d.ts файлов (используя такие вещи, как «Перейти к ссылке»)
  • target: "ES2021" устанавливает целевой синтаксис для сгенерированного javascript
  • sourceMap: true, чтобы отладчики могли найти исполняемый в данный момент javascript.

Вы можете изменить lib, declaration, declarationMap, target и sourceMap без особых хлопот.

module и moduleResolution изменяют способ генерации javascript в отношении импорта как внутреннего в вашу библиотеку, так и того, как импортируется сама библиотека. Эти свойства влияют друг на друга и должны быть изменены при ссылке на документы. nodenext обычно является хорошим выбором для проектов, использующих современную версию node.

Кроме того, будьте внимательны при изменении baseUrl или outDir, так как они требуют внесения изменений в другие файлы, как мы вскоре увидим.

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

dist
node_modules

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

Записи, которые мы собираемся установить, это main и types , первая из которых устанавливает основной файл javascript, представляющий запись для библиотеки и экспортирующий все из нее.

types указывает, где можно найти сгенерированный *.d.ts. Мы укажем его как шаблон глобуса, чтобы перехватывать все файлы типов в проекте.

Вот как будут выглядеть записи main и types (остальные записи опущены):

{
    "main": "dist/index.js",
    "types": "dist/*/**.d.ts"
}

Обязательно замените все существующие main или types, если они там есть.

Теперь мы можем приступить к написанию кода. Начните с создания файла index.ts под libs/fancy-logger/, а затем создайте рядом с ним папку с именем src:

index.ts будет точкой входа в библиотеку, экспортируя все, что мы хотим из библиотеки.

Давайте добавим базовый класс, задача которого состоит только в том, чтобы записывать сообщения с красивыми смайликами. Мы добавим это под libs/fancy-logger/src/fancy-logger.ts :

export class FancyLogger {
  static log(message: string) {
    console.log(`🚀🚀🚀 ${message} 🚀🚀🚀`);
  }
}

Затем экспортируйте его из соседнего файла index.ts:

export * from "./src/fancy-logger";

Вот основная организация файлов на данный момент:

Давайте соберем пакет и посмотрим, все ли идет по плану. Выполните следующую команду:

npm run -w fancy-logger build

Сделав это, вы увидите новую папку dist в библиотеке со следующим содержимым:

Вот, построенная библиотека! Теперь мы можем импортировать его в наш интерфейс и сервер и сделать с ним небольшой пример. Обратите внимание, что содержимое папки dist может быть другим, если вы изменили свойства module или moduleResolution в файле tsconfig.json.

Чтобы импортировать пакет, добавьте его в массив зависимостей файлов package.json оба бэкенда и внешнего интерфейса следующим образом:

"dependencies": {
  // ...
  "fancy-logger": "*"
}

После добавления его как для интерфейса, так и для сервера, запустите npm install, чтобы npm связал и установил пакеты для нас. Это то, что нам нужно сделать только один раз, когда мы впервые настраиваем библиотеку.

Теперь, если вы проверите папку node_modules в верхней части монорепозитория, вы увидите там пакет fancy-logger:

Давайте используем пакет во внешнем и внутреннем интерфейсе, чтобы регистрировать сообщение при первом запуске. Для серверной части мы можем сделать это в файле main.ts под packages/backend/src:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

// Importing our library 🚀
import { FancyLogger } from 'fancy-logger';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);

  // Using the library 🚀
  FancyLogger.log('Backend has started');
}
bootstrap();

То же самое сделаем для внешнего интерфейса в файле app.component.ts под packages/frontend/src/app/:

import { Component } from "@angular/core";

// Importing our library 🚀
import { FancyLogger } from "fancy-logger";

@Component({
  selector: "app-root",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"],
})
export class AppComponent {
  title = "frontend";

  constructor() {
    // Using the library 🚀
    FancyLogger.log("Frontend is up and running");
  }
}

Обратите внимание, что вывод для внешнего интерфейса будет в консоли браузера, а не в терминале.

Теперь давайте запустим серверную часть, внешний интерфейс и библиотеку в режиме просмотра и посмотрим, что происходит.

Команды:

npm run -w backend start:dev

npm run -w frontend start

npm run -w fancy-logger build:watch

Вы можете запустить каждый из них в отдельном терминале/панели.

Теперь, на момент истины, вот вывод:

С бэкендом слева и интерфейсом справа вы можете ясно видеть вывод нашего регистратора.

Теперь, поскольку наша библиотека работает в режиме просмотра, изменение окружающих смайликов должно привести к мгновенному изменению журналов:

Вы можете найти репозиторий со всем, что мы сделали в этой статье, здесь:



Предостережения

  1. Мы не используем сборщик. Есть еще много возможностей для оптимизации, такой как встряхивание деревьев и более быстрая компиляция.
  2. Как вы могли заметить в терминале при запуске Angular, интерфейс не любит commonjs модулей.
  3. Мы не настроили nx.json для обработки наших операций сборки. Я рекомендую вам взглянуть на документы для этого.

Бонус: Скрипт для мгновенного создания библиотеки

Слушай, я понимаю. Мы все ленивы. Ни у кого нет времени повторять всю вышеописанную работу при создании новой библиотеки, поэтому я создал скрипт, который создаст ее для вас! Вы можете найти это здесь:



Заключение

Используя рабочие области npm и nx, мы создали монорепозиторий машинописных текстов с двумя приложениями и библиотекой, где два приложения импортируют и используют библиотеку.

Мы создали библиотеку typescript и настроили ее tsconfig для генерации javascript, который можно импортировать в наши приложения, с режимом наблюдения для пересборки библиотеки при изменениях, а также для пересборки импортирующих приложений.

Рекомендации