NPM 包开发与优化全面指南

前言

  • Hey, 我是 Immerse
  • 系列文章首发于【Immerse】,更多内容请关注该网站
  • 转载说明:转载请注明原文出处及版权声明!

1. 理解 NPM 包的结构

1.1 package.json 文件:包的核心

package.json文件是 NPM 包的中央配置,定义了包的各个方面,从基本元数据到复杂的发布配置。

json 复制代码
{
    "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 <[email protected]>",
    "license": "MIT",
    "dependencies": {
        "lodash": "^4.17.21"
    },
    "devDependencies": {
        "typescript": "^4.5.5",
        "tsup": "^5.11.13",
        "jest": "^27.4.7"
    }
}

让我们详细解析一些关键字段:

  • nameversion:这两个字段组成了包在 NPM 注册表中的唯一标识符。
  • mainmoduletypes:这些指定了不同模块系统和 TypeScript 支持的入口点。
  • files:这个数组指定了发布包时应该包含哪些文件和目录。
  • scripts:这些是常见任务(如构建和测试)的命令快捷方式。

1.2 理解包的入口点

现代 JavaScript 生态系统支持多种模块格式。您的包应该通过提供多个入口点来适应不同的环境。

  1. main:主要入口点,通常用于 CommonJS (CJS)模块。
  2. module:用于 ECMAScript (ESM)模块的入口点。
  3. browser:用于浏览器环境的入口点。
  4. types:TypeScript 类型声明的入口点。

以下是一个包结构的示例:

tree 复制代码
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配置:

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进行导出。

javascript 复制代码
// 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 模块的现代标准,使用importexport语句。

javascript 复制代码
// 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、全局变量)中工作的模式。

javascript 复制代码
(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:

  1. 使用 ES 模块
  2. 避免副作用
  3. package.json中使用"sideEffects"字段
json 复制代码
{
    "name": "my-utils",
    "version": "1.0.0",
    "sideEffects": false
}

如果某些文件确实有副作用:

json 复制代码
{
    "name": "my-utils",
    "version": "1.0.0",
    "sideEffects": ["./src/polyfills.js", "*.css"]
}

3.2 代码分割和动态导入

对于大型包,考虑使用代码分割,允许用户只导入他们需要的部分:

javascript 复制代码
// heavyFunction.js
export function heavyFunction() {
    // ... 一些计算密集型操作
}

// main.js
async function doHeavyWork() {
    const { heavyFunction } = await import('./heavyFunction.js');
    heavyFunction();
}

3.3 条件导出

使用条件导出为不同的环境或导入条件提供不同的入口点:

json 复制代码
{
    "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 修复时
bash 复制代码
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.01.0.0-beta.11.0.0-rc.1
bash 复制代码
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 使用标签发布

使用标签发布不同版本或预发布版本:

bash 复制代码
npm publish --tag next
npm publish --tag beta

用户可以安装特定版本:

bash 复制代码
npm install my-package@next
npm install my-package@beta

5. 持续集成和部署 (CI/CD)

5.1 使用 GitHub Actions 进行自动发布

创建一个.github/workflows/publish.yml文件:

yaml 复制代码
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 的示例:

yaml 复制代码
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 "[email protected]"
                  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 这样的工具进行内联文档:

javascript 复制代码
/**
 * 将两个数字相加。
 * @param {number} a - 第一个数字。
 * @param {number} b - 第二个数字。
 * @returns {number} a和b的和。
 */
function add(a, b) {
    return a + b;
}

6.2 测试

使用像 Jest 这样的框架实现全面的测试:

javascript 复制代码
// 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

javascript 复制代码
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文件:

json 复制代码
{
    "singleQuote": true,
    "trailingComma": "es5",
    "tabWidth": 2,
    "semi": true,
    "printWidth": 100
}
相关推荐
Avan_菜菜5 小时前
Nuxt3 中使用 pnpm 安装的 NuxtImg 使用会提示找不到图片
前端·npm·nuxt.js
抠脚小弟7 小时前
实现vue组件库并发布npm上使用流程
前端·vue.js·npm
HarryHY8 小时前
检查项目中的依赖是否有更新——npm outdated
前端·npm·node.js
持久的棒棒君19 小时前
npm安装electron下载太慢,导致报错
前端·electron·npm
陪我一起学编程1 天前
关于nvm与node.js
vue.js·后端·npm·node.js
前端百草阁2 天前
从npm库 Vue 组件到独立SDK:打包与 CDN 引入的最佳实践
前端·vue.js·npm
夏日米米茶2 天前
Windows系统下npm报错node-gyp configure got “gyp ERR“解决方法
前端·windows·npm
蓝胖子的多啦A梦2 天前
npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1,因为在此系统上禁止运行脚
前端·npm·node.js
qziovv2 天前
Pnpm的使用
npm
XI锐真的烦2 天前
横向对比npm和yarn
前端·npm·node.js