前言
- Hey, 我是 Immerse
- 系列文章首發於【Immerse】,更多內容請關注該網站
- 轉載說明:轉載請註明原文出處及版權宣告!
1. 理解 NPM 包的結構
1.1 package.json 檔案:包的核心
package.json
檔案是 NPM 包的中央配置,定義了包的各個方面,從基本後設資料到複雜的釋出配置。
{
"name": "my-awesome-package",
"version": "1.0.0",
"description": "一個令人驚歎的包",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"files": ["dist"],
"scripts": {
"build": "tsup src/index.ts --format cjs,esm --dts",
"test": "jest"
},
"keywords": ["awesome", "package"],
"author": "Your Name <you@example.com>",
"license": "MIT",
"dependencies": {
"lodash": "^4.17.21"
},
"devDependencies": {
"typescript": "^4.5.5",
"tsup": "^5.11.13",
"jest": "^27.4.7"
}
}
讓我們詳細解析一些關鍵欄位:
name
和version
:這兩個欄位組成了包在 NPM 登錄檔中的唯一識別符號。main
,module
和types
:這些指定了不同模組系統和 TypeScript 支援的入口點。files
:這個陣列指定了釋出包時應該包含哪些檔案和目錄。scripts
:這些是常見任務(如構建和測試)的命令快捷方式。
1.2 理解包的入口點
現代 JavaScript 生態系統支援多種模組格式。您的包應該透過提供多個入口點來適應不同的環境。
- main:主要入口點,通常用於 CommonJS (CJS)模組。
- module:用於 ECMAScript (ESM)模組的入口點。
- browser:用於瀏覽器環境的入口點。
- types:TypeScript 型別宣告的入口點。
以下是一個包結構的示例:
my-awesome-package/
├── src/
│ ├── index.ts
│ └── utils.ts
├── dist/
│ ├── index.js (CJS構建)
│ ├── index.mjs (ESM構建)
│ ├── index.d.ts (TypeScript宣告)
│ └── browser.js (瀏覽器特定構建)
├── package.json
└── tsconfig.json
對應的package.json
配置:
{
"name": "my-awesome-package",
"version": "1.0.0",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"browser": "./dist/browser.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"require": "./dist/index.js",
"import": "./dist/index.mjs",
"types": "./dist/index.d.ts"
}
}
}
2. 深入理解模組格式
2.1 CommonJS (CJS)
CommonJS 是 Node.js 的傳統模組格式。它使用require()
進行匯入,使用module.exports
進行匯出。
// mathUtils.js
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
module.exports = {
add,
subtract,
};
// main.js
const mathUtils = require('./mathUtils');
console.log(mathUtils.add(5, 3)); // 輸出: 8
2.2 ECMAScript 模組 (ESM)
ESM 是 JavaScript 模組的現代標準,使用import
和export
語句。
// mathUtils.mjs
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
// main.mjs
import { add, subtract } from './mathUtils.mjs';
console.log(add(5, 3)); // 輸出: 8
2.3 通用模組定義 (UMD)
UMD 是一種允許模組在多種環境(CommonJS、AMD、全域性變數)中工作的模式。
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['exports'], factory);
} else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
// CommonJS
factory(exports);
} else {
// 瀏覽器全域性變數
factory((root.mathUtils = {}));
}
})(typeof self !== 'undefined' ? self : this, function (exports) {
exports.add = function (a, b) {
return a + b;
};
exports.subtract = function (a, b) {
return a - b;
};
});
3. 高階包最佳化技術
3.1 Tree Shaking 和副作用
Tree shaking 是現代打包工具用來消除死程式碼的技術。要使您的包可以進行 tree shaking:
- 使用 ES 模組
- 避免副作用
- 在
package.json
中使用"sideEffects"
欄位
{
"name": "my-utils",
"version": "1.0.0",
"sideEffects": false
}
如果某些檔案確實有副作用:
{
"name": "my-utils",
"version": "1.0.0",
"sideEffects": ["./src/polyfills.js", "*.css"]
}
3.2 程式碼分割和動態匯入
對於大型包,考慮使用程式碼分割,允許使用者只匯入他們需要的部分:
// heavyFunction.js
export function heavyFunction() {
// ... 一些計算密集型操作
}
// main.js
async function doHeavyWork() {
const { heavyFunction } = await import('./heavyFunction.js');
heavyFunction();
}
3.3 條件匯出
使用條件匯出為不同的環境或匯入條件提供不同的入口點:
{
"name": "my-package",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs",
"browser": "./dist/browser.js"
},
"./utils": {
"import": "./dist/utils.mjs",
"require": "./dist/utils.cjs"
}
}
}
4. 版本管理和釋出
4.1 語義化版本控制 (SemVer)
語義化版本使用三部分版本號:主版本號.次版本號.修訂號
- 主版本號:進行不相容的 API 更改時
- 次版本號:以向後相容的方式新增功能時
- 修訂號:進行向後相容的 bug 修復時
npm version patch -m "版本更新到 %s - 修復文件中的拼寫錯誤"
npm version minor -m "版本更新到 %s - 新增新的實用函式"
npm version major -m "版本更新到 %s - 更改API結構"
4.2 預釋出版本
對於預釋出版本,使用帶連字元的標籤:
latest
: 最新線上版本alpha
: 內部測試版本beta
: 公開測試版本rc
: 發行候選版本- Tips: 可以將這些識別符號新增到版本號中,同時也可以新增額外版本:如:
1.0.0-alpha.0
和1.0.0-beta.1
和1.0.0-rc.1
- Tips: 可以將這些識別符號新增到版本號中,同時也可以新增額外版本:如:
npm version prerelease --preid=alpha
# 1.0.0 -> 1.0.1-alpha.0
npm version prerelease --preid=beta
# 1.0.1-alpha.0 -> 1.0.1-beta.0
npm version prerelease --preid=rc
# 1.0.1-beta.0 -> 1.0.1-rc.0
4.3 使用標籤釋出
使用標籤釋出不同版本或預釋出版本:
npm publish --tag next
npm publish --tag beta
使用者可以安裝特定版本:
npm install my-package@next
npm install my-package@beta
5. 持續整合和部署 (CI/CD)
5.1 使用 GitHub Actions 進行自動釋出
建立一個.github/workflows/publish.yml
檔案:
name: 釋出包
on:
release:
types: [created]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '14'
registry-url: 'https://registry.npmjs.org'
- run: npm ci
- run: npm test
- run: npm run build
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
publish-gpr:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '14'
registry-url: 'https://npm.pkg.github.com'
- run: npm ci
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}
這個工作流程將在您建立新版本時自動將您的包釋出到 NPM 和 GitHub Packages。
5.2 自動化版本更新
您可以在 CI/CD 管道中自動化版本更新。以下是使用 GitHub Action 的示例:
name: 更新版本
on:
push:
branches:
- main
jobs:
bump-version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-node@v2
with:
node-version: '14'
- name: 更新版本
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
npm version patch -m "更新版本到 %s [skip ci]"
- name: 推送更改
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: ${{ github.ref }}
這個動作將在每次向主分支推送更改時自動更新包的修訂版本號。
6. 包開發最佳實踐
6.1 文件
良好的文件對於包的採用至關重要。考慮使用像 JSDoc 這樣的工具進行內聯文件:
/**
* 將兩個數字相加。
* @param {number} a - 第一個數字。
* @param {number} b - 第二個數字。
* @returns {number} a和b的和。
*/
function add(a, b) {
return a + b;
}
6.2 測試
使用像 Jest 這樣的框架實現全面的測試:
// math.js
export function add(a, b) {
return a + b;
}
// math.test.js
import { add } from './math';
test('1 + 2 應該等於 3', () => {
expect(add(1, 2)).toBe(3);
});
6.3 程式碼檢查和格式化
使用 ESLint 進行程式碼檢查,使用 Prettier 進行程式碼格式化。以下是一個示例.eslintrc.js
:
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 12,
sourceType: 'module',
},
plugins: ['@typescript-eslint'],
rules: {
// 在這裡新增自定義規則
},
};
以及一個.prettierrc
檔案:
{
"singleQuote": true,
"trailingComma": "es5",
"tabWidth": 2,
"semi": true,
"printWidth": 100
}