創建現代 npm 包的最佳實踐
技術一直在變化,我們的流程和做法也需要跟上這些變化。因此,雖然 npm 已經有 12 年的歷史了,但圍繞 npm 包創建的做法應該更現代。
在這節課中,我們使用現代最佳實踐(截至 2022 年)一步一步地創建一個 npm 包。首先學習如何創建一個 npm 包,這樣你就可以熟悉構建和發佈一個包到 npm 註冊表。
然後,再學習如何通過建立測試框架、持續集成和部署管道、安全檢查以及發佈的自動語義版本管理,來製作一個更健壯、可用於生產的 npm 包。
簡單的 npm 包示例
我們先通過一個簡單的例子來熟悉創建和發佈 npm 包的過程。
創建項目
-
創建一個 GitHub 倉庫: https://github.com/new
-
克隆本地的 repo。例如:git clone https://github.com/snyk-labs/simple-npm-package.git
-
打開你的終端, 進入到克隆的項目文件夾。例如:cd simple-npm-package
-
運行
npm init -y
來創建package.json
文件。注意:如果克隆了示例倉庫,就不需要做這一步。 -
在 package.json 取一個名稱,對應 name 字段
-
爲該包編寫你的代碼
創建 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) <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
註冊表上,讓其他人可以使用。以下是你要遵循的步驟,在執行之前檢查將發佈的內容,然後運行實際的發佈過程。
- 在終端,運行
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
- 在終端,運行
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 包
雖然前面的例子的包可以在生產中使用,但它涉及到人工成本來保持其長期的維護。使用工具和自動化以及適當的測試和安全檢查將有助於最大限度地減少保持軟件包順利運行的總工作量。讓我們深入瞭解一下這其中的內容。
-
構建 CommonJS(CJS)和 ECMAScript(ESM)模塊
-
設置和編寫單元測試
-
實施安全檢查
-
實現版本管理和發佈的自動化
構建 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" 語句應該如何被提及。 -
outDir
和declarationDir
屬性向 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
文件的 main
和 types
字段,以指向軟件包的 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.js
、Chai.js
和 ts-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)
管道中的測試
按照下面的步驟,創建一個測試工作流,作爲項目管道的一部分。
-
爲倉庫創建一個新的 GitHub Action :
https://github.com/<your-account-or-organization>/<your-repo-name>/actions/new
-
將工作流程重命名爲
test.yml
-
在工作流程文件中插入以下 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 包進行測試?" 讓我們來看看。
包測試
包上傳完成後,除了單元測試外,我們還要測試在另一個項目引入我們包使用的情況,看看是否像我們所期望那樣。這裏有五種可以測試的方法:
-
通過 npm pack 輸出安裝
-
通過相對路徑安裝
-
通過 npm 鏈接安裝
-
通過註冊表安裝(如 npmjs.com 的 npm 公共註冊表)。
-
使用 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
命令來安裝本地包:
-
在當前包目錄中,在終端運行
npm link
-
改變目錄到你想使用 npm 包的項目目錄。例如:
cd /path/to/project
-
在項目中運行
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 參考項目,可以幫助啓動這方面的工作,併爲你的項目可能使用的其他編程語言和工具提供例子。
-
Snyk 是免費的,這裏可以進行註冊。
-
在 GitHub 上將你的 Snyk API 令牌添加爲倉庫祕密:
https://github.com/<your-account-or-organization>/<your-repo-name>/settings/secrets/actions/new
-
倉庫創建一個新的 GitHub Action:
https://github.com/<your-account-or-organization>/<your-repo-name>/actions/new
-
將 workflow 重命名爲
snyk.yml
-
在 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 }}
-
提交你的修改。
-
驗證 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 整合來幫助我們自動修改版本併發布。實現這一過程自動化的關鍵是,你在向項目提交變更時使用所謂的常規提交。這使得自動化能夠相應地更新一切,並知道如何爲你準備項目的下一個版本。
-
運行:
npm i -D semantic-release
-
npx semantic-release-cli setup
-
按照終端的提示,提供所需的令牌
-
需要一個來自 GitHub 的個人訪問令牌。要創建一個,請到 https://github.com///settings/secrets/actions/new
-
在創建此令牌時,請使用以下作用域
- 還需要一個來自 npm 的自動化類型的訪問令牌,只在 CI 環境中使用,這樣它就能繞過你的賬戶的 2FA。要創建一個,請到 https://www.npmjs.com/settings//tokens。請確保選擇 "Automation" 類型,因爲這將用於 CI/CD 工作流程中。
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 %
- 將 npm 令牌作爲倉庫祕密添加到 GitHub 倉庫中:
https://github.com/<your-name-or-organization/<your-repository>/settings/secrets/actions/new
。將祕密的名稱設置爲NPM_TOKEN
,其值是你在前面步驟中檢索到的
- 回到項目中,進入
package.json
文件,像下面這樣添加一個release
鍵。如果你的版本庫的主分支仍然叫master
而不是main
,那麼就相應地更新上述分支的值。
"release": {
"branches": ["main"]
}
- 在
package.json
文件中也添加一個publishConfig
鍵。
"publishConfig": {
"access": "public"
}
-
通過使用
semantic-release npm
腳本進行模擬運行來測試一切。採用以下命令,並將NPM_TOKEN=
和GH_TOKEN=
值設置爲使用您各自的令牌值。然後在你的終端中複製並運行完整的命令,看看一切是否運行正常。你會看到進程被記錄在終端的輸出中。如果出現任何問題,它們會在這裏顯示出來,並提供解決這些問題的細節。 -
在確認試運行成功後,可以爲 GitHub 倉庫設置一個新的 GitHub 動作來爲你處理發佈過程。轉到你在 GitHub 上的倉庫,點擊 "Actions"。
-
點擊新建工作流程選項。
-
將工作流程重命名爲 release.yml。
-
在新的工作流程文件中加入以下 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
- 提交你的本地修改並推送到你的 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/
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/YuxSv6TuWylxVNydny4mhg