CodeWorlds
Powrót do kolekcji
Przewodnik14 min czytaniaZespół CodeWorlds

Nx, graf zadań i bufor w monorepozytorium

Nx 23.1.1 na licencji MIT, ale pakiet nx-cloud deklaruje proprietary. Co jest darmowe, ile kosztuje bufor zdalny i kiedy Nx to przerost formy.

Nx, graf zadań i bufor w monorepozytorium

Nx buduje graf zależności między projektami w jednym repozytorium, uruchamia tylko zadania dotknięte zmianą i zapamiętuje wyniki, żeby nie liczyć dwa razy tego samego. Wersja 23.1.1 pakietu nx w rejestrze npm wyszła 30 lipca 2026 roku na licencji MIT. Płatna jest osobna usługa obok narzędzia, nie samo narzędzie, i właśnie to rozróżnienie bywa źródłem rozczarowań.

Co Nx właściwie robi

Nx czyta repozytorium i wylicza dwa grafy. Pierwszy to graf projektów: kto od kogo zależy, ustalony z importów w kodzie oraz z pól implicitDependencies. Drugi to graf zadań, w którym węzłem jest para projekt plus cel, a krawędzie bierze się z dependsOn. Na podstawie pierwszego działa nx affected, na podstawie drugiego kolejność uruchamiania i równoległość.

Do tego dochodzi buforowanie. Przed uruchomieniem zadania Nx liczy skrót z jego wejść, czyli plików wskazanych przez inputs i namedInputs, wersji zależności oraz zmiennych środowiskowych. Jeśli taki skrót już widział, odtwarza pliki wskazane w outputs i wypisuje zapamiętane wyjście terminala zamiast wykonywać pracę. Bufor lokalny trafia domyślnie do katalogu .nx/cache, a metadane grafu do .nx/workspace-data. Ścieżkę zmienia pole cacheDirectory w nx.json albo zmienna NX_CACHE_DIRECTORY. Od strony implementacji to baza danych obsługiwana przez moduł natywny, a nie zwykły katalog z archiwami.

Trzecia warstwa to generatory i wtyczki. Generator tworzy albo modyfikuje pliki, wtyczka potrafi wykryć konfigurację narzędzia w projekcie i wystawić na jej podstawie gotowe cele, których nikt nie wpisywał ręcznie. Migracje wersji obsługuje nx migrate, które zapisuje plan zmian do pliku i pozwala go uruchomić osobno.

Czego Nx nie robi: nie buduje kodu. Kompilacją zajmują się narzędzia niższej warstwy, Vite, esbuild czy Rspack, a Nx tylko decyduje, które z nich i w jakiej kolejności wywołać oraz czy wynik da się odtworzyć z bufora. Nie sprawdza też typów ani nie formatuje kodu, więc TypeScript i Biome zostają w potoku niezależnie.

Code
Bash
# instalacja i inicjalizacja w istniejącym repozytorium
npm install --save-dev nx
npx nx init

# zadania tylko dla projektów dotkniętych zmianą względem gałęzi bazowej
npx nx affected -t build test --base=main --head=HEAD

# to samo dla wszystkich projektów, z jawną równoległością
npx nx run-many -t lint --parallel=4

# podgląd grafu i pełnej konfiguracji jednego projektu
npx nx graph
npx nx show projects
npx nx show project moja-biblioteka --json

# diagnostyka: pominięcie bufora, czyszczenie stanu, naprawa konfiguracji
npx nx build moja-biblioteka --skip-nx-cache
npx nx reset
npx nx repair

Licencja: MIT w rdzeniu, proprietary tuż obok

Sprawdzenie z trzech źródeł daje spójny obraz dla samego Nx i niespójny dla usługi obok niego.

W repozytorium nrwl/nx na gałęzi master leży plik LICENSE z tekstem licencji MIT i notą „Copyright (c) 2017-2026 Narwhal Technologies Inc.". Plików LICENSE.md, LICENSING.md, NOTICE ani COPYING w korzeniu nie ma, sprawdzone przez bezpośrednie pobranie. Pole license w rejestrze npm dla wersji 23.1.1 ma wartość MIT. Rozpakowana paczka zawiera package/LICENSE z tym samym tekstem MIT oraz realny kod: 1256 plików i 17 187 924 bajtów po rozpakowaniu, z czego zdecydowana większość przypada na katalog dist/src. To jest zgodne w każdym z trzech miejsc. Dziesięć opcjonalnych pakietów z binariami natywnymi, po jednym na parę system plus architektura, także deklaruje MIT i niesie ten sam plik LICENSE.

Inaczej wygląda pakiet nx-cloud. Jego najnowsza wersja to 19.1.3 z 31 marca 2026 roku, a pole license ma wartość proprietary. Sama ta deklaracja jest rzadka, bo pakiety z zamkniętym kodem częściej pomijają to pole. Zaskoczenie jest jednak w środku paczki: plik package/LICENSE nie zawiera żadnej licencji oprogramowania, tylko pełny tekst Creative Commons Attribution-NoDerivs 3.0 Unported, czyli licencji przeznaczonej dla treści. Ten sam odnośnik powtarza się w README.md. Deklaracja w metadanych i plik w paczce mówią więc dwie różne rzeczy.

Co z tego wynika praktycznie. CC BY-ND pozwala kopiować i rozpowszechniać dzieło w niezmienionej postaci, wymaga zachowania oznaczenia autorstwa i wprost odmawia prawa do tworzenia opracowań, co przy programie oznacza brak prawa do modyfikacji i do rozpowszechniania zmienionych wersji. Wartość proprietary w metadanych nie przyznaje żadnych praw poza tymi, które wynikają z osobnej umowy z dostawcą. Żadna z tych dwóch ścieżek nie zawiera daty przekształcenia w licencję otwartą, jaką znamy z licencji typu BSL. Jeśli prowadzisz w firmie listę licencji zależności, wpisz obie wartości i zaznacz rozbieżność, bo automat czytający wyłącznie pole license pokaże inną rzecz niż automat skanujący pliki w paczce.

Warto rozdzielić jeszcze jedno. Paczka nx-cloud waży po rozpakowaniu 1 594 351 bajtów i ma zaledwie 11 plików. To nie jest silnik usługi, tylko cienki klient. Właściwy kod klienta pobierany jest w czasie działania: moduł dist/src/nx-cloud/update-manager.js w rdzeniu Nx odpytuje /nx-cloud/client/verify pod adresem https://cloud.nx.app, po czym ściąga i rozpakowuje wskazaną paczkę. Adres podmienia zmienna NX_CLOUD_API. Co więcej, sam pakiet nx na licencji MIT wystawia dwa polecenia w polu bin: nx oraz nx-cloud. Instalując otwarte narzędzie, dostajesz więc gotowy punkt wejścia do zamkniętej usługi, choć bez konta nic on nie zrobi.

Osobna rodzina to płatne wtyczki bufora samodzielnie hostowanego: @nx/s3-cache, @nx/gcs-cache, @nx/azure-cache i @nx/shared-fs-cache. Wszystkie w wersji 5.0.7 deklarują license o wartości Commercial, wszystkie zależą od pakietu @nx/key służącego do aktywacji licencji, i wszystkie mają zakres zależności równorzędnej nx ustawiony na >= 18 < 23. Przy Nx 23.1.1 ten zakres nie jest spełniony, więc menedżer pakietów zgłosi konflikt. Ostatnie wydanie tej rodziny pochodzi z 22 maja 2026 roku.

Cennik Nx Cloud i limit planu darmowego

Strona cennika Nx Cloud jest zbudowana na Framerze i część treści, w tym rozwijane odpowiedzi w sekcji pytań oraz znaczniki obecności funkcji w tabeli porównawczej, nie znajduje się w surowym HTML. Wartości liczbowe z kart planów są jednak dostępne bez JavaScriptu i to je podaję.

PlanKredyty miesięczneWspółtwórcyRównoległe połączenia CIDopłaty
Hobby50 000, reset co miesiącdo 510brak, plan darmowy
Team50 000 w cenie5 w cenie10 w cenie19 USD za współtwórcę, 5,50 USD za 10 000 kredytów, 2,25 USD za połączenie
Enterprisewycena indywidualnabez limituwycena indywidualnawycena indywidualna

Plan Team ma na karcie napis „Starts at $0", co znaczy tyle, że opłata bazowa nie istnieje i płacisz wyłącznie za przekroczenia. Współtwórca jest zdefiniowany na stronie jako osoba lub podmiot, który był autorem zatwierdzenia obsłużonego przez potok CI w bieżącym okresie rozliczeniowym, więc licznik nie odpowiada liczbie miejsc w zespole ani liczbie kont.

Arytmetyka wygląda tak: 50 000 kredytów przy stawce 5,50 USD za 10 000 kredytów odpowiada wartości 27,50 USD, a pojedynczy kredyt kosztuje 0,00055 USD. Ile kredytów zjada konkretne zadanie, zależy od klasy zasobu i tego nie da się odczytać z surowego HTML strony cennika; dostawca odsyła do osobnej strony o zużyciu kredytów. Nie podaję tu liczb, których nie potwierdziłem.

W planie Enterprise wymienione są funkcje niedostępne niżej: reguły zgodności, wykrywanie cykli, widok wielu repozytoriów, SSO oraz instalacja jednodzierżawcza i lokalna. Instalacja lokalna jest więc możliwa, ale wyłącznie na tym poziomie i po rozmowie handlowej.

Bufor zdalny bez konta u dostawcy

Otwarta alternatywa istnieje i jest wbudowana w rdzeń na licencji MIT. W pliku dist/src/tasks-runner/cache.js widać gałąź, która przy ustawionej zmiennej NX_SELF_HOSTED_REMOTE_CACHE_SERVER tworzy HttpRemoteCache zamiast klienta usługi. Zapytania idą przez moduł natywny w Rust, dlatego kod dopisuje ostrzeżenie o wyłączonej weryfikacji certyfikatów, gdy ustawisz NODE_TLS_REJECT_UNAUTHORIZED na zero. W kompilacji do WebAssembly ta ścieżka nie działa i Nx zgłasza to komunikatem.

Protokół jest udokumentowany jako specyfikacja OpenAPI 1.0.0 pod nazwą „Nx custom remote cache specification". Sprowadza się do dwóch operacji na ścieżce /v1/cache/{hash}: put wysyła wynik zadania, get go pobiera, uwierzytelnianie idzie tokenem bearerToken, a ciałem jest binarne archiwum tar. Serwer możesz napisać w czymkolwiek. To realna droga wyjścia dla zespołu, który chce współdzielenia bufora, ale nie chce konta u dostawcy.

Code
Bash
# własny serwer bufora zgodny ze specyfikacją OpenAPI dostawcy
export NX_SELF_HOSTED_REMOTE_CACHE_SERVER="https://cache.wewnetrzna-siec.example"
export NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN="$CACHE_TOKEN"

# górny limit rozmiaru bufora lokalnego
export NX_MAX_CACHE_SIZE="10gb"

# jednorazowe pominięcie bufora zdalnego przy zachowaniu lokalnego
npx nx run-many -t build --skip-remote-cache

Istnieje też pakiet nx-remotecache-custom na licencji MIT, pozwalający dopisać własny magazyn. Jego najnowsza wersja to 20.0.0 z zakresem zależności równorzędnej nx równym ^20.0.0, czyli o trzy wydania główne za obecnym Nx. Traktowałbym go dziś jako ślepy zaułek, a nie jako plan awaryjny.

Konfiguracja: nx.json i project.json

Plik nx.json w korzeniu opisuje zachowanie całego repozytorium. Poniżej pola faktycznie obecne w schemacie dołączonym do wersji 23.1.1.

Code
JSON
{
  "$schema": "./node_modules/nx/schemas/nx-schema.json",
  "defaultBase": "main",
  "parallel": 3,
  "useDaemonProcess": true,
  "useInferencePlugins": true,
  "cacheDirectory": ".nx/cache",
  "neverConnectToCloud": true,
  "namedInputs": {
    "default": ["{projectRoot}/**/*", "sharedGlobals"],
    "production": ["default", "!{projectRoot}/**/*.spec.ts"],
    "sharedGlobals": ["{workspaceRoot}/tsconfig.base.json"]
  },
  "targetDefaults": {
    "build": {
      "cache": true,
      "dependsOn": ["^build"],
      "inputs": ["production", "^production"],
      "outputs": ["{projectRoot}/dist"]
    },
    "test": {
      "cache": true,
      "inputs": ["default", "^production"]
    }
  },
  "plugins": [
    {
      "plugin": "@nx/vite/plugin",
      "include": ["packages/*"],
      "options": {}
    }
  ]
}

Pole neverConnectToCloud ustawione na true jest wygodnym bezpiecznikiem, jeśli decyzja o usłudze jeszcze nie zapadła. Obok niego schemat zna nxCloudAccessToken, nxCloudUrl i nxCloudEncryptionKey.

Pojedynczy projekt opisuje project.json, o ile nie wystarczy wykrycie przez wtyczkę. Nazwy pól celu też pochodzą wprost ze schematu.

Code
JSON
{
  "name": "moja-biblioteka",
  "root": "packages/moja-biblioteka",
  "sourceRoot": "packages/moja-biblioteka/src",
  "projectType": "library",
  "tags": ["scope:shared"],
  "implicitDependencies": ["konfiguracja-eslint"],
  "targets": {
    "build": {
      "command": "vite build",
      "cache": true,
      "outputs": ["{projectRoot}/dist"],
      "dependsOn": [{ "dependencies": true, "target": "build" }]
    },
    "serve": {
      "command": "vite dev",
      "continuous": true
    }
  }
}

Cel opisany polem command jest skrótem do wbudowanego wykonawcy poleceń. Alternatywą jest executor wskazujący funkcję z wtyczki, wraz z options, configurations i defaultConfiguration. Ta druga forma daje więcej, ale mocniej wiąże konfigurację z Nx, o czym niżej.

Kiedy Nx się opłaca, a kiedy jest przerostem formy

Zysk z Nx to iloczyn dwóch rzeczy: udziału zadań, które da się pominąć, oraz czasu, jaki jedno zadanie zajmuje. Przy jednym pakiecie pierwszy czynnik wynosi zero, bo każda zmiana dotyka jedynego projektu, więc affected zawsze zwraca wszystko. Zostaje wtedy sam bufor, który przy budowaniu trwającym kilkanaście sekund oszczędza kilkanaście sekund, i to wyłącznie przy powtórzeniu identycznego wejścia.

Po drugiej stronie stoi koszt stały, który da się policzyć dokładniej. Instalacja to 17,2 MB i 1256 plików rozpakowanej paczki nx, do tego jeden z dziesięciu opcjonalnych pakietów z binariami natywnymi, dobierany do systemu i architektury. Lista zależności ma 120 pozycji przypiętych do dokładnych wersji. W repozytorium przybywa plik nx.json, katalog .nx do wpisu w regułach ignorowania oraz demon działający w tle, którego trzeba czasem ubić przez nx reset. Do tego dochodzi czas nauki: pojęcia inputs, namedInputs, outputs, targetDefaults i wtyczek wykrywających cele to kilka godzin czytania dla każdego nowego członka zespołu.

Progu nie da się podać jedną liczbą i nie znalazłem pomiaru, który mógłbym uczciwie zacytować, więc nazywam to wprost jako regułę kciuka, a nie zmierzony fakt: warstwa grafu zaczyna się bronić, gdy zadań w repozytorium jest kilkanaście lub więcej, pełny przebieg liczy się w minutach, a typowa zmiana dotyka mniejszości projektów. Jeśli którykolwiek z tych trzech warunków nie zachodzi, dokładasz konfigurację bez zwrotu. Największe rozczarowanie bierze się właśnie stąd: bufor lokalny działa od razu, ale pomaga tylko tej osobie, która już raz zbudowała dany stan. Realny skok pojawia się dopiero wtedy, gdy bufor jest wspólny dla wszystkich maszyn i dla serwera CI, a to jest albo usługa z cennikiem powyżej, albo własny serwer HTTP, który ktoś musi napisać i utrzymać.

Nx, Turborepo i warstwa budowania

Turborepo, opisane osobno w tekście o Turborepo, rozwiązuje ten sam problem węższym zestawem środków. Poniżej różnice, które faktycznie zmieniają decyzję.

CechaNx 23.1.1Turborepo 2.10.11Vite, esbuild, Rspack
Warstwaorkiestracja zadań i generowanie koduorkiestracja zadańkompilacja pojedynczego pakietu
Licencja rdzeniaMITMITMIT lub Apache 2.0 zależnie od narzędzia
Bufor lokalnytak, w rdzeniutak, w rdzeniuwłasny, wewnętrzny dla narzędzia
Bufor zdalny bez opłatserwer HTTP według specyfikacji OpenAPI, do napisania samodzielnieprotokół udokumentowany, dostępne wdrożenia własnenie dotyczy
Rozpraszanie zadań na wiele maszyntylko w płatnej usłudzebrak w narzędziunie dotyczy
Generatory i migracje wersjitak, nx generate i nx migratebrakbrak
Wykrywanie celów z konfiguracji narzędzitak, przez wtyczkinie, zadania z package.jsonnie dotyczy
Ślad w repozytoriumnx.json, .nx, opcjonalnie project.jsonjeden plik konfiguracyjnyplik konfiguracyjny narzędzia

Zestawianie Nx z Vite czy esbuildem jest mylące, bo to inne piętro. Nx nie zastąpi bundlera i sam z niego korzysta. Sensowne porównanie brzmi: czy potrzebujesz warstwy nad bundlerem, a nie który bundler wybrać. Podobnie z testami, gdzie Nx tylko wywołuje Vitest i buforuje jego wynik.

Co Nx zostawia w repozytorium i jak z niego wyjść

Ślad jest większy niż jeden plik. Poza nx.json i katalogiem .nx dochodzą pliki project.json w tych projektach, gdzie wykrycie nie wystarcza, wpisy migracji generowane przez nx migrate oraz konfiguracje pisane przez generatory pod konwencje Nx. Najsilniejsze przywiązanie tworzą dwie rzeczy: cele wykrywane przez wtyczki, których nie ma nigdzie w package.json, oraz cele oparte na executor, czyli na funkcji dostarczanej przez wtyczkę, a nie na poleceniu, które da się wpisać w terminalu.

Wyjście jest wykonalne i wygląda mniej więcej tak. Najpierw nx show project <nazwa> --json dla każdego projektu, żeby zobaczyć pełną rozwiniętą listę celów razem z tymi wykrytymi automatycznie. Potem przepisanie każdego celu na zwykły skrypt w package.json, przy czym cele z command przenoszą się niemal wprost, a cele z executor wymagają odtworzenia właściwego wywołania narzędzia z jego opcjami. Na końcu usunięcie nx.json, katalogu .nx, plików project.json i zależności. Trudność jest wprost proporcjonalna do tego, ile korzystałeś z wykonawców i generatorów zamiast z gołych poleceń. Zespół, który od początku pisze cele jako command, wychodzi w jedno popołudnie. Zespół z rozbudowanymi wykonawcami i własnymi generatorami traci znacznie więcej.

Typowe błędy

Niezadeklarowane outputs to najczęstsza pułapka. Zadanie trafia do bufora, przy powtórzeniu Nx melduje trafienie i nie uruchamia niczego, ale plików wynikowych nie odtwarza, bo nie wie, gdzie leżą. Objaw to zielony przebieg z pustym katalogiem wyjściowym.

Za szerokie inputs dają odwrotny efekt: skrót zmienia się przy każdej modyfikacji czegokolwiek w projekcie, więc trafień nie ma nigdy. Typowa przyczyna to brak wejścia production odsiewającego pliki testowe przy celu build.

Płytkie sklonowanie repozytorium psuje nx affected. Bez historii sięgającej gałęzi bazowej porównanie z --base nie ma z czym się zestawić i wynik jest przypadkowy. W konfiguracji CI trzeba jawnie zwiększyć głębokość pobrania.

Instalacja płatnej wtyczki bufora przy Nx 23 kończy się konfliktem zależności równorzędnych, bo rodzina @nx/*-cache w wersji 5.0.7 deklaruje nx w zakresie >= 18 < 23. To nie jest błąd konfiguracji, tylko brak wydania obsługującego bieżące Nx.

Ostatnia rzecz to złudzenie, że bufor lokalny to już cały zysk. Bez wspólnego magazynu każda maszyna i każdy przebieg CI zaczynają od zera.

FAQ

Czy Nx jest darmowy?

Samo narzędzie tak. Pakiet nx w wersji 23.1.1 jest na licencji MIT w rejestrze, w pliku w repozytorium i w pliku wewnątrz paczki. Darmowe są graf, affected, bufor lokalny, generatory, migracje i wtyczki @nx/* do frameworków. Płatne są bufor zdalny u dostawcy, rozpraszanie zadań na wiele maszyn oraz wtyczki bufora samodzielnie hostowanego z licencją Commercial.

Czym różni się pakiet nx od nx-cloud?

nx to otwarte narzędzie z kodem w paczce. nx-cloud w wersji 19.1.3 to cienki klient o jedenastu plikach, z polem license równym proprietary i z tekstem CC BY-ND 3.0 w pliku LICENSE. Właściwy klient usługi pobierany jest w czasie działania z serwera dostawcy.

Czy da się mieć bufor współdzielony bez konta u dostawcy?

Tak. Zmienna NX_SELF_HOSTED_REMOTE_CACHE_SERVER włącza w rdzeniu klienta HTTP, a protokół sprowadza się do put i get na ścieżce /v1/cache/{hash} z tokenem bearerToken. Serwer trzeba napisać albo wdrożyć samodzielnie.

Nx czy Turborepo?

Jeśli chcesz wyłącznie pomijać niezmienione zadania i mieć wspólny bufor, Turborepo jest mniejsze i zostawia mniejszy ślad. Jeśli potrzebujesz generatorów, migracji wersji między wydaniami głównymi i wykrywania celów z konfiguracji narzędzi, tego w Turborepo nie ma.

Co się dzieje, gdy nie ma połączenia z usługą?

Nx wypisuje ostrzeżenie, że nie udało się pobrać klienta, i kontynuuje bez zapisu i odczytu z bufora zdalnego. Przebieg nie jest przerywany, tracisz tylko trafienia zdalne.

Czytaj dalej

Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie