Kas tai per žvėris – Moon.js?
Jei dirbi su JavaScript ekosistema ir esi girdėjęs apie monorepo koncepciją, greičiausiai žinai, kad tai gali būti ir palaiminimas, ir prakeiksmas vienu metu. Monorepo struktūra leidžia laikyti visą projektą vienoje vietoje, bet kartu kelia nemažai iššūkių – kaip efektyviai valdyti priklausomybes, kaip greitai buildinit projektus, kaip užtikrinti, kad pakeitimai viename pakete nesugadintų visko kitur?
Čia į sceną įžengia Moon.js – santykinai naujas, bet labai įdomus build sistema, specialiai sukurta monorepo projektams. Skirtingai nuo populiaresnių sprendimų kaip Turborepo ar Nx, Moon.js turi savo unikalų požiūrį ir yra parašytas Rust kalba, kas jau savaime žada greitaveiką. Bet ar tai tik dar vienas įrankis, kuris po metų dings iš radaro, ar tikrai verta dėmesio alternatyva?
Moon.js kūrėjai sako, kad jų tikslas – padaryti monorepo valdymą ne tik greitesnį, bet ir paprastesnį. Sistema automatiškai aptinka projekto struktūrą, supranta priklausomybes tarp paketų ir optimizuoja build procesus taip, kad dirbtumėte su mažesniais projektais, net jei jūsų monorepo turi dešimtis ar šimtus paketų.
Kodėl dar viena build sistema?
Gera klausimas. Juk jau turime Lerna, Nx, Turborepo, Rush ir dar keliolika kitų sprendimų. Bet kiekvienas iš jų turi savo trūkumų. Lerna jau seniai nebėra aktyviai prižiūrima (nors ir buvo bandymų ją atgaivinti), Nx yra galingas, bet gali būti per daug sudėtingas mažesniems projektams, o Turborepo, nors ir greitas, kartais jaučiasi kaip „vieno triuko arkliukas”.
Moon.js bando užpildyti tarpą tarp paprastumo ir galingumo. Štai keletas dalykų, kurie jį išskiria:
Rust pagrindas – tai reiškia greitį. Ne tik teorinį, bet ir praktinį. Kai buildinsite didelį projektą, skirtumas tarp Node.js pagrįstos sistemos ir Rust pagrįstos gali būti ženklus.
Automatinis projekto aptikimas – nebereikia rankiniu būdu konfigūruoti kiekvieno paketo. Moon.js pats nuskanuoja jūsų monorepo ir supranta, kas yra kas.
Išmanus caching – sistema įsimena, kas buvo padaryta anksčiau, ir neperbuildinina to, kas nepasikeitė. Tai gali sutaupyti daug laiko, ypač CI/CD pipeline’uose.
Task orchestration – galite apibrėžti užduotis ir jų priklausomybes, o Moon.js pasirūpins, kad jos būtų vykdomos teisingai tvarka ir lygiagrečiai, kur įmanoma.
Kaip tai veikia praktikoje
Geriausia būdas suprasti bet kokį įrankį – išbandyti jį. Moon.js įdiegimas yra gana paprastas. Pirmiausia reikia įsidiegti pačią sistemą:
npm install -g @moonrepo/cli
Arba jei naudojate Yarn ar pnpm:
yarn global add @moonrepo/cli
pnpm add -g @moonrepo/cli
Kai turite Moon.js įdiegtą, galite inicializuoti jį savo projekte:
moon init
Ši komanda sukurs .moon direktoriją su pagrindiniais konfigūracijos failais. Pagrindinis failas yra workspace.yml, kur apibrėžiate savo monorepo struktūrą ir nustatymus.
Pavyzdžiui, paprastas workspace.yml gali atrodyti taip:
projects:
- 'packages/*'
- 'apps/*'
vcs:
manager: 'git'
defaultBranch: 'main'
Čia sakome Moon.js, kad mūsų projektai yra packages ir apps direktorijose, ir kad naudojame Git su main kaip pagrindine šaka.
Užduočių konfigūravimas ir vykdymas
Vienas iš pagrindinių Moon.js privalumų – galimybė apibrėžti užduotis (tasks) ir jų priklausomybes. Kiekviename pakete galite turėti moon.yml failą, kuriame aprašote, kokios užduotys yra prieinamos.
Pavyzdys:
tasks:
build:
command: 'tsc'
inputs:
- 'src/**/*'
outputs:
- 'dist/**/*'
deps:
- '~:typecheck'
test:
command: 'jest'
inputs:
- 'src/**/*'
- 'tests/**/*'
deps:
- '~:build'
typecheck:
command: 'tsc --noEmit'
Šiame pavyzdyje matome tris užduotis. build užduotis priklauso nuo typecheck, o test priklauso nuo build. Moon.js automatiškai supras šias priklausomybes ir vykdys užduotis teisingai tvarka.
inputs ir outputs laukai naudojami caching’ui. Jei failai, nurodyti inputs, nepasikeitė, ir outputs jau egzistuoja, Moon.js praleis užduotį ir naudos cache’intą rezultatą.
Užduotis vykdyti galite taip:
moon run my-package:build
Arba jei norite paleisti užduotį visuose paketuose:
moon run :build
Tai paleis build užduotį visuose paketuose, kurie ją turi, ir padarys tai lygiagrečiai, kur įmanoma, atsižvelgdamas į priklausomybes.
Dependency graph ir projekto struktūros supratimas
Vienas iš sunkiausių dalykų monorepo projektuose – suprasti, kaip skirtingi paketai yra susiję tarpusavyje. Pakeičiate vieną failą vienoje vietoje, ir staiga sugenda testai visai kitame pakete. Kodėl? Nes tas paketas priklauso nuo to, kurį pakeitėte, bet jūs apie tai net nežinojote.
Moon.js turi įmontuotą dependency graph analizę. Galite vizualizuoti savo projekto struktūrą su:
moon graph
Tai sugeneruos grafinį projekto priklausomybių vaizdą. Dar geriau, Moon.js naudoja šią informaciją, kad suprastų, kuriuos paketus reikia rebuildiniti, kai kažkas pasikeičia.
Tarkime, turite tokią struktūrą:
packages/
ui-components/
utils/
api-client/
apps/
web-app/
admin-panel/
Jei web-app naudoja ui-components, kuris savo ruožtu naudoja utils, Moon.js automatiškai supras, kad pakeitus utils, reikės rebuildiniti ir ui-components, ir web-app.
Bet štai kas įdomu – jei pakeisite tik admin-panel kodą, kuris nenaudoja utils, Moon.js supras, kad kitus paketus rebuildininti nereikia. Tai gali sutaupyti daug laiko, ypač CI/CD procese.
Caching strategijos ir optimizacija
Caching yra viena iš svarbiausių build sistemos dalių. Blogai įgyvendintas caching gali sukelti daugiau problemų nei naudos – gausite senus build’us, kai tikitės naujų. Gerai įgyvendintas caching gali pagreitinti jūsų workflow’ą kelis kartus.
Moon.js naudoja kelių lygių caching strategiją:
Lokalus cache – saugomas .moon/cache direktorijoje. Tai greičiausias variantas, nes viskas yra jūsų mašinoje.
Nuotolinis cache – galite konfigūruoti nuotolinį cache serverį, kad komandos nariai galėtų dalintis build rezultatais. Tai ypač naudinga CI/CD sistemose.
Git-aware caching – Moon.js supranta Git istoriją ir gali nuspręsti, ar cache vis dar aktualus, remiantis commit’ais.
Konfigūruoti nuotolinį cache galite workspace.yml faile:
runner:
cacheLifetime: '7 days'
archiveableTargets: ['build', 'test']
remoteCache:
host: 'https://your-cache-server.com'
token: '${MOON_CACHE_TOKEN}'
Čia apibrėžiame, kad cache galioja 7 dienas, ir kad build bei test užduočių rezultatai gali būti archyvuojami ir dalijami per nuotolinį serverį.
Praktinis patarimas: pradėkite su lokaliu cache ir tik tada, kai komanda išaugs, galvokite apie nuotolinį. Nuotolinio cache serverio palaikymas reikalauja papildomų resursų ir konfigūracijos.
Integracija su CI/CD sistemomis
Monorepo tikroji vertė atsiskleidžia CI/CD procese. Galimybė buildiniti tik tai, kas pasikeitė, gali sumažinti pipeline vykdymo laiką nuo valandų iki minučių.
Moon.js puikiai integruojasi su populiariausiomis CI/CD sistemomis. Štai pavyzdys su GitHub Actions:
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install Moon
run: npm install -g @moonrepo/cli
- name: Install dependencies
run: moon docker scaffold
- name: Run affected tasks
run: moon ci
Komanda moon ci yra speciali – ji automatiškai nustato, kurie projektai buvo pakeisti (lyginant su base branch), ir vykdo tik jų užduotis. Tai gali drastiškai sumažinti CI laiką.
moon docker scaffold yra kita įdomi komanda – ji optimizuoja Docker layer caching, generuodama minimalų package.json su tik reikalingomis priklausomybėmis.
Kaip tai atrodo realiame projekte
Teorija yra gražu, bet kaip tai veikia praktikoje? Pabandžiau Moon.js vidutinio dydžio projekte su ~15 paketų. Projektas naudojo TypeScript, React, ir turėjo kelias Node.js backend aplikacijas.
Pirmasis įspūdis – setup’as buvo gana sklandus. moon init sukūrė reikalingus failus, ir po kelių minučių konfigūracijos jau galėjau paleisti moon run :build. Pirmasis build’as užtruko panašiai kaip su ankstesne sistema (Lerna), bet antras build’as, kai nieko nepakeitėme, buvo praktiškai akimirksnis – visi rezultatai buvo paimti iš cache.
Kas tikrai patiko – dependency graph vizualizacija. Galėjau aiškiai matyti, kaip mūsų paketai yra susiję, ir tai padėjo identifikuoti kelis cirkularines priklausomybes, kurių anksčiau net nepastebėjome.
Kas buvo sunkiau – migracija egzistuojančių script’ų. Turėjome nemažai custom build script’ų package.json failuose, ir juos perkelti į Moon.js užduotis reikalavo šiek tiek laiko. Bet rezultatas buvo vertas – viskas tapo aiškiau ir labiau standartizuota.
Performance’o prasme, skirtumas buvo juntamas CI/CD. Mūsų GitHub Actions workflow’ai, kurie anksčiau užtrukdavo ~20 minučių, dabar užtrukdavo ~8-12 minučių, priklausomai nuo to, kiek paketų buvo pakeista. Tai gana reikšmingas pagerinimas, ypač kai PR’ai vyksta dažnai.
Ką reikėtų žinoti prieš pradedant
Moon.js yra įdomus įrankis, bet jis nėra tobulas ir ne kiekvienam projektui tinkamas. Štai keletas dalykų, kuriuos verta apsvarstyti:
Mokymosi kreivė – nors Moon.js bando būti paprastas, vis tiek reikia laiko suprasti jo konceptus ir konfigūraciją. Jei jūsų komanda jau gerai pažįsta kitą sistemą (pvz., Nx), migracija gali būti nelengva.
Ekosistemos brandumas – Moon.js yra santykinai naujas projektas. Tai reiškia, kad dokumentacija kartais gali būti neišsami, o community mažesnė nei populiaresnių alternatyvų. Jei susiduriate su problema, gali būti sunkiau rasti sprendimą.
Tooling integracija – ne visi įrankiai ir frameworkai turi oficialų Moon.js palaikymą. Nors dažniausiai galite sukonfigūruoti bet ką per custom komandas, kartais tai reikalauja papildomo darbo.
Komandos dydis – jei dirbate vienas ar mažoje komandoje su nedideliu monorepo, Moon.js gali būti overkill. Paprastas workspace setup su npm/yarn/pnpm workspaces gali būti pakankamas.
Bet jei turite vidutinį ar didelį monorepo, aktyvią komandą, ir norite optimizuoti build procesus, Moon.js tikrai verta apsvarstyti. Ypač jei jums svarbus greitis ir efektyvumas CI/CD procese.
Ar verta bandyti, ir kokia ateitis laukia
Atsakymas į klausimą „ar verta bandyti Moon.js” priklauso nuo jūsų situacijos. Jei esate patenkinti dabartine sistema ir ji veikia gerai – galbūt neverta keisti. Bet jei jaučiate, kad jūsų build procesai galėtų būti greitesni, jei CI/CD užtrunka per ilgai, arba jei tiesiog norite išbandyti kažką naujo – Moon.js tikrai verta dėmesio.
Projektas yra aktyviai vystomas, ir kūrėjai atrodo esantys atsidavę. Rust pagrindas suteikia tvirtą fundamentą greitaveikai, o architektūra atrodo gerai apgalvota. Taip, yra dalykų, kuriuos reikia tobulinti – dokumentacija galėtų būti išsamesnė, community galėtų būti didesnė, bet tai natūralu naujam projektui.
Jei nuspręsite išbandyti, rekomenduočiau pradėti nuo mažo eksperimento. Sukurkite test monorepo su keliais paketais, sukonfigūruokite Moon.js, ir pažiūrėkite, kaip jis veikia. Tai leis jums įvertinti, ar jis tinka jūsų workflow’ui, neprisiimdami didelio commitment’o.
Galų gale, įrankiai yra tik įrankiai. Svarbiausia – rasti tai, kas veikia jūsų komandai ir projektui. Moon.js siūlo įdomų požiūrį į monorepo valdymą, ir net jei nuspręsite jo nenaudoti, jo idėjos gali įkvėpti jus pagerinti savo dabartinį setup’ą. O jei nuspręsite jį išbandyti – gali būti, kad rasite būtent tą trūkstamą gabalėlį, kurio ieškojote savo development workflow’ui.
