Exploring Modern JavaScript Registry JSR in the Multi-Runtime Era
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.
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.
- 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.
Mr. Hinonawa has also written an article stating that Deno users should use jsr import instead of https import.
Deno ユーザーは https import と jsr import のどちらを使うべきか?
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.
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.
{
"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.
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"));
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.
{
"name": "hello-jsr",
"version": "1.0.0",
"dependencies": {
"@luca/cases": "npm:@jsr/luca__cases@^1.0.0"
}
}
It can be used as ESM.
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.
{
"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.
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.
/** 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.
{
"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.
Once the scope is created, a button to create a package appears.
Click the button to create the package and an Authorization screen appears, so click Approve
.
It has been published.
Right after publication, the package appeared in a timeline-like place and it was embarrassing, but it quickly flowed away (laughs).
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.
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
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.
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.
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.