IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    我如何开始写一个 TypeScript 库

    Hyoban\'s Blog发表于 2024-02-15 07:14:39
    love 0

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

    技术栈选择#

    TypeScript + ESLint#

    TypeScript 自不必多说,我使用 ESLint 来检查代码风格并格式化代码,而不是使用 Prettier,想要了解更多请看 Why I don't use Prettier。如果你和 prettier 的 printWidth 也做过斗争并不能忍受的话,就会喜欢用 eslint 了,并且我们现在有了 ESLint Stylistic 这种开箱即用的配置。

    Note

    要配置自己的 eslint config, 可以参考偶像的 @antfu/eslint-config。

    pnpm + bunchee + tsx + vitest#

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

    ignore-workspace-root-check=true
    public-hoist-pattern=[]
    

    使用 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 的其它选择,可以看看 unbuild 和 tsup

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

    正确设置 package.json#

    便捷地维护包的基本信息#

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

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

    设置包导出的内容#

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

    npx publint
    npx -p @arethetypeswrong/cli attw --pack .
    

    我最终得出的配置如下:

    {
      "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 查找项目中未使用的文件、依赖项和导出。

    npx knip
    

    使用 taze 或者 package-json-upgrade 来更新依赖。

    发版流程#

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

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

    如果每次都要手动做这些事情,那就太麻烦了,所以我们可以使用 release-it 来帮助我们自动化这些步骤。借助 release-it 的 hooks 功能,只在发包之前的各个阶段进行一些操作。

    {
      "release-it": {
        "git": {
          "commitMessage": "chore: release v${version}"
        },
        "hooks": {
          "before:init": [
            "pnpm run lint",
            "pnpm run typecheck",
            "pnpm run test --run"
          ]
        }
      }
    }
    

    我并不想将其作为依赖安装在项目中,我使用 GitHub Action 来完成 release 的操作。先看完整的配置。

    name: Release
    
    permissions:
      contents: write
      id-token: write
    
    on:
      workflow_dispatch:
        inputs:
          increment:
            description: "The release type"
            required: true
            default: "patch"
            type: choice
            options:
              - "major"
              - "minor"
              - "patch"
          preRelease:
            description: "The pre-release type"
            required: true
            default: "not"
            type: choice
            options:
              - "alpha"
              - "beta"
              - "rc"
              - "not"
    
    jobs:
      release:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
            with:
              fetch-depth: 0
    
          - name: Git config
            run: |
              git config user.name "${GITHUB_ACTOR}"
              git config user.email "${GITHUB_ACTOR}@users.noreply.github.com"
    
          - name: Install pnpm
            uses: pnpm/action-setup@v3
    
          - name: Set node
            uses: actions/setup-node@v3
            with:
              node-version: lts/*
    
          - name: Login to NPM
            run: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc
            env:
              NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
    
          - name: Setup
            run: npm i -g @antfu/ni release-it changelogithub should-semantic-release
    
          - name: Install
            run: nci
    
          - name: Pre Release
            run: if should-semantic-release --verbose ; then release-it ${{ github.event.inputs.increment }} --verbose --preRelease ${{ github.event.inputs.preRelease }} && changelogithub ; fi
            if: "${{ github.event.inputs.preRelease != 'not' }}"
            env:
              GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
              NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
    
          - name: Release
            run: if should-semantic-release --verbose ; then release-it ${{ github.event.inputs.increment }} --verbose && changelogithub ; fi
            if: "${{ github.event.inputs.preRelease == 'not' }}"
            env:
              GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
              NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
    

    单独使用 release-it 的话不能自动选择正确的下一个版本号,我又没找到完全符合自己想法的插件,就借助 GitHub Action 的 workflow_dispatch 条件来允许手动触发 release,传递 increment 和 preRelease 两个参数给 release-it。

    khtKubnnN0

    生成 GitHub Release#

    使用 changelogithub

    其它我尝试过的工具#

    生成 changelog#

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

    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

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

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

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

    尽可能早的发现问题#

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

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


沪ICP备19023445号-2号
友情链接