如何从0到1开发并发布一个npm包?

223 阅读6分钟

总结

发布一个 npm 包的完整流程如下:

  1. 创建并初始化项目。
  2. 配置开发工具(如 Rollup、TypeScript、Jest 等)并编写源代码。
  3. 打包并生成类型声明文件(如果使用 TypeScript)。
  4. 配置 package.json,准备发布。
  5. 发布包到npm registry。
  6. 根据需求更新版本并继续发布更新

一、创建并初始化项目

  1. 在终端中创建一个新的文件夹,进入该文件夹。
mkdir my-npm-package
cd my-npm-package
  1. 使用 npm init 命令来初始化 package.json 文件。
npm init

二、配置开发依赖并编写源代码

配置开发相关依赖

1. TypeScript(可选,如果需要使用 TypeScript)

安装相关依赖
npm install typescript -D
配置tsconfig
{
  "compilerOptions": {
    "target": "es5",  // 编译后的 JavaScript 代码目标版本为 ES5。
    "module": "esnext",  // 设置模块系统为 ESNext。
    
    // 输出的目录
    "outDir": "./lib",  // 编译后的 JavaScript 和类型声明文件输出到 `lib` 目录。可以根据需要调整为其他目录名。
    
    "strict": true,  // 启用 TypeScript 的严格类型检查模式。
    
    "noImplicitAny": false,  // 允许隐式 `any` 类型。
    
    // 模块的解析策略
    "moduleResolution": "node",  // 设置模块解析方式为 `node`。
    
    "esModuleInterop": true,  // 允许使用 CommonJS 模块的默认导入(即 `require`),并使其能够以 ES6 模块的语法进行导入。
    
    "skipLibCheck": true,  // 跳过库文件的类型检查。这可以提高编译速度,特别是对于包含大量类型定义的第三方库。
    
    "forceConsistentCasingInFileNames": true,  // 强制文件名的大小写一致性。
    
    // 只生成类型文件,不转换代码
    "declaration": true,  // 启用类型声明文件的生成,生成 `.d.ts` 文件。
    
    "emitDeclarationOnly": true,  // 只生成类型声明文件,不生成 JavaScript 代码。这通常用于只发布类型声明的库。
  },
  
  // 只编译 src 目录下的文件
  "include": [
    "src" 
  ],
  
  "exclude": [
    "tests"  // 排除 `tests` 目录下的文件不进行编译。
  ]
}

2. ESLint(代码规范检查)

安装依赖
npm install eslint @eslint/js globals -D
  • @eslint/js: ESLint 的一个扩展包,包含了一组新的 JavaScript 编码规则,用于在项目中启用和定制特定的 JavaScript 规则(例如:ES6+ 语法支持、代码风格等)。

  • globals: 包含了常见 JavaScript 环境中预定义的全局变量列表。例如,浏览器环境中的 window 和 document,或者 Node.js 中的 process 和 Buffer 等。

若项目使用了ts,需要同步安装以下依赖

npm install eslint -D
配置eslint.config.js

在根目录新建eslint.config.js

import globals from "globals";
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";


/** @type {import('eslint').Linter.Config[]} */
export default [
  {files: ["**/*.{js,mjs,cjs,ts}"]},
  {languageOptions: { globals: globals.browser }},
  pluginJs.configs.recommended,
  ...tseslint.configs.recommended,
];

3. Rollup 配置(打包工具)

安装rollup相关依赖
npm install rollup @rollup/plugin-commonjs @rollup/plugin-node-resolve @rollup/plugin-typescript -D
  • @rollup/plugin-commonjs 这个插件用于将 CommonJS 模块转换为 ES 模块,使得 Rollup 可以处理和打包 CommonJS 格式的第三方库(例如 Node.js 模块)。 创建 rollup.config.js 文件,配置如何打包你的包。
  • @rollup/plugin-node-resolve 这个插件让Rollup 能够正确解析并打包外部的依赖(例如:你安装的 npm 包),而不需要显式地指定模块路径
  • @rollup/plugin-typescript 这个插件用于让 Rollup 处理 TypeScript 文件(.ts 和 .tsx)。此插件会使用 tsc(TypeScript 编译器)来编译 TypeScript 文件,并生成相应的 JavaScript 文件。
配置rollup
// rollup.config.js
import resolve from '@rollup/plugin-node-resolve';
import typescript from '@rollup/plugin-typescript';
import commonjs from '@rollup/plugin-commonjs';
import pkg from './package.json' assert { type: 'json' };

const footer = `
if (typeof window !== 'undefined') {
  window._Dry_VERSION_ = '${pkg.version}'
}`;

export default {
  input: './src/index.ts',//input 配置指定了打包的入口文件。
  output: [
    {
      file: pkg.main,
      format: 'cjs',// 输出一个 CommonJS 格式的文件,适合在 Node.js 环境中使用。该文件会被存储在 pkg.main 指定的路径中。
      footer,
    },
    {
      file: pkg.module,
      format: 'esm',// 输出一个 ES模块 格式的文件,适用于现代 JavaScript 环境(包括浏览器和支持 ES模块的 Node.js)。
      footer,
    },
    {
      file: pkg.browser,
      format: 'umd',// 输出一个 UMD 格式的文件,适合在浏览器和 Node.js 环境中使用(即兼容浏览器、Node.js 和 AMD 模块系统)。该文件会被存储在 pkg.browser 指定的路径中。
      name: 'Dry',// 'Dry' 是指在浏览器环境下,UMD 模块会暴露一个全局变量 Dry,它指向模块的导出。
      footer,// 在每个输出格式的文件结尾加上 footer 中定义的代码,以便在浏览器中使用时将版本号暴露给全局变量
    },
  ],
  plugins: [
    typescript(),
    commonjs(),
    resolve(),
  ],
};

4. Jest(单元测试)

安装 Jest:
npm install jest ts-jest @types/jest -D
  1. 配置jest 在根路径下新建jest.config.js
// jest.config.js
/** @type {import('ts-jest').JestConfigWithTsJest} **/
module.exports = {
  testEnvironment: "node",
  transform: {
    "^.+.tsx?$": ["ts-jest",{}],
  },
};
  1. 编写测试文件
// src/__tests__/add.test.js

const { add } = require('../index');

test('adds 1 + 2 to equal 3', () => {
  expect(add(1, 2)).toBe(3);
});
  1. 在 package.json 中配置构建脚本
"scripts": {
  "test": "jest"
}

编写包的核心代码

创建一个 src 文件夹来存放源代码。比如,创建一个 src/index.ts 文件 示例代码(一个简单的函数)

// src/index.ts

/**
 * Adds two numbers
 * @param {number} a
 * @param {number} b
 * @returns {number} sum
 */
function add(a:number, b:number):number {
  return a + b;
}
module.exports = { add };

三、打包并生成类型声明文件

  1. 在 package.json 中配置构建脚本,使用 Rollup 来打包代码。
"scripts": {
  "build": "rollup -c",
}
  1. 构建运行与测试
npm run build  # 打包项目
npm test       # 运行测试

四、配置 package.json,准备发布

在 package.json 中配置必要的字段,确保包的名称、版本、入口文件等都设置正确。重要字段包括:

  • name:包的名称,必须是唯一的。
  • version:遵循语义版本控制(SemVer),如 1.0.0。
  • main:CommonJS 入口文件。
  • module:ESM 入口文件。
  • types:类型声明文件的位置(如果使用 TypeScript)。
  • files:发布时包含的文件或文件夹,通常包括 dist、lib 等。
{
  "name": "my-npm-package",
  "version": "1.0.0",
  "main": "dist/bundle.cjs.js",
  "module": "dist/bundle.esm.js",
  "types": "dist/index.d.ts",
  "files": [
    "dist"
  ]
}

可供参考的package.json配置

{
  "name": "your-package-name",  // 包的名称,必须是唯一的,格式支持 scoped 包(如 @scope/package-name)
  "version": "1.0.0",  // 包的版本号,遵循语义版本控制(SemVer)
  "main": "lib/bundle.cjs.js",  // CommonJS 模块的入口文件,用于 Node.js 环境中的 `require`
  "module": "lib/bundle.esm.js",  // ES 模块的入口文件,用于现代 JS 环境中的 `import`
  "browser": "lib/bundle.browser.js",  // 浏览器环境的入口文件,通常用于前端项目
  "types": "types/index.d.ts",  // TypeScript 类型声明文件的位置,帮助 TypeScript 用户了解该包的类型
  "type": "module",  // 指定模块系统为 ES Modules (ESM)
  "files": [  // 定义要包括在发布包中的文件,这里指定了 lib 文件夹
    "lib"
  ],
  "engines": {
  "node": ">=14.0.0"//规定了包支持的 Node.js 版本。避免用户安装在不兼容的 Node.js 版本中。
  },
  "repository": {  // 包的源代码仓库信息,通常用于指向 GitHub 或 GitLab 仓库
    "type": "git",  // 仓库类型
    "url": ""  // 仓库 URL,这里需要填写你的代码仓库地址
  },
  "homepage":"",//包的主页,可以是 GitHub 页面或文档页面等
  "exports": {  // 定义包对外提供的模块导出格式,适应不同环境(Node.js、浏览器)
    ".": {
      "import": "./lib/bundle.esm.js",  // ESM 模块的导出路径
      "require": "./lib/bundle.cjs.js"  // CommonJS 模块的导出路径
    },
    "./browser": {
      "browser": "./lib/bundle.browser.js"  // 浏览器环境下的导出路径
    }
  },
  "scripts": {  // 定义可以执行的脚本命令
    "build": "rollup -c",  // 执行构建任务,使用 Rollup 打包项目
    "build:types": "tsc",  // 生成 TypeScript 类型声明文件
    "test": "jest",  // 执行测试命令,使用 Jest 进行测试
    "publish": "npm publish"  // 执行发布任务,使用 npm 将包发布到 NPM Registry
  },
  "author": "",  // 包的作者信息,通常是作者的名字或联系方式
  "license": "ISC",  // 包的开源许可证类型,通常是 `MIT` 或 `ISC` 等
  "description": "",  // 包的简短描述,说明包的用途
  "keywords": [  // 包的关键词,帮助用户通过搜索找到你的包
  ],
  "devDependencies": {  // 开发依赖,包含用于开发、构建和测试的依赖
  },
  "dependencies": {  // 运行时依赖,包含包正常运行所需要的依赖
  },
  "peerDependencies": {// 声明包所依赖的其他包版本
  }
}

五、发布包到npm registry

  1. 在发布之前,确保已经注册了一个 npm 账户,并在终端中登录
npm login
  1. 发布包 运行以下命令来发布包:
npm publish
  • 默认情况下,npm publish 会将包发布到 npm 注册表,使用的是包的当前访问级别设置。
  • 对于新包,默认是 public 访问级别,也就是说任何人都可以安装这个包。
  • 对于已经存在的包,npm publish 会遵循包当前的访问级别设置,不改变访问权限。

如果希望以公开访问的方式发布包,可使用以下命令:

 npm publish --access public

六、根据需求更新版本并继续发布更新

如果需要发布更新,修改代码后增大版本号(遵循 SemVer),然后执行以下命令:

npm version patch   # 或者 minor/major,根据更改的内容
npm publish