开发 Node.js lib 个人最佳实践
Jan 06, 2023
偶尔会团队开发一些 Node.js 工具库之类的小玩意,这篇文章就简单介绍一下我经常会用到的工具和一些实践,想到哪写到哪。
创建项目
GitHub 上有一些模板仓库,例如 egoist/ts-lib-starter 和 antfu/starter-ts,你可以点击 Use this template,GitHub 就会自动帮你创建一个仓库。 如果你想要参考的仓库不是一个 template 仓库,又或者你这是一个公司内部的项目,需要你手动创建 git,那么你也可以使用 degit 这个工具。 例如我想使用 egoist/ts-lib-starter 来创建一个本地项目:
~ degit https://github.com/egoist/ts-lib-starter my-lib
~ cd my-lib
~ git init
包管理器
我所有个人新项目都使用 pnpm 作为包管理器,快速,节约硬盘空间。
请在项目根目录创建一个 .npmrc
文件,里面写好你的 npm registry,不要依赖全局的配置。
# .npmrc
registry = <private npm registry or public mirror>
Typescript
不管多么简单的库,我都是用 ts。本地调试的时候,可以使用 tsx 这个包,它让你执行 ts 文件就像执行一个普通的 js 文件一样。一个简单的 tsx index.ts
就能运行。不需要 tsc 编译,也比 ts-node 要快。
关于 tsconfig.json
tsconfig 的配置我想说两个东西。target 和 moduleResolution。
- target:Node.js v12 已经 end of life 了,因此建议将项目 Node 版本至少升级到 14。Node 14 版本已经支持了大部分 ES2020 的特性了,参考 node.green。因此我们也可以将 target 设置为 ES2020 或者更低的版本。2023 年了,不要再写
target: "es5"
了。 - moduleResolution:建议设置为
Node16
或者NodeNext
,这样 ts 会支持package.json
里的exports
特性,否则的话你的 VS Code 可能会给你报错,具体参考 Announcing TypeScript 4.7
附上我个人使用的 tsconfig.json
{
"compilerOptions": {
"target": "ES2018",
"module": "ESNext",
"lib": [
"ESNext"
],
"jsx": "preserve",
"resolveJsonModule": true,
"esModuleInterop": true,
"moduleResolution": "Node16",
"allowJs": true,
"strict": true,
"skipLibCheck": true,
"noUnusedLocals": true,
"noImplicitAny": true,
"sourceMap": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true
}
}
打包
打包工具使用 tsup,简单易用,基于 esbuild,快速,支持 watch。
一行简单的 tsup src/index.ts --format cjs,esm
就能生成支持 CommonJS 和 ES Module 的产物。--dts
可以生成类型。
可以参考 文档 和 这篇 blog。
需要注意的是 tsup 是一个 bundler,因此它可能会将你的依赖一起打进去。如果你不想将某个依赖打进去可以使用 --external <package>
。dependencies
和 peerDependencies
默认是 external 的,所以正确的区分你的依赖是 dev 还是 prod 很重要。
package.json
请在你的 package.json
里补充上你的 publishConfig.registry
,避免你的一个内部包发布到外网。
// package.json
{
"publishConfig": {
"registry": "<private npm registry>",
"access": "public"
},
"files": [
"dist",
"CHANGELOG.md",
"README.md"
],
}
如果你的包是一个带有 scope 的包,还得在 publishConfig
里补充上 "access": "public"
,否则会被当作是 private 的无法发布。
声明 files
字段,不要把一些无关的文件给带上,导致增加 package 的体积。
关于 package entry points
Node 16 已经支持了识别 package.json 下的 exports 字段。大部分情况下抄以下的配置就行了
// package.json
{
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"require": "./dist/index.cjs",
"import": "./dist/index.mjs",
"default": "./dist/index.cjs"
}
}
}
main
、module
和 types
作为 fallback,为了兼容不支持 exports
字段的环境。
建议熟读 Node.js 文档, 也可以参考 打包JavaScript库的现代化指南。
发布工作流
使用 changesets。
在你 commit 之前,运行下 pnpm changeset
生成 changeset 文件。到要发布之前运行
pnpm changeset version && git commit -am 'bump version' && git push
最终 publish 就行了。