V našem týmu používáme už delší dobu správce NPM balíčků Yarn. Od verze 1.0, která už tu s námi nějaký čas je, můžeme používat funkci nazvanou “workspaces”. O co jde a proč by nás to mělo zajímat?

Yarn workspaces: O co jde?

Workspaces jsou koncept objevující se často v monorepech, což je architektura repozitáře, která obsahuje samostatně vyvíjené celky, které spolu ale mohou na sobě záviset. Závislé celky, v našem případě NPM balíčky mohou mít zároveň společně nějaký externí balíčky, na kterých závisejí, a my chceme samozřejmě optimalizovat strukturu node_modules tak, aby se nám externí balíčky neduplikovaly a nevznikaly mezi nimi konflikty.

Pokud máme strukturu repozitáře, která vypadá jako na příkladu níže, musíme v adresáři my-packagemy-utils spustit yarn install, abychom pro oba balíčky nainstalovali jejich závislosti. Tím ale nevyřešíme závislost my-package balíčku na my-utils.

.git
my-package/
  package.json   # obsahuje dependencies: { lodash: 4, my-utils: * }
  node_modules/
    lodash/
my-utils
  package.json   # obsahuje dependencies: { lodash: 4 }
  node_modules/
    lodash/

Tady nám k pomoci přicházejí právě Yarn a jeho workspaces. Stačí v rootu repozitáře vytvořit package.json, ve kterém workspaces nadefinujeme, a na tom samém místě spustit yarn install.

{
  "private": true,
  "workspaces": ["my-package", "my-utils"]
}

Nyní bude struktura repozitáře vypadat takto:

.git
package.json
my-package/
  package.json    # obsahuje dependencies: { lodash: 4, my-utils: * }
my-utils
  package.json    # obsahuje dependencies: { lodash: 4 }
node_modules
  lodash/
  my-utils/       # symlink to ../my-utils

Yarn použil techniku zvanou hoisting a závislosti obou balíčků (v našem příkladě lodash) posunul v adresářové struktuře výše do node_modules v rootu repozitáře.

Také zjistil, že my-package závisí na balíčku my-utils, přičemž jeden z nadefinovaných workspaců je balíček se stejným názvem. Proto my-utils nenainstaloval z npm registru balíčků, ale vytvořil v node_modules filesystémový odkaz na tento workspace (adresář).

Uvnitř balíčku my-package můžeme teď naimportovat a použít my-utils stejně jako kdybychom jej instalovali z NPM registrů pomocí yarn add.

// my-package/src/index.js
import * as utils from 'my-utils';

Výše uvedených vlastností využívají nejznámější javascriptové nástroje a knihovny jako třeba Babel, React.js nebo Storybook pro správu svých monorepozitářů.

Jak využíváme workspaces v Ackee

V Ackee rádi tvoříme Open source a Yarn workspaces využíváme právě v případě, že se některý z balíčků rozroste do většího ekosystému, který je spravovaný v monorepu, jako třeba Antonio – nadstavba nad fetch API s utilitami a bindingy do jiných nástrojů, které běžně při vývoji používáme.

V těchto případech se běžně využívá zavedená konvence adresáře packages, který obsahuje všechny hlavní balíčky:

.git
packages/
  my-package/
  my-utils/
node_modules/

přičemž seznam workspaců se poté nedefinuje jako seznam těchto balíčků:

"workspaces": ["packages/my-package", "packages/my-utils"]

ale zjednodušeně jen pomocí wildcard zápisu:

"workspaces": ["packages/*"]

Ale i v případě, kdy vyvíjíme open source balíček, který je jednoduchý, nepotřebuje složitý ekosystém a víme tedy, že bude jen jeden, se Yarn workspaces mohou hodit.

Dobrý vývojář dbá na kvalitu kódu (open source kódu dvojnásob 😁), a proto – pokud vyvíjíme nějaký nový NPM balíček – si ho chceme ještě před publishem (nebo při vývoji nějaké nové featury) vyzkoušet. A to ideálně pomocí importu z node_modules, stejně jako to budou dělat všichni, kdo si ho nainstalují z NPM registrů.

V takovém případě využíváme často architekturu, kdy náš balíček my-simple-packge přesuneme v repozitáři do stejně pojmenované složky a vytvoříme workspace example, který na balíčku závisí:

.git
package.json
example
  package.json      # obsahuje dependencies: { my-simple-package: * }
my-simple-package
  package.json
node_modules/

Jak už určitě tušíte, definice workspaces v root package.jsonu repozitáře bude:

"workspaces": ["my-simple-package", "example"]

a uvnitř adresáře (workspacu) example pak můžeme vyzkoušet, zda import a použití balíčku funguje tak, jak si představujeme:

// example/src/index.js
import { ... } from 'my-simple-package'

Dříve jsme pro tento účel využívali yarn_link případně později spíše yalc což je skvělý nástroj pro přilinkování lokální verze nějakého balíčku do existujícího projektu. Pro odzkoušení balíčku v izolaci jsme ale často naráželi na některé nevýhody týkající se hoistingu závislostí a více node_modules adresářů s více verzemi balíčků. Pokud totiž použijete přilinkování balíčku, skončíte s následující architekturou:

example
  package.json      # obsahuje dependencies: { my-simple-package: * }
  node_modules/
    my-simple-package/     # symlinked using yarn link to ../../my-simple-package

my-simple-package
  package.json
  node_modules/

Při buildu example pak můžete narazit na špatné vyresolvování závislostí, případně konflikt, protože se berou z dvou oddělených node_modules.

Yarn workspaces s použitím hostingu do top level node_modules se zdají v tomto případě jako mnohem lepší řešení…

Závěrem

Workspaces se v komunitě staly tak rozšířenou a používanou funkcí, že originální správce balíčků npm ji od verze 7 podporuje již také, a není tak nutností použití Yarnu.

Všechny uvedené příklady jsou samozřejmě značně zjednodušené a rozhodně nepodchycují všechny výhody i kompromisy, které na vás při použití mohou čekat. Pro ukázání principů a příkladů jejich využití nám ale posloužily dobře.

Workspaces jsou podle me skvělým případem nástroje, který perfektně slouží jednomu účelu, kterým je správa a orchestrace externích a interních závislostí, a dělá to dobře. Proto je lze krásně zkombinovat s dalšími tooly a tvořit tak robustní dev tech stacky.

Máte zájem o spolupráci? Pojďme to probrat osobně!

Napište nám >