創建現代npm包的最佳實踐

語言: CN / TW / HK

本文首發於微信公眾號:大遷世界, 我的微信: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賬户。

  • 通過 https://www.npmjs.com/signup 註冊
  • 為了提高安全性,請在您的npm賬户上啟用2FA:https://docs.npmjs.com/configuring-two-factor-authentication
  • 使用 npm login 命令在終端中用你的 npm賬户登錄,並按照屏幕上的指示操作。

```

npm login npm notice Log in on https://registry.npmjs.org/ Username: clarkio Password: Email: (this IS public) npm notice Please use the one-time password (OTP) from your authenticator application Enter one-time password from our authenticator app: 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/[email protected] 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/[email protected] ```

  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/[email protected] 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: + @clarkio/[email protected]

```

現在,我們已經完成了構建和部署自己的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/[email protected] 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. 按照終端的提示,提供所需的令牌

  4. 需要一個來自 GitHub 的個人訪問令牌。要創建一個,請到 https://github.com///settings/secrets/actions/new

  5. 在創建此令牌時,請使用以下作用域

image.png

  1. 還需要一個來自npm的自動化類型的訪問令牌,只在CI環境中使用,這樣它就能繞過你的賬户的2FA。要創建一個,請到https://www.npmjs.com/settings//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中通過其版本控制功能做到這一點。

  • 在所有這些設置完成後,現在可以使用傳統的提交方式將修改推送到你的主分支(或通過合併拉動請求),然後發佈工作流就會運行(當然是在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 已收錄,有一線大廠面試完整考點、資料以及我的系列文章。