áthelyezés több tárolóból egy lerna-js monórepóba
At mitter.io, van egy pár nyilvános felületűnpm
csomagok, amelyeket közzé kell tennünk, és nemrégiben egy Lerna által kezelt mono-repo struktúrába költöztünk, hogy mindegyikhez külön tárolók legyenek. Ma szeretném megosztani tapasztalatainkat erről a migrációról és a telepítésünkről az új monorepo struktúrával. Minden csomagunk vagy SDK cél, vagy az SDK célok függősége:
-
@mitter-io/core
– a mitter alapvető funkciója.Io SDK-k -
@mitter-io/models
– a typescript modellek (osztályok, típus álnevek, interfészek stb.) az SDK-khoz -
@mitter-io/web
– a webes SDK -
@mitter-io/react-native
– a React natív SDK -
@mitter-io/node
– a csomópont.js SDK (használt csomópont.js backends) -
@mitter-io/react-scl
– a ReactJS alkalmazások szabványos komponenskönyvtára
minden csomagunk gépírással van megírva, és a gépelés magával a csomagokkal együtt történik, és nem terjesztünk külön gépelési csomagokat (amelyeket általában a @types/
kezdetűekkel látunk). A rollup segítségével ezeket a csomagokat mind UMD, mind ES5 modul formátumban csomagoljuk. Ezen felül a typedoc segítségével generáljuk dokumentációnkat, amelyet azután közzéteszünk egy nyilvános vödörben az AWS S3-ban.
a Lerna használata előtt külön tárolónk volt minden csomagunkhoz, és jól működött, miközben csak a web SDK volt. Ahogy haladtunk, és egyre több fejlesztő dolgozott az SDK-n, néhány problémával szembesültünk a beállításunkkal:
- mivel az SDK logikájának nagy része a
@mitter-io/core
, szinte minden változást, ami acore
csomagban történt, és az összes többi csomagot frissíteni kellett, hogy az új verzióra mutasson. Tehát még akkor is, ha volt egy hiba, amelyet javítani kellett, mondjuk a React Native számára, a változáscore
, de a frissítésnek tükröznie kell az összes többi célt, azazweb
node
andreact-native
. Elég gyakori volt, hogy egy fejlesztő elmulasztott egy célt. - az SDK szinte minden változása az 5 csomagból legalább 3-ban változásokat eredményez.
- hatalmas előnyt láttunk abban, hogy ugyanazt a verziót megtartottuk a csomagok között (megkönnyíti a fejlesztők számára, hogy kitalálják, mi lenne a target legújabb verziója), de ennek kézi nyomon követése nehézkessé vált.
-
npm link
(vagyyarn link
ha szeretné) saját problémái voltak a függőségek összekapcsolásának biztosításával, majd anpm
helyes használatához anpm
és vissza a helyi hivatkozáshoz a fejlesztéshez. - meglehetősen gyakori volt a parancsfájlok futtatása csomagok között (pl., hogy tegye közzé a typescript docs), és mi egy törékeny állvány symlinks és bash szkriptek kezelni ugyanazt.
abban az időben találkoztunk Lerna-val, és úgy tűnt, hogy tökéletesen illeszkedik az igényeinkhez.
úgy döntöttünk, hogy a legegyszerűbb utat követjük, megpróbálva az alapértelmezett értékeket a lehető legnagyobb mértékben használni. Abból, amit tapasztaltunk, vándorló Lerna volt a szél. Kezdje egy új Lerna repo létrehozásával:
mkdir my-new-monorepo && cd my-new-monorepo
git init .
lerna init
válaszoljon néhány egyszerű kérdésre (ahol mindig az alapértelmezetthez fordultunk), és minden készen áll. A régi csomagok áthelyezése a repóikból az újba (amitől rettegtünk, mivel azt gondoltuk, hogy hatalmas fájdalmat okozna) sokkal könnyebb volt a vártnál:
lerna import ~/projects/my-single-repo-package-1 --flatten
vegye figyelembe a
--flatten
lehet, hogy szükség van rá, de anélkül is szembesültünk a problémákkal.
a Lerna-ban az a csodálatos, hogy az összes git-kötelezettséget magával hozza (elveszíthet némi előzményt a --flatten
segítségével), így az új repo esetében a történelem úgy néz ki, mintha a fejlődés mindig is ebben a monorepo-ban történt volna. Ez feltétlenül szükséges, mert szükséged lesz git blame
valakinek egy hibára, amelyet a monorepo-ba költözés után fedeztél fel.
jelenlegi beállítás
a Lerna-val most egyetlen adattárat kezelünk az összes csomagunkhoz, egy könyvtárszerkezettel, amely így néz ki:
packages/
core/
models/
node/
react-native/
web/
lerna.json
package.json
a módosított csomagok közzétételéhez most egyszerűen meg kell tennünk:
lerna boostrap
lerna publish
nem kell tennie lerna bootstrap
minden alkalommal; csak akkor, ha először ellenőrzi a repót. Amit csinál, egyszerűen telepíti az egyes csomagok összes függőségét e repo alatt.
ugyanakkor úgy döntöttünk, hogy egy kicsit egyszerűsítjük a folyamatot, és hozzáadtuk az összes csomagolási feladatot a npm
életciklusán belül. Ne feledje, hogy ennek semmi köze a Lerna-hoz; ennek ideális esetben minden npm csomagban ott kell lennie, függetlenül a repo struktúrától. Az egyes csomagok esetében a következő szkriptek vannak jelen az egyes pacakge.json
:
"scripts": {
...
"prepare": "yarn run build",
"prepublishOnly": "./../../ci-scripts/publish-tsdocs.sh"
...
}
Ez építi a csomagot a typescript fordítóval, csomagolja össze az összesítővel, és generál docs typedoc:
"scripts": {
...
"build": "tsc --module commonjs && rollup -c rollup.config.ts && typedoc --out docs --target es6 --theme minimal --mode file src"
...
}
Az egyetlen repo struktúra lehetővé teszi a közös szkriptek egyetlen helyen tartását is, hogy a változások minden csomagra vonatkozzanak (a build szkriptet is külön szkriptbe kell helyeznünk, tekintettel arra, hogy mára meglehetősen összetett bash parancs lett).
A fejlesztői folyamat
A fejlesztői folyamat a kiadásokon kívül változatlan. A fejlesztő létrehoz egy problémát a GitLab – on (vagy hozzá van rendelve), létrehoz egy új ágat a problémához, majd a kód felülvizsgálata után egyesíti a módosításokat a masterbe. A kiadási életciklus most egy rendkívül strukturált folyamatot követ:
- amikor egy mérföldkő befejeződött, és új kiadást tervezünk, az egyik fejlesztő (az adott kiadásért felelős) létrehoz egy új verziót a
lerna version
futtatásával. - Lerna egy rendkívül hasznos és könnyen használható prompt kitalálni a következő verzió
(master) mitter-js-sdk ツ lerna version --force-publish
lerna notice cli v3.8.1
lerna info current version 0.6.2
lerna info Looking for changed packages since v0.6.2
? Select a new version (currently 0.6.2) (Use arrow keys)
❯ Patch (0.6.3)
Minor (0.7.0)
Major (1.0.0)
Prepatch (0.6.3-alpha.0)
Preminor (0.7.0-alpha.0)
Premajor (1.0.0-alpha.0)
Custom Prerelease
Custom Version
Ha egy új verzió van kiválasztva, Lerna megváltoztatja a verziók a csomagok, létrehoz egy tag a távoli repo, és kitolja a módosításokat a GitLab példány. Ezen túlmenően a fejlesztőknek nem kell mást tenniük. A CI-t úgy állítottuk be, hogy minden olyan címkét felépítsen, amelynek neve hasonló a szemantikai verziószámhoz.
megjegyzés futtatjuk
lerna version
a--force-publish
mert azt akarjuk, hogy minden csomagnak pontosan ugyanaz legyen a verziója. Tehát néha olyan csomagok lesznek, amelyek nem különböznek a különböző verziók között. Az Ön preferenciájától függően dönthet úgy, hogy nem teszi meg.
A CI beállítás
a GitLab integrált CI-jét használjuk az összes projektünk (JS és Java) felépítéséhez, teszteléséhez és közzétételéhez. Az új js monorepo esetében két szakaszunk van:
- Build
- Publish
a build fázis rendkívül egyszerű, és a következő két szkriptet futtatja:
lerna bootstrap
lerna run build
Ez a fázis minden egyes kötelezettségvállaláson fut, hogy lényegében érvényesítse a csomag épelméjűségét. A közzétételi szakasz viszont a következőket futtatja:
git checkout master
lerna bootstrap
git reset --hard
lerna publish from-package --yes
rájöttünk, hogy egy
git checkout master
és egygit reset --hard
mert a GitLab klónozza (vagy lekéri, a konfigurációtól függően) a repo, majd ellenőrzi a megépítendő elkötelezettséget. Ez a munkakönyvtárat “leválasztott fej” módba állítja, azaz a refHEAD
nem mutat sehova. Lerna használjaHEAD
, hogy kitaláljuk, az aktuális változata a csomag és a hibák ki a leválasztott fej állapotban.
azt is meg kell futtatni lerna publish from-package
szemben lerna publish
, mint végrehajtó egy egyszerű lerna publish
volna Lerna panaszkodik, hogy az aktuális verzió már megjelent, mint a metaadatok frissült, amikor a fejlesztő helyben futtatta a lerna version
parancsot. A from-package
argumentum azt mondja a Lerna-nak, hogy tegyen közzé minden olyan verziót, amely jelenleg nincs jelen az npm-ben egy adott csomaghoz. Ez akkor is segít, ha egy közzététel valamilyen okból sikertelen, és újra megpróbálja a folyamatot.
a közzétételi fázis úgy van beállítva, hogy csak olyan címkéken fusson, amelyek megfelelnek a következő regex jóváírásnak:
^v(0|\d*)\.(0|\d*)\.(0|\d*)(-(0|\d*|\d**)(\.(0|\d*|\d**))*)?(\++(\.+)*)?$
Ez egy kicsit divatos, és a legtöbb csapat számára és a legtöbb célra egyszerűen ^v*$
működnie kell. 🙂
Megjegyzés Bár még nem tettük meg, mivel kis csapat vagyunk, a fenti regexet követő címkéket is meg lehet jelölni a GitLab-ban védettként, hogy korlátozzuk, ki tehet közzé csomagokat az npm-ben.
a monorepo-t ahttps://github.com/mitterio/js-sdk címen tekintheti meg (ezt a belső GitLab repo tükrözi).
gyors megjegyzések
gyakori szkriptek futtatásakor (mint a typescript dokumentumok közzétételénél), nagyon hasznos tudni a szkriptet futtató csomag adatait. Ez vonatkozik az npm életciklusában lévő parancsfájlokra, valamint a lerna run
vagy lerna exec
parancsfájlokra. Egy adott csomag npm, npm teszi a teljes package.json
elérhető egy script segítségével környezeti változók. Tehát egy adott csomag esetében a következő package.json
:
{
"name": "@mitter-io/core",
"version": "0.6.28",
"repository": {
"type": "git"
}
}
a következő változók lesznek elérhetők bármilyen életciklus-szkript futtatása közben:
npm_package_name=@mitter-io/core
npm_package_version=0.6.28
npm_package_repository_type=git
Quirks/Issues
néhány dolog, amin még dolgozunk az új beállítással (ezek közül néhány probléma, míg néhányat valószínűleg csak nem tudunk jobban):
- nem biztos benne, hogy lehetséges-e, de szeretnénk, ha minden csomagunkhoz közös életciklus-szkriptek lennének. Ezek deklarálása a
package.json
gyökérben nem működik. - rendkívül nehéz teljesen tesztelni a Lerna beállítását anélkül, hogy ténylegesen közzétennénk valamit az npm-nek. Nem biztos benne, hogy van-e valahol
--dry-run
. - Lerna van egy módja annak, hogy egy közös config-blokk
devDependencies
úgy, hogy az összesdevDependencies
azonos verziójú minden alcsomag. Ez egy nagyon jó funkció, de eltart egy kis időt, hogy kigyomlálja az összes közös is. - ugyanez vonatkozhat más függőségekre is, tehát bár nem akarunk közös
dependencies
konfigurációs blokkot, jó lenne, ha a projektekben elérhető változók kifejezhetők lennének. Például a Java / Kotlin monorepo – ban agradle.properties
változókat használjuk, mint példáulspringBootVersion
springCoreVersion
stb., amelyeket aztán az egyes gradle szkriptek használnak.
gondolataink a monorepos-ról
az utóbbi időben heves vita folyt a monorepos-szal, és hogy látunk-e ismét hatalmas számot ugrálni a kocsira, ami eléggé emlékeztet arra az időre, amikor a mikroszolgáltatások voltak a divat.
az itt követett struktúra több monorepóval rendelkezik, és ez nem az első alkalom, hogy monorepókat kezelünk. A teljes platform és backend egy monorepo, amely privát, telepíthető kódot és több nyilvános néző csomagokat, amelyek közzé bintray. A fő weboldalunk rugós háttérrel is fut, a frontend webcsomaggal van ellátva, amely támogatja a forró újratöltést (webpack watch
) stb. Soha nem döntöttünk úgy, hogy egyetlen mono-repóval megyünk a szervezeten keresztül, mert a szerszámok egyszerűen nem voltak ott.
a Java kódunk nagy része egyetlen repóban működik, mert agradle
biztosítja a Java monorepo és alerna
szerszámokat, valamint az npm életciklus biztosítja a JS SDK monorepo szerszámait. Tehát egyszerűen fogalmazva, a monorepos nagyszerű, ha azonosítja a repóban bekövetkező változások lefedettségét. A mi Java backend, láttuk több MRs egész projektek egyetlen funkció, amely hajlik arra, hogy menjen egy monorepo csak az adott projekt, az összes többi kódot még külön repó. És miután láttuk, hogy hasonló minta jelenik meg a JS SDK-k esetében is, Lerna-ba költöztünk.
vegye figyelembe, hogy körülbelül 9 mérnökből álló kis csapat vagyunk; tehát ami nekünk működik, Lehet, hogy nem működik a különböző méretű csapatoknál. Amit leginkább szeretnénk rámutatni, hogy bármely megoldás elfogadásának nem kell binárisnak lennie, ahol vagy az előírt módon csináljuk, vagy egyáltalán nem.
néhány motivációt láttunk egy monorepo határozottan alkalmazni minket, és sok közülük nem. Például, egyszerűen nem szabad időt szánnunk a szerszámok felépítésére, ha a teljes kódbázisunkat egyetlen repóra helyezték át-függetlenül attól, hogy milyen előnyöket tapasztalhatunk vagy nem. Tehát a vita valójában nem arról szól, hogy van — e “egyetlen repó” – önmagában ez nem más, mint egy új könyvtárszerkezet. Ezek felírása bizonyos problémák enyhítésére szolgál, és mint minden “ezüst golyó” esetében, vannak figyelmeztetések.
a vita a szoftveriparban felmerülő közös kérdésekről szól, és arról, hogy milyen megoldásokat hoztak általában; a “közös” a kulcsszó. Az a terület, ahol eltér a “közös” alkalmazástól, az az, ahol innovációt, változtatásokat és egy kicsit építhet.