注目イベント!
アドベントカレンダー2024開催中!
一年を締めくくる特別なイベント、アドベントカレンダーを今年も開催しています!
初心者からベテランまで楽しめる内容で、毎日新しい技術トピックをお届けします。
詳細はこちらから!
event banner

Exploring Modern JavaScript Registry JSR in the Multi-Runtime Era

| 11 min read
Author: masahiro-kondo masahiro-kondoの画像
Information

To reach a broader audience, this article has been translated from Japanese.
You can find the original version here.

Introduction

#

JSR is a package registry for JavaScript/TypeScript.

JSR: the JavaScript Registry

Portal

It is currently positioned as an open beta and can be signed up with a GitHub account.

Introducing JSR - the JavaScript Registry

It is primarily developed by Deno, but it can be used in environments such as Deno, Node.js, and Bun.

The background for the construction of JSR includes the following changes from 2009 to the present:

  • The emergence of ESM
  • The emergence of TypeScript
  • The appearance of many JavaScript runtimes such as Node.js, Deno, and Bun

npm was not designed with these changes in mind, so it seems to be positioned as a redesigned package registry to complement npm. Ryan Dahl has stated that JSR is not meant to replace npm but to complement it.

JSR Is Not Another Package Manager

The JSR documentation also writes about the motivation for building JSR.

Why JSR? - Docs - JSR

  • Native TypeScript support
  • ESM only
  • Cross-runtime support
  • Developer experience
  • Fast, secure, high reliability

These are the reasons to consider using JSR.

Deno has added JSR support from version 1.42.

Deno 1.42: Better dependency management with JSR

The differences between JSR and npm are also written in the FAQ as follows:

  • Automatic documentation generation
  • Package scoring
  • Native TypeScript support
  • No build step, better user experience
  • Resistance to supply chain attacks with tokenless publishing

Frequently Asked Questions - Docs - JSR

As a Japanese resource, the slides by Mr. Hinonawa from Deno are very easy to understand.

JSR の紹介

Mr. Hinonawa has also written an article stating that Deno users should use jsr import instead of https import.

Deno ユーザーは https import と jsr import のどちらを使うべきか?

Information

In projects generated with deno init, the test code also imports the assert package using jsr import.

import { assertEquals } from "jsr:@std/assert";

Using Packages

#

After a long introduction, here is how to use JSR packages. Let's try it in Deno, Node.js, and Bun projects. As shown in the example in the official documentation, we will use Luca's cases package.

@luca/cases - JSR

Trying with Deno

#

First, create a Deno project.

mkdir hello-jsr && cd hello-jsr
deno init

Add a package with deno add.

$ deno add @luca/cases
Add @luca/cases - jsr:@luca/cases@^1.0.0

It will be added to deno.json imports.

deno.json
{
  "tasks": {
    "dev": "deno run --watch main.ts"
  },
  "imports": {
    "@luca/cases": "jsr:@luca/cases@^1.0.0"
  }
}

Call the camelCase function that converts a given string to camel case.

main.ts
import { camelCase } from "@luca/cases";

console.log(camelCase("hello jsr"));

Execution result.

$ deno run main.ts
helloJsr

If you write jsr import directly in the code, it can be used without adding it with deno add.

import { camelCase } from "jsr:@luca/cases";

console.log(camelCase("hello jsr"));
Information

The recommended method is using deno add.
Due to my settings, there was no error in VS Code with the jsr import method, but when I added it to deno.json with deno add, it seemed that TypeScript information could not be obtained, and it showed an error. I want to investigate this further.

Trying with Node.js

#

When using JSR packages in a Node.js project, execute the jsr command with npx instead of npm install.

First, create a Node.js project.

mkdir hello-jsr && cd hello-jsr
npm init --y

Execute jsr add with npx. After installing the jsr package, the target package will be installed.

$ npx jsr add @luca/cases
Need to install the following packages:
jsr@0.12.4
Ok to proceed? (y) 

Setting up .npmrc...ok
Installing @luca/cases...
$ npm install @luca/cases@npm:@jsr/luca__cases

added 1 package, and audited 2 packages in 599ms

found 0 vulnerabilities

Completed in 675ms

The line $ npm install @luca/cases@npm:@jsr/luca__cases is included in the output message. Internally, npm install is used. The following dependencies were added to package.json.

package.json
{
  "name": "hello-jsr",
  "version": "1.0.0",
  "dependencies": {
    "@luca/cases": "npm:@jsr/luca__cases@^1.0.0"
  }
}

It can be used as ESM.

index.mjs
import { camelCase } from "@luca/cases";

console.log(camelCase("hello-jsr"));

Execution result.

$ node index.mjs
helloJsr

Trying with Bun

#

When installing JSR packages in Bun, execute jsr with bunx instead of bun add.

First, create a Bun project.

mkdir hello-jsr && cd hello-jsr
bun init -y       

Execute jsr add with bunx.

$ bunx jsr add @luca/cases
Setting up bunfig.toml...ok
Installing @luca/cases...
$ bun add @luca/cases@npm:@jsr/luca__cases
bun add v1.1.7 (b0b7db5c)

 installed @luca/cases@1.0.0

 1 package installed [499.00ms]

Completed in 519ms

Internally, bun add is called.

Dependencies are added to package.json.

package.json
{
  "name": "hello-jsr",
  "module": "index.ts",
  "type": "module",
  "devDependencies": {
    "@types/bun": "latest"
  },
  "peerDependencies": {
    "typescript": "^5.0.0"
  },
  "dependencies": {
    "@luca/cases": "npm:@jsr/luca__cases"
  }
}

It can be used as ESM.

index.ts
import { camelCase from "@luca/cases";

console.log(camelCase("hello bun"));

Execution result.

$ bun index.ts 
helloBun

Publishing and Releasing Packages

#

Finally, let's also try publishing packages. It is possible in environments like Node.js and Bun, but the Deno environment, which is natively supported, might be easier.

Publishing packages - Docs - JSR

I made a simple addition package, adder (using the code generated by deno init).

mkdir adder && cd adder
deno init

I am exporting the add function. In JSR, if you work hard on writing JSDoc, it will be reflected in the documentation on the publication page.

main.ts
/** Add two numbers
 *
 * @param a The first number
 * @param b The second number
 * @returns The sum of the two numbers
 */
export function add(a: number, b: number): number {
  return a + b;
}

In deno.json, write the package information. If you are not using Deno, it seems that you can create jsr.json instead of deno.json.

deno.json
{
  "name": "@kondoumh/adder",
  "version": "0.1.0",
  "exports": "./main.ts"
}

The name specifies the scope name @kondoumh and the package name adder separated by /.
Specify the file to be published in exports. It might have been better to use mod.ts instead of main.ts as it is a module.

Once the code is written, publish it. In Deno, you can publish with the publish command. In the case of Node.js, execute it like npx jsr publish.

deno publish

When executed, the package is checked, and it waits while the publication screen opens in the browser.

Check file:///Users/kondoh/dev/adder/main.ts
Checking for slow types in the public API...
Check file:///Users/kondoh/dev/adder/main.ts
'@kondoumh/adder' doesn't exist yet. Visit https://jsr.io/new?scope=kondoumh&package=adder&from=cli to create the package
Waiting...

Since it is the first time creating a package, there is no scope @kondoumh in JSR, so click the Create button to create it.

Publish a package

Once the scope is created, a button to create a package appears.

Create Package

Click the button to create the package and an Authorization screen appears, so click Approve.

Authorization

It has been published.

Published

Information

Right after publication, the package appeared in a timeline-like place and it was embarrassing, but it quickly flowed away (laughs).

Recent updates

Let's try using the package immediately. I will use it in a Bun project instead of Deno.

$ bunx jsr add @kondoumh/adder
Installing @kondoumh/adder...
$ bun add @kondoumh/adder@npm:@jsr/kondoumh__adder
bun add v1.1.7 (b0b7db5c)

 installed @kondoumh/adder@0.1.0

 1 package installed [437.00ms]

Completed in 455ms

It has been installed.

Add code that uses the adder package.

index.ts
import { camelCase } from "@luca/cases";
import { add } from "@kondoumh/adder"

console.log(camelCase("hello bun"));
console.log(add(2, 3));

Execution result.

$ bun index.ts
helloBun
5

It was executed successfully.

This time I published it by uploading from the local environment, but publishing via GitHub Actions is supported.

Publishing from GitHub Actions | Publishing packages - Docs - JSR

Information

When published via GitHub Actions, JSR creates a Sigstore transparency log and makes the provenance of the package traceable.

Provenance and trust - Docs - JSR

As future support, an implementation will be added to add a signature to the package manifest at the time of upload and publish it to the Sigstore transparency log. It is likely to be positioned as a registry resistant to supply chain attacks.

The use of Sigstore in GitHub Actions is also covered in the following article.

Software Supply Chain Security Workflows for GitHub Actions

Conclusion

#

I thought it was very convenient to be able to create and publish packages in TypeScript without a build step.

The usage policy for JSR is as follows:

  • A must in Deno
  • In Node.js and Bun, adopt ESM over CommonJS, and preferentially use what is available in JSR

I would like to get used to using it in this way.

Information

How JSR is built is discussed in Luca's blog article. The JSR API seems to be built with an HA-configured PostgreSQL cluster and Rust code.

How we built JSR

豆蔵では共に高め合う仲間を募集しています!

recruit

具体的な採用情報はこちらからご覧いただけます。