如何编写一个健壮的 npm 包
无脑发布 npm
比如老王我,用npm init新建一个包,改把改把,然后来个npm publish,so easy ️!
Too young too naive, baby !
请容我讲述一些发布过程中踩过的坑。
首先,算了也可以之后有空再说,我们需要通读npm的配置文档。
通用性
指定发布文件
利用package.json中files字段精简发布体积。
{
"files": ["dist", "lib", "module"]
}
若不指定files,每次发布会把所有不以.开头的文件都发布出去,导致发布体积过大(node_modules默认也不会被发布)。
README.md作为主文档,加不加都会发布,package.json也是。
指定源代码
{
"source": "src/index.ts",
"repository": {
"type": "git",
"url": "https://github.com/yourname/yourproject.git"
}
}
通常来说我是不在npm发布中包括源代码的,因此都没有加过source字段,只是用repository来告知一下git仓库地址即可。
如果仓库是内部仓库或私人仓库并不对外,则source字段就有用了,将源代码发布后可让人帮忙debug找问题。
注意如果有source,则files也要加上souce对应的文件或文件夹。
发布sourcemap
一般来说我们发布的都是经过编译的代码,为了给使用者方便调试,只要不是源码,都要有对应的sourcemap文件,例如发布了一个dist/index.js则也需要一个dist/index.js.map文件与之配套。
指定安装源
如果你从来不用私有源,可跳过该项。
利用.npmrc指定安装源,用于当前项目与你的全局配置区分开。
否则当前项目很可能指定的内部npm源,导致外部用户无法利用lock文件安装。
例如
registry=https://registry.npmjs.org/
精确指定dependencies、devDependencies、peerDependencies
dependencies要尽量少,只有在运行时确实用到才放进去。
依赖的版本号要清晰指明,如"react": "16.x || 17.x"
否则,如果指定了"react": "17.0.0",则在使用了react 16的项目中,会引入两份react,造成一些莫名其妙的问题。
这种情况,react应放到peerDependencies中。
指定发布目标
如果你从来不在私有源发布,可跳过该项。
在package.json中指定发布地址,在当前包与全局配置不一致时非常必要。
{
"publishConfig": {
"registry": "https://registry.npmjs.org"
}
}
sideEffects
对应配置:
{ "sideEffects": false }
作用:在打包时进行treeshake可根据是否使用而优化相关的代码。
如果sideEffects为true,则一旦引入,不管是否调用都不能被treeshake掉。
专用性
类型配套
无论针对哪个环境,目前自带类型已经是既成事实的标配。
记得生成类型的.d.ts文件,并在package.json中指定。
{
"types": "type/index.d.ts",
"typings": "type/index.d.ts"
}
我一般会用一个专用的tsconfig.declaration.json来专门生成类型:
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": false,
"emitDeclarationOnly": true,
"declaration": true,
"outDir": "types"
}
}
作为后端库
package.json中指定main字段。
编译结果需要在nodejs环境中运行,输出commonjs格式模块。
为了兼容最新与将来,同时也要输出esmodule格式模块。
相关配置:
{
"main": "lib/index.js",
"module": "module/index.js",
"jsnext:main": "module/index.js"
}
module与jsnext:main都是指esmodule格式,只是为了兼容某些特殊环境的别名。可能还有其他别名单我暂时就见过这俩。
其中module中的文件推荐使用特定的后缀名,例如.esm.js或.mjs,但在一些工程相关工具中是否会有未知为题,不好说。
未来已来,现在大部分前端工程工具都会优先使用module指定的文件,单如果没有指定module,也会为了兼容去加载main。
作为前端库
前端库其实要求比后端库更高,为啥?
因为现代前端开发环境要求支持所有后端环境,并延伸出前端环境的额外支持。也就是说后端库要求一般是前端库要求的子集。
需要扩展的是纯前端环境的运行格式,老格式amd已经被淘汰可以不用考虑,现在基本都被umd格式统一。
{
"main": "lib/index.js",
"module": "module/index.js",
"unpkg": "dist/index.js",
"umd:main": "dist/index.js",
"jsdelivr": "dist/index.umd.production.min.js"
}
其中unpkg,umd:main,jsdelivr都是为了更广泛兼容的指向浏览器环境运行的同一个目标别名。
通常来说commonjs,esmodule,umd都不会将其依赖的其他包包括进去,只是在运行时才加载。
还有一种情况,可能只有我自己用到过,就是发布包中有些东西与外部环境有冲突,因此除了这些通用模式之外我又加了一个independent(取名叫standalong也比较合适)格式,将这个包的所有依赖都封装进去,可以不依赖外部环境独立使用。
例如mobx-value的独立运行文件。
注意浏览器环境输出的都是优化后的.production.min格式,也必须同时输出.development后缀的开发模式,为了方便使用者调试方便。
因为最大的使用者,往往就是我们自己,不要连自己都糊弄了事~
作为命令行工具
多配置兼容
命令行工具一般需要很多参数,例如tsc,当参数过多时没人愿意每次都输入长长的参数,因此需要配置文件的支持。
那么选哪种配置格式呢?
此时cosmiconfig隆重登场!以一句名言形容,小孩子才做选择,成年人全都要!
兼容各种配置,各种位置,详情参见其api。
还有一点,如果需要读取一些周边的json配置,不要用原生的JSON.parse,很多json是带注释的或者编写不规范,用json5读取兼容好。
还有一个精简版:lilconfig,功能差不多,我下次打算试试。
配置文件类型校验
刚入门typescript时,我尝试用typescript作为配置文件,然后在运行时利用类型机制达到校验配置的目的。
但这样会丢失很多灵活性,限制死了配置文件的来源与格式,并由于库的typescript环境与应用所在的typescript环境不一致,也导致了很多工程问题(对我说的就是ts-gear)。
后来发现通过注释文档的方式,js文件中也同样可以校验类型,而且js文件对运行时更友好。
例如webpack.config.js这样配置
/**
* @type {import('webpack').Configuration}
* */
const config = {...}
export default config
配置文件运行时校验
我们的程序要读配置,但配置是使用者提供的,谁知道用户会写些什么,即使有上面那步提到的类型校验把关,也会有很多边界问题类型根本管不了。
因此,运行时配置数据校验就是必备环节。
不光是校验不通过时终止运行,还必须给出一个合理且精准的错误提示。
推荐一个协议、两个校验工具与一个漂亮的格式化提示工具。
协议是json schema,校验工具为joi或ajv,提示输出工具为chalk。
指定可运行文件
在package.json中指定bin:
{
"bin": "bin/run.js"
}
对于大部分js脚本,都要在运行文件头部指定运行环境。
#! /usr/bin/env node
然后别忘了在发布前添加可执行属性,务必整合在自动化发布脚本中。
chmod +x bin/run.js
可调用api
例如babel,我们不光能使用@babel/cli在命令行使用,也可以在自己的程序里import babel from 'babel'来调用其api。
一个命令行工具通常也是一个第三方库,方便集成到调用者自身的脚本与环境中。
其他特定环境
例如针对react-native,这个我就见过,没实际用过。
{
"react-native": "dist/index.esm.js"
}
最后不论什么格式,都记得输出配套sourcemap的.map文件。
健壮性
指定运行环境:engine与os
尤其对于命令行工具,这俩点很重要,不然很容易就换个人换个电脑就莫名报错。
{
"engine": "node>=14",
"os": ["linux", "darwin"]
}
有否配套测试用例
- 有可运行的配套测试用例。
- 在
README.md上有可见的测试覆盖率统计,让人可以放心使用。
测试用例放在哪?
最初我习惯按照jest推荐的模式,将所有测试用例放在__tests__文件夹内。
最近两年看了好多别的语言的单测用例,我现在更倾向于将测试文件与源文件放在一起。因为测试用例,就是源代码的一部分!
比如以下这种目录结构
src/setter.ts
src/setter.test.ts
测试运行时机
npm prepublishOnly的钩子一定要加上运行测试用例。
有余力的情况,可以再配置个额外的流水线,github上有好多免费的配套流水线,自己折腾折腾。
代码校验配套
项目必须有一个较好的文档规则校验流程,大多数情况我使用eslint,然后配上airbnb与prettier的校验规则。
校验有两个重要作用,一个是真的能解决很多隐性bug,另一个是代码漂亮,之后看你项目源码的人也会觉得舒服,关键是面试时也能拿的出手。
如果有面试者给我看自己的开源作品,如果代码风格都不行,立即就判定不行,也不用再看什么逻辑能力了,招进来也是挖坑。
好的代码风格必须依赖校验工具,最好把校验流程也集成到发布的钩子上。
推广性
文档
使用.markdownlint配置规范自己的markdown文档,否则很容易写飞了。
要不人家一看文档,项目质量很容易就露馅了不是
配套展示用例
- 一个方法是在项目中自带一个可运行的样例,让人
clone之后运行指定命令即可查看样例。 - 更好一些,部署一个可以在线查看的例子,并在主文档上附上直达链接。
- 更进一步,项目增大之后,需要说明的地方越来越多,一个
README已经太长。使用docusaurus等类似的工具部署一个独立的文档站点。
有否自动化版本管理
Why?因为版本号与兼容性是强相关的,具体参考semver规范。
- 使用
husky/yorkie等规范提交日志。 - 使用
standard-version等自动生成CHANGELOG并根据规则自动提升版本号。
最后留个作业
- 你有什么
npm发布时的关键经验这里没提到的,帮我补充下 - 当我们再一次运行
npm publish,脑编译一下,想想这期间都发生了些什么,还少些什么?
作者:京东零售 王凡
内容来源:京东云开发者社区
如何编写一个健壮的 npm 包的更多相关文章
- 开发一个健壮的npm包
项目地址:loan-calculate-utils npm包的发布.更新查看上一篇文章 开发一个基础的npm包 目前我们的目录是这个样子: . ├── source 源代码目录 │ └── ind ...
- 开发一个基础的npm包
初始化项目 # 新建文件夹 mkdir whosmeya-npm-package-test # 进入 cd whosmeya-npm-package-test/ # 初始化 package.json, ...
- 如何开发一个自己的npm包
目录 一.初始化npm包 二.新建自己的工具类 三.新建入口文件index.js 四.编写单元测试 五.登录仓库 六.发布包 七.安装使用 八.删除包 一.初始化npm包 npm init 运行输入包 ...
- IM服务器:编写一个健壮的服务器程序需要考虑哪些问题
如果是编写一个服务器demo,比较简单,只要会socket编程就能实现一个简单C/S程序,但如果是实现一个健壮可靠的服务器则需要考虑很多问题.下面我们看看需要考虑哪些问题. 一.维持心跳 为何要维持心 ...
- 如何自己写一个公用的NPM包
以markdown-clear,创建过程为例,讲解整个NPM包创建和发布流程 1 如何创建一个包 1.1 创建并使用一个工程 在GitHub上新建一个仓库,其名markdown-clear clone ...
- 发布一个简单的npm包
本文简单地记录了发布一个简单npm包的过程,以便后续参考使用. 初始化npm init 通过npm init创建一个package.json文件 D:\robin\lib\weapp-utils> ...
- npkill 一个方便的npm 包清理工具
npm 包很好用,但是占用空间太多了,npkill 提供了一个方便的工具,可以帮助我们查找安装的npm 包,以及进行清理 安装 npm install -g npkill 简单使用 命令 npkill ...
- npm_一个有意思的npm包
$ npm install yosay const yosay = require('yosay'); console.log(yosay('Hello, and welcome to my fant ...
- 如何发布一个 TypeScript 编写的 npm 包
前言 在这篇文章中,我们将使用TypeScript和Jest从头开始构建和发布一个NPM包. 我们将初始化一个项目,设置TypeScript,用Jest编写测试,并将其发布到NPM. 项目 我们的库称 ...
- (转)前端开发-发布一个NPM包之最简单易懂流程
原文地址:https://www.cnblogs.com/sghy/p/6829747.html 1.npm官网创建npm账户 npm网站地址:https://www.npmjs.com/ npm网站 ...
随机推荐
- pytorch的dataset与dataloader解析
整理一下pytorch获取的流程: 创建Dataset对象 创建DataLoader对象,装载有dataset对象 循环DataLoader对象,DataLoader.__iter__返回的是Data ...
- T-Dubbo,最好的RPC接口测试工具,支持nacos、zookeeper两大主流注册中心,真香!
这可能是有史以来最好用的RPC接口测试工具 文末有视频简介 获取方式 一只小Coder 简介 T-Dubbo,是一个基于Dubbo的全自动RPC接口测试平台为当下最流行的微服务架构中的RPC接口提供了 ...
- 深入理解 Python 虚拟机:集合(set)的实现原理及源码剖析
深入理解 Python 虚拟机:集合(set)的实现原理及源码剖析 在本篇文章当中主要给大家介绍在 cpython 虚拟机当中的集合 set 的实现原理(哈希表)以及对应的源代码分析. 数据结构介绍 ...
- Redis 缓存雪崩 |击穿 |穿透 概念及解决方案
一.雪崩 1.概念 指某一时间段,缓存集中过期失效,无数的请求绕开缓存,直接访问数据库. 2.解决方案 让redis数据永不过期,这种方式最可靠的.最安全的,但占用空间,内存消耗大,并且不能保持数据 ...
- 简单入门echart方法
图表用echart, 然后前端的 HTML 跟 nodejs , nodejs 去调用 后端PHP的接口 链接:https://www.jianshu.com/p/1f2c37c5c02f 官网:h ...
- [Java]排序算法>插入排序>【折半插入排序】(O(N*N)/稳定/N较大/无序/顺序存储)
1 折半插入排序 1.1 算法思想 相比于[直接插入排序]:采用"顺序查找法"查找当前记录在已排好序的序列中的插入位置, 折半插入排序利用"折半查找法"快速查出 ...
- day84:luffy:优惠活动策略&用户认证&购物车商品的勾选/结算
目录 1.课程列表页活动和真实价格计算 1.优惠活动策略的model表结构 2.课程列表页显示优惠类型名称 3.课程列表页显示真实价格 4.将优惠类型名称和真实价格显示到前端页面上 5.课程列表页显示 ...
- ORA-17629: Cannot connect to the remote database server
rman远程连接目标库,提示报错ORA-17629: Cannot connect to the remote database server,首先排查网络问题是否通路,结果发现目标端防火墙是开着的, ...
- JAVASE和JAVAEE的区别
JAVASE和JAVAEE的区别 JavaEE: Java Enterprise Edition,Java企业版,多用于企业级开发,包括web开发等等.企业版本帮助开发和部署可移植.健壮.可伸缩切安全 ...
- MySQL(四)用户与权限管理
用户与权限管理 用户管理 MySQL用户分为普通用户和root用户,提供了许多语句来管理包括登录.退出MySQL服务器.创建用户.删除用户.密码管理和权限管理等内容. 登录MySQL服务器 mysql ...