对于前端菜鸟的我而言,npm的用途就是管理项目以及安装各种工具包。但进入职场之后,接到一个需求,要求我把项目中某个功能抽离出来,单独发布一个 npm 包。脑子第一想法,我这菜鸟也配发布 npm 包。但是就算再菜,需求来了,也要硬着头皮上啊。本文就记录个人是如何去发布了一个属于自己的 npm 包的。
node.js
中对于包的定义如下:
包是由
package.json
文件描述的文件夹树。 包由包含package.json
文件的文件夹和所有子文件夹组成,直到包含另一个package.json
文件的下一个文件夹或名为node_modules
的文件夹。
一 、创建npm包
npm 包,通俗点说就是一个 node.js 项目,只不过这个项目进行了一些特殊配置和处理,并且注册到了 npm 仓库中,可共享给所有开发者。
所以创建一个包,其实就是创建一个项目,本文就以dali-publish-test
为例:
- 首先创建一个名为
dali-publish-test
的文件夹 - 然后命令行切换到当前目录,然后执行
npm init
,快速初始化。(该命令其实就是在当前目录下创建一个package.json
文件,其中包含项目的一些基本信息,用于管理项目)
这样我们就初始化好一个 npm 包了:

本文采用的是最基础的方式初始化一个 npm 包,当然社区中已经有很多成熟的构建工具。
紧接着,我们为该包创建一个 git 仓库,用于进行包的版本控制。如下图所示,在github(或:gitlab、gitee等)中创建一个新的仓库:

然后依次执行以下命令:
ruby
git init
git add .
git commit -m "first commit"
git branch -M main
git remote add origin <[email protected]>:dali-yy/dali-publish-test.git
git push -u origin main
这样就可以将本地创建的包和原创仓库建立联系了。
创建了git仓库之后,那么就可以在 package.json
文件中更新 repository
字段信息如下:
json
"repository": {
"type": "git",
"url": "创建的仓库地址"
}
最后,我们在根目录下新建一个index.js
文件,并输入以下内容:
JavaScript
function daliPublishTest() {
console.log('dali-test-publish');
}
module.exports = daliPublishTest;
这样一个简单的 npm 包就创建好了。
注意:这里介绍的只是创建包的一种简单方式,主要是用作示例。创建一个包并不是一定要按上述流程,还有其他更好的方式。
二、发布npm包
上述创建好一个简单的 npm 包之后,接下来进入主题,如何将该包发布到 npm 仓库呢。
2.1 创建 npm 账户
在发布之前,我们首先要创建一个 npm 账户,链接:www.npmjs.com/signup
2.2 添加注册表用户
创建好 npm 账户之后,我们可以使用 npm adduser
命令在指定的注册表中创建新用户,并将凭据保存到 .npmrc 文件中。(如果未指定注册表,则将使用默认注册表)
markdown
// 按 npm 官方提示,`npm adduser` 后面会被拆分为 `npm login` 和 `npm register` 两个命令
npm adduser
如下图所示,我们执行 npm add-user
命令之后,需要输入 Username
、Password
、Email
等信息,然后输入邮箱收到的验证码,验证成功后即可添加。

需要注意的是,为了提高安装速度,大部分人的镜像源可能已经切换到其他的镜像源。这可能会因为没有权限而导致您执行npm addUSer
失败。所以,如果执行失败,可尝试执行如下命令,切换到官方镜像源:
arduino
npm config set registry https://registry.npmjs.org/
2.3 发布包
上述准备工作都完成以后,我们就可以执行如下命令发布npm包了。
npm publish

然后我们去官网搜索 dali-publish-test
,如下图所示:

这样一个简单的 npm 包就发布好了。
2.4 使用包
npm 包发布好之后,我们就可以执行如下命令安装该包:
css
npm i dali-publish-test
然后在代码中按如下方式使用:
JavaScript
const daliPublishTest = require('dali-publish-test');
daliPublishTest();

2.5 更新包
后期想要对包进行更新迭代,我们可以使用 npm version
来更新包的版本号。
arduino
// patch:补丁号,修复bug,小变动,如 v1.0.0->v1.0.1
npm version patch
// minor:次版本号,增加新功能,如 v1.0.0->v1.1.0
npm version minor
// major:主版本号,不兼容的修改,如 v1.0.0->v2.0.0
npm version major
例如我对包的内容进行简单的修改后,然后执行npm version patch
命令,包的版本号就更新到了 1.0.1
(执行npm version
命令之前要确保git已经提交)

然后再执行 npm publish
即可更新包的版本了。

2.6 取消发布
我们可以执行如下命令从这将从注册表中删除软件包版本,删除其条目并删除 tarball。
css
npm unpublish --force

注意:
- 按官方提示,如果您的目的是鼓励用户升级,或者您不想再维护软件包,请考虑使用 deprecate 命令。所以,尽量不要去执行
npm unpublish
。- 这个命令是删除软件包版本,而不是移除该包。如上图所示,只是移除了 [email protected]版本而已。去npm官网仍然可以搜索到 [email protected] 版本。
三、npm cli包
npm 不仅支持发布像上述这样包去共享代码,也支持发布一个 cli 工具。
例如我们安装 webpack
的同时,也会伴随安装 webpack-cli
工具。安装好后,我们就可以在命令行执行 npx webpack
命令去对项目进行打包。(关于为什么安装了 webpack-cli
之后,尽可以在命令行执行 npx webpack
呢?具体原因可见此推文。)
那么,如果我们想要发布一个这样的 cli 工具,需要进行哪些特殊处理呢。我们以上述发布的包为例,继续进行如下修改:
- 首先我们创建
bin
目录,并在该目录下新建一个cli.js
文件,文件内容如下:
JavaScript
#!/usr/bin/env node
const daliPublishTest = require('../index');
function main() {
daliPublishTest();
}
注意 :文件开头的
#!/usr/bin/env node
一定不能省略 ,这是声明执行当前脚本的环境是node
环境。
- 在
package.json
文件中添加bin
字段,它是命令名到本地文件名的映射:
json
{
"bin": {
"dali-publish-test": "./bin/cli.js"
}
}
其中:
dali-publish-test
:命令名./bin/cli.js
:实际执行的脚本文件
至此,我们就可以执行 npm publish
进行发布了。
发布完成后,我们再去安装该包,在命令行执行 npx dali-publish-test
就可以看到命令行输出 "dali-publish-test"。

此外,市面上还提供了很多好用的 npm
包来协助我们发布一个 cli
工具:
- mri、yargs-parser:用于解析命令行参数
- cli-color、chalk:用于设置命令行文本颜色
- commander.js:Node.js 命令行界面的完整解决方案。
- ......
四、 同时支持 CommonJS 和 ES module
上述我们发布的包,未进行任何额外配置,会默认被视为 CommonJS
模块。但目前社区发布的很多包都是同时支持ES模块
和CommonJS
的,所以这里我们也来探索一下如何发布一个能够同时支持这两个模块的包。
探索之前,首先介绍 package.json
文件中的几个相关字段:
4.1 包入口点
在 package.json
文件中,main
和 exports
两个字段都可以用于定义包的入口点,也均适用于 ES Module 和 CommonJS。
main
:仅定义包的主要入口点。(所有 node.js 版本都支持,且 node.js 10 以下版本该字段必须)exports
:"main"
的现代替代方案,允许定义多个入口点、环境之间的条件入口解析支持,并防止除"exports"
中定义的入口点之外的任何其他入口点。此封装允许模块作者清楚地为他们的包定义公共接口。
json
{
"name": "my-package",
"exports": {
/* 相当于 main 字段的语法糖*/
".": "./lib/index.js",
"./lib": "./lib/index.js",
"./lib/index": "./lib/index.js",
"./lib/index.js": "./lib/index.js",
"./feature": "./feature/index.js",
"./feature/index": "./feature/index.js",
"./feature/index.js": "./feature/index.js",
"./package.json": "./package.json"
}
}
4.2 type
字段
type
字段定义了 Node.js 应该如何解释 .js
文件。
4.3 条件导出
条件导出提供了一种根据特定条件映射到不同路径的方法。CommonJS 和 ES 模块导入都支持它们。
比如,包想要为 require()
和 import
提供不同的 ES 模块导出可以这样写:
json
// package.json
{
"exports": {
"import": "./index-module.js",
"require": "./index-require.cjs"
},
"type": "module"
}
所以如果我们想发布一个同时支持 ES Module 和 CommonJS 的包,首先我们需要生成两套规范的代码,并在package.json
文件中通过 exports
字段进行配置即可。
关于代码的生成或打包,大家可以考虑使用 Rollup
。
5. 发布一个 TypeScript 包
TypeScript是JavaScript的的超集,其与JavaScript的主要区别在于类型声明。
如果您的包是使用 TypeScript 编写的,只需在发布前使用 tsc
将ts代码编译成js代码,并生成类型声明即可。
本文到这里就结束啦!