记一次 npm 包的开发与发布过程

1,974 阅读17分钟

写在最前:

环境要求:

node(必需!)

npm 包的开发,就是利用 nodejs 的包机制来实现的,这没啥说的

涉及技术栈:

最基础:nodejs + JS

为了增强代码的可读写和可维护性,可以加上:TypeScript。

如果是设计组件,可以使用我们当前 react。

为了优化,或者更好地开发,还可以使用到 webpack 等。

其它一些如 ESLint、Prettier 等一些配置。

总之,把 npm 包的开发,当成是一个项目的开发就行了。

一份简单的 demo 代码

前言

npm 包的使用,作为前端开发都不陌生,我们经常在网上下载一些 npm 包(或者也有说成第三方库)在自己的项目中使用, 如 lodashdayjsaxios 等等。

平时我们都是从网上下载使用其他人写的 npm 包,肯定也有想自己写个 npm 包的想法。于是有了这篇文档。

npm 包的开发和平时我们业务项目开发一样,也有易有难,这篇只是简单创建个 npm 包,仅仅做为一个了解入门的项目。

npm 带来的好处?使用场景有哪些?

下面结合平时项目开发,举例两个简单的场景来说明。

场景一:

我们平时项目开发时,通常一套规则,可能会使用到好几个项目中,比如说目前管理端的

  • 判断设备是 argos 设备还是 lora 设备还是普通设备。
  • 根据设备型号和硬件版本推断设备类型名。
  • 判断设备是否是网关。
  • 等...

如果你的业务涉及到多个端,如小程序,公众号,PC端管理端/用户端等,假如,我们公司有这么多端的项目,而且这些项目都用到了某个规则,突然某一天,产品说这个规则需要改一下,或者在原来基础上加一些新的规则,我们要改的话,就需要每个端的项目都去改一遍,而如果这里我们是用的 npm 包来做的话,我们只需要去更改 npm 包的代码规则,然后发布,然后各个项目只需要更新到最新版本的 npm 包即可。

场景二:

一些规定的数据枚举,其实也可以使用 npm 包来返回,如

  • 性别。
  • 设备工作模式。
  • 设备工作状态。
  • 等...

这种通过后端返回某个字段,展示对应的文案,在 npm 包统一返回,前端所有项目,直接拿到对应的数据进行使用即可,如果有所变化,直接改变 npm 包,前端所有项目更新 npm 包版本即可。

场景三:

上面只是涉及到数据类的处理,npm 包,也可以是某个组件,比如说,我们好几个 PC 端的项目,都有涉及到某些公共的模块,每个项目里开发,都会将这部分抽离成一个公共的组件来使用,但是如果是开发一个新的项目,遇到这种模块的时候,我们又得重新在新的项目里封装。这时使用 npm 包来开发,可以直接在新的项目中下载这个包来直接使用。

经常有变动,最好就不要用 npm 包的形式开发了

npm 包,也是有版本管理的,前端不同项目,通过使用不同版本的 npm 包,也能拿到不一样的数据或者可以使用不一样的处理逻辑。

通过上面几个场景,大概应该能了解到 npm 包的开发使用带来了哪些好处了。下面就开始简单介绍 npm 包项目是如何创建、发布、更新的。

npm 包项目的创建

默认 node 已安装。

1. 新建 npm 包项目目录

字面意思,随便使用命令或者手动新建一个目录就行了。

2. 初始化 package.json 文件

npm 包的项目目录建好之后,在此目录下,执行下面命令行,初始化一个新的 package.json 文件。

npm init

如果只输入 npm init ,它会向你提一系列的问题,如果你觉得不用修改,或者创建完之后你再去修改,可以直接一路回车即可。或者执行 npm init -y

3. 绑定远程仓库

这里以 GitHub 为例

  • GitHub 上创建一个新项目。
  • 回到项目目录下,执行 git init 命令初始化。

这个时候通过 git status 就能看到,工作区已经有个上面新添加的 package.json 文件了。

  • 上传当前修改到远程仓库。
git add .
git commit -m 'feat: git init'
git remote add origin git@github.com:kivet-h/kivet_npm_package.git // ? 本地项目绑定远程仓库,地址是仓库的地址
git push -u origin master

4. 新建 README.md 文件

这个文件看似可以无,但在 npm 包开发的项目中十分重要,需要用它来记录你的 npm 包怎么安装,怎么使用,每个版本,更新了哪些内容等,这样使用你 npm 包的开发者才能看懂你 npm 包的相关信息。如果内容过多,还可以建个专门的目录来总结归纳。

暂时先将文件建起,等项目开发完最后再来添加内容。

5. 新建 src 目录,写入逻辑测试代码

通常情况,我们源码都是写在 src 目录下的。在 src 目录下,新建出口文件 index.js 文件以及随便建一个存放逻辑的文件如处理数组的文件 array.js,也可以再创建一个存放基础数据的文件如 baseData.js

index.js 文件,最好只作为一个导出出口文件,不做逻辑的书写。逻辑的书写写在专门的文件里,如上面的

  • array.js 文件,写了一个处理数组的方法,然后导出,
  • baseData.js 文件,写个个基础数据的枚举配置,然后导出。
  • index.js 文件,统一导入项目中所有的逻辑处理或者一些基础数据,再统一导出。

使用这个 npm 包的时候,其实就是导入使用这里 index.js 统一导出的内容。

// index.js

const { joinArr } = require('./array');
const genderData = require('./baseData');

module.exports = {
  joinArr,
  genderData,
};
// array.js

/** 随便写个数组相关的函数,如拼接数组元素 */
const joinArr = (arr = []) => arr.join('-');

exports.joinArr = joinArr;
// baseData.js

module.exports = {
  1: '男',
  2: '女',
};

6. 修改设置包的入口点

上面第一步,使用 npm init 命令创建初始化的 package.json ,它默认的入口文件是项目根目录下的 index.js 文件,上面我们第 5 步新建了 src,并以 src/index.js 文件统一导出,所以需要将项目的入口点改成这个文件

// package.json

{
  "name": "kivet_npm_package",
  "version": "1.0.0",
  "description": "一个简单的 npm 包项目",
    
- "main": "index.js",
+ "main": "src/index.js",
    
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1"
  },
  "author": "kivet",
  "license": "ISC"
}

npm 包本地开发测试

npm 包的开发,其实就可以想象成平时项目中对公共模块的抽离,只是这里将公共模块抽离写成了一个 npm 包而已。但是这样该如何本地测试呢?毕竟不可能一直通过发布 npm 包,然后又去项目中更新到最新版本来测试。我们想要做到的,肯定是本地开发测试都没问题了,然后再发布。

网上有很多解决办法,这里使用的是 yalc

下面假设我们开发了个名为 kivet_npm_package 的本地 npm 包。

  • 全局安装 yalc

npm install yalc -g

有可能安装不起,这有可能是 mac 权限的问题,前面加个 sudo 就行了 sudo npm install yalc -g

  • 发布本地 npm 包

// npm 包项目目录下

yalc publish

  • 本地安装上面发布的 npm 包

可以自己新建一个,或者随便找个什么项目,能安装 npm 包就行

在新的项目中安装上面本地发布的 npm 包

yalc add kivet_npm_package

或安装指定版本

yalc add [my_package_name@version]

安装完成后,可以看到,新的项目中会多出一个名为 yalc.lock文件。

同时,在 package.json 对应的包名会有个地址为 file:.yalc/[my_package_name] 的值。

  • 本地使用上面发布的 npm 包

  • 更新

本地开发,肯定设计到修改,然后重新看结果的情况,这里的更新比较麻烦,不像我们平时前端开发,直接保存后浏览器就自己刷新了,它这里需要两步操作。

  1. 修改了 npm 包项目代码后,在 npm 包项目目录下,重新执行

yalc publish

  1. 在使用这个 npm 包的项目目录下,执行

yalc update

yalc update kivet_npm_package

这样,在使用 npm 包的项目中,才能看到修改后的 npm 包的样子。

附:

暂时我只知道这样子操作才能生效,不清楚是否还有更简便的方式来实现更新。以后要是知道有什么更好的办法,再来加上。

  • 移除

npm 包此次版本开发完成,需要在测试使用这个 npm 包的项目中移除本地发布的 npm

// 在测试使用这个 npm 包的项目目录下执行

yalc remove kivet_npm_package

附:

其它,yalc 的使用方法,自行去翻阅 yalc 相关的文档。

npm 包的发布

本地开发测试完成后,就要将包发布到 npm 上了

  • 注册 npm 账号

要想将自己的 npm 包发布到 npm 上,首先你肯定是需要一个 npm 包的账号的。所以需要先去 npm 官网注册一个 mpm 的账号。

  • 在终端登录 npm

npm adduser

npm login

两个二选其一即可,首次登录,会叫你输入 usernamepasswordemail,这就是上面注册 npm 输入的内容。

  • 发布

登录成功,就可以发布我们的 npm 包到网上了。执行

npm publish

这样,就已经发布成功了,去 npm 官网 搜索,就能看到我们发布的包了。

或者直接访问 www.npmjs.com/package/kiv…npm 包的连接,就是(www.npmjs.com/package/[my…

下载使用发布的 npm 包

还是在刚才测试的项目中来使用,先使用 yalc remove kivet_npm_package 命令移除项目中本地的 npm 包,然后执行 yarn add kivet_npm_package -D 进行安装。

安装成功后,还是刚才那个测试文件测试一下。

npm 包的更新

每次发布 npm 包,都会有一个对应的 npm 包版本号,而且这个版本号还不能与上一次发布的一样,

如上面已经发布了第一个 npm 包,版本号就是 v1.0.0,如果你再次执行 npm publish,是发布不成功了,因为现在的版本号和显示的版本号是一样的。不过如果你忘记修改版本号也没关系,因为你根本推不上去。

这里先简单说一下 npm 版本号的构造。

package.json 中,有这样一个属性: "version":"1.0.0"

这就是 npm 的版本号,它是由三个数字组成。

  • 第一个数字是主版本号。
  • 第二个数字是次版本号。
  • 第三个数字是补丁版本号。

修改对应的版本号,代表着不同的含义:

  • 补丁版本号:仅修复缺陷的版本。
  • 次版本号 :引入向后兼容的更改的版本。
  • 主版本号 :具有重大更改的版本。

再次发布的时候,必须先更新版本号,而且只能通过执行命令的方式来修改,亲测直接手动去修改 package.json 里的 version 是不起作用的。

针对三个不同的版本号,npm 提供了不同的命令行来更新版本号

patch:增加补丁版本号    v1.0.1->v1.0.2
$ npm version patch

minor:增加次版本号     v1.0.2->v1.1.0
$ npm version minor

major:增加主版本号     v1.1.0->v2.0.0
$ npm version major

这里就不测试了,步骤大概就分下面几步:

  • 默认第一个版本(v1.0.0)的 npm 包以开发完成,并成功发布。
  • 假设准备开始开发新的功能,本地开发测试时,还是和上面本地开发测试的步骤一样
  • 开发完成,如想发布一个补丁版本,即递增补丁版本号,执行 npm version patch,这个时候,版本号就变成了 v1.0.1 了
  • 然后发布,执行 npm publish。新的版本的 npm 包发布成功。

提示:

如果你的项目绑定了远程仓库,这个时候你执行修改版本号的命令是会报错的,提示你 npm ERR! Git working directory not clean. ,这个时候直接先将本地代码 push 了就可以了。

npm 包的版本控制

从上面每次发布都需要修改版本号可以看出,npm 包也是有版本控制这一概念的。每个版本都有自己的一套代码逻辑,简单举个路子,如下图

————————分割线

截止这里,npm 包的开发,测试,发布,更新的一系列步骤都已经完成了。下面是针对 npm 包项目做的一些优化配置操作。

package.json 文件部分配置属性解释

首先,先了解一下 package.json 文件的一些配置,这里只说明一些可能需要用到的配置,具体所有的配置对应的是什么功能,可访问 npm 给出的 package.json 配置官方解释文档查看。或者自行 Google

name

npm 包名字。

version

包当前版本号

上面执行修改版本号的命令后,这里也会跟着变化。

description

包的一个简单描述

main

设置包的入口文件

如果你的包需要先 build 打包后再发布,name这个就得设置成你 build 后的打包目录下面的入口文件。

repository

当你需要给你的 npm 包添加你的 GitHub 仓库地址

// ? package.json
{
	"repository": {
    "type": "git",
    "url": "https://github.com/kivet-h/kivet_npm_package"
  },
}

bugs

你的 npm 包,发布到网上去了,如果有人使用并发现有问题,肯定需要有途径来告知你出了什么 bug

// ? package.json
{
	"bugs": {
    "url": "https://github.com/kivet-h/kivet_npm_package/issues",
    "email": "kivet666@dingtalk.com"
  }
}

homepage

为你的项目添加一个首页,这样用户就能快速访问你的项目,通常是项目的 README.md 文档。

// package.json
{
	"homepage": "https://github.com/kivet-h/kivet_npm_package/blob/master/README.md"
}

type

项目中加载模块的方式,它可以被设置成两个值

{
	"type": "commonjs", // Node.js 专用的 CommonJS 模块,不设置 type 时默认是 commonjs
	"type": "module",   // ES6 模块(简称ESM)
}

具体区别可访问查看阮一峰老师的Node.js 如何处理 ES6 模块

其它,遇到需要的再添加...

添加 npm 包发布时忽略文件 .npmignore 文件

这个和 git 提交时的忽略文件 .gitignore 是一个道理的,只是 .gitignoregit 提交时忽略上传某些文件,而 .npmignore 是 npm 包发布时忽略上传某些文件。

// .npmignore
/node_modules

// .gitignore git 的忽略文件也应该添加上
/node_modules

目前暂时只有 node_modules 目录需要被忽略,随着项目的开发进度,肯定会有更多的目录或者文件需要被忽略,后面遇到再说。

添加 TypeScript 支持

安装 TypeScript

yarn add typescript -D

添加 TS 的配置文件 tsconfig.json

大概简单配置下

// tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "module": "ES6",
    "declaration": true,
    "outDir": "./build",
    "strict": true,
    "lib": ["es6"],
    "downlevelIteration": true
  },
  "include": ["src"],
  "exclude": ["node_modules", "**/__tests__/*"]
}

简单介绍下部分配置项的含义

  • target:编译之后生成的 JavaScript 文件需要遵循的标准,可选值:"ES3"(默认),"ES5","ES6"/"ES2015","ES2016","ES2017"或"ESNext"。我们选择了 es5 为了使包具有更好的浏览器兼容性。

  • module:指定生成哪个模块系统代码,默认值:target === "ES3" or "ES5" ? "CommonJS" : "ES6"。

  • declaration:是否生成对应的声明文件,默认值:false。在构建包时,应该设置为 true,这样 TypeScript 会将生成的声明文件和对应编译后的 JavaScript 代码一起导出,以便包可以在 TypeScript 和 JavaScript 项目中同时使用。声明文件,就是打包 ts 代码后,生成的每个 js 文件,对应的一个后缀名为 .d.ts 的文件。

  • outDir:指定输出目录。如上面的配置,编译后的 JavaScript 代码会在与 tsconfig.json 同级的 build 文件夹中。

  • strict:是否启用所有严格类型检查选项,默认值:false。

  • lib:编译需要的库文件,例如你指定的 target 是 ES5,但是在代码中使用了 ES6 特性,就需要在 lib 中加上 ES6。默认值:如果 lib 没有指定默认注入的库的列表,默认注入的库为:

    • target ES5:DOM,ES5,ScriptHost。
    • target ES6:DOM,ES6,DOM.Iterable,ScriptHost。
  • include:指定要编译的目录。

  • exclude:指定不编译的目录。node_modules 和 tests 只是在开发阶段使用,构建阶段无需编译。

其它更多编译选项,可访问 此链接 查看。

将之前所有 .js 文件后缀改成 .ts

将后缀名改了之后,你会发现有很多报错,

一些是 TS 的校验报错,有些 TS 校验报错是可以不需要的,直接去 tsconfig.json 文件中去配置即可

还有一些原因,是因为 nodeCommonjs 导致的,前面说了, package.json 中的 type 配置,如果没有设置,默认就是 commonjs ,这就可能导致一些 TS 校验报错的问题,将 type 设置成 "mudole",以 ES6 的模块形式加载

// package.json
{
	"type": "module"
}

如果你非要想要在 commonjs模式下开发,也没问题,不过需要安装额外的名为 @types/node 的一个包,但是这个包也并不能解决所有的 TS 校验报错的问题,

如当你在 array.ts 文件中使用 joinArr 函数名导出,然后在 index.ts 文件下也使用 joinArr 这个名字导入,就会报

之所以会这样,是因为在 Commonjs 规范里,没有像 ESModule 能形成闭包的「模块」概念,所有的模块在引用时都默认被抛至全局,因此当再次声明某个模块时,TypeScript 会认为重复声明了两次相同的变量进而抛错。

所有干脆就直接将模式改成 ES6,毕竟我们平时写代码也是这个模式下写的。

然后优化下之前写的代码

// src/array.ts

/**
 * 拼接数组
 * @param arr 需要拼接的数组数据
 * @param {string} str 以传入 str 进行拼接,默认为 '-'
 * @returns {string} 拼接完成后的字符串
 */
const joinArr = <T>(arr: T[] = [], str: string = '-'): string => arr.join(str);

/**
 * 数组去重
 * @param arr 需要去重的数组数据
 * @returns 去重完成后的数组
 */
const clearSame = <T>(arr: T[] = []): T[] => [...new Set(arr)];

export { joinArr, clearSame };
// src/baseData.ts

/**
 * 性别基础数据枚举
 */
export const genderData = {
  1: '男',
  2: '女',
};
// src/index.ts

export { joinArr, clearSame } from './array.js';
export { genderData } from './baseData.js';

// !!! 注意:type: 'module' 后,导入文件的 .js 后缀不能省略
参考:https://stackoverflow.com/questions/65384754/error-err-module-not-found-cannot-find-module

我们平时写 ts 的项目可以省略,是因为通过 webpack 等工具配置了的,比如说它会默认先找 .ts,没有再找 .es,再找 .js
参考:https://segmentfault.com/q/1010000009891745

提示:

养成写注释的好习惯,这样开发者在使用你的包的时候,能一眼看出你的数据是什么数据,你的方法是干什么用的,需要传些什么值

在 package.json 文件中添加 build script

// package.json

{
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1",
    "build": "tsc"
  },
}

node 是无法直接运行 .ts 的文件的,所有需要将项目所有的 .ts 文件都编译成 .js 文件。

执行 npm run build 测试一下,发现成功打包。

这里每个文件对应的 .d.ts ,就是上面说的声明文件。

重新配置忽略文件

// .npmignore
/node_modules
/src

// .gitignore
/node_modules
/build

npm 包发布时,直接发布打包后的 build 目录即可,所以源码文件目录 src 可以忽略上传。

git 代码提交时,直接提交源码目录 src 即可,所有打包文件目录 build 可以忽略上传。

修改入口文件

上面说了,npm 包上传时,只会上传打包目录 build 目录,所有线上的入口文件也需要换成 build/index.js

// package.json
{
	"main": "build/index.js",
}

优化

前面,通过 .npmignore 文件,可以忽略部分文件或目录的上传,相当于是一个黑名单,但是这并不是很友好,因为这样的话,你每在根目录下新增一个文件或目录,都要在这里面添加,我们想要的只是将打包后的文件上传,要是有个白名单,设置只上传打包后的目录文件即可。

// package.json

{
	"files": ["build/**/*"]
}

然后删除 .npmignore 文件。

完善 README.md 文档

每次新的一个版本的 npm 包的开发,都需要在 README.md 文档中记录清楚,无论是增加、删除还是修改了什么功能。

还有,你新增加的功能是怎么使用的。给个简单的示例 demo

其它

其它一些配置

  • 添加代码校验命令行。
  • 添加格式化命令行。
  • 添加webpack。
  • 添加git提交规范。
  • 等等。。。

不一一说明了。

其它创建 npm 包项目

可以直接使用 TSDX,有点像 reactcreate-react-app,直接开箱即用。

如果是想使用 react + TS 等开发一个组件,也可以使用 dumi

应该还有其它方式,目前只知道这些。


参考链接:

[0] 如何优雅地在本地测试 npm 包