建立現代npm包的最佳實踐

前端小智發表於2023-03-30
本文首發於微信公眾號:大遷世界, 我的微信:qq449245884,我會第一時間和你分享前端行業趨勢,學習途徑等等。
更多開源作品請看 GitHub https://github.com/qq449245884/xiaozhi ,包含一線大廠面試完整考點、資料以及我的系列文章。

技術一直在變化,我們的流程和做法也需要跟上這些變化。因此,雖然npm已經有12年的歷史了,但圍繞 npm 包建立的做法應該更現代。

在這節課中,我們使用現代最佳實踐(截至2022年)一步一步地建立一個npm包。首先學習如何建立一個npm包,這樣你就可以熟悉構建和釋出一個包到 npm 登錄檔。

然後,再學習如何透過建立測試框架、持續整合和部署管道、安全檢查以及釋出的自動語義版本管理,來製作一個更健壯、可用於生產的npm包。

簡單的npm包示例

我們先透過一個簡單的例子來熟悉建立和釋出npm包的過程。

建立專案

  1. 建立一個 GitHub 倉庫: https://github.com/new
  2. 克隆本地的 repo。
    例如:git clone https://github.com/snyk-labs/simple-npm-package.git
  3. 開啟你的終端,進入到克隆的專案資料夾。
    例如:cd simple-npm-package
  4. 執行 npm init -y 來建立 package.json 檔案。注意:如果克隆了示例倉庫,就不需要做這一步。
  5. 在package.json 取一個名稱,對應 name 欄位
  6. 為該包編寫你的程式碼

建立 npm 賬戶

為了能夠讓我們的 npm 包供他人使用,需要一個npm賬戶。

> npm login
npm notice Log in on https://registry.npmjs.org/
Username: clarkio
Password:
Email: (this IS public) <email address>
npm notice Please use the one-time password (OTP) from your authenticator application
Enter one-time password from our authenticator app: <OTP>
Logged in as clarkio on https://registry.npmjs.org/.

如何釋出 npm 包

一旦你有了一個npm專案和一個npm賬戶,你就可以把你的npm包釋出到公開的官方npmjs登錄檔上,讓其他人可以使用。以下是你要遵循的步驟,在執行之前檢查將釋出的內容,然後執行實際的釋出過程。

  1. 在終端,執行 npx npm-packlist 來檢視將被包含在釋出版本的軟體包中的內容。

這可以確保我們沒有遺漏任何原始碼檔案,這些檔案是軟體包正常執行所需要的。這也是一個好的做法,以確保我們不會意外地將敏感資訊洩露給公眾,如帶有資料庫憑證或API金鑰的本地配置檔案。

> npx npm-packlist
LICENSE
index.js
package.json
README.md

在終端,執行npm publish --dry-run,看看實際執行命令時將會做什麼。

> npm publish --dry-run
npm notice
npm notice ?@clarkio/simple-npm-package@0.0.1
npm notice === Tarball Contents ===
npm notice 1.1kB LICENSE
npm notice 1.2kB README.md
npm notice 95B index.js
npm notice 690B package.json
npm notice === Tarball Details===
npm notice name: @clarkio/simple-npm-package
npm notice version: 0.0.1
npm notice filename:@clarkio/simple-npm-package-0.0.1.tgz
npm notice package size:1.7 kB
npm notice unpacked size: 3.1 kB
npm notice shasum:40ede3ed630fa8857c0c9b8d4c81664374aa811c
npm notice integrity:sha512-QZCyWZTspkcUXL... ]L60ZKBOOBRLTg==
npm notice total files:4
npm notice
+ @clarkio/simple-npm-package@0.0.1
  1. 在終端,執行 npm publish --access=public 來發布軟體包到npm。

注意:--access=public對於作用哉內的包(@clarkio/modern-npm-package)是需要的,因為它們預設是私有的。如果它不是作用哉內的,並且在你的 package.json 中沒有將private 欄位設定為 true,它也將是公開的。

> npm publish --access=public
npm notice
npm notice ?@clarkio/simple-npm-package@0.0.1
npm notice === Tarball Contents ===
npm notice 1.1kB LICENSE
npm notice 1.2kB README.md
npm notice 95B index.js
npm notice 690B package.json
npm notice === Tarball Details===
npm notice name: @clarkio/simple-npm-package
npm notice version: 0.0.1
npm notice filename:@clarkio/simple-npm-package-0.0.1.tgz
npm notice package size:2.1 kB
npm notice unpacked size: 4.1 kB
npm notice shasum:6f335d6254ebb77a5a24ee729650052a69994594
npm notice integrity:sha512-VZ1K1eMFOKeJW[...]7ZjKFVAxLcpdQ==
npm notice total files:4
npm notice
This operation requires a one-time password.
Enter OTP: <OTP>
+ @clarkio/simple-npm-package@0.0.1

現在,我們已經完成了構建和部署自己的npm包。接下來,我們來看一下如何製作一個更強大的包,為生產環境做好準備,並得到更廣泛的使用。

生產就緒的npm包

雖然前面的例子的包可以在生產中使用,但它涉及到人工成本來保持其長期的維護。使用工具和自動化以及適當的測試和安全檢查將有助於最大限度地減少保持軟體包順利執行的總工作量。讓我們深入瞭解一下這其中的內容。

  1. 構建CommonJS(CJS)和ECMAScript(ESM)模組
  2. 設定和編寫單元測試
  3. 實施安全檢查
  4. 實現版本管理和釋出的自動化

構建 CommonJS(CJS)和ECMAScript(ESM)模組

雖然ECMAScript模組格式現在在Node.js的12+版本中被原生支援,但它還沒有被社群廣泛採用。為了面向未來並支援這兩種格式,我們來看下使用 TypeScript怎麼來配置。

首先,建立一個基本的 TypeScript 配置檔案 tsconfig.base.json。這是通用的編譯設定,無論你的目標是哪種模組格式,都可以使用。

{
  "compilerOptions": {
    "strict": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true,
    "checkJs": true,
    "allowJs": true,
    "declaration": true,
    "declarationMap": true,
    "allowSyntheticDefaultImports": true
  },
  "files": ["../src/index.ts"]
}

然後為 CommonJS 格式建立一個TypeScript配置檔案,命名為tsconfig.cjs.json

  • lib 屬性向TypeScript指出它應該參考哪些型別。
  • target 屬性向TypeScript指出要編譯的專案程式碼的JavaScript版本。
  • module 屬性向 TypeScript 指出在編譯的專案程式碼時應該使用哪種JavaScript模組格式。
  • moduleResolution 屬性幫助 TypeScript 弄清 "import"語句應該如何被提及。
  • outDirdeclarationDir 屬性向TypeScript指出了將編譯的程式碼和定義其中使用的型別的結果放在哪裡。
{
  "extends": "./tsconfig.base.json",
  "compilerOptions": {
    "lib": ["ES6", "DOM"],
    "target": "ES6",
    "module": "CommonJS",
    "moduleResolution": "Node",
    "outDir": "../lib/cjs",
    "declarationDir": "../lib/cjs/types"
  }
}

之後,為 ECMAScript 格式建立一個TypeScript配置檔案,命名為tsconfig.esm.json。這裡的屬性與你在 CommonJS 配置中看到的相同,但現在針對現代ECMAScript模組格式作為其輸出。

{
  "extends": "./tsconfig.base.json",
  "compilerOptions": {
    "lib": ["ES2022", "DOM"],
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "NodeNext",
    "outDir": "../lib/esm",
    "declarationDir": "../lib/esm/types"
  }
}

更新 package.json 檔案,增加一個 files 欄位,指向lib資料夾,裡面有 TypeScript為你構建軟體包的結果。

更新 package.json 檔案中的 exports 欄位,以定義如何根據使用的模組載入器(CJS vs. ESM)查詢原始檔。

"exports": {
    ".": {
      "import": {
        "types": "./lib/esm/types/index.d.ts",
        "default": "./lib/esm/index.mjs"
      },
      "require": {
        "types": "./lib/cjs/types/index.d.ts",
        "default": "./lib/cjs/index.js"
      }
    }
  },

更新 package.json 檔案的 maintypes 欄位,以指向軟體包的CJS版本。這將作為一個預設的、後備的選項。

“types": "./lib/cjs/types/index.d.ts",
"main": "./lib/cjs/index.js",

package.json 檔案中新增一個 files 欄位,以表明當 npm 打包你的程式碼進行釋出時,應該包括哪些檔案。

"files": [
   "lib/**/*"
],

透過 package.json 中的 scripts 欄位建立命令,使用 tsc 並編譯包的 CJS 和 ESM 格式,並生成 lib 檔案。

clean 命令是用來刪除過去構建的輸出,並從一個乾淨的地方開始。

build:esm命令末尾的 mv lib/esm/index.js lib/esm/index.mjs 重新命名了副檔名,這樣Node.js模組載入器就知道它是一個ESM模組。

prepack命令是npm在打包npm包準備釋出到登錄檔之前使用的。

    "clean": "rm -rf ./lib",
    "build": "npm run clean && npm run build:esm && npm run build:cjs",
    "build:esm": "tsc -p ./configs/tsconfig.esm.json && mv lib/esm/index.js lib/esm/index.mjs",
    "build:cjs": "tsc -p ./configs/tsconfig.cjs.json",
    "prepack": "npm run build"

現在可以在終端執行 npm run build,讓TypeScript構建你的專案,為使用和釋出做準備

這就是使用 TypeScript 構建 npm 包所需要做的所有設定,它同時支援 CommonJS 和ECMAScript模組格式。

設定和新增測試

為了對程式碼的行為和結果有信心,我們需要有一個測試過程。測試迫使在第一次建立程式碼時,在happy-path 之外,以不同的方式思考程式碼的功能。舉個例子,可以想辦法打破一個函式,使它丟擲一個錯誤或產生一個非預期的結果。這樣做將使你的應用程式更有彈性和可持續性,並確保在新增更多內容時不會出現問題。

單元測試

要確保庫以我們想要的方式執行,需要針對程式碼編寫測試。我們需要一些工具來幫助設定我們專案來執行單元測試並顯示結果。

這些工具有 Mocha.jsChai.jsts-node。Mocha.js 是一個測試執行器,Chai.js是一個斷言庫,幫助確定你是否從你的程式碼中得到你所期望的結果,而 ts-node 幫助我們在TypeScript專案中使用這些工具。按照下面的步驟,為 npm包設定和執行測試。

  • 在終端中使用以下命令安裝開發者的依賴:
    npm i -D mocha @type/mocha chai @types/chai ts-node
  • 在專案的根目錄下建立一個新檔案 .mocharc.json,內容如下:

    {
      "extension": ["ts"],
      "spec": "./**/*.spec.ts",
      "require": "ts-node/register"
    }
  • 在專案的根目錄下建立一個 tests 資料夾。
  • index.spec.ts 檔案中寫單元測試來測試 index.ts 中的程式碼。
  • package.json 檔案的 scripts 部分新增一個 test 屬性,給它一個 mocha 的值。
  "scripts": {
    "clean": "rm -rf ./lib",
    "build": "npm run clean && npm run build:esm && npm run build:cjs",
    "build:esm": "tsc -p ./configs/tsconfig.esm.json && mv lib/esm/index.js lib/esm/index.mjs",
    "build:cjs": "tsc -p ./configs/tsconfig.cjs.json",
    "prepack": "npm run build",
    "test": "mocha"
  },
  • 最後,在終端執行 npm test
bc@mbp-snyk modern-npm-package % npm test

> @clarkio/modern-npm-package@0.0.0-development test
> mocha

  NPM Package
    ✔️ should be an object
    ✔️ should have a helloworld property

  Hello World Function
    ✔️  should be a function
    ✔️ should return the hello world message

4 passing (22ms)

管道中的測試

按照下面的步驟,建立一個測試工作流,作為專案管道的一部分。

  1. 為倉庫建立一個新的GitHub Action :https://github.com/<your-account-or-organization>/<your-repo-name>/actions/new
  2. 將工作流程重新命名為 test.yml
  3. 在工作流程檔案中插入以下Snyk動作指令碼:
name: Tests

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [12.x, 14.x, 16.x, 18.x]

    steps:
      - uses: actions/checkout@v3
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci
      - run: npm test

這個YAML指令碼檢查出你的最新程式碼,安裝其依賴性,並執行 npm test命令來執行測試。它對node-version欄位中列出的每一個Node.js版本都會這樣做,所以可以確保程式碼在每次執行時都能按預期工作。

現在已經完成了對專案的設定,以便對npm包的程式碼進行執行和評估測試。然而,你可能在想 "我如何在另一個專案中使用我的npm包進行測試?" 讓我們來看看。

包測試

包上傳完成後,除了單元測試外,我們還要測試在另一個專案引入我們包使用的情況,看看是否像我們所期望那樣。這裡有五種可以測試的方法:

  1. 透過 npm pack 輸出安裝
  2. 透過相對路徑安裝
  3. 透過npm連結安裝
  4. 透過登錄檔安裝(如npmjs.com的npm公共登錄檔)。
  5. 使用Verdaccio(一個開源的npm私有npm註冊專案)來執行端到端的軟體包釋出和安裝步驟,作為你CI的一部分。

npm pack

這種方法將利用npm pack命令將 npm 包打包並壓縮成一個檔案(<package-name>.tgz)。然後你可以到你想使用該包的專案中,透過這個檔案安裝它。這樣做的步驟如下。

  • 終端執行 npm pack。注意它產生的.tgz檔案和它的位置。
  • 改變目錄到你想使用 npm 包的專案目錄。例如:cd /path/to/project
  • 執行npm install /path/to/package.tgz
  • 然後就可以在專案中使用該包來測試東西了

npm link

利用 npm link 命令來安裝本地包:

  1. 在當前包目錄中,在終端執行 npm link
  2. 改變目錄到你想使用npm包的專案目錄。例如:cd /path/to/project
  3. 在專案中執行 npm link <name-of-your-package>

這樣在專案中就可以使用我們的包。

相對路徑

這種類似於npm link。

  • 在終端執行 npm install /path/to/your/package

npm link 的方法類似,這允許我們在專案中快速測試包的功能,但不會給你完整的類似生產的體驗。這是因為它指向完整的軟體包原始碼目錄,而不是你在npm登錄檔中找到的軟體包的構建版本。

npm registry

這種方法利用了npm包的公共(或你自己)登錄檔。它涉及到釋出的包,並像你通常對任何其他npm包那樣進行安裝。

  • 使用本文前面概述的步驟,透過 npm publish 命令釋出npm包
  • 改變目錄到想使用npm包的專案目錄。例如:cd /path/to/project
  • 在專案目錄中執行 npm install <name-of-your-package>

實施安全檢查

就像你不希望在自己的專案中出現安全漏洞一樣,你也不希望在其他人的專案中引入漏洞。構建一個預計會在許多其他專案中使用的npm包,這就增加了確保事情安全的責任。你需要有安全檢查,以幫助監測、提醒和提供幫助來減少漏洞。這就是像Snyk這樣的工具可以簡化完成這些需求所需的工作的地方。

對於這個例子中的npm包,你使用GitHub作為你的原始碼控制管理工具,所以利用它的GitHub Actions功能將Snyk整合到工作流程中。Snyk 有一個GitHub Actions參考專案,可以幫助啟動這方面的工作,併為你的專案可能使用的其他程式語言和工具提供例子。

  1. Snyk是免費的,這裡可以進行註冊
  2. 在GitHub上將你的Snyk API令牌新增為倉庫秘密: https://github.com/<your-account-or-organization>/<your-repo-name>/settings/secrets/actions/new
  3. 倉庫建立一個新的GitHub Action: https://github.com/<your-account-or-organization>/<your-repo-name>/actions/new
  4. 將workflow 重新命名為 snyk.yml
  5. 在 workflow 檔案中插入以下Snyk Action 指令碼:
name: Snyk Security Check
on: [push,pull_request]
jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@main
      - name: Run Snyk to check for vulnerabilities
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
  1. 提交你的修改。
  2. 驗證Action 成功執行: https://github.com/<your-account-or-organization>/<your-repo-name>/actions

有了這個設定,任何時候任何人推送到你的版本庫或針對它開啟一個拉動請求,都會進行安全檢查,以確保它不會在軟體包中引入任何漏洞。如果發現了問題,行動將失敗,並提醒你發現的安全問題的細節。接下來,你將圍繞版本管理和釋出你的npm包進行自動化處理。

關於目前的設定,需要注意的一點是,它只利用了Snyk開源(SCA)產品,而不是Snyk程式碼(SAST)。Snyk Code是我們的程式碼安全產品,你需要首先透過你的Snyk賬戶啟用它(免費),然後在這裡新增到你的工作流程指令碼中,以充分利用它。

實現版本管理和釋出的自動化

每當在主分支中合併變化時,我們不想每次都手動更新npm包的版本併發布它。相反,會想讓這個過程自動發生。如果你還記得本篇文章前面那個簡單的npm包的例子,用以下命令來更新npm包的版本,然後釋出它。

npm version <major|minor|patch>
npm publish

什麼是語義版本管理?

語義版本管理規定,版本要用三個佔位符進行編號。第一個是主要版本,第二個是次要版本,而最後一個是補丁版本。

Semantic Release的工具可以與 GitHub Actions 整合來幫助我們自動修改版本併發布。實現這一過程自動化的關鍵是,你在向專案提交變更時使用所謂的常規提交。這使得自動化能夠相應地更新一切,並知道如何為你準備專案的下一個版本。

  1. 執行:npm i -D semantic-release
  2. npx semantic-release-cli setup
  3. 按照終端的提示,提供所需的令牌

    • 需要一個來自 GitHub 的個人訪問令牌。要建立一個,請到 https://github.com/<your-name-or-github-organization>/<your-repo-name>/settings/secrets/actions/new
    • 在建立此令牌時,請使用以下作用域

image.png

  1. 還需要一個來自npm的自動化型別的訪問令牌,只在CI環境中使用,這樣它就能繞過你的賬戶的2FA。要建立一個,請到https://www.npmjs.com/settings/<your-npm-account>/tokens。請確保選擇 "Automation"型別,因為這將用於CI/CD工作流程中。

image.png

bc@mbp-snyk modern-npm-package % npx semantic-release-cli setup
? What is your npm registry? https://registry.npmjs.org/
? What is vour nom username? clarkio
? What is your pm password? [hidden]
? What is your NPM two-factor authentication code? <2FA code>
Provide a GitHub Personal Access Token (create a token at https://github.com/settings/tokens/new?scopes=repo
<token>
? What CI are you using? Github Actions
bc@mbp-snyk modern-npm-package %
  1. 將npm令牌作為倉庫秘密新增到GitHub倉庫中:https://github.com/<your-name-or-organization/<your-repository>/settings/secrets/actions/new。將秘密的名稱設定為NPM_TOKEN,其值是你在前面步驟中檢索到的

image.png

  1. 回到專案中,進入package.json檔案,像下面這樣新增一個release鍵。如果你的版本庫的主分支仍然叫master而不是main,那麼就相應地更新上述分支的值。
"release": {
    "branches": ["main"]
  }
  1. package.json 檔案中也新增一個publishConfig鍵。
"publishConfig": {
    "access": "public"
 }
  1. 透過使用semantic-release npm指令碼進行模擬執行來測試一切。採用以下命令,並將NPM_TOKEN=GH_TOKEN=值設定為使用您各自的令牌值。然後在你的終端中複製並執行完整的命令,看看一切是否執行正常。你會看到程式被記錄在終端的輸出中。如果出現任何問題,它們會在這裡顯示出來,並提供解決這些問題的細節。
  2. 在確認試執行成功後,可以為GitHub倉庫設定一個新的GitHub動作來為你處理釋出過程。轉到你在GitHub上的倉庫,點選 "Actions"。
  3. 點選新建工作流程選項。
  4. 將工作流程重新命名為release.yml。
  5. 在新的工作流程檔案中加入以下YAML指令碼。這個指令碼主要是說,一旦Snyk安全檢查工作成功完成,就執行釋出工作。釋出作業會檢查程式碼,設定Node.js環境,安裝你的依賴項,然後使用你的GitHub和npm令牌執行語義釋出。
name: Release
on:
  workflow_run:
    workflows: ['Snyk Security Check', 'Tests']
    branches: [main]
    types:
      - completed

jobs:
  release:
    name: Release
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
        with:
          fetch-depth: 0
      - name: Setup Node.js
        uses: actions/setup-node@v2
        with:
          node-version: 'lts/*'
      - name: Install dependencies
        run: npm ci
      - name: Release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
        run: npx semantic-release

13.提交你的本地修改並推送到你的GitHub倉庫

  • 可以透過在終端執行命令 git commit -am '<your commit message>',然後git push來實現。
  • 也可以在VS Code中透過其版本控制功能做到這一點。
  1. 在所有這些設定完成後,現在可以使用傳統的提交方式將修改推送到你的主分支(或透過合併拉動請求),然後釋出工作流就會執行(當然是在Snyk安全檢查之後)。你可以在modern-npm-package版本庫工作流程的例子中看到這種情況。

總結

我們總結一下在本文中學到的一切。首先,熟悉了設定、建立和部署一個簡單的npm包。這對於熟悉首次釋出自己的npm包來說是很好的。然而,如果想製作一個供生產使用的npm包,這樣做是相當費力的,也是不可持續的。

為了完成製作一個可用於生產的包,隨後學會了如何為CommonJS(CJS)和ECMAScript(ESM)模組格式進行構建,設定和編寫單元測試,實現安全檢查,並自動進行版本管理和釋出。有了這些知識,現在已經準備好製作更多屬於你自己的npm包了,這些包很容易被社群或你的公司所使用。

來源:https://snyk.io/blog/best-practices-create-modern-npm-package/

程式碼部署後可能存在的BUG沒法實時知道,事後為了解決這些BUG,花了大量的時間進行log 除錯,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug

交流

有夢想,有乾貨,微信搜尋 【大遷世界】 關注這個在凌晨還在刷碗的刷碗智。

本文 GitHub https://github.com/qq449245884/xiaozhi 已收錄,有一線大廠面試完整考點、資料以及我的系列文章。

相關文章