Publish pure ESM npm package written in TypeScript to JSR
Tags: javascript, webdev, github
Published: 2024-05-12 18:29:36

## Abstract

- You can publish a npm package to JSR
- JSR's API docs generation is delightful for a small OSS project, even if it doesn't use Deno

## Introduction

[JSR](https://jsr.io/) is a new package registry for JavaScript, which has compatibility to npm. JSR supports ES modules and TypeScript by default.

I'm developing [vremel](https://jsr.io/@fabon/vremel), an utility library for [Temporal API](https://tc39.es/proposal-temporal/docs/) (similar to [date-fns](https://date-fns.org/) for Date). It's a pure ESM package[^cjs] and written in TypeScript.

[^cjs]: Actually it's a dual package, but its structure and configurations are no different from a pure ESM package.

The package is formerly published only to official npm registry, but recently I started to publish it to JSR **in addition**. At first it was just for fun; I want to try a new thing. However, after I've published the package to JSR, I noticed that JSR saved me a headache: documentation.

## API docs problem

JavaScript (TypeScript) ecosystem has various types of API docs generators. Maybe the most popular one is [TypeDoc](https://typedoc.org/). While generating API docs itself is easy, hosting API docs is pretty hard. Publishing generated HTML to static hosting service like GitHub Pages is the method I adopted previously, but it's not an ideal solution because we can't view docs for older versions.

We can generate docs for all previous versions each time a new version is released, or save generated docs of older versions to external storage. Yes, it's technically possible, but complicated.

Now JSR have changed this situation. After publishing the package, we can view API docs of each version (similar to [docs.rs](https://docs.rs/) in Rust or [pkg.go.dev](https://pkg.go.dev/) in Go). All we have to do is to write few lines of JSON. Optionally you can publish a package from GitHub Actions by adding only few lines to a workflow file. Any other setup (install packages, write config for document generator...) is not needed.

example: [API docs for vremel v0.3.3](https://jsr.io/@fabon/vremel@0.3.3/doc)
![v0.3.3 docs](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cie0fb5joivv8nop9sek.png)

## npm packages in JSR

JSR handles TypeScript source files very well, so publishing original TypeScript files is better than publishing generated JS and TypeScript definition files.

If the project has `package.json`, then `jsr publish` command can resolve builtin modules of Node, npm packages listed in the `dependencies` field of `package.json`. Also it can handle a `.js` extension in relative imports from `.ts` files (called 'sloppy imports' in Deno), which is the rule of pure ESM npm packages written in TypeScript[^imports].

[^imports]: These Node's style imports are rewritten to Deno's style internally by `jsr publish` command before publishing.

## Actual steps

### Create a package

Access https://jsr.io/new and create a package.

### Write `jsr.json`

Write `jsr.json`. See also [JSR package config](https://jsr.io/docs/package-configuration). For now you can specify subpath exports and files to be included or excluded.

In my case:

```json
{
  "name": "@fabon/vremel",
  "version": "0.3.3",
  "exports": {
    ".": "./src/index.ts",
    "./duration": "./src/duration/index.ts"
  },
  "publish": {
    "include": [
      "CHANGELOG.md",
      "README.md",
      "LICENSE",
      "src/**/*.ts",
      "jsr.json"
    ],
    "exclude": [
      "**/*.test.ts"
    ]
  }
}
```

Note that you have to include `jsr.json` itself in `publish.include`. Otherwise `jsr publish` will fail (at least). In contrast, `package.json` is not necessary.

### Fix 'slow types'

You can run `npx jsr publish --dry-run` to check whether the package is fine. You may encounter the error like `missing explicit return type in the public API` due to JSR's restriction (See [About "slow types"](https://jsr.io/docs/about-slow-types)). In most cases all you have to do is just clarify return types.

```typescript
// before
export function rand() {
  return Math.random();
}
//after
export function rand(): number {
  return Math.random();
}
```

You can pass `--allow-slow-types` to `jsr publish` if you have to depend on slow types, although it's not recommended by JSR.

In my case: [Clarify return types explicitly · fabon-f/vremel@50d2963 · GitHub](https://github.com/fabon-f/vremel/commit/50d2963ddbada084aaf0cd9d129f2059304c93d8)

### Set up GitHub Actions

Of course you can publish to JSR from your PC, but it's better to publish from CI. Fortunately publishing to JSR from GitHub Actions is super easy, just follow official guides.

Note that if the package depends on external npm packages, you have to install dependencies to `node_modules` before running `jsr publish`. This is because `jsr` command uses Node.js compatibility mode called [BYONM](https://deno.com/blog/v1.38#nodejs-compatibility-improvements) when `package.json` exist in the project.

Example of workflow file:

```yaml
name: Publish the package

on:
  push:
    tags:
      - "v*"

jobs:
  jsr:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - name: Publish package
        run: npx jsr publish
```

After pushing a release tag, the package will be published to JSR from GitHub Actions.

## API docs generation in JSR

JSR uses Deno's document generator ([deno_doc](https://github.com/denoland/deno_doc)), which is under development and doesn't support all JSDoc tags yet[^unsupported]. So your JSDoc comment can be showed differently from your intention. You can run `deno doc --unstable-sloppy-imports --html path/to/entrypoint.ts` to check how generate docs will look.

[^unsupported]: For example `@description` and `@summary` is not supported yet (in 2024-05-13); these tags will be simply ignored for now.

See also: [How to document your JavaScript package](https://deno.com/blog/document-javascript-package) from Deno team blog.

## Conclusion

JSR is now under heavy development. I'm very looking forward to its rapid advance, especially in API docs generation.