Jak napsat, otestovat a publikovat balíček NPM

Jak vytvořit svůj vlastní balíček, napsat testy, spustit balíček lokálně a uvolnit jej do NPM.

Začínáme

V tomto tutoriálu se budete chtít ujistit, že máte na svém počítači nainstalovaný Node.js (doporučuje se nejnovější verze LTS – v době psaní 16.13.1). Pokud jste dosud nenainstalovali Node.js, nejprve si přečtěte tento tutoriál.

Nastavení projektu

Pro začátek vytvoříme novou složku pro náš balíček na našem počítači.

Terminál

mkdir package-name

Dále chceme cd do této složky a vytvořte package.json soubor:

Terminál

cd package-name && npm init -f

Zde npm init -f sdělí NPM (Node Package Manager, nástroj, který budeme používat k publikování našeho balíčku), aby inicializoval nový projekt a vytvořil package.json soubor v adresáři, kde byl příkaz spuštěn. -f znamená "force" a říká NPM, aby vyplivlo šablonu package.json soubor. Pokud vyloučíte -f , NPM vám pomůže vytvořit package.json pomocí jejich podrobného průvodce.

Jakmile budete mít package.json soubor, dále chceme provést mírnou úpravu souboru. Pokud jej otevřete, chceme přidat speciální pole type k objektu nastavenému na hodnotu "module" jako řetězec, takto:

{
  "type": "module",
  "name": "@cheatcodetuts/calculator",
  "version": "0.0.0",
  "description": "",
  "main": "./dist/index.js",
  "scripts": { ... },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": { ... }
}

Na úplný začátek objektu JSON jsme přidali "type": "module" . Když je náš kód spuštěn, sděluje to Node.js, že očekáváme, že soubor bude používat syntaxi modulu ES (ECMAScript Module nebo zkráceně ESM) na rozdíl od syntaxe Common JS. ESM používá moderní import a export syntaxe, zatímco CJS používá require() výpis a module.exports syntax. Preferujeme moderní přístup, tedy nastavením "type": "module" , povolujeme podporu pro použití import a export v našem kódu.

Poté chceme vytvořit dvě složky uvnitř naší složky balíčku:src a dist .

  • src bude obsahovat "zdrojové" soubory pro náš balíček.
  • dist bude obsahovat sestavené (zkompilované a minifikované) soubory pro náš balíček (toto budou ostatní vývojáři načítat do své aplikace při instalaci našeho balíčku).

Uvnitř src adresář, chceme vytvořit index.js soubor. Zde napíšeme kód pro náš balíček. Později se podíváme na to, jak vezmeme tento soubor a sestavíme jej, přičemž vytvořená kopie bude automaticky převedena do dist .

/src/index.js

export default {
  add: (n1, n2) => {
    if (isNaN(n1) || isNaN(n2)) {
      throw new Error('[calculator.add] Passed arguments must be a number (integer or float).');
    }

    return n1 + n2;
  },
  subtract: (n1, n2) => {
    if (isNaN(n1) || isNaN(n2)) {
      throw new Error('[calculator.subtract] Passed arguments must be a number (integer or float).');
    }

    return n1 - n2;
  },
  multiply: (n1, n2) => {
    if (isNaN(n1) || isNaN(n2)) {
      throw new Error('[calculator.multiply] Passed arguments must be a number (integer or float).');
    }

    return n1 * n2;
  },
  divide: (n1, n2) => {
    if (isNaN(n1) || isNaN(n2)) {
      throw new Error('[calculator.divide] Passed arguments must be a number (integer or float).');
    }

    return n1 / n2;
  },
};

Pro náš balíček vytvoříme jednoduchou kalkulačku se čtyřmi funkcemi:add , subtract , multiply a divide s každým akceptuje dvě čísla pro provedení jejich příslušné matematické funkce.

Funkce zde nejsou příliš důležité (doufejme, že jejich funkčnost je jasná). Co jsme skutečně chci věnovat pozornost, je export default nahoře a throw new Error() řádků uvnitř každé funkce.

Všimněte si, že místo toho, abychom každou z našich funkcí definovali jednotlivě, definovali jsme je na jediném objektu, který je exportován z našeho /src/index.js soubor. Cílem je, aby byl náš balíček importován do aplikace, jako je tato:

import calculator from 'package-name';

calculator.add(1, 3);

Zde je exportovaný objekt calculator a každá funkce (v JavaScriptu se funkce definované na objektu označují jako "metody") je přístupná přes tento objekt, jak vidíme výše. Poznámka :takto chceme, aby se náš vzorový balíček choval, ale váš balíček se může chovat jinak – to je vše například.

Zaměření na throw new Error() Všimněte si, že jsou všechny téměř totožné. Cílem je říci „pokud n1 argument nebo n2 argumenty se nepředávají jako čísla (celá čísla nebo plovoucí čísla), vyvolá chybu."

Proč to děláme? Dobře, zvažte, co děláme:vytváříme balíček pro ostatní. To se liší od toho, jak bychom mohli psát svůj vlastní kód, kde jsou vstupy předvídatelné nebo řízené. Při vývoji balíčku si musíme být vědomi možného zneužití tohoto balíčku. Můžeme to vysvětlit dvěma způsoby:psaním opravdu dobré dokumentace, ale také tím, že náš kód bude odolný proti chybám a bude poučný.

Zde, protože náš balíček je kalkulačka, můžeme uživateli pomoci správně používat balíček tím, že budeme mít přísný požadavek, aby nám předal čísla, se kterými bude provádět výpočty. Pokud ne, poskytneme nápovědu, co udělali špatně a jak problém vyřešit na úrovni kódu . To je důležité pro přijetí balíčku. Čím více je váš kód pro vývojáře, tím je pravděpodobnější, že váš balíček využijí ostatní.

V dalším prosazování tohoto bodu se dále naučíme, jak napsat několik testů pro náš balíček a naučíme se, jak je spouštět.

Psaní testů pro váš kód balíčku

Chceme mít co největší důvěru v náš kód, než jej zpřístupníme dalším vývojářům. I když můžeme jen slepě věřit tomu, co jsme napsali jako funkční, není to moudré. Místo toho můžeme před vydáním našeho balíčku napsat automatizované testy, které simulují uživatele správně (nebo nesprávně) pomocí našeho balíčku a ujistit se, že náš kód odpovídá, jak bychom očekávali.

K napsání našich testů použijeme knihovnu Jest z Facebooku. Jest je jedinečný nástroj v tom, že kombinuje:

  • Funkčnost pro vytváření testovacích sad a jednotlivých testů.
  • Funkčnost pro provádění tvrzení v rámci testů.
  • Funkčnost pro spouštění testů.
  • Funkce pro hlášení výsledků testů.

Tyto nástroje jsou nám tradičně zpřístupňovány prostřednictvím několika nezávislých balíčků. Jest umožňuje snadné nastavení testovacího prostředí tím, že je kombinuje dohromady. Chcete-li přidat Jest do našeho vlastního balíčku, musíme nainstalovat jeho balíčky přes NPM (meta!):

Terminál

npm install -D jest jest-cli

Zde říkáme nainstalovat jest a jeho jest-cli balíček (druhý je rozhraní příkazového řádku, které používáme ke spouštění testů) jako závislosti pouze pro vývoj (předáním -D příznak na npm install ). To znamená, že máme v úmyslu používat Jest pouze ve vývoji a není chceme, aby byla přidána jako závislost, která bude nainstalována spolu s naším vlastním balíčkem v našem uživatelském kódu.

/package.json

{
  "type": "module",
  "name": "@cheatcodetuts/calculator",
  "version": "0.0.0",
  "description": "",
  "scripts": {
    "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
    "test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "jest": "^27.4.3",
    "jest-cli": "^27.4.3",
  }
}

Nyní se ponoříme do detailů. Zde v našem package.json chceme přidat dva řádky do našeho scripts objekt. Tyto scripts jsou známé jako „skripty NPM“, což jsou, jak název napovídá, opakovaně použitelné skripty příkazového řádku, které můžeme spouštět pomocí NPM npm run funkce v terminálu.

Zde přidáváme test a test:watch . První skript bude použit k jednorázovému spuštění našich testů a vygenerování zprávy při test:watch spustí naše testy jednou a poté znovu, kdykoli se změní testovací soubor (nebo související kód). První je užitečný pro rychlou kontrolu věcí před nasazením a druhý je užitečný pro spouštění testů během vývoje.

Podívejte se zblízka na test skript node --experimental-vm-modules node_modules/jest/bin/jest.js vedeme to zvláštním způsobem. Obvykle bychom mohli napsat náš skript jako nic jiného než jest (doslova "test": "jest" ) a fungovalo by to, protože bychom chtěli psát naše testy pomocí modulů ES (na rozdíl od Common JS), musíme to povolit v Jestu, stejně jako jsme to udělali zde v našem package.json pro náš kód balíčku.

K tomu potřebujeme spustit Jest přímo přes Node.js, abychom mohli předat --experimental-vm-modules flag na Node.js (vyžaduje Jest, protože rozhraní API, která používají k implementaci podpory ESM, to stále považují za experimentální funkci).

Protože ke spuštění Jestu používáme Node (a ne jest-cli 's jest příkaz přímo), musíme také ukazovat přímo na binární verzi Jestu (technicky to je jest-cli odkazuje na nás prostřednictvím jest ale kvůli požadavku vlajky musíme jít přímo).

test:watch příkaz je téměř stejný. Jediný rozdíl je v tom, že na konec musíme přidat --watch příznak, který Jestovi říká, aby po prvním spuštění pokračoval v běhu a sledoval změny.

/src/index.test.js

import calculator from './index';

describe('index.js', () => {
  test('calculator.add adds two numbers together', () => {
    const result = calculator.add(19, 88);
    expect(result).toEqual(107);
  });
});

Když dojde na psaní našich testů, Jest automaticky spustí všechny testy umístěné v *.test.js soubor, kde * může být jakékoli jméno, které si přejeme. Výše pojmenujeme náš testovací soubor tak, aby odpovídal souboru, ve kterém je uložen kód našeho balíčku:index.test.js . Myšlenka je taková, že chceme ponechat náš testovací kód vedle skutečného kódu, který je určen k testování.

To může znít zmateně, ale zvažte, co děláme:snažíme se simulovat skutečného uživatele, který volá náš kód ze své aplikace. To jsou testy v programování. Samotné testy jsou pouze prostředky, které používáme k automatizaci procesu (např. na rozdíl od tabulky s manuálními kroky, které bychom sledovali a prováděli ručně).

Náš testovací soubor se skládá ze dvou hlavních částí:sady a jeden nebo více testů . Při testování představuje „sada“ skupinu souvisejících testů. Zde definujeme jednu sadu pro popis našeho index.js pomocí describe() funkce v Jest. Tato funkce má dva argumenty:název sady jako řetězec (používáme pouze název souboru, který testujeme) a funkci k volání, ve které jsou definovány naše testy.

Test následuje po podobném nastavení. Vezme popis testu jako řetězec pro svůj první argument a poté funkci, která je zavolána ke spuštění testu.

Zaměření na test() funkce, kterou zde máme, jako příklad jsme přidali test, který zajišťuje naše calculator.add() metoda funguje tak, jak bylo zamýšleno a sečte dvě čísla dohromady, aby vytvořila správný součet. K napsání skutečného testu (známého v testovacím žargonu jako „provedení“) zavoláme naše calculator.add() funkce předání dvou čísel a uložení součtu do proměnné result . Dále ověříme že funkce vrátila hodnotu, kterou očekáváme.

Zde očekáváme result rovno 107 což je součet, který bychom očekávali, že dostaneme, pokud se naše funkce chová správně. V Jestu (a jakékoli testovací knihovně) můžeme do testu přidat více asercí, pokud si to přejeme. Opět, stejně jako skutečný kód v našem balíčku, co/kdy/jak/proč se to změní na základě záměru vašeho kódu.

Pojďme přidat další test pro ověření špatného nebo nešťastného cestu k našemu calculator.add() funkce:

/src/index.test.js

import calculator from './index';

describe('index.js', () => {
  test('calculator.add throws an error when passed arguments are not numbers', () => {
    expect(() => {
      calculator.add('a', 'b');
    }).toThrow('[calculator.add] Passed arguments must be a number (integer or float).');
  });

  test('calculator.add adds two numbers together', () => {
    const result = calculator.add(19, 88);
    expect(result).toEqual(107);
  });
});

Tady trochu jinak. Připomeňme, že dříve v našem kódu balíčku jsme přidali kontrolu, abychom se ujistili, že hodnoty předávané každé z našich funkcí kalkulačky byly předány čísly jako argumenty (pokud ne, vyvolá se chyba). Zde chceme otestovat, že ve skutečnosti dojde k chybě, když uživatel předá nesprávná data.

Toto je důležité! Znovu, když píšeme kód, který ostatní budou konzumovat ve svém vlastním projektu, chceme si být co nejblíže jisti, že náš kód bude dělat to, co očekáváme (a to, co říkáme ostatním vývojářům, že očekáváme).

Zde, protože chceme ověřit, že naše funkce kalkulačky vyvolá chybu, předáme funkci našemu expect() a zavolejte naši funkci zevnitř to funkce, předávání špatných argumentů. Jak test říká, očekáváme calculator.add() vyvolat chybu, pokud předané argumenty nejsou čísla. Protože zde předáváme dva řetězce, očekáváme, že funkce bude throw které funkce předala expect() "zachytí" a použije k vyhodnocení, zda je tvrzení pravdivé pomocí .toThrow() metoda tvrzení.

To je podstata psaní našich testů. Pojďme se podívat na úplný testovací soubor (stejné konvence se jen opakují pro každou jednotlivou funkci kalkulačky).

/src/index.test.js

import calculator from './index';

describe('index.js', () => {
  test('calculator.add throws an error when passed argumen ts are not numbers', () => {
    expect(() => {
      calculator.add('a', 'b');
    }).toThrow('[calculator.add] Passed arguments must be a number (integer or float).');
  });

  test('calculator.subtract throws an error when passed arguments are not numbers', () => {
    expect(() => {
      calculator.subtract('a', 'b');
    }).toThrow('[calculator.subtract] Passed arguments must be a number (integer or float).');
  });

  test('calculator.multiply throws an error when passed arguments are not numbers', () => {
    expect(() => {
      calculator.multiply('a', 'b');
    }).toThrow('[calculator.multiply] Passed arguments must be a number (integer or float).');
  });

  test('calculator.divide throws an error when passed arguments are not numbers', () => {
    expect(() => {
      calculator.divide('a', 'b');
    }).toThrow('[calculator.divide] Passed arguments must be a number (integer or float).');
  });

  test('calculator.add adds two numbers together', () => {
    const result = calculator.add(19, 88);
    expect(result).toEqual(107);
  });

  test('calculator.subtract subtracts two numbers', () => {
    const result = calculator.subtract(128, 51);
    expect(result).toEqual(77);
  });

  test('calculator.multiply multiplies two numbers', () => {
    const result = calculator.multiply(15, 4);
    expect(result).toEqual(60);
  });

  test('calculator.divide divides two numbers', () => {
    const result = calculator.divide(20, 4);
    expect(result).toEqual(5);
  });
});

Pro každou funkci kalkulačky jsme zopakovali stejný vzor:ověřte, zda je vyvolána chyba, pokud předané argumenty nejsou čísla, a očekáváme, že funkce vrátí správný výsledek na základě zamýšlené metody (sčítání, odečítání, násobení nebo dělení) .

Pokud to provedeme v Jestu, měli bychom vidět, že naše testy proběhnou (a projdou):

To je vše pro naše testy a kód balíčku. Nyní jsme připraveni přejít do závěrečných fází přípravy našeho balíčku na vydání.

Vytváření našeho kódu

I když bychom mohli technicky uvolnit tento kód již nyní, chceme si uvědomit dvě věci:zda vývojářský vlastní projekt bude podporovat náš kód balíčku a velikost kódu.

Obecně řečeno, je dobré použít nástroj pro sestavení kódu, který vám pomůže s těmito problémy. Pro náš balíček budeme používat esbuild balíček:jednoduchý a neuvěřitelně rychlý nástroj pro tvorbu JavaScriptu napsaný v Go. Chcete-li začít, přidejte jej do našeho projektu jako závislost:

Terminál

npm install -D esbuild

Opět, jak jsme se dříve naučili s Jestem, budeme potřebovat pouze esbuild ve vývoji, takže používáme npm install -D příkaz k instalaci balíčku v našem devDependencies .

/package.json

{
  "type": "module",
  "name": "@cheatcodetuts/calculator",
  "version": "0.0.0",
  "description": "",
  "main": "./dist/index.js",
  "scripts": {
    "build": "./node_modules/.bin/esbuild ./src/index.js --format=esm --bundle --outfile=./dist/index.js --platform=node --target=node16.3 --minify",
    "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
    "test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "esbuild": "^0.14.1",
    "jest": "^27.4.3",
    "jest-cli": "^27.4.3",
    "semver": "^7.3.5"
  }
}

Podobně jako jsme to udělali pro Jest výše, zpět v našem package.json chceme přidat další skript, tentokrát nazvaný build . Tento skript bude zodpovědný za volání na esbuild vygenerovat vytvořenou kopii našeho kódu balíčku.

./node_modules/.bin/esbuild ./src/index.js --format=esm --bundle --outfile=./dist/index.js --platform=node --target=node16.3 --minify

Chcete-li zavolat na číslo esbuild , opět, podobně jako jsme spustili Jest, začínáme náš skript s ./node_modules/.bin/esbuild . Zde ./ na začátku je zkrácený způsob, jak říci „spustit skript na této cestě“ a předpokládá, že soubor na této cestě obsahuje skript shellu (všimněte si, že jej importujeme z .bin složku přes node_modules s esbuild jejich automatická instalace jako součást npm install -D esbuild ).

Když voláme tuto funkci, jako první argument předáme cestu k souboru, který chceme vytvořit, v tomto případě:./src/index.js . Dále použijeme některé nepovinné příznaky pro sdělení esbuild jak provést sestavení a kam uložit jeho výstup. Chceme provést následující:

  • Použijte --format=esm příznak, abychom zajistili, že náš kód bude vytvořen pomocí syntaxe ESM.
  • Použijte --bundle příznak sdělit esbuild sbalit do výstupního souboru jakýkoli externí JavaScript (pro nás to není nutné, protože v tomto balíčku nemáme žádné závislosti třetích stran, ale je dobré vědět pro vás).
  • Použijte --outfile=./dist/index.js příznak pro uložení konečného sestavení v dist složku, kterou jsme vytvořili dříve (pomocí stejného názvu souboru jako pro kód našeho balíčku).
  • Nastavte --platform=node příznak na node takže esbuild ví, jak správně zacházet se všemi vestavěnými závislostmi Node.js.
  • Nastavte --target=16.3 označit verzi Node.js, na kterou chceme cílit naše sestavení. Toto je verze Node.js běžící na mém počítači při psaní tohoto tutoriálu, ale můžete ji upravit podle potřeby na základě požadavků vašeho vlastního balíčku.
  • Použijte --minify příznak sdělit esbuild k minimalizaci kódu, který vydává.

Ten poslední --minify zjednoduší náš kód a zkomprimuje jej na nejmenší možnou verzi, aby byl náš balíček co nejlehčí.

To je vše, co musíme udělat. Ověřte, že je váš skript správný, a poté v terminálu (z kořenové složky vašeho balíčku) spusťte:

Terminál

npm run build

Po několika milisekundách (esbuild je neuvěřitelně rychlý), měli byste vidět zprávu, že sestavení je dokončeno, a pokud se podíváte do /dist složku, měli byste vidět nový index.js soubor obsahující zkompilovanou, zmenšenou verzi našeho kódu balíčku (nebude čitelný pro člověka).

Opravdu rychle, než tento krok nazveme „hotový“, musíme aktualizovat naše package.json 's main pole, abyste se ujistili, že NPM nasměruje vývojáře na správnou verzi našeho kódu, když jej importují do svých vlastních projektů:

/package.json

{
  "type": "module",
  "name": "@cheatcodetuts/calculator",
  "version": "0.0.0",
  "description": "",
  "main": "./dist/index.js",
  "scripts": {
    "build": "./node_modules/.bin/esbuild ./src/index.js --format=esm --bundle --outfile=./dist/index.js --platform=node --target=node16.3 --minify",
    "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
    "test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "esbuild": "^0.14.1",
    "jest": "^27.4.3",
    "jest-cli": "^27.4.3",
    "semver": "^7.3.5"
  }
}

Zde část, které chceme věnovat pozornost, je "main": "./dist/index.js" . To zajišťuje, že když je náš balíček nainstalován, spuštěný kód je kód umístěný na zde uvedené cestě. Chceme, aby to bylo naše postaveno zkopírovat (přes esbuild ) a ne náš zdrojový kód, protože, jak jsme naznačili výše, vytvořená kopie je menší a pravděpodobněji bude podporována aplikací vývojáře.

Psaní skriptu vydání

Na závěr si nyní chceme trochu usnadnit naši dlouhodobou práci na našem balíčku. Technicky vzato můžeme náš balíček uvolnit prostřednictvím NPM pomocí npm publish . I když to funguje, vytváří to problém:nemáme způsob, jak náš balíček lokálně otestovat. Ano, můžeme otestovat kód pomocí našich automatických testů v Jestu, ale vždy je dobré ověřit, že náš balíček bude fungovat tak, jak má, když je spotřebován v aplikaci jiného vývojáře (opět:tento proces je o zvýšení jistoty, že náš kód funguje tak, jak má) .

Bohužel samotný NPM nenabízí možnost lokálního testování. I když můžeme nainstalovat balíček lokálně na náš počítač pomocí NPM, proces je trochu chaotický a přidává zmatek, který může vést k chybám.

V další části se seznámíme s nástrojem nazvaným Verdaccio (vur-dah-chee-oh), který nám pomáhá spouštět falešný server NPM na našem počítači, na který můžeme „fiktivně publikovat“ náš balíček (bez uvolnění náš kód pro veřejnost).

V rámci přípravy na to nyní napíšeme skript pro vydání našeho balíčku. Tento skript vydání nám umožní dynamicky...

  1. Verze našeho balíčku a aktualizace našeho package.json 's version pole.
  2. Uvolněte náš balíček podmíněně na náš server Verdaccio nebo na NPM pro veřejné vydání.
  3. Zabraňte tomu, aby číslo verze našeho veřejného balíčku nebylo synchronizováno s číslem naší vývojové verze.

Chcete-li začít, #3 je nápověda. Chceme otevřít naše package.json soubor ještě jednou a přidejte nové pole:developmentVersion , nastavte jej na 0.0.0 .

/package.json

{
  "type": "module",
  "name": "@cheatcodetuts/calculator",
  "version": "0.0.0",
  "developmentVersion": "0.0.0",
  "description": "",
  "main": "./dist/index.js",
  "scripts": {
    "build": "./node_modules/.bin/esbuild ./src/index.js --format=esm --bundle --outfile=./dist/index.js --platform=node --target=node16.3 --minify",
    "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
    "test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "esbuild": "^0.14.1",
    "jest": "^27.4.3",
    "jest-cli": "^27.4.3"
  }
}

V horní části našeho souboru, těsně pod version pole, přidali jsme developmentVersion a nastavte jej na 0.0.0 . Je důležité si uvědomit, že developmentVersion je nestandardní pole v souboru package.json . Toto pole je pouze pro nás a NPM ho neuznává.

Naším cílem v tomto poli – jak uvidíme dále – je mít verzi našeho balíčku, která je nezávislá na produkční verzi. Je to proto, že kdykoli uvolníme náš balíček (lokálně nebo produkčně/veřejně), NPM se pokusí o verzi našeho balíčku. Vzhledem k tomu, že pravděpodobně budeme mít několik vývojových verzí, chceme se vyhnout přeskakování produkčních verzí z něčeho jako 0.1.0 na 0.50.0 kde 49 vydání mezi těmito dvěma pouze testujeme naši vývojovou verzi balíčku (a neodráží skutečné změny v základním balíčku).

Abychom se tomuto scénáři vyhnuli, náš skript vydání vyjedná mezi těmito dvěma verzemi na základě hodnoty process.env.NODE_ENV a udržujte v našich verzích pořádek.

/release.js

import { execSync } from "child_process";
import semver from "semver";
import fs from 'fs';

const getPackageJSON = () => {
  const packageJSON = fs.readFileSync('./package.json', 'utf-8');
  return JSON.parse(packageJSON);
};

const setPackageJSONVersions = (originalVersion, version) => {
  packageJSON.version = originalVersion;
  packageJSON.developmentVersion = version;
  fs.writeFileSync('package.json', JSON.stringify(packageJSON, null, 2));
};

const packageJSON = getPackageJSON();
const originalVersion = `${packageJSON.version}`;
const version = semver.inc(
  process.env.NODE_ENV === 'development' ? packageJSON.developmentVersion : packageJSON.version,
  'minor'
);

const force = process.env.NODE_ENV === "development" ? "--force" : "";

const registry =
  process.env.NODE_ENV === "development"
    ? "--registry http://localhost:4873"
    : "";

try {
  execSync(
    `npm version ${version} --allow-same-version ${registry} && npm publish --access public ${force} ${registry}`
  );
} catch (exception) {
  setPackageJSONVersions(originalVersion, version);
}

if (process.env.NODE_ENV === 'development') {
  setPackageJSONVersions(originalVersion, version);
}

Toto je celý náš skript vydání. Opravdu rychle, nahoře si všimnete další závislosti, kterou musíme přidat semver :

Terminál

npm install -D semver

Zaměříme se na střed kódu našeho skriptu vydání, první věc, kterou musíme udělat, je získat aktuální obsah našeho package.json soubor načtený do paměti. Za tímto účelem jsme do horní části našeho souboru přidali funkci getPackageJSON() který načte obsah našeho souboru do paměti jako řetězec pomocí fs.readFileSync() a poté tento řetězec analyzuje do objektu JSON pomocí JSON.parse() .

Dále s naším package.json soubor načtený v proměnné packageJSON , uložíme nebo "zkopírujeme" originalVersion , ujistěte se, že ukládáte hodnotu do řetězce pomocí zpětného zaškrtnutí (toto přijde do hry, když dynamicky nastavíme verzi zpět v našem package.json soubor dále ve skriptu).

Poté pomocí semver balíček, který jsme právě nainstalovali, chceme zvýšit verzi našeho balíčku. Zde semver je zkratka pro sémantická verze, což je široce přijímaný standard pro psaní verzí softwaru. semver balíček, který zde používáme, nám pomáhá generovat sémantická čísla verzí (jako 0.1.0 nebo 1.3.9 ) a analyzujte je pro vyhodnocení v našem kódu.

Zde semver.inc() je navržen tak, aby zvýšil sémantickou verzi, kterou předáme jako první argument, a zvýší ji na základě „pravidla“, které předáme jako druhý argument. Zde říkáme „if process.env.NODE_ENV je vývoj, chceme zvýšit developmentVersion z našeho package.json a pokud ne, chceme zvýšit normální version pole z našeho package.json ."

Jako druhý argument zde používáme minor pravidlo, které říká semver zvýšit naši verzi na základě středního čísla v našem kódu. Takže to je jasné, sémantická verze má tři čísla:

major.minor.patch

Ve výchozím nastavení nastavíme obě naše developmentVersion a version na 0.0.0 a tak při prvním spuštění vydání očekáváme, že se toto číslo zvýší na 0.1.0 a poté 0.2.0 a tak dále.

S naší novou verzí uloženou v version proměnná, dále musíme učinit další dvě rozhodnutí, obě na základě hodnoty process.env.NODE_ENV . První je rozhodnout se, zda chceme vynutit zveřejnění našeho balíčku (to vynutí zveřejnění verze) a druhý rozhodne, do kterého registru chceme publikovat (na náš server Verdaccio nebo do hlavního registru NPM). Pro registry předpokládáme, že Verdaccio poběží na svém výchozím portu na localhost, takže nastavíme --registry příznak na http://localhost:4873 kde 4873 je výchozí port Verdaccio.

Protože tyto proměnné vložíme force a registry do příkazu níže, pokud nejsou je vyžadováno, vrátíme pouze prázdný řetězec (což je podobné prázdné hodnotě/žádnému nastavení).

/release.js

try {
  execSync(
    `npm version ${version} --allow-same-version ${registry} && npm publish --access public ${force} ${registry}`
  );
} catch (exception) {
  setPackageJSONVersions(originalVersion, version);
}

if (process.env.NODE_ENV === 'development') {
  setPackageJSONVersions(originalVersion, version);
}

Nyní k té zábavnější části. Abychom vytvořili vydání, musíme spustit dva příkazy:npm version a npm publish . Zde npm version je zodpovědný za aktualizaci verze našeho balíčku v package.json a npm publish provede skutečné zveřejnění balíčku.

Pro npm version Všimněte si, že předáváme zvýšený version jsme vygenerovali pomocí semver.inc() výše a také registry proměnnou jsme určili těsně před tímto řádkem. To říká NPM, aby nastavil verzi na verzi předávanou jako version a ujistěte se, že tuto verzi spouštíte proti příslušnému registry .

Dále pro skutečné publikování zavoláme npm publish příkaz předávající --access příznak jako public spolu s naším force a registry vlajky. Zde --access public část zajišťuje, že balíčky používající rozsah name jsou zpřístupněny veřejnosti (ve výchozím nastavení jsou tyto typy balíčků nastaveny jako soukromé).

Balíček s rozsahem je ten, jehož jméno vypadá něco jako @username/package-name kde @username část je "rozsah". Naproti tomu balíček bez rozsahu je pouze package-name .

Chcete-li spustit tento příkaz, všimněte si, že používáme execSync() funkce importovaná z Node.js child_process balíček (toto je vestavěno do Node.js a ne něco, co musíme instalovat samostatně).

I když se to technicky postará o naše vydání, existují další dva řádky, které je třeba zavolat. Nejprve si všimněte, že jsme spustili náš execSync() zavolejte try/catch blok. Je to proto, že musíme předvídat případné chyby při zveřejnění našeho balíčku. Přesněji řečeno, chceme se ujistit, že v našem package.json omylem nezanecháme novou verzi, která ještě nebyla publikována (kvůli selhání skriptu). soubor.

Abychom to pomohli zvládnout, přidali jsme funkci nazvanou setPackageJSONVersions() který přebírá originalVersion a nový version jsme vytvořili dříve ve skriptu. Říkáme tomu v catch bloku našeho kódu zde, abychom zajistili, že verze budou v případě selhání udržovány čisté.

/release.js

const setPackageJSONVersions = (originalVersion, version) => {
  packageJSON.version = originalVersion;
  packageJSON.developmentVersion = version;
  fs.writeFileSync('package.json', JSON.stringify(packageJSON, null, 2));
};

Tato funkce přebírá packageJSON hodnotu, kterou jsme dříve získali a uložili do této proměnné, a upraví její version a developmentVersion pole. Pokud se podíváme zblízka, ujistíme se, že jsme nastavili version pole zpět na originalVersion a developmentVersion na nový version .

Toto je záměrné. Když spustíme npm version v příkazu, který jsme předali execSync() , bez ohledu na to, NPM se pokusí zvýšit version pole v našem package.json soubor. To je problematické, protože to chceme udělat pouze tehdy, když se snažíme provést skutečné produkční vydání. Tento kód tento problém zmírňuje tím, že přepisuje všechny změny, které NPM provede (což bychom považovali za nehodu), a zajišťuje, že naše verze zůstanou synchronizované.

Pokud se podíváme zpět do našeho skriptu vydání, přímo dole, provedeme volání této funkce znovu, pokud process.env.NODE_ENV === 'development' , záměrem je přepsat změněný version pole zpět na původní/aktuální verzi a aktualizujte developmentVersion na novou verzi.

Skoro hotovo! Nyní, když máme připravený skript pro vydání, musíme udělat poslední přírůstek do našeho package.json soubor:

/package.json

{
  "type": "module",
  "name": "@cheatcodetuts/calculator",
  "version": "0.4.0",
  "developmentVersion": "0.7.0",
  "description": "",
  "main": "./dist/index.js",
  "scripts": {
    "build": "./node_modules/.bin/esbuild ./src/index.js --format=esm --bundle --outfile=./dist/index.js --platform=node --target=node16.3 --minify",
    "release:development": "export NODE_ENV=development && npm run build && node ./release.js",
    "release:production": "export NODE_ENV=production && npm run build && node ./release.js",
    "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
    "test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "esbuild": "^0.14.1",
    "jest": "^27.4.3",
    "jest-cli": "^27.4.3",
    "semver": "^7.3.5"
  }
}

Zde chceme přidat dva nové scripts :release:development a release:production . Jména by zde měla být poměrně jasná. Jeden skript je určen k vydání nové verze našeho balíčku ve vývoji (pro Verdaccio), zatímco druhý je určen k publikování do hlavního registru NPM.

Skript má tři části:

  1. Nejprve se ujistí, že je nastavena vhodná hodnota pro process.env.NODE_ENV (buď development nebo production ).
  2. Spustí nové sestavení našeho balíčku prostřednictvím npm run build volání na naše build skript výše.
  3. Spustí náš skript vydání pomocí node ./release.js .

A je to. Nyní, když spustíme buď npm run release:development nebo npm run release:production , nastavíme vhodné prostředí, vytvoříme náš kód a uvolníme náš balíček.

Lokální testování pomocí Verdaccio a Joystick

Nyní, abychom si to vše vyzkoušeli, jsme konečně nechá Verdaccio nastavit lokálně. Dobrá zpráva:stačí nainstalovat jeden balíček a poté spustit server; to je ono.

Terminál

npm install -g verdaccio

Zde používáme npm install ale všimněte si, že používáme -g příznak, což znamená nainstalovat Verdaccio globálně na našem počítači, nejen v rámci našeho projektu (záměrné, protože chceme mít možnost spustit Verdaccio odkudkoli).

Terminál

verdaccio

Po instalaci stačí ke spuštění zadat verdaccio do našeho terminálu a spusťte jej. Po několika sekundách byste měli vidět výstup podobný tomuto:

$ verdaccio
warn --- config file  - /Users/rglover/.config/verdaccio/config.yaml
warn --- Plugin successfully loaded: verdaccio-htpasswd
warn --- Plugin successfully loaded: verdaccio-audit
warn --- http address - http://localhost:4873/ - verdaccio/5.2.0

Díky tomuto běhu nyní můžeme spustit testovací verzi našeho balíčku. Zpět v kořenové složce balíčku zkusme spustit toto:

Terminál

npm run release:development

Pokud vše půjde dobře, měli byste vidět výstup podobný tomuto (číslo vaší verze bude 0.1.0 :

> @cheatcodetuts/[email protected] build
> ./node_modules/.bin/esbuild ./src/index.js --format=esm --bundle --outfile=./dist/index.js --platform=node --target=node16.3 --minify

  dist/index.js  600b

⚡ Done in 19ms
npm WARN using --force Recommended protections disabled.
npm notice
npm notice 📦  @cheatcodetuts/[email protected]
npm notice === Tarball Contents ===
npm notice 50B   README.md
npm notice 600B  dist/index.js
npm notice 873B  package.json
npm notice 1.2kB release.js
npm notice 781B  src/index.js
npm notice 1.6kB src/index.test.js
npm notice === Tarball Details ===
npm notice name:          @cheatcodetuts/calculator
npm notice version:       0.8.0
npm notice filename:      @cheatcodetuts/calculator-0.8.0.tgz
npm notice package size:  1.6 kB
npm notice unpacked size: 5.1 kB
npm notice shasum:        87560b899dc68b70c129f9dfd4904b407cb0a635
npm notice integrity:     sha512-VAlFAxkb53kt2[...]EqCULQ77OOt0w==
npm notice total files:   6
npm notice

Nyní, abychom si ověřili, že náš balíček byl pro Verdaccio uvolněn, můžeme otevřít náš prohlížeč na http://localhost:4873 a podívejte se, zda se objeví náš balíček:

I když je skvělé, že to fungovalo, nyní chceme tento balíček rychle otestovat ve skutečné aplikaci.

Testování balíčku ve vývoji

K otestování našeho balíčku využijeme rámec Joystick CheatCode, který nám pomůže rychle vytvořit aplikaci, kterou můžeme testovat. Chcete-li jej nainstalovat, spusťte ve svém terminálu:

Terminál

npm install -g @joystick.js/cli

A jakmile je nainstalován, spusťte mimo adresář balíčku:

Terminál

joystick create package-test

Po několika sekundách uvidíte zprávu z joysticku, která vám řekne cd do package-test a spusťte joystick start . Než spustíte joystick start umožňuje nainstalovat náš balíček do složky, která byla pro nás vytvořena:

Terminál

cd package-test && npm install @cheatcodetuts/calculator --registry http://localhost:4873

Zde jsme cd do naší složky testovací aplikace a spusťte npm install zadáním názvu našeho balíčku následovaného --registry příznak nastavený na URL pro náš server Verdaccio http://localhost:4873 . To říká NPM, aby hledalo zadaný balíček na této adrese URL . Pokud ponecháme --registry část zde, NPM se pokusí nainstalovat balíček ze svého hlavního registru.

Jakmile je váš balíček nainstalován, pokračujte a spusťte joystick:

Terminál

joystick start

Dále pokračujte a otevřete toto package-test složku v IDE (např. VSCode) a poté přejděte na index.server.js soubor vygenerovaný pro vás v kořenovém adresáři této složky:

/index.server.js

import node from "@joystick.js/node";
import calculator from "@cheatcodetuts/calculator";
import api from "./api";

node.app({
  api,
  routes: {
    "/": (req, res) => {
      res.status(200).send(`${calculator.divide(51, 5)}`);
    },
    "*": (req, res) => {
      res.render("ui/pages/error/index.js", {
        layout: "ui/layouts/app/index.js",
        props: {
          statusCode: 404,
        },
      });
    },
  },
});

V horní části tohoto souboru chceme importovat výchozí export z našeho balíčku (v příkladu calculator objekt, který jsme předali export default v našem kódu balíčku).

Abychom to otestovali, „unesli jsme“ příklad / trasu v naší demo aplikaci. Zde používáme server Express.js vestavěný do joysticku, abychom řekli „vraťte stavový kód 200 a řetězec obsahující výsledky volání calculator.divide(51, 5) ." Za předpokladu, že to funguje, když otevřeme webový prohlížeč, měli bychom vidět číslo 10.2 vytištěné v prohlížeči:

Úžasný! Pokud to vidíme, znamená to, že náš balíček funguje, protože se nám ho podařilo naimportovat do naší aplikace a zavolat jeho funkčnost bez problémů (získání zamýšleného výsledku).

Uvolnění do výroby

Dobře. Čas na velký finiš. Když je vše hotovo, jsme konečně připraveni publikovat náš balíček veřejnosti prostřednictvím NPM. Opravdu rychle, ujistěte se, že jste nastavili účet na NPM a abyste se k tomuto účtu přihlásili v počítači pomocí npm login metoda:

Terminál

npm login

Poté dobrá zpráva:stačí jediný příkaz, abyste to udělali. Z kořenového adresáře naší složky balíčku:

Terminál

npm run release:production

Totožné s tím, co jsme viděli při našem volání na release:development , po několika sekundách bychom měli vidět výstup podobný tomuto:

$ npm run release:production

> @cheatcodetuts/[email protected] release:production
> export NODE_ENV=production && npm run build && node ./release.js


> @cheatcodetuts/[email protected] build
> ./node_modules/.bin/esbuild ./src/index.js --format=esm --bundle --outfile=./dist/index.js --platform=node --target=node16.3 --minify

  dist/index.js  600b

⚡ Done in 1ms
npm notice
npm notice 📦  @cheatcodetuts/[email protected]
npm notice === Tarball Contents ===
npm notice 50B   README.md
npm notice 600B  dist/index.js
npm notice 873B  package.json
npm notice 1.2kB release.js
npm notice 781B  src/index.js
npm notice 1.6kB src/index.test.js
npm notice === Tarball Details ===
npm notice name:          @cheatcodetuts/calculator
npm notice version:       0.5.0
npm notice filename:      @cheatcodetuts/calculator-0.5.0.tgz
npm notice package size:  1.6 kB
npm notice unpacked size: 5.1 kB
npm notice shasum:        581fd5027d117b5e8b2591db68359b08317cd0ab
npm notice integrity:     sha512-erjv0/VftzU0t[...]wJoogfLORyHZA==
npm notice total files:   6
npm notice

A je to! Pokud přejdeme k NPM, měli bychom vidět náš balíček zveřejněný (spravedlivé varování, NPM má agresivní mezipaměť, takže možná budete muset několikrát aktualizovat, než se objeví):

Vše hotovo. Gratulujeme!

Zabalení

In this tutorial, we learned how to write an NPM package using Node.js and JavaScript. We learned how to write our package code, write tests for it using Jest, and how to build it for a production release using esbuild . Finally, we learned how to write a release script that helped us to publish to both a local package repository (using Verdaccio) and to the main NPM repository.