Skip to content

写一个 TypeScript 库

要全自己折腾的话,或许会陷入无尽的坑,所以我选择从偶像的 starter-ts 开始,按照自己的习惯进行一些改造。

技术栈选择

TypeScript + ESLint + Prettier

我依然倾向于将格式化和代码检查分开,关于具体的细节,可以参考我在机密术典写的 代码格式化代码检查 这两篇文章。

pnpm + bunchee + tsx + vitest

为了方便的测试我们写的库,pnpmworkspace 是必不可少的,可以很方便的开一个 playground。 此外,它默认不提升依赖的特性也能够防止我们不小心引用了没有定义的依赖。

使用 bunchee 来完成打包任务,它读取 package.json 中的 exports 字段作为打包的输入输出,无需手动指定配置。

如果你希望更清楚精细的控制打包流程,可以使用 rollup 配合一些插件来自己写配置。这里有一些常用的插件推荐。

  1. rollup-plugin-dts
  2. rollup-plugin-swc 或者 rollup-plugin-esbuild
  3. @rollup/plugin-node-resolve
  4. @rollup/plugin-commonjs

如果你想看看类似 bunchee 的其它选择,可以看看 unbuildtsup

开发过程中,非必要的情况下,基本上没人想先打包再跑代码。因此,我使用 tsx 来直接执行 ts 文件,用 vitest 来测试代码。

正确设置 package.json

便捷地维护包的基本信息

作为一个起手模板,它需要提前写好包的基本信息,并可以在开一个新坑的时候快速的查找替换。

基本信息处于两个位置,一个是 package.json,一个是 README.md,通过全局替换 pkg-placeholder_description_ 可以让你的包快速就位并发布。

设置包导出的内容

首先你可以阅读 Ship ESM & CJS in one PackageTypes for Submodules 这两篇文章来了解同时发布 esm 和 cjs 两种格式包相关的基础信息。 然后可以使用 publintarethetypeswrongmodern-guide-to-packaging-js-library 这些工具来检查你的包是否符合规范。

我最终得出的配置如下:

json
{
  "sideEffects": false,
  "exports": {
    ".": {
      "import": {
        "types": "./dist/index.d.ts",
        "default": "./dist/index.js"
      },
      "require": {
        "types": "./dist/index.d.cts",
        "default": "./dist/index.cjs"
      }
    }
  },
  "main": "./dist/index.cjs",
  "module": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "typesVersions": {
    "*": {
      "*": ["./dist/*", "./dist/index.d.ts"]
    }
  },
  "files": ["dist"]
}

保持依赖定义正确

使用 knip 查找项目中未使用的文件、依赖项和导出。 使用 taze 或者 package-json-upgrade 来更新依赖。

发版流程

要使我们的包版本号 +1,大抵有以下几个步骤:

  1. 写这次更新的日志
  2. 更新版本号
  3. commit && tag
  4. 发布到 npm
  5. 在 GitHub 上发布 release

如果每次都要手动做这些事情,那就太麻烦了,所以我们可以使用一些工具来帮助我们自动化这些步骤。

生成 changelog

使用 changelogen 可以自动生成 changelog,根据的是 commit message。 所以或许我们需要一个用来规范 commit message 的 cli,比如 cz-git 或者 cz-cli 加上 cz-conventional-changelog

sh
changelogen --output && prettier --write CHANGELOG.md && git add CHANGELOG.md

将 CHANGELOG.md 添加到 git 中,在下一步中将会被一起 commit。

更新版本号

使用 bumpp 来更新版本号,通过 cli 可以方便的选择下一个升级的版本号,bumpp 会自动更新 package.json 中的 version,然后完成 commit,tag,push 一条龙。

npx bumpp

sh
bumpp --all --execute \"pnpm changelog\" && npm publish

为了将上一步生成的 CHANGELOG.md 一同提交,需要使用 --all 参数。 通过 --execute 参数可以在 commit 之前执行一些命令,这里自然就是生成 changelog 了。

对于小型项目来说,我们不需要通过 GitHub Action 来编译打包,直接在本地 npm publish 即可。

生成 GitHub Release

使用 changelogithub

尽可能早的发现问题

为了保证我们每次 commit 的代码都没有弄坏什么事情,我使用 simple-git-hooks 来在 commit 之前进行检查。

json
{
  "simple-git-hooks": {
    "pre-commit": "pnpm check"
  },
  "scripts": {
    "prepare": "simple-git-hooks",
    "check": "pnpm lint && pnpm typecheck && pnpm build",
    "prepublishOnly": "pnpm check"
  }
}

同样的,我们可以配置 GitHub Action 来执行检查,这可以让检查跑在更丰富的环境中。

使用 release-it

release-it 是一个功能更强大更自动的发版工具,替换 changelogen + bumpp。

sh
ni -D release-it @release-it/conventional-changelog
json
{
  "scripts": {
    "release": "release-it"
  }
}

借助 release-it 的 hooks 功能,只在发包之前的各个阶段进行一些操作。 如果你不希望检查的操作卡住 commit 的操作,将检查只在 release 时进行是推荐的做法。

json
{
  "git": {
    "commitMessage": "chore: release ${version}"
  },
  "plugins": {
    "@release-it/conventional-changelog": {
      "preset": {
        "name": "angular"
      },
      "infile": "CHANGELOG.md",
      "ignoreRecommendedBump": true
    }
  },
  "hooks": {
    "before:init": ["pnpm lint", "pnpm typecheck"],
    "after:bump": "pnpm build",
    "before:release": "pnpm prettier --write CHANGELOG.md && git add CHANGELOG.md"
  }
}