乐趣区

关于npm:如何编写一个健壮的-npm-包-京东云技术团队

无脑公布 npm

比方老王我,用 npm init 新建一个包,改把改把,而后来个npm publish,so easy ✌️!

Too young too naive, baby 👶!

请容我讲述一些公布过程中踩过的坑。

首先,算了也能够之后有空再说,咱们须要通读 npm 的配置文档。

package.json doc

通用性👷

指定公布文件

利用 package.jsonfiles字段精简公布体积。

{"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/

准确指定dependenciesdevDependenciespeerDependencies

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 可依据是否应用而优化相干的代码。

如果 sideEffectstrue,则一旦引入,不论是否调用都不能被 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"
}

modulejsnext: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"
}

其中 unpkgumd:mainjsdelivr 都是为了更宽泛兼容的指向浏览器环境运行的同一个指标别名。

通常来说 commonjsesmoduleumd 都不会将其依赖的其余包包含进去,只是在运行时才加载。

还有一种状况,可能只有我本人用到过,就是公布包中有些货色与外部环境有抵触,因而除了这些通用模式之外我又加了一个 independent(取名叫standalong 也比拟适合)格局,将这个包的所有依赖都封装进去,能够不依赖外部环境独立应用。

例如 mobx-value 的独立运行文件。

mobx-value independent

留神浏览器环境输入的都是优化后的 .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,校验工具为joiajv,提醒输入工具为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,而后配上airbnbprettier的校验规定。

校验有两个重要作用,一个是真的能解决很多隐性 bug,另一个是代码丑陋,之后看你我的项目源码的人也会感觉难受,要害是面试时也能拿的出手。

如果有面试者给我看本人的开源作品,如果代码格调都不行,立刻就断定不行,也不必再看什么逻辑能力了,招进来也是挖坑。

好的代码格调必须依赖校验工具,最好把校验流程也集成到公布的钩子上。

推广性🤹

文档

应用 .markdownlint 配置标准本人的 markdown 文档,否则很容易写飞了。

要不人家一看文档,我的项目品质很容易就露馅了不是🤭

配套展现用例

  • 一个办法是在我的项目中自带一个可运行的样例,让人 clone 之后运行指定命令即可查看样例。
  • 更好一些,部署一个能够在线查看的例子,并在主文档上附上中转链接。
  • 更进一步,我的项目增大之后,须要阐明的中央越来越多,一个 README 曾经太长。应用 docusaurus 等相似的工具部署一个独立的文档站点。

有否自动化版本治理

Why?因为版本号与兼容性是强相干的,具体参考 semver 标准。

  • 应用 husky/yorkie 等标准提交日志。
  • 应用 standard-version 等主动生成 CHANGELOG 并依据规定主动晋升版本号。

最初留个作业

  • 你有什么 npm 公布时的要害教训这里没提到的,帮我补充下🤝
  • 当咱们再一次运行npm publish,脑编译一下,想想这期间都产生了些什么,还少些什么?

作者:京东批发 王凡

内容起源:京东云开发者社区

退出移动版