Hyoban

Hyoban

Don’t do what you should do, do you want.
twitter
github
email
telegram

How to start writing a TypeScript library

If you want to tinker with everything yourself, you might end up in endless pits, so I chose to start with antfu's starter-ts and make some modifications according to my own habits.

Technology Stack Selection#

TypeScript + ESLint + Prettier#

TypeScript goes without saying. I use ESLint to check code style and potential issues, and Prettier to format the code.

If you have struggled with prettier's printWidth and can't stand it, please read Why I don't use Prettier. You may prefer using ESLint to format the code, and now we have ESLint Stylistic, which is a ready-to-use configuration.

To configure your own ESLint config, you can refer to antfu's @antfu/eslint-config. My own configuration does not include ESLint Stylistic configuration:

Personally, I prefer to use a separate formatting tool for the following reasons:

  1. I want to enable as many ESLint rules that require type checking as possible, which generally increases lint time and affects the experience of saving during development.
  2. This is the recommended practice by ESLint, typescript-eslint, and Prettier.

I may also switch to using ESLint to format the code:

  1. Prettier's preferences can make me uncomfortable in certain situations, especially when most behaviors cannot be configured.
  2. Using ESLint alone to accomplish multiple tasks makes me, as a perfectionist, feel very comfortable.

pnpm + bunchee + tsx + vitest#

To facilitate testing the libraries we write, pnpm's workspace is essential, as it allows us to easily open a playground. In addition, it prevents accidental references to undefined dependencies by not hoisting dependencies by default. My .npmrc is:

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

Use bunchee to handle the bundling task. It reads the exports field in package.json as the input and output for bundling, without the need to manually specify the configuration.

If you want more clear and fine-grained control over the bundling process, you can use rollup with some plugins to write your own configuration. Here are some commonly used plugin recommendations.

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

If you want to see other options similar to bunchee, you can take a look at unbuild and tsup.

During development, unless necessary, no one wants to bundle the code before running it. Therefore, I use tsx to directly execute ts files and use vitest to test the code.

Correctly Set package.json#

Conveniently maintain basic information of the package#

As a starting template, it needs to have the basic information of the package written in advance, and it should be able to quickly find and replace when starting a new project.

The basic information is located in two places, one is package.json, and the other is README.md. By globally replacing pkg-placeholder and $description$, you can quickly set up your package and publish it.

Set the exported content of the package#

First, you can read Ship ESM & CJS in one Package, Types for Submodules, and moduleResolution 总结 to understand the basic information related to publishing both esm and cjs formats.
Then you can use tools like publint, arethetypeswrong, and modern-guide-to-packaging-js-library to check if your package meets the specifications.

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

The final configuration I came up with is as follows:

{
  "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"]
}

Keep dependency definitions correct#

Use knip to find unused files, dependencies, and exports in the project.

npx knip

Use taze to manually update dependencies or use renovatebot to automatically update them on a regular basis.

Automatically fix lint#

To ensure that the code in the commit complies with the code style, you can use simple-git-hooks to perform checks before committing.

{
  "simple-git-hooks": {
    "pre-commit": "pnpm lint-staged"
  },
  "lint-staged": {
    "*": "eslint --fix"
  }
  "scripts": {
    "prepare": "simple-git-hooks",
  }
}

However, the problem with this solution is that if your ESLint configuration changes, there may still be issues that need to be fixed in the committed code. So I prefer to perform checks in the CI. Use git-auto-commit-action to automatically fix and apply changes to the current branch. Another benefit of this approach is that you don't need to require other contributors to set up the correct environment completely.

Release Process#

To increment the version number of our package, there are several steps:

  1. Perform pre-release checks
  2. Update the version number
  3. Publish to npm
  4. Commit && tag
  5. Write release notes and publish the release on GitHub

If you have to do these things manually every time, it would be too cumbersome. Therefore, we can use release-it to automate these steps. With the help of release-it's hooks feature and custom plugins, we can easily define the release process we need.

release-it has almost zero support for pnpm workspaces, so if you need to publish multiple packages in a monorepo, you need to switch to another solution. I wrote a plugin to provide simple support.

In this plugin, I also automatically determine the next version number according to my preferences, and integrate bumpp and changelogithub.

My Starter#

I have open-sourced the starter I have prepared. If you have the same preferences as mine, you can use it as a basis to modify your own starter.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.