Publish pure ESM npm package written in TypeScript to JSR

fabon

fabon

Posted on May 12, 2024

Publish pure ESM npm package written in TypeScript to JSR

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 is a new package registry for JavaScript, which has compatibility to npm. JSR supports ES modules and TypeScript by default.

I'm developing vremel, an utility library for Temporal API (similar to date-fns for Date). It's a pure ESM package1 and written in TypeScript.

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. 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 in Rust or 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
v0.3.3 docs

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 TypeScript2.

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. For now you can specify subpath exports and files to be included or excluded.

In my case:



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


Enter fullscreen mode Exit fullscreen mode

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"). In most cases all you have to do is just clarify return types.



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


Enter fullscreen mode Exit fullscreen mode

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

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 when package.json exist in the project.

Example of workflow file:



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


Enter fullscreen mode Exit fullscreen mode

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), which is under development and doesn't support all JSDoc tags yet3. 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.

See also: How to document your 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.


  1. Actually it's a dual package, but its structure and configurations are no different from a pure ESM package. 

  2. These Node's style imports are rewritten to Deno's style internally by jsr publish command before publishing. 

  3. For example @description and @summary is not supported yet (in 2024-05-13); these tags will be simply ignored for now. 

💖 💪 🙅 🚩
fabon
fabon

Posted on May 12, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related