JavaScript >> Javascript 文檔 >  >> Tags >> npm

如何編寫、測試和發布 NPM 包

如何構建自己的包,編寫測試,在本地運行包,然後發佈到 NPM。

開始使用

對於本教程,您需要確保在您的計算機上安裝了 Node.js(建議使用最新的 LTS 版本 — 撰寫本文時為 16.13.1)。如果您之前沒有安裝過 Node.js,請先閱讀本教程。

設置項目

首先,我們將在我們的計算機上為我們的包設置一個新文件夾。

終端

mkdir package-name

接下來,我們要00 進入該文件夾並創建一個 11 文件:

終端

cd package-name && npm init -f

這裡,20 告訴 NPM(節點包管理器,我們將用來發布包的工具)初始化一個新項目,創建一個 36 運行命令的目錄中的文件。 49 代表“force”,告訴 NPM 吐出一個模板 53 文件。如果您排除 67 ,npm會幫你創建70 使用他們的分步嚮導文件。

一旦你有一個 89 文件,接下來,我們要對文件做一點修改。如果你打開它,我們要添加一個特殊字段 97 將對象設置為“模塊”的值作為字符串,如下所示:

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

在 JSON 對象的最頂部,我們添加了 103 .當我們的代碼運行時,這告訴 Node.js 我們希望文件使用 ES 模塊(ECMAScript 模塊或簡稱 ESM)語法,而不是通用 JS 語法。 ESM 使用現代 115125 語法,而 CJS 使用 135 語句和 146 句法。我們更喜歡現代方法,因此通過設置 153 ,我們啟用對使用 162 的支持 和 173 在我們的代碼中。

在這之後,接下來,我們要在裡面創建兩個文件夾 我們的包文件夾:189191 .

  • 201 將包含我們包的“源”文件。
  • 214 將包含我們包的構建(編譯和縮小)文件(這是其他開發人員在安裝我們的包時將在他們的應用中加載的文件)。

225 內部 目錄,我們要創建一個 235 文件。這是我們將為我們的包編寫代碼的地方。稍後,我們將看看我們如何獲取這個文件並構建它,自動將構建的副本輸出到 244 .

/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;
  },
};

對於我們的包,我們將創建一個具有四個函數的簡單計算器:259 , 262 , 270 , 和 287 每個接受兩個數字來執行它們各自的數學函數。

這裡的功能不是很重要(希望它們的功能很清楚)。我們真的 要注意的是294 在頂部和 304 每個函數內部的行。

請注意,我們沒有單獨定義每個函數,而是將它們定義在從 318 導出的單個對像上 文件。這裡的目標是將我們的包導入到這樣的應用程序中:

import calculator from 'package-name';

calculator.add(1, 3);

這裡,被導出的對像是 324 並且每個函數(在 JavaScript 中,定義在對像上的函數稱為“方法”)通過該對象訪問,就像我們在上面看到的那樣。 注意 :這就是我們希望示例包的行為方式,但您的包的行為可能會有所不同——僅此而已。

專注於334 聲明,請注意,這些幾乎都是相同的。這裡的目標是說“如果 347 參數或 359 參數不作為數字(整數或浮點數)傳遞,拋出錯誤。”

我們為什麼這樣做呢?好吧,考慮一下我們正在做什麼:我們正在構建一個包供其他人使用。這與我們在輸入可預測或受控的情況下編寫自己的代碼的方式不同。在開發包時,我們需要注意該包的潛在誤用。我們可以通過兩種方式來解釋這一點:編寫非常好的文檔,以及使我們的代碼具有容錯性和指導性。

在這裡,因為我們的包是一個計算器,所以我們可以通過嚴格要求用戶向我們傳遞數字來執行數學運算,從而幫助用戶正確使用該包。如果他們不這樣做,我們會提示他們出了什麼問題以及如何在代碼級別解決問題 .這對於包採用很重要。您的代碼對開發人員越友好,您的包就越有可能被其他人使用。

進一步推動這一點,接下來,我們將學習如何為我們的包編寫一些測試並學習如何運行它們。

為您的包代碼編寫測試

在將代碼提供給其他開發人員之前,我們希望對我們的代碼有盡可能多的信心。雖然我們可以盲目地相信我們所寫的功能,但這並不明智。相反,在我們發布我們的包之前,我們可以編寫自動化測試來正確(或不正確地)模擬用戶使用我們的包,並確保我們的代碼響應我們的預期。

為了編寫我們的測試,我們將使用 Facebook 的 Jest 庫。 Jest 是一個獨特的工具,它結合了:

  • 用於編寫測試套件和單獨測試的功能。
  • 在測試中執行斷言的功能。
  • 運行測試的功能。
  • 報告測試結果的功能。

傳統上,這些工具是通過多個獨立的軟件包提供給我們的。 Jest 通過將它們組合在一起,可以輕鬆地設置測試環境。要將 Jest 添加到我們自己的包中,我們需要通過 NPM(元!)安裝它的包:

終端

npm install -D jest jest-cli

在這裡,我們說要安裝 363 及其376 包(後者是我們用來運行測試的命令行界面)作為僅開發依賴項(通過傳遞 383 標記為 391 )。這意味著我們只打算在開發中使用 Jest 而 希望它作為一個依賴項添加到我們自己的包中並安裝在我們用戶的代碼中。

/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",
  }
}

現在深入研究細節。在這裡,在我們的 406 文件,我們想在 410 中添加兩行 目的。這些 420 被稱為“NPM 腳本”,顧名思義,它們是我們可以使用 NPM 的 435 運行的可重用命令行腳本 終端中的函數。

在這裡,我們添加 442455 .第一個腳本將用於一次運行我們的測試並生成報告,而 462 每當測試文件(或相關代碼)更改時,將一次又一次地運行我們的測試。前者可用於在部署前快速檢查事物,後者可用於在開發期間運行測試。

近距離觀察 473 腳本 488 我們以一種奇怪的方式運行它。通常,我們可以將腳本編寫為 493 (字面意思是 505 ) 並且它會起作用,但是,因為我們想使用 ES 模塊(而不是 Common JS)來編寫我們的測試,我們需要在 Jest 中啟用它,就像我們在 514 為我們的包代碼。

為此,我們需要直接通過 Node.js 運行 Jest,以便我們可以傳遞 528 標記到 Node.js(Jest 需要,因為他們用來實現 ESM 支持的 API 仍然認為它是一個實驗性功能)。

因為我們使用 Node 來執行 Jest(而不是 537541 直接命令),我們還需要直接指向 Jest 的二進製版本(這在技術上是 556 通過 560 為我們指出 但是因為flag的要求,我們必須直接去)。

579 命令幾乎相同。唯一不同的是,最後我們需要添加588 標誌告訴 Jest 在初始運行後繼續運行並觀察變化。

/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);
  });
});

在編寫我們的測試時,Jest 將自動運行位於 590 中的任何測試 604 所在的文件 可以是我們希望的任何名稱。上面,我們命名我們的測試文件以匹配我們的包代碼所在的文件:617 .這裡的想法是我們希望將我們的測試代碼保持在它旨在測試的真實代碼旁邊。

這可能聽起來令人困惑,但考慮一下我們正在做的事情:我們試圖模擬一個真實世界的用戶從他們的應用程序中調用我們的代碼。這就是編程中的測試。測試本身只是我們用來自動化流程的手段(例如,與我們手動執行和執行的手動步驟的電子表格相反)。

上面,我們的測試文件由兩個主要部分組成: suite 和一個或多個測試 .在測試中,一個“套件”代表一組相關的測試。在這裡,我們定義了一個套件來描述我們的 621 使用 638 的文件 Jest 中的函數。該函數有兩個參數:套件的名稱作為一個字符串(我們只是使用我們正在測試的文件的名稱)和一個要在其中定義我們的測試的函數。

測試遵循類似的設置。它將測試的描述作為第一個參數的字符串,然後是一個被調用來運行測試的函數。

專注於640 我們這裡有一個函數,作為一個例子,我們添加了一個測試來確保我們的 655 方法按預期工作,並將兩個數字相加以產生正確的總和。為了編寫實際的測試(在測試術語中稱為“執行”),我們調用我們的 664 函數傳遞兩個數字並將總和存儲在變量 679 中 .接下來,我們驗證 該函數返回了我們期望的值。

在這裡,我們期望 680 等於 696 如果我們的函數運行正常,這是我們期望得到的總和。在 Jest(和任何測試庫)中,如果我們願意,我們可以將多個斷言添加到測試中。同樣,就像我們包中的實際代碼一樣,它的內容/時間/方式/原因將根據您的代碼意圖而改變。

讓我們添加另一個測試來驗證不好或不滿意 700 的路徑 功能:

/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);
  });
});

這里略有不同。回想一下,在我們的包代碼中,我們添加了一個檢查以確保傳遞給我們每個計算器函數的值是作為參數傳遞的數字(如果不是則拋出錯誤)。在這裡,我們要測試當用戶傳遞不正確的數據時是否真的拋出了錯誤。

這個很重要!同樣,當我們編寫其他人將在他們自己的項目中使用的代碼時,我們希望盡可能地確定我們的代碼將執行我們期望的(以及我們告訴其他開發人員我們期望的)它會執行的操作。

在這裡,因為我們要驗證我們的計算器函數是否拋出錯誤,我們將一個函數傳遞給我們的 711 並從 that 中調用我們的函數 函數,傳遞給它不好的參數。正如測試所說,我們預期 722 如果傳遞給它的參數不是數字,則拋出錯誤。在這裡,因為我們要傳遞兩個字符串,所以我們希望函數 730 函數傳遞給 746 將“捕獲”並使用 758 評估斷言是否為真 斷言方法。

這就是編寫我們的測試的要點。讓我們看一下完整的測試文件(只是對每個單獨的計算器函數重複相同的約定)。

/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);
  });
});

對於每個計算器函數,我們重複了相同的模式:如果傳遞的參數不是數字,則驗證是否引發錯誤,並期望函數根據預期的方法(加、減、乘或除)返回正確的結果.

如果我們在 Jest 中運行它,我們應該會看到我們的測試運行(並通過):

這就是我們的測試和包代碼。現在我們已準備好進入準備發布包的最後階段。

構建我們的代碼

雖然我們現在可以在技術上發布此代碼,但我們要注意兩件事:開發者自己的項目是否支持我們的包代碼,以及代碼的大小。

一般來說,最好為您的代碼使用構建工具來幫助解決這些問題。對於我們的包,我們將使用 768 package:一個簡單且非常快速的構建工具,用於用 Go 編寫的 JavaScript。首先,讓我們將它作為依賴項添加到我們的項目中:

終端

npm install -D esbuild

同樣,就像我們之前在 Jest 中學到的那樣,我們只需要 778 在開發中,所以我們使用 789 在我們的 793 中安裝包的命令 .

/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"
  }
}

類似於我們上面為 Jest 所做的,回到我們的 805 文件我們要添加另一個腳本,這次稱為 816 .該腳本將負責調用 822 生成我們的包代碼的構建副本。

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

調用 834 ,再次,類似於我們運行 Jest 的方式,我們從 844 開始我們的腳本 .這裡,857 開頭是一種簡寫方式,表示“在此路徑上運行腳本”,並假設該路徑上的文件包含一個 shell 腳本(注意我們是從 868 文件夾通過 879 使用 887 腳本將它們作為 892 的一部分自動安裝 )。

當我們調用該函數時,作為第一個參數,我們將路徑傳遞給我們希望它構建的文件,在這種情況下:905 .接下來,我們使用一些可選的標誌來告訴 913 如何執行構建以及在哪裡存儲它的輸出。我們要做到以下幾點:

  • 使用 920 標誌以確保我們的代碼是使用 ESM 語法構建的。
  • 使用 937 告訴 947 的標誌 將任何外部 JavaScript 捆綁到輸出文件中(對我們來說不是必需的,因為我們在這個包中沒有任何第三方依賴項,但很高興知道您自己的)。
  • 使用 951 將最終構建存儲在 967 中的標誌 我們之前創建的文件夾(使用與包代碼相同的文件名)。
  • 設置978 標記為 980 這樣 990 知道如何正確處理任何內置的 Node.js 依賴項。
  • 設置1006 標記到我們想要構建的 Node.js 版本。這是編寫本教程時在我的機器上運行的 Node.js 版本,但您可以根據自己包的要求進行必要的調整。
  • 使用 1019 告訴 1024 的標誌 縮小它輸出的代碼。

最後一個 1034 將簡化我們的代碼並將其壓縮到盡可能小的版本,以確保我們的包盡可能輕量級。

這就是我們需要做的。驗證您的腳本是否正確,然後在您的終端(從包文件夾的根目錄)運行:

終端

npm run build

幾毫秒後(1049 非常快),您應該會看到構建完成的消息,如果您查看 1051 文件夾,您應該會看到一個新的 1063 包含我們的包代碼的已編譯、縮小版本的文件(這將不是人類可讀的)。

在我們將此步驟稱為“完成”之前,我們需要更新我們的 10781082 字段以確保 NPM 在將開發人員導入到他們自己的項目中時將其指向我們代碼的正確版本:

/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"
  }
}

這裡,我們要注意的部分是1098 .這樣可以確保在安裝我們的包時,運行的代碼是位於此處指定路徑的代碼。我們希望這是我們的構建 複製(通過 1103 ) 而不是我們的源代碼,就像我們在上面暗示的那樣,構建的副本更小,更有可能被開發人員的應用程序支持。

編寫發布腳本

現在,在最後階段,我們希望讓我們在包裝上的長期工作更容易一些。從技術上講,我們可以通過 NPM 發布我們的包,只需使用 1116 .雖然這可行,但它產生了一個問題:我們沒有辦法在本地測試我們的包。是的,我們可以通過我們在 Jest 中的自動化測試來測試代碼,但是驗證我們的包在另一個開發人員的應用程序中使用時是否能按預期工作總是很好的(同樣:這個過程是為了提高我們的代碼按預期工作的信心) .

不幸的是,NPM 本身不提供本地測試選項。雖然我們可以通過 NPM 在我們的機器上本地安裝一個包,但這個過程有點混亂,並增加了可能導致錯誤的混亂。

在下一節中,我們將學習一個名為 Verdaccio (vur-dah-chee-oh) 的工具,它可以幫助我們在我們的計算機上運行一個模擬 NPM 服務器,我們可以將我們的包“虛擬發布”到(無需發布我們的代碼向公眾公開)。

為此,現在,我們將為我們的包編寫一個發布腳本。這個發布腳本將允許我們動態地...

  1. 版本我們的包,更新我們的 11211136 字段。
  2. 有條件地將我們的包發佈到我們的 Verdaccio 服務器,或者發佈到 NPM 以進行公開發布。
  3. 避免我們的公共包的版本號與我們的開發版本號不同步。

首先,#3 是一個提示。我們要打開我們的 1149 再次文件並添加一個新字段:1157 ,將其設置為 1161 .

/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"
  }
}

靠近我們文件的頂部,就在 1178 下方 字段,我們添加了 1182 並將其設置為 1199 .請務必注意 developmentVersion 是 package.json 文件中的非標準字段 .該字段僅供我們使用,不被 NPM 識別。

我們在這個領域的目標——正如我們接下來會看到的——是擁有一個獨立於生產版本的包版本。這是因為每當我們發布我們的包(本地或生產/公共)時,NPM 都會嘗試對我們的包進行版本控制。由於我們可能有多個開發版本,我們希望避免從 1201 之類的東西跳轉生產版本 到 1212 兩者之間的 49 個版本只是我們測試包的開發版本(並不反映對核心包的實際更改)。

為了避免這種情況,我們的發布腳本將根據 1221 的值在這兩個版本之間進行協商 並保持我們的版本整潔。

/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);
}

這是我們發布腳本的全部內容。很快,您會注意到我們需要添加一個額外的依賴項 1234

終端

npm install -D semver

重點關注我們的發布腳本代碼中間,首先我們需要獲取我們的1247的當前內容 文件加載到內存中。為此,我們在文件頂部附近添加了一個函數 1250 它使用 1261 將我們文件的內容作為字符串讀入內存 然後使用 1271 將該字符串解析為 JSON 對象 .

接下來,使用我們的 1283 在變量 1298 中加載的文件 ,我們存儲或“複製” 1307 ,確保使用反引號將值存儲在字符串中(當我們在 1312 中動態設置版本時,這將發揮作用 在腳本後面的文件中)。

在此之後,使用 1323 我們剛剛安裝的包,我們想要增加我們包的版本。這裡,1339 是語義版本的縮寫,它是一種被廣泛接受的編寫軟件版本的標準。 1340 我們在這裡使用的包幫助我們生成語義版本號(如 13541366 ) 並在我們的代碼中解析它們以進行評估。

這裡,1374 旨在增加我們作為第一個參數傳遞的語義版本,根據我們作為第二個參數傳遞的“規則”來增加它。在這裡,我們說“如果 1380 是開發,我們要增加 1399 來自我們的 1409 如果沒有,我們要增加正常的 1418 1429 中的字段 。”

對於這裡的第二個參數,我們使用 1437 告訴 1448 的規則 根據代碼中的中間數字增加我們的版本。很明顯,一個語義版本有三個數字:

major.minor.patch

默認情況下,我們同時設置 145914621478 所以我們第一次運行一個版本時,我們希望這個數字增加到 1487 然後是 1491 等等。

我們的新版本存儲在 1509 變量,接下來,我們需要再做兩個決定,都基於 1519 的值 .首先是決定我們是否要強制 我們的包的發布(這將強制發布版本),第二個決定我們想要發佈到哪個註冊表(我們的 Verdaccio 服務器,或者,到主 NPM 註冊表)。對於 1528 變量,我們預計 Verdaccio 將在 localhost 上的默認端口上運行,因此我們設置 1532 標記為 1548 其中 1554 是默認的 Verdaccio 端口。

因為我們將嵌入這些變量 15631570 進入下面的命令,如果他們不是 必需,我們只返回一個空字符串(類似於空值/無設置)。

/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);
}

現在是有趣的部分。為了創建一個版本,我們需要運行兩個命令:15881592 .這裡,1600 負責更新 1618 內我們包的版本 和 1621 執行包的實際發布。

對於 1633 步驟,注意我們傳遞了遞增的 1649 我們使用 1659 生成 以上以及 1667 我們在這一行之前確定的變量。這告訴 NPM 將版本設置為作為 1677 傳遞的版本 並確保針對適當的 1682 運行此版本 .

接下來,對於實際發布,我們調用 1694 傳遞 1706 的命令 標記為 1712 連同我們的 17251731 標誌。在這裡,1746 部分確保使用 scoped 的包 名稱可供公眾訪問(默認情況下,這些類型的包是私有的)。

作用域包是一個名稱類似於 1759 的包 1762 部分是“範圍”。相比之下,無範圍的包只是 1770 .

要運行此命令,請注意我們使用的是 1789 從 Node.js 1798 導入的函數 包(這是 Node.js 內置的,不需要我們單獨安裝)。

雖然這在技術上負責我們的發布,但還有兩行需要說明。首先,請注意我們已經運行了 1801 調用 1811 堵塞。這是因為我們需要在發布我們的包時預測任何潛在的失敗。更具體地說,我們要確保我們不會在 1825 中意外留下尚未發布的新版本(由於腳本失敗) 文件。

為了幫助管理這一點,我們在頂部添加了一個名為 1832 的函數 它採用 1844 和新的 1855 我們之前在腳本中創建。我們在 1864 中稱之為 我們的代碼塊在這裡,以確保版本在發生故障時保持乾淨。

/release.js

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

此函數採用 1877 我們之前檢索並存儲在該變量中的值並修改其 18801890 字段。如果我們仔細觀察,我們會確保設置 1904 字段返回 19151926 到新的 1931 .

這是故意的。當我們運行 1947 在我們傳遞給 1953 的命令中 ,無論如何,NPM 都會嘗試增加 1966 1972 中的字段 文件。這是有問題的,因為我們只想在嘗試執行 actual 時這樣做 生產發布。此代碼通過覆蓋 NPM 所做的任何更改(我們認為是意外)來緩解此問題,確保我們的版本保持同步。

如果我們回顧一下我們的發布腳本,就在底部,如果 1983 我們再次調用這個函數 ,意圖是覆蓋更改的 1997 字段恢復到原始/當前版本並更新 2009 到新版本。

快完成了!現在,我們的發布腳本準備好了,我們需要在我們的 2017 中添加最後一個 文件:

/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"
  }
}

在這裡,我們要添加兩個新的 2023 :20322042 .這裡的名字應該很明顯。一個腳本旨在發布我們正在開發中的包的新版本(給 Verdaccio),而另一個腳本旨在發佈到主 NPM 註冊表。

腳本分為三部分:

  1. 首先,它確保為 2052 設置適當的值 (20632070 )。
  2. 通過 2088 運行我們的包的全新構建 調用我們的 2090 上面的腳本。
  3. 使用 2106 運行我們的發布腳本 .

而已。現在當我們運行 21112128 ,我們將設置適當的環境,構建我們的代碼,並發布我們的包。

使用 Verdaccio 和操縱桿進行本地測試

現在,為了測試所有這些,我們終於 將在本地設置 Verdaccio。好消息:我們只需要安裝一個包然後啟動服務器;就是這樣。

終端

npm install -g verdaccio

在這裡,我們使用 2131 但請注意,我們使用的是 2142 表示在全局安裝 Verdaccio 的標誌 在我們的計算機上,而不僅僅是在我們的項目中(故意的,因為我們希望能夠從任何地方運行 Verdaccio)。

終端

verdaccio

安裝後,要運行它,我們需要做的就是輸入 2152 進入我們的終端並運行它。幾秒鐘後,您應該會看到如下輸出:

$ 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

通過運行,現在我們可以運行我們的包的測試版本。回到包文件夾的根目錄,讓我們嘗試運行這個:

終端

npm run release:development

如果一切順利,您應該會看到一些類似的輸出(您的版本號將是 2161

> @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

現在,為了驗證我們的包已經發佈到 Verdaccio,我們可以打開瀏覽器到 2175 看看我們的包是否出現:

雖然這很好用,但現在,我們想在一個真實的應用程序中快速測試這個包。

測試開發中的包

為了測試我們的包,我們將利用 CheatCode 的操縱桿框架來幫助我們快速啟動一個我們可以測試的應用程序。要安裝它,在你的終端運行:

終端

npm install -g @joystick.js/cli

安裝完成後,從你的包目錄之外運行:

終端

joystick create package-test

幾秒鐘後,您將看到來自操縱桿的消息,告訴您 2181 進入 2197 並運行 2204 .在運行 2213 之前 讓我們在為我們創建的文件夾中安裝我們的包:

終端

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

在這裡,我們 2221 進入我們的測試應用文件夾並運行 2235 指定我們包的名稱,後跟 2243 標誌設置為我們的 Verdaccio 服務器 2251 的 URL .這告訴 NPM 在該 URL 上查找指定的包 .如果我們離開 2264 離開這裡,NPM 將嘗試從其主註冊表安裝包。

安裝包後,繼續啟動操縱桿:

終端

joystick start

接下來,繼續打開 2276 IDE(例如 VSCode)中的文件夾,然後導航到 2283 在該文件夾的根目錄為您生成的文件:

/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,
        },
      });
    },
  },
});

在該文件的頂部,我們要從我們的包中導入默認導出(在示例中,2292 我們傳遞給 2300 的對象 在我們的包代碼中)。

為了測試它,我們“劫持”了示例 2317 在我們的演示應用程序中路由。在那裡,我們使用 Joystick 內置的 Express.js 服務器來表示“返回狀態碼 200 和包含調用 2323 的結果的字符串 ." 假設這可行,如果我們打開網絡瀏覽器,我們應該會看到數字 2337 在瀏覽器中打印:

驚人的!如果我們能看到這一點,這意味著我們的包正在工作,因為我們能夠將它導入我們的應用程序並調用它的功能而沒有任何問題(獲得預期的結果)。

發佈到生產

好的。大結局的時候了。完成所有這些後,我們終於準備好通過 NPM 向公眾發布我們的包了。非常快,請確保您已在 NPM 上設置了一個帳戶,並已在您的計算機上使用 2349 登錄到該帳戶 方法:

終端

npm login

在那之後,好消息是:只需一個命令即可完成。從我們的包文件夾的根目錄:

終端

npm run release:production

與我們調用 2352 時看到的相同 , 幾秒鐘後我們應該會看到這樣的輸出:

$ 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

而已!如果我們轉到 NPM,我們應該會看到我們的包已發布(公平警告,NPM 具有激進的緩存,因此您可能需要刷新幾次才能顯示):

全部完成。恭喜!

總結

在本教程中,我們學習瞭如何使用 Node.js 和 JavaScript 編寫 NPM 包。我們學習瞭如何編寫我們的包代碼,使用 Jest 為其編寫測試,以及如何使用 2369 為生產版本構建它 .最後,我們學習瞭如何編寫發布腳本,幫助我們發佈到本地包存儲庫(使用 Verdaccio)和主 NPM 存儲庫。


Tutorial JavaScript 教程
  1. 使用延遲加載 XT 重新定義延遲加載

  2. 根據兩個下拉值過濾內容

  3. 理解 JS 框架基準

  4. 如何將 WebGLRender 背景設置為透明

  5. 介紹通量 V1

  6. NestJS 控制器深入了解

  7. 5 分鐘內的異步 JavaScript

  1. 形式和功能

  2. JavaScript class 和 Object 的實際例子通過製作一個待辦事項列表應用程序

  3. 使用來自 JavaScript 的動態內容填充 IFRAME

  4. 如何使用 jQuery 禁用文本選擇?

  5. 當瀏覽器失去焦點時,Chrome(也許是 Safari?)在輸入字段上觸發兩次模糊

  6. 未知指令“@isAuthenticated”。使用 Neo4j 和 Graphql

  7. 反應生命週期方法

  1. 差異化服務所需的代碼可減少 20% 以上!

  2. 電子自動更新介紹

  3. 深入了解 React useEffect

  4. nest.js + TypeORM + PostgreSQL