Gratulujeme! Úspěšně jste se přihlásili k odběru novinek Frontend Garden.
Ajaj. Při pokusu o přihlášení k odběru novinek došlo k chybě. Zkuste to prosím znovu.
Frontend Garden
  • Články
  • Slovník nové
  • Instagram
  • GitHub
12. únor 2025 —
  • JavaScript,
  • Nástroje
— čtení na 10 min.

Jak předcházet chaotické organizaci npm skriptů

Řešíte, jak organizovat skripty napříč projekty přehledně a konzistentně? Inspirujte se systémem, který zjednoduší práci vám i kolegům.

Tomáš Litera

Tomáš Litera

Tomáš je webový vývojář, kajakář a skaut z Kolína. Zaměřuje se na React, Node a serverless řešení, ale i HTML, CSS a JS obecně. Rád objevuje nové řeky a učí mladé lidi, jak přežít v přírodě. Více o autorovi

Jak předcházet chaotické organizaci npm skriptů

Při práci na kterémkoli projektu v javascriptovém ekosystému se setkáváme s opakujícím se problémem: jak se bude daný projekt ovládat?

Řeč je samozřejmě o npm skriptech, které již plně nahradily nástroje jako byl Grunt, Gulp či Yeoman.

npm skripty umí definovat řadu užitečných příkazů, které se hodí třeba ke kontrole testů, spuštění lokálního vývoje nebo při nasazování do produkce. Problémem zůstává, že každý vývojář si dané skripty pojmenovává po svém, a to vývojářům mírně komplikuje přecházení mezi projekty. Každý projekt má vlastní sadu příkazů, které je potřeba znát, neboť neexistuje jednotná sada skriptů, která by zaručovala, že se projekty alespoň v základu budou stejně ovládat.

Jak se zorientovat v npm skriptech

Formát JSON, a tím pádem i package.json samotný, nám neposkytuje moc prostředků, jak samotné skripty vhodně pojmenovávat nebo organizovat.

Samotné zavolání příkazu npm -h (pozn. red. – pro stručnost uvádíme v článku pouze příklady s jedním správcem balíčků, a to npm) vypíše pouze nápovědu s dostupnými příkazy daného správce balíčků, nikoli informaci o dostupných příkazech v projektu.

Nápověda k příkazu npm -h

Naštěstí i na tohle vývojáři package managerů mysleli, a tak díky npm run můžeme vypsat dostupné npm příkazy – včetně scriptů volaných příkazem. Jenže co když nechceme trávit čas dekódováním skriptů a chceme přesně vědět, co se provede?

Ukázka dostupných npm skriptů uvnitř projektu

Kéž by byla možnost, jak dané příkazy lépe popsat… Ku příkladu v Makefile lze pomocí komentářů vytvořit samodokumentující nápovědu o dostupných příkazech. Funguje to celkem jednoduše: každý příkaz má komentář, který je možné vypsat jako nápovědu v konzoli.

Ukázka dokumentovaných příkazů v Makefile

Bohužel, ani něco tak jednoduchého v případě package.json nejde. Pomohlo by, kdyby každému příkazu bylo možné přiřadit vlastní komentář, ale JSON formát toto neumožňuje.

Doba se však vyvíjí a specifikace JSON5 či JSONC již komentáře povolují. I přes navržená RFC pro komentáře či RFC pro JSON5 nebo dotazy ohledně podpory JSON5, npm zatím stále tyto požadavky ze stran vývojářů nebere v potaz a odvolává se, že Node.js také nepodporuje JSON5. A tak stále není moc cest, jak dostat package.json do čitelnější podoby. Jediným řešením tak je nastavení jmenných konvencí při pojmenovávání npm skriptů a jejich dodržování.

Jmenné konvence

V jednom pull requestu jsme s kolegy řešili, že bychom potřebovali jeden příkaz, který by nám zkontroloval celý projekt. Názvy jako all, ci či check zprvu působily slibně, avšak:

  • all vychází z lokálního použití v PHP projektech, ale už samotný název je matoucí. Co „všechno“ by příkaz měl spouštět?
  • ci simuluje CI pipeline, s čímž už npm počítalo, poněvadž existuje nativní příkaz npm ci.
  • check může být matoucí při spouštění unit testů a v kontextu Yarn je již rezervováno.

Brzy jsme zjistili, že není co vymýšlet. Vrátíme-li se o pár kroků zpět, zjistíme, že nápovědu dostáváme již při zavolání příkazu npm init, který generuje jediný příkaz a tím je npm test. A to bylo ono. 🎉

npm init a jediný existující skript se jmenuje test
„It's far more convenient to know I can rely on npm test  in all projects than having to remember different testing task names across different projects. It's a part of the standard set of tasks: the built-in install (or ci), then build, test, and start.“
– Adam Kudrna (2021)

A tím bylo rozhodnuto. Každý projekt bude minimálně obsahovat příkaz test, který bude testovat celý repozitář – provede unit testy, lintování, kontroly typů, aj.

npm má kromě test zavedené i další příkazy.

A tak se nám začala formovat první pravidla:

  • start – pro spuštění projektu,
  • stop – pro zastavení projektu,
  • test – pro otestování projektu.

Jmenné prostory a aliasy

K ovládání celého projektu je potřeba více příkazů než jen start, stop a test. Drtivou většinu projektů je nutné navíc sestavit (build), provést validaci kódu (lint), zkontrolovat formátování (format), typy (types – v případě TypeScriptu), nemluvě o unit či end-to-end testech. Tím se potřeba vhodného pojmenování značně umocňuje.

Nač vymýšlet kolo, když je možné se inspirovat u větších a dobře zaběhlých projektů. Za zmínku zde stojí Bootstrap, který má příkazy rozdělené podle kontextu a jako oddělovač používá dvojtečku :. Definuje tím „namespace“, kde nejkratší příkaz spouští přidružené příkazy, které se ve jmenném prostoru vyskytují.

Při použití pomlčky jako oddělovače bychom mohli narazit na problém víceslovných příkazů, které se ztrácejí v kontextu:

"format-changelog-design-system":

// versus:

"format:changelog:design-system":

Proto se dvojtečka ukazuje jako lepší volba.

Pravidla pro sestavení příkazů

Již víme, že každý projekt by měl obsahovat příkaz test. Předpokládáme, že v rámci něj dojde k otestování celého projektu – unit a end-to-end testy, statická analýza lintery a formátovači, kontrola typů, atp.

"test":             // testování celého projektu (test:unit, lint, types, format)
"test:unit":        // kontrola unit testů
"test:e2e":         // end-to-end testy
"lint":             // lintování kódu
"lint:scripts":     // lintování kódu, resp. skriptů
"lint:scripts:fix": // lintování kódu, resp. oprava skriptů
"format":           // kontrolu formátu (Prettier)
"types":            // kontrolu typů

S výše zmíněnými pravidly je možné libovolně škálovat strukturu příkazů. Pokud v rámci pravidel definujeme příkazy, které se v projektech budou vždy vyskytovat, lze přecházet z projektu na projekt s jistotou, že základní sada příkazů bude neměnná.

Celá sada pak může vypadat jako na příkladu níže. 👇

"dev":                  start the development
"start":                start the production
"build":                build the project
"clean":                clean build files and other things
"test":                 main script for testing entire project
"test:unit":            run unit tests
"test:unit:ci":         run unit tests for CI
"test:unit:local":      run unit tests on local
"test:unit:watch":      run unit tests in watch mode
"test:unit:coverage":   run unit tests with code coverage
"lint":                 main linting script
"lint:scripts":         run ecma script linting
"lint:scripts:fix":     run ecma script fixing
"lint:commit":          lint commit
"lint:markdown":        lint markdown
"lint:text":            lint text
"lint:text:fix":        fix text
"format":               run format checker
"format:fix":           run format fixer
"types":                run type checking

Kompletní pravidla:

  • název namespace je oddělován pomocí :,
  • víceslovné výrazy jsou oddělovány pomocí -,
  • namespace je aliasem na spuštění sdružených příkazů.

Seznam povinných příkazů:

  • test – testování celého projektu,
  • test:unit – kontrola unit testů,
  • lint – statická analýza kódu lintery,
  • format – kontrola formátování,
  • types – kontrola typů,
  • build – sestavení projektu pro produkci,
  • dev – spuštění lokálního vývoje,
  • start – spuštění produkční konfigurace,
  • release – příprava nového releasu,
  • deploy – nasazení projektu (na produkci, staging, atp.).

Spouštění skupiny příkazů

Vzhledem k výše popsanému pojmenovávání příkazů a jejich slučování do jmenných prostorů potřebujeme tyto příkazy umět spouštět paralelně či sériově. K tomu lze přistoupit dvěma způsoby.

Využití operátoru ;, && nebo &

První možností je využití shell operátorů jako jsou ; a && pro sériově spuštění příkazů anebo & v případě paralelního spuštění.

Paralelní spuštění příkazů

"lint": "lint:scripts & lint:css & lint:html"

Řešení je to bezesporu čitelné, ale bohužel může způsobit nechtěné patálie. Při použití & dochází k vytváření subprocesu, který způsobí, že původní npm proces nedokáže říct, jestli subproces doběhl bez chyby či nikoliv. To může být problém obzvláště u dlouho běžících skriptů nebo když potřebujeme optimalizovat integrační pipeliny.

Sériové spuštění příkazů

"lint": "lint:scripts; lint:css; lint:html"

I v tomto případě můžeme narazit na nechtěné chování. Následující příkaz se spouští nehledě na návratovou hodnotu toho prvního. Tedy přestože první příkaz vrátí chybu, následující příkazy se i tak spustí.

Pokud toto chování chceme změnit, máme k dispozici &&. Pokud první příkaz selže s jiným návratovým kódem než 0 (což znamená úspěšné dokončení), tak se další příkaz již nespustí.

"lint": "lint:scripts && lint:css && lint:html"

Využití balíčku npm-run-all

K dispozici je druhá možnost, která mj. řeší i problém zmíněný výše, a to použití balíčku npm-run-all , resp. npm-run-all2.

ℹ️
Autor původního npm-run-all projekt neudržuje, proto vznikl jeho udržovaný fork npm-run-all2.

Balíček nabízí dva skripty:

  • run-p pro paralelní zpracování příkazů:
"lint": "run-p lint:scripts lint:css lint:html"
  • run-s pro sériové zpracování příkazů:
"lint": "run-s lint:scripts lint:css lint:html"

Pro lepší čitelnost skriptů může být vhodnější používat plný název npm-run-all s přepínači --parallel nebo --serial. Zápis sice není úsporný, ale za to explicitnější.

Další výhodou npm-run-all je, že umí používat zástupný znak * pro nahrazení skupiny výrazů. Díky jmenným prostorům můžeme skupinu příkazů:

"lint": "npm-run-all --serial lint:scripts lint:css lint:html"

zjednodušit na:

"lint": "npm-run-all --serial lint:*"

A co life cycle hooks?

Je vítanou vlastností npm, že při spouštění jakéhokoli příkazu provede i takzvané life cycle hooks, neboli pre a postskripty. Nespustí se pouze příkaz samotný (v našem případě lint), ale před ním i prelint , resp. po něm  postlint – za předpokladu, že jsou příkazy definované.

Příkazy se provedou následovně:

  • (prelint),
  • lint,
  • (postlint).

Je nutné zmínit jedno velké „ale“. U příkazů jako jsou start a prestart nebo serve a jeho hook preserve dochází k úplné změně významu. Na základě těchto problémů Yarn úplně odebral podporu pro pre a post hooky u většiny skriptů. 👇

In particular, we intentionally don't support arbitrary pre and post hooks for user-defined scripts (such as prestart). This behavior caused scripts to be implicit rather than explicit, obfuscating the execution flow. It also sometimes led to surprising behaviors, like yarn serve also running yarn preserve.
– Lifecycle Scripts, Yarn

Otázkou však zůstává: Pokud přicházíme o podporu pre a post hooků, jak pojmenovávat příkazy, aby byly explicitní?

Řešení je nasnadě, i když vyžaduje trochu více psaní. pre lze nahradit za prepare a post za finalize.

Posloupnost pak vypadá následovně:

  • build:prepare
  • build
  • build:finalize

V kombinaci s výše uvedenými pravidly pro jmennou konvenci a způsoby pro spouštění příkazů můžeme definovat npm skripty pro build tímto způsobem:

"build": "npm-run-all --serial build:prepare build:compile build:finalize"
"build:prepare": "shx rm -rf dist"
"build:compile": "rollup"
"build:finalize": "mv package.json dist/"

Řazení skriptů

Čitelnost skriptů lze umocnit pomocí správného řazení skriptů v package.json. Nejjednodušší volbou se může jevit abecední řazení, které ale v důsledku nemusí být příliš praktické.

V praxi je efektivnější řadit skripty podle kontextu. Jako příklad může posloužit příkaz build zmíněný výše. Na první místo se uvádí alias (hlavní volání celého kontextu) a na následujících řádcích jsou použité skripty.

Celé bloky je možné řadit buď dle abecedy, anebo, což je často lepší řešení, podle četnosti použití skriptů vývojářem. Ve většině případů je při práci s novým projektem klíčové co nejjednodušší a nejrychlejší nastartování vývoje. Proto se příkazy jakostart nebo dev umisťují na začátek. Následují příkazy důležité pro vývoj jako test, lint, types, a mezi posledními bývá build a deploy. Na konec patří obslužné a pomocné skripty, které nepatří do žádného kontextu.

Celé to pak může vypadat jako v package.json design systému Spirit.

Závěrem

Pojmenovávat npm skripty a postavit jejich logiku tak, aby jim někdo další rozuměl, je opravdu těžké. Přitom vhodně zvolená organizace příkazů zlepšuje nejen čitelnost a udržitelnost projektů, ale také usnadňuje spolupráci v týmu a přechod mezi různými projekty.

Systém představený v tomto článku by měl být robustní a udržitelný jak z hlediska pojmenovávání, tak i jejich spouštění. Stačí následovat tato doporučení:

  1. Konzistentní jmenné konvence využívající oddělovače : a -.
  2. Vytváření jmenných prostorů pro lepší organizaci skriptů.
  3. Používání aliasů pro zjednodušení spouštění skupin skriptů.
  4. Efektivní využití nástrojů jako npm-run-all pro paralelní a sériové spouštění skriptů.
  5. Nahrazení pre a post hooků explicitnějšími názvy jako prepare a finalize.

Přestože současná omezení formátu JSON v package.json představují výzvu, situace se snad postupem času zlepší. Do té doby si musíme vystačit s pochopitelným a explicitním pojmenováváním. Ať už zvolíte kterékoliv řešení, klíčem k úspěchu je konzistence, čitelnost a snadná škálovatelnost.


Související odkazy

  • Bootstrap a jeho package.json – Příklad rozsáhlého projektu s dobře organizovanými skripty (GitHub)
  • A NPM package Script Strategy – Další pohled na strategii organizace npm skriptů (Medium, Zachary Leighton, 2018)
  • NPM Scripts: Tips Everyone Should Know – Užitečné tipy pro práci s npm skripty (Corgi Bytes, Kamil Ogórek, 2017)

Anglickou verzi článku najdete na webu autora.


Tomáš Litera

Tomáš Litera

Tomáš je webový vývojář, kajakář a skaut z Kolína. Zaměřuje se na React, Node a serverless řešení, ale i HTML, CSS a JS obecně. Rád objevuje nové řeky a učí mladé lidi, jak přežít v přírodě. Více o autorovi

Další články od autora:

  • Element <dialog>: nevymýšlej znovu modál!
Více k tématu:
  • JavaScript
  • Nástroje

Sdílejte

Líbí se vám článek? Podpořte jej sdílením!

X (Twitter) Facebook LinkedIn

Komentujte

Chcete k článku něco doplnit? Našli jste chybu? Napište e-mail.

Adam Kudrna

Nejnovější články

CSS — čtení na 6 min.

Kind of Rebeccapurple. Příběh vzniku nového loga CSS

CSS má nové logo – a nese hluboký příběh. Jak komunita rozhodla o barvě rebeccapurple a proč je víc než jen odstín? 💜

  • Ondřej Konečný
    Ondřej Konečný
Kind of Rebeccapurple. Příběh vzniku nového loga CSS
WebExpo — čtení na 3 min.

Pozvánka na WebExpo 2025 + SLEVA NA VSTUPENKU

Rok se s rokem sešel, přichází měsíc květen a s ním WebExpo 2025, které se po nejistých letech usadilo v tomto jarním termínu. Jaké bude?

  • Adam Kudrna
    Adam Kudrna
Pozvánka na WebExpo 2025 + SLEVA NA VSTUPENKU
Reportáž — čtení na 2 min.

FrontKon 2024 obrazem

Frontendisti letos připravili třetí ročník konference FrontKon. Ta se letos historicky poprvé odehrála naživo v Praze, a to v prostorách O2 Universum.

  • Kateřina Klouček Dlouhá
    Kateřina Klouček Dlouhá
FrontKon 2024 obrazem

Odběr novinek

Zadejte svůj e-mail a nenechte si ujít další nové články!

Odesláním formuláře souhlasíte se .
Zpracování osobních údajů probíhá za účelem zasílání newsletteru. Můžete se spolehnout, že vaše osobní údaje nebudeme s nikým sdílet. Z newsletteru se můžete kdykoli odhlásit. Stejně tak můžete kdykoli požádat o úplné smazání svých osobních údajů z naší databáze.
Zkontrolujte svoji e-mailovou schránku a potvrďte své přihlášení kliknutím na odkaz.

Všechna témata

  • Bootstrap
  • CSS
  • Design
  • Dokumentace
  • HTML
  • ITCSS
  • JavaScript
  • Kariéra
  • Kvalita kódu
  • Nástroje
  • No-code
  • Přístupnost
  • PWA
  • Reportáž
  • Rozhovor
  • Rychlost
  • Sass
  • Spolupráce
  • Typografie
  • Variable fonts
  • WebExpo
  • ✏️ Napište článek
  • Autoři
  • Cookies
  • Instagram
  • GitHub

Obsah na tomto webu je publikován pod licencí Creative Commons CC BY-NC 4.0.
Frontend Garden vysázel, zastřihuje a okopává (s ♥️) Adam Kudrna.
Založeno v květnu 2019.