deeperthinker

如何上传一个NPM包

如何创建NPM包:超详细指南

创建NPM(Node Package Manager)包是将您的代码模块化、进行版本控制并与全球开发者社区共享的强大方式。本指南将提供一个全面而详细的逐步教程,帮助您从零开始创建和发布一个专业的NPM包。

1. 初始化项目:构建基础

项目的初始化是创建NPM包的第一步,它涉及为您的代码设置一个结构化的环境。

1.1. 创建项目目录

首先,为您的NPM包创建一个专用的文件夹。这个文件夹将包含您所有的包文件。

mkdir my-awesome-package
cd my-awesome-package

1.2. 初始化NPM项目 (npm init)

在项目目录中运行 npm init 命令,这会引导您创建一个 package.json 文件。package.json 是您包的清单文件,包含了所有关于您包的重要元数据,NPM和用户都依赖它来理解和使用您的包。

npm init

执行 npm init 后,您将看到一系列交互式提示:

  • package name (包名):

    • 重要性: 这是您包在NPM注册表上的唯一标识符。一旦发布,它就不能更改。

    • 命名规则:

      • 必须小写。

      • 可以包含连字符 (-)。

      • 不能包含空格或其他特殊字符(除了点 . 和下划线 _)。

      • 如果您的包是一个组织或个人项目的一部分,可以使用 scoped packages。例如:@your-org/my-package。这有助于防止命名冲突,并将相关包组织在一起。

    • 建议: 选择一个简洁、描述性强且易于记忆的名称。

  • version (版本号):

    • 重要性: 遵循 Semantic Versioning (SemVer) 规范。格式为 MAJOR.MINOR.PATCH

      • MAJOR (主版本号): 当您进行了不兼容的API更改时,需要增加此版本号。

      • MINOR (次版本号): 当您以向后兼容的方式添加了新功能时,需要增加此版本号。

      • PATCH (修订号): 当您进行了向后兼容的bug修复时,需要增加此版本号。

    • 默认值: 通常是 1.0.00.1.0 (如果您还在开发初期,0.x.x 表示不稳定的API)。

  • description (描述):

    • 重要性: 您的包的简短概括。NPM官网搜索结果会显示这个描述,所以要确保它能吸引用户。

  • main (入口文件):

    • 重要性: 指定了当用户 require()import 您的包时,Node.js将加载的JavaScript文件。

    • 默认值: index.js。通常,这是一个很好的选择。

    • CommonJS vs. ES Modules: 在现代JavaScript中,您可能需要考虑同时支持 CommonJS (require()) 和 ES Modules (import)。这通常通过在 package.json 中配置 main (CommonJS), module (旧版ES Modules), 和 exports (现代ES Modules) 字段来实现。我们将在后面详细讨论。

  • scripts (脚本):

    • 重要性: 定义了可以作为NPM脚本运行的命令行命令(例如 npm test, npm start, npm build)。

    • 常见脚本:

      • "test": "echo \"Error: no test specified\" && exit 1" (这是默认值,需要替换为实际的测试命令)。

      • "start": "node index.js" (如果您的包是一个可执行应用)。

      • "build": "babel src -d lib" (如果需要编译代码)。

  • keywords (关键词):

    • 重要性: 描述您包功能的词语列表。有助于用户在NPM注册表上搜索和发现您的包。

  • author (作者):

    • 重要性: 您的姓名或组织名称。格式通常为 "Your Name <your.email@example.com> (http://your-website.com)"

  • license (许可证):

    • 重要性: 指定了其他用户如何使用、分发和修改您的代码的法律条款。

    • 常见许可证:

      • MIT (最宽松): 允许几乎无限的使用,只需保留版权和许可证声明。

      • ISC (类似于MIT): 比MIT更简洁,常用于开源项目。

      • Apache 2.0: 包含专利授权,更适合企业。

      • GPLv3 (更严格): 任何基于此代码的衍生作品也必须开源。

    • 建议: 如果不确定,MIT 或 ISC 是很好的起点。

  • repository (仓库):

    • 重要性: 您的代码仓库的URL(例如 GitHub)。这允许用户查看源代码、提交问题和贡献。

    • 示例: "repository": { "type": "git", "url": "https://github.com/your-username/my-awesome-package.git" }

  • bugs (Bug 追踪):

    • 重要性: 用户报告问题的地方(例如 GitHub Issues 页面的URL)。

    • 示例: "bugs": { "url": "https://github.com/your-username/my-awesome-package/issues" }

  • homepage (主页):

    • 重要性: 您的包的官方文档或项目主页的URL。

    • 示例: "homepage": "https://github.com/your-username/my-awesome-package#readme"

  • private (私有包):

    • 重要性: 如果设置为 true,则NPM将拒绝发布此包。这对于不打算公开发布的私有项目或单体仓库中的子项目非常有用。

  • 快速初始化: 如果您想跳过交互式问答并使用默认值,可以使用:

    npm init -y
    
    

    这会生成一个基本的 package.json 文件,您可以稍后手动编辑它。

1.3. .gitignore 文件

为了保持代码仓库的整洁,通常会创建一个 .gitignore 文件来忽略不需要版本控制的文件和目录。

touch .gitignore

示例 .gitignore 内容:

# Node modules
node_modules/

# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# dotenv environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# Build artifacts
dist/
build/
lib/ # If you compile your code into a 'lib' directory

# Test reports
coverage/
.nyc_output/

# OS generated files
.DS_Store
Thumbs.db

# Editor directories and files
.idea/
.vscode/
*.swp

2. 编写您的包代码:实现核心功能

这是您NPM包的核心所在——编写实际的JavaScript代码。

2.1. 创建入口文件

根据您在 package.jsonmain 字段的定义,创建您的主文件。例如,如果 mainindex.js,则创建 index.js 文件:

touch index.js

2.2. 编写模块代码 (CommonJS 导出)

index.js 中编写您的JavaScript代码。您需要使用 module.exports 来导出您的功能,以便其他用户可以通过 require() 来导入和使用它。

示例 index.js (一个包含问候和数据处理功能的模块):

// index.js

/**
 * 问候指定的名字,如果没有提供名字则问候“世界”。
 * @param {string} [name] - 要问候的人的名字。
 * @returns {string} 问候语。
 */
function greet(name) {
  if (typeof name !== 'string' && name !== undefined) {
    throw new TypeError("Name must be a string or undefined.");
  }
  return name ? `Hello, ${name}!` : "Hello, world!";
}

/**
 * 获取当前日期和时间,格式为本地化字符串。
 * @returns {string} 当前的本地日期时间字符串。
 */
function getCurrentDateTime() {
  const now = new Date();
  return now.toLocaleString();
}

/**
 * 计算给定数组中所有数字的总和。
 * @param {number[]} numbers - 要相加的数字数组。
 * @returns {number} 数组的总和。
 * @throws {TypeError} 如果输入不是数字数组。
 */
function sumArray(numbers) {
  if (!Array.isArray(numbers) || !numbers.every(num => typeof num === 'number')) {
    throw new TypeError("Input must be an array of numbers.");
  }
  return numbers.reduce((acc, current) => acc + current, 0);
}

// 导出所有公共函数,使其可以在外部被访问。
// 这是一个 CommonJS 模块的导出方式。
module.exports = {
  greet,
  getCurrentDateTime,
  sumArray
};

2.3. 支持 ES Modules (可选但推荐)

随着ES Modules (import/export) 成为JavaScript的标准,您的包最好也支持它。

方法一:使用 type: "module" (Node.js 12.x 及更高版本):

package.json 中添加 "type": "module"。这将使您的所有 .js 文件都被视为ES Modules。

// package.json
{
  "name": "my-awesome-package",
  "version": "1.0.0",
  "main": "index.js",
  "type": "module", // <-- 添加这一行
  // ...其他字段
}

此时,您的 index.js 需要使用 export 语法:

// index.js (现在是 ES Module)

export function greet(name) {
  // ...
}

export function getCurrentDateTime() {
  // ...
}

export function sumArray(numbers) {
  // ...
}

用户可以通过 import { greet } from 'my-awesome-package'; 来使用。

方法二:使用 exports 字段 (推荐,同时支持 CommonJS 和 ES Modules):

这是最推荐的方式,因为它允许您明确指定不同环境下的入口点。

首先,您的 main 字段仍然指向 CommonJS 版本(例如 index.js)。

然后,创建一个新的ES Module版本的文件(例如 index.mjs 或 esm/index.js)。

# 创建ES Module版本的入口文件
touch esm/index.js

index.js (CommonJS 版本):

// index.js (CommonJS)

function greet(name) { /* ... */ }
function getCurrentDateTime() { /* ... */ }
function sumArray(numbers) { /* ... */ }

module.exports = {
  greet,
  getCurrentDateTime,
  sumArray
};

esm/index.js (ES Module 版本):

// esm/index.js (ES Module)

export function greet(name) { /* ... */ }
export function getCurrentDateTime() { /* ... */ }
export function sumArray(numbers) { /* ... */ }

package.json 中的 exports 配置:

// package.json
{
  "name": "my-awesome-package",
  "version": "1.0.0",
  "main": "index.js", // CommonJS 入口
  "exports": {
    ".": {
      "import": "./esm/index.js", // 用于ES Module环境
      "require": "./index.js"     // 用于CommonJS环境
    },
    // 如果您想允许用户直接导入子路径,也可以在这里定义:
    "./utils": {
      "import": "./esm/utils.js",
      "require": "./utils.js"
    }
  },
  // 或者使用更简洁的写法 (推荐用于简单包)
  // "exports": {
  //   "import": "./esm/index.js",
  //   "require": "./index.js"
  // },
  // ...其他字段
}

这样,用户就可以根据他们的环境自动选择正确的模块格式。

2.4. 添加外部依赖 (Optional)

如果您的包依赖于其他NPM包,您需要将它们添加到 dependencies 中。

npm install lodash # 安装一个示例依赖

这会自动更新您的 package.json

// package.json
{
  "name": "my-awesome-package",
  "version": "1.0.0",
  "dependencies": {
    "lodash": "^4.17.21" // 或者其他版本号
  },
  // ...
}

3. 添加说明文档 (README.md):用户的第一印象

一个高质量的 README.md 文件是NPM包的门面。它应该提供所有必要的信息,帮助用户快速理解、安装和使用您的包。

README.md 的核心内容应包括:

  • 包名和标语: 简短而引人入胜的标题和一句话描述。

  • 徽章 (Badges):

    • NPM 版本: 显示当前包的版本。

      ![npm version](https://img.shields.io/npm/v/my-awesome-package.svg)

    • NPM 下载量: 显示包的流行度。

      ![npm downloads](https://img.shields.io/npm/dt/my-awesome-package.svg)

    • 许可证: 显示包的许可证类型。

      ![license](https://img.shields.io/badge/License-MIT-blue.svg)

    • CI/CD 状态: 如果您使用 GitHub Actions, Travis CI, CircleCI 等,可以显示构建状态。

      ![build status](https://github.com/your-username/my-awesome-package/actions/workflows/ci.yml/badge.svg)

  • 安装说明: 清晰的命令,告诉用户如何安装您的包。

    npm install my-awesome-package
    # 或者
    yarn add my-awesome-package
    
    
  • 使用方法/示例:

    • 提供代码片段,展示如何导入和使用您的包的核心功能。

    • 尽量提供多种使用场景的示例。

    • 解释预期的输出。

  • API 文档:

    • 详细列出您的包导出的所有函数、类或对象。

    • 为每个API提供:

      • 函数签名(包括参数类型和返回类型)。

      • 参数的详细说明(是否可选、默认值、用途)。

      • 功能的简短描述。

      • 示例代码。

  • 贡献指南 (可选,但推荐):

    • CONTRIBUTING.md 文件:链接到更详细的贡献指南,说明如何提交 bug 报告、功能请求和拉取请求。

    • 提及开发环境设置、运行测试、提交代码规范等。

  • 许可证信息: 明确说明您的包使用的许可证。通常会链接到 LICENSE 文件。

  • 更新日志 (CHANGELOG.md):

    • 虽然不在 README.md 中,但强烈建议在项目中维护一个 CHANGELOG.md 文件。

    • 记录每个版本发布的所有重要更改,包括新功能、bug 修复和破坏性更改。

    • 遵循 Keep a Changelog 规范。

示例 README.md (更详细的):

# My Awesome Package ![npm version](https://img.shields.io/npm/v/my-awesome-package.svg) ![npm downloads](https://img.shields.io/npm/dt/my-awesome-package.svg) ![license](https://img.shields.io/badge/License-MIT-blue.svg)

一个多功能NPM包,提供简单的问候、日期时间获取以及数组数字求和功能。旨在帮助开发者快速处理常见的文本和数值操作。

## 目录
- [安装](#安装)
- [使用方法](#使用方法)
- [API](#api)
  - [`greet(name)`](#greetname)
  - [`getCurrentDateTime()`](#getcurrentdatetime)
  - [`sumArray(numbers)`](#sumarraynumbers)
- [贡献](#贡献)
- [更新日志](#更新日志)
- [许可证](#许可证)

## 安装

您可以通过 npm 或 yarn 安装 `my-awesome-package`:

```bash
npm install my-awesome-package
# 或者
yarn add my-awesome-package

使用方法

CommonJS 模块 (Node.js)

const myPackage = require('my-awesome-package');

console.log(myPackage.greet('Alice'));
// 输出: Hello, Alice!

console.log(myPackage.greet());
// 输出: Hello, world!

console.log(myPackage.getCurrentDateTime());
// 输出示例: 2023/10/26 下午 03:30:00 (取决于您的本地设置)

console.log(myPackage.sumArray([1, 2, 3, 4, 5]));
// 输出: 15

try {
  myPackage.sumArray([1, 'a', 3]);
} catch (error) {
  console.error(error.message); // 输出: Input must be an array of numbers.
}

ES Modules (Node.js, 浏览器打包工具如 Webpack/Rollup)

import { greet, getCurrentDateTime, sumArray } from 'my-awesome-package';

console.log(greet('Bob'));
// 输出: Hello, Bob!

console.log(getCurrentDateTime());
// 输出示例: 2023/10/26 下午 03:30:00

const numbersToAdd = [10, 20, 30];
console.log(`Sum: ${sumArray(numbersToAdd)}`);
// 输出: Sum: 60

API

greet(name)

一个返回问候语的函数。

  • 参数:

    • name (可选, string): 要问候的人的名字。如果未提供,将问候 "world!"。

  • 返回: string - 格式为 "Hello, [name]!" 的问候语。

  • 抛出: TypeError - 如果 name 存在但不是字符串类型。

示例:

console.log(myPackage.greet('Developer')); // "Hello, Developer!"
console.log(myPackage.greet());            // "Hello, world!"

getCurrentDateTime()

一个返回当前本地日期和时间字符串的函数。

  • 参数:

  • 返回: string - 当前日期和时间的本地化字符串表示。

示例:

console.log(myPackage.getCurrentDateTime()); // "10/26/2023, 3:30:00 PM" (取决于您的本地设置)

sumArray(numbers)

计算给定数字数组中所有元素的总和。

  • 参数:

    • numbers (必选, number[]): 包含要相加的数字的数组。

  • 返回: number - 数组中所有元素的总和。

  • 抛出: TypeError - 如果输入不是一个数字数组。

示例:

console.log(myPackage.sumArray([10, 20, 30])); // 60
try {
  myPackage.sumArray([1, 'two']);
} catch (e) {
  console.error(e.message); // "Input must be an array of numbers."
}

贡献

我们欢迎并感谢社区的贡献!如果您想为 my-awesome-package 做出贡献,请参阅我们的 贡献指南 文件,了解如何提交 bug 报告、功能请求和拉取请求。

更新日志

请参阅 CHANGELOG.md 文件,了解每个版本发布的所有更改。

许可证

my-awesome-package 根据 MIT License 发布。


## 4. 编写测试 (可选但强烈推荐):确保代码质量

编写测试是软件开发中不可或缺的一部分。它可以确保您的包按预期工作,并在您未来修改代码时防止引入新的错误(回归错误)。

### 4.1. 选择测试框架

有许多优秀的JavaScript测试框架可供选择:

* **Jest (推荐):** 由 Facebook 开发,功能全面,包括断言库、模拟(mocking)、代码覆盖率报告等,配置简单。
* **Mocha:** 灵活的测试框架,需要搭配断言库(如 Chai)和/或模拟库(如 Sinon)。
* **Vitest:** 基于 Vite 的极速测试框架,适用于 Vite 生态系统。
* **Jasmine:** 另一个功能全面的测试框架。

以 **Jest** 为例,它因其开箱即用的特性而广受欢迎。

### 4.2. 安装测试框架 (以 Jest 为例)

在您的项目根目录中运行以下命令来安装 Jest 作为开发依赖:

```bash
npm install --save-dev jest
# 或者使用 yarn
yarn add --dev jest
```--save-dev` 标志将Jest添加到 `devDependencies` 中,这表示它只在开发过程中需要,而不是包运行时的依赖。

### 4.3. 在 `package.json` 中添加测试脚本

打开您的 `package.json` 文件,并修改 `scripts` 部分,将默认的测试命令替换为 Jest:

```json
// package.json
{
  "name": "my-awesome-package",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "jest", // <-- 将此行修改为 "jest"
    "test:watch": "jest --watch", // 实时监控文件变化并运行测试
    "test:coverage": "jest --coverage" // 生成代码覆盖率报告
  },
  // ...
}

现在,您可以通过运行 npm test 来执行您的测试。

4.4. 创建测试文件

约定俗成,测试文件通常放置在 __tests__ 目录下,或者与源文件同级,以 .test.js.spec.js 结尾。

mkdir __tests__
touch __tests__/index.test.js

4.5. 编写测试用例

__tests__/index.test.js 文件中,编写测试代码。测试用例应该覆盖您包的每个公共函数和边缘情况。

// __tests__/index.test.js

// 根据您的模块导出方式导入包
// 如果是 CommonJS:
const myPackage = require('../index');
// 如果是 ES Module (如果配置了 "type": "module" 或 "exports" 字段):
// import * as myPackage from '../index'; // 或者 { greet, sumArray } from '../index';


describe('my-awesome-package', () => {

  // 测试 greet 函数
  describe('greet', () => {
    test('应该返回带有名字的问候语', () => {
      expect(myPackage.greet('Bob')).toBe('Hello, Bob!');
    });

    test('如果没有提供名字,应该返回默认问候语 "Hello, world!"', () => {
      expect(myPackage.greet()).toBe('Hello, world!');
    });

    test('如果提供空字符串,应该返回带有空字符串的问候语', () => {
      expect(myPackage.greet('')).toBe('Hello, !');
    });

    test('如果输入不是字符串或undefined,应该抛出 TypeError', () => {
      expect(() => myPackage.greet(123)).toThrow(TypeError);
      expect(() => myPackage.greet(null)).toThrow(TypeError);
      expect(() => myPackage.greet(true)).toThrow(TypeError);
      expect(() => myPackage.greet({})).toThrow(TypeError);
    });
  });

  // 测试 getCurrentDateTime 函数
  describe('getCurrentDateTime', () => {
    test('应该返回一个字符串', () => {
      expect(typeof myPackage.getCurrentDateTime()).toBe('string');
    });

    test('返回的日期时间字符串应该包含当前年份', () => {
      const currentYear = new Date().getFullYear().toString();
      expect(myPackage.getCurrentDateTime()).toContain(currentYear);
    });

    test('返回的日期时间字符串应该包含当前月份(通常是两位数)', () => {
      // 简单检查格式,更精确的日期时间测试通常需要模拟 Date 对象
      const currentMonth = (new Date().getMonth() + 1).toString().padStart(2, '0');
      expect(myPackage.getCurrentDateTime()).toMatch(new RegExp(`\\b${currentMonth}\\b`));
    });
  });

  // 测试 sumArray 函数
  describe('sumArray', () => {
    test('应该正确计算正数数组的总和', () => {
      expect(myPackage.sumArray([1, 2, 3, 4, 5])).toBe(15);
    });

    test('应该正确计算负数数组的总和', () => {
      expect(myPackage.sumArray([-1, -2, -3])).toBe(-6);
    });

    test('应该正确计算混合正负数的数组的总和', () => {
      expect(myPackage.sumArray([-1, 0, 1])).toBe(0);
    });

    test('应该返回空数组的总和为 0', () => {
      expect(myPackage.sumArray([])).toBe(0);
    });

    test('如果输入不是数组,应该抛出 TypeError', () => {
      expect(() => myPackage.sumArray(123)).toThrow(TypeError);
      expect(() => myPackage.sumArray(null)).toThrow(TypeError);
      expect(() => myPackage.sumArray(undefined)).toThrow(TypeError);
      expect(() => myPackage.sumArray({})).toThrow(TypeError);
    });

    test('如果数组中包含非数字元素,应该抛出 TypeError', () => {
      expect(() => myPackage.sumArray([1, 'a', 3])).toThrow(TypeError);
      expect(() => myPackage.sumArray([1, null, 3])).toThrow(TypeError);
      expect(() => myPackage.sumArray([1, true, 3])).toThrow(TypeError);
    });
  });

});

4.6. 运行测试和代码覆盖率

npm test
# 或者
npm run test:watch # 实时监控文件变化并运行测试
npm run test:coverage # 生成代码覆盖率报告,查看哪些代码被测试覆盖了

代码覆盖率报告会显示您的测试覆盖了多少代码,这有助于发现未被测试到的代码路径。

5. 准备发布:确保万无一失

在将您的包发布到NPM注册表之前,进行最后的检查是至关重要的。

5.1. 仔细检查 package.json

确保所有关键信息都是准确和完整的:

  • name: 必须是唯一的。

  • version: 使用正确的SemVer版本号。

  • description: 简洁明了,能吸引用户。

  • main / exports: 正确指向您的入口文件,并考虑ES Modules兼容性。

  • repository / bugs / homepage: 确保这些链接是正确的。

  • keywords: 包含所有相关的搜索词。

  • license: 明确指定许可证类型。

  • author: 您的信息。

  • files (可选但推荐):

    • 如果您想精确控制哪些文件被包含在您的NPM包中,可以在 package.json 中添加 files 字段。它是一个字符串数组,指定了应该包含的文件和目录(包括子目录)。

    • 如果此字段存在,NPM只会包含这些指定的文件,即使 .npmignore 没有忽略它们。

    • 示例: "files": ["index.js", "esm/", "LICENSE", "README.md"]

5.2. 版本号管理

每次发布新版本时,都必须更新 package.json 中的 version 字段。NPM不允许发布与现有版本号相同的包。

  • 手动更新: 直接编辑 package.json 中的 version 字段。

  • 使用 npm version 命令 (推荐):

    • npm version patch: 增加修订号(例如:1.0.0 -> 1.0.1)。用于bug修复。

    • npm version minor: 增加次版本号(例如:1.0.0 -> 1.1.0)。用于新增向后兼容的功能。

    • npm version major: 增加主版本号(例如:1.0.0 -> 2.0.0)。用于引入不兼容的API更改。

    • 这些命令还会自动为您的Git仓库创建一个对应的标签(tag),这对于版本管理非常有用。

    示例:

    npm version patch -m "Fix: 修复了一个重要bug"
    # 这会更新 package.json,并创建一个类似 v1.0.1 的 Git 标签
    
    

5.3. .npmignore 文件 (或 files 字段的配合使用)

package.json 中的 files 字段和项目根目录的 .npmignore 文件用于控制哪些文件会被打包并发布到NPM。

  • 优先级: 如果 files 字段存在,则只有 files 中列出的文件才会被包含。如果 files 不存在,NPM会默认包含所有文件,除了 .npmignore.gitignore 中列出的文件。

  • .npmignore 的作用: 明确排除您不希望发布到NPM的文件。它的语法与 .gitignore 类似。

    • 常见要忽略的文件:

      • .git/ (Git 版本控制相关文件)

      • node_modules/ (您的开发依赖,用户安装时会重新下载)

      • __tests__/test/ (测试文件)

      • *.log (日志文件)

      • temp/tmp/ (临时文件)

      • src/ (如果您发布的是编译后的代码,原始源代码可能不需要包含)

      • 构建工具的配置文件(例如 .babelrc, .eslintrc.js,除非它们对用户很重要)

      • .env 文件等敏感信息

示例 .npmignore:

.git/
node_modules/
__tests__/
*.log
temp/
src/ # 如果您的包是编译后发布的,且编译产物在其他目录
.babelrc
.eslintrc.js
jest.config.js
coverage/

5.4. 许可证文件 (LICENSE)

在项目根目录创建一个 LICENSE 文件。将您在 package.json 中声明的许可证类型的完整文本粘贴到此文件中。这是法律要求,也是对其他开发者和用户开放源代码的承诺。

5.5. 代码格式化和代码风格 (可选但推荐)

  • Prettier: 一个代码格式化工具,可以自动统一您的代码风格。

    • 安装: npm install --save-dev prettier

    • package.json 中添加脚本: "format": "prettier --write ."

  • ESLint: 一个可配置的JavaScript代码检查工具,可以帮助您发现潜在的错误和不符合代码风格的问题。

    • 安装: npm install --save-dev eslint

    • 初始化: npx eslint --init

    • package.json 中添加脚本: "lint": "eslint ."

    • 在发布前运行这些工具,可以确保您的代码干净、一致,并符合最佳实践。

5.6. Pre-publish 脚本 (可选)

您可以在 package.jsonscripts 中定义一些在 npm publish 之前自动运行的钩子:

  • prepublishOnly: 这个脚本会在 npm publish 运行之前执行,并且在打包(npm pack)和本地安装(npm install)时不会执行。这对于编译代码、运行测试或进行其他发布前的检查非常有用。

    "scripts": {
      "prepublishOnly": "npm test && npm run build" // 确保测试通过并编译代码
    },
    
    

    这里假设您有一个 build 脚本来编译您的代码。

6. 登录NPM账号:连接注册表

要将您的包发布到NPM注册表,您需要拥有一个NPM账号并登录。

6.1. 注册NPM账号

如果您还没有NPM账号,请访问 npm website 并完成注册流程。请务必记住您的用户名和密码。

6.2. 在命令行登录

在您的终端中运行以下命令:

npm login

系统会提示您输入NPM用户名、密码和邮箱地址。请按照提示操作。成功登录后,您的凭据将安全地存储在本地。

提示: 强烈建议为您的NPM账号启用双重认证 (2FA),以提高安全性。您可以在NPM网站的个人设置中配置。

7. 发布您的包:共享您的工作

一切准备就绪后,就是将您的包推送到NPM注册表的时候了。

npm publish

在执行 npm publish 后,NPM会进行以下操作:

  1. 运行 prepublishOnly 脚本(如果定义了)。

  2. 创建一个 .tgz 压缩包,其中包含所有要发布的文件(根据 files.npmignore 规则)。

  3. 将此压缩包上传到NPM注册表。

可能遇到的问题及解决方案:

  • You do not have permission to publish this package. (您没有发布此包的权限。)

    • 这意味着该包名已被其他用户或组织占用。您需要更改 package.json 中的 name 字段为唯一的名称。如果是 scoped package (例如 @your-org/my-package),请确保您是该组织的成员且有发布权限。

  • No package version supplied. (未提供包版本。)

    • 确保您的 package.json 中的 version 字段已更新。使用 npm version patch/minor/major 是个好习惯。

  • 网络连接问题:

    • 检查您的网络连接。

  • 需要指定访问权限 (Scoped packages):

    • 如果您发布的是一个作用域包 (以 @ 开头,例如 @your-username/my-package),并且您没有购买私有包服务,则需要明确指定为公共访问:

      npm publish --access public
      
      

      对于非作用域包,--access public 是默认行为。

一旦发布成功,您将看到一条成功消息。此时,您的包就可以在NPM官网上被搜索到,其他人也可以通过 npm install your-package-name 来安装和使用了。

8. 更新和维护:持续改进

发布包只是第一步,持续的更新和维护是NPM包生命周期中不可或缺的部分。

8.1. 发布新版本

当您对包进行了更改(如bug修复、新功能、性能优化等)并希望发布新版本时,请遵循以下流程:

  1. 修改您的代码: 进行必要的代码更改。

  2. 更新版本号: 根据更改的性质,使用 npm version patch/minor/major 来更新 package.json 中的 version 字段。这将创建相应的Git标签。

    npm version patch -m "Fix: 修复了sumArray中的边界情况"
    
    
  3. 更新更新日志 (CHANGELOG.md): 记录所有新功能、bug 修复和任何破坏性更改。

  4. 重新运行测试: 在发布前,务必运行所有测试,确保您的更改没有引入新的问题。

    npm test
    
    
  5. 再次发布:

    npm publish
    
    

8.2. 处理弃用和删除

  • 弃用某个版本: 如果某个版本存在严重bug或安全漏洞,您可以将其标记为弃用,NPM会在用户安装该版本时显示警告。

    npm deprecate <package-name>@<version> "<message>"
    # 示例: npm deprecate my-awesome-package@1.0.0 "这个版本有严重bug,请升级到最新版!"
    
    
  • 弃用整个包: 如果您不再维护某个包,可以弃用整个包。

    npm deprecate <package-name> "<message>"
    
    
  • 删除版本 (不推荐): 通常不建议删除已发布的版本,因为这可能会破坏依赖于它的其他项目。NPM只允许在某些特定情况下删除包(例如,在发布24小时内,或者在联系NPM支持后)。

    • 删除特定版本:npm unpublish <package-name>@<version>

    • 删除整个包(非常危险,慎用):npm unpublish <package-name> --force

8.3. 破坏性更改 (Breaking Changes)

当您在包中引入了不向后兼容的API更改时,这称为“破坏性更改”。

  • 版本号: 必须增加主版本号(MAJOR version),例如从 1.x.x 升级到 2.0.0

  • 文档:README.mdCHANGELOG.md 中详细说明所有破坏性更改以及用户如何迁移到新版本。

总结:NPM包生命周期

  1. 初始化项目: mkdir my-package && cd my-package && npm init (配置 package.json.gitignore)

  2. 编写代码: 实现核心功能,考虑 CommonJS/ES Modules 导出。

  3. 添加文档: 创建详细的 README.md,可以考虑 CONTRIBUTING.mdCHANGELOG.md

  4. 编写测试: 使用 Jest 等工具编写单元测试和集成测试,确保代码质量。

  5. 准备发布: 检查 package.json、更新版本号、创建 LICENSE、管理 .npmignorefiles

  6. 登录NPM: npm login

  7. 发布: npm publish

  8. 更新和维护: 持续迭代,发布新版本,管理弃用。

通过遵循这些详细的步骤,您将能够创建、发布并有效维护您的NPM包,为JavaScript社区做出贡献!

posted on 2025-06-30 14:40  gamethinker  阅读(12)  评论(0)    收藏  举报  来源

导航