JSR User Guide
introduce
Recently, Deno** released a Javascript Registry (JSR), a new JavaScript package registry. It is similar to npm, but is not a package management tool, but provides package storage services for package registration and package release.
It can be used with npm
, yarn
and pnpm
, etc., and supports runtimes such as Node.js
, Deno
, Bun
and browser
.
The goal of JSR is not to replace NPM, but to be a superset of NPM, which is more in line with the needs of modern JavaScript developers and improve the developer's experience. And get better support in all aspects such as reliability, security, and performance.
Using JSR
You can use JSR packages in any runtime that supports ES modules, such as Deno, Node.js, Bun, Cloudflare Workers, etc. You can also use JSR packages with build tools that support ES modules, such as Vite, esbuild, Webpack, and Rollup.
You can import the JSR package into your project using any of the following commands:
#deno
deno add @luca/cases
# npm (One of the following options depends on your package manager)
npx jsr add @luca/cases
yarn dlx jsr add @luca/cases
pnpm dlx jsr add @luca/cases
bunx jsr add @luca/cases
If you use the Deno, the deno add
command will generate the specified JSR module to add the import map entry in the deno.json
file:
{
"imports": {
"@luca/cases": "jsr:@luca/cases@^1.0.1"
}
}
For npm and npm-compatible package managers, the jsr command adds dependencies to your package.json
file, And add the .npmrc
file to your project root directory, which contains the configuration you need to use JSR with npm.
{
"dependencies": {
"@luca/cases": "npm:@jsr/luca__cases@^1.0.1"
}
}
This npm dependency configuration uses a special custom scope called @jsr, Automatically add the following configuration in .npmrc
:
@jsr:registry=https://npm.jsr.io
Note
You should add .npmrc
to your source control to use it later when installing or updating the JSR package.
Why choose JSR?
Node.js's great success is largely due to the success of NPM. NPM has 2 million+ (may reach 3 million in the future) packages, Probably the most successful package manager and registry in history. This is an achievement that the JavaScript community is proud of.
So, since there is already an NPM, why do you still need to build a JSR? Because in today's world, there have been earth-shaking changes from when NPM was introduced:
- **ECMAScript module has become the standard. ** The web platform now uses ESM as its preferred module format, replacing CommonJS.
- **In addition to Node.js and Browser, there are more JavaScript runtimes. ** With Deno, Bun, workerd, and other new JavaScript environments emerging, the
Node.js
-centric package registry is no longer meeting the needs. - Typescript has become the de facto standard. TypeScript, as a test platform for JavaScript superset and the latest ECMAScript features, has become the choice of many important JavaScript libraries. Modern registries should be designed with TypeScript in mind.
Here are the reasons why you consider using JSR:
Native Typescript Support
JSR is designed with TypeScript support in mind. TypeScript source files can be published directly to JSR. These files can be used directly on platforms that support TypeScript (such as Deno).
For other environments that lack native support for TypeScript (such as Node.js), JSR converts the source code to JavaScript and generates a d.ts
type declaration file. TypeScript tools to support Node.js projects. Module authors do not require additional configuration or build steps.
JSR will also generate reference documents for packages from TypeScript source code, providing rich online documentation that you can maintain with your code.
ESM modules only
The web standard for JavaScript modules is ESM. A modern package registration center should unite around this standard and turn the community in that direction. Therefore, JSR is designed for ESM only.
Cross-runtime support
The goal of JSR is to be able to work anywhere JavaScript works and provide a runtime-independent registry for JavaScript and TypeScript code. Today, JSR can be used with Deno and other NPM environments including node_modules
. This means that Node.js, Bun, Cloudflare Workers, and other projects that use the package.json
management dependencies can also interoperate with JSR.
JSR is a superset of NPM
JSR is a superset of npm, just as TypeScript is a superset of JavaScript.
JSR is designed to interoperate with npm-based projects and packages. You can use the JSR package in any runtime environment that uses the node_modules
folder. The JSR module can import dependencies from npm.
Excellent development experience
JSR has many features designed to help module publishers improve productivity, including but not limited to:
- Easily publish with a single command - CLI will walk you through the rest
- Automatically generate API documentation from source code
- Zero configuration publishing from GitHub Actions
- Automatically include
.d.ts
files distributed by Node.js/npm - Automatic guidance on TypeScript best practices will make your code load as fast as possible
- More...
Fast, safe and reliable
JSR is designed to be safe, fast, flexible, and also works well in resource-constrained environments.
- JSR uses global CDN to provide packages and uses local cache and high parallelism to speed up downloads.
- JSR package uploads are immutable, so you can trust that the package will never change or disappear at your bottom after downloading.
- JSR package downloads are very efficient and only download the exact file you are importing.
- JSR publishes packages from the CI using OIDC-based authentication and publishes from the local computer using the token-free interactive authentication process.
JSR package rules
All packages uploaded to JSR are automatically processed and verified during the release process to ensure that all code hosted on the JSR adheres to a consistent set of rules. These rules are designed to achieve portability across environments. The code must follow these rules before it can be published to the JSR.
Only ESM modules are supported
The JSR package only supports ESM modules, which means you can only publish modules that use the import
and export
keywords. Unable to publish CommonJS modules
Support NPM packages
You can depend on npm packages by specifying them in dependencies
of package.json
, Or use the npm:
specifier to reference them in your code, for example as import { cloneDeep } from "npm:lodash@4";
.
Support JSR packages
Relying on JSR packages by specifying them in dependents
of package.json
, Or use the jsr:
specifier to reference them in your code, for example import { encodeBase64 } from "jsr:@std/encoding@1/base64";
.
Support node: built-in functions
Use the node:
scheme to import Node.js built-in functions. For example, you can use import { readFile } from "node:fs";
to import the fs module. If the package has package.json
, You can also import Node.js built-in programs using the naked specifier (without the node:
prefix).
Simple file name
The file name must be compatible with Windows and Unix. This means that the file name cannot contain characters such as *
, :
or ?
. Multiple files with the same name but different upper and lower cases should be avoided.
It is best not to use TypeScript "slow types"
To speed up type checking, support for document generation and Node.js compatibility, JSR packages should not use certain TypeScript types in exported functions, classes, or variables. This is enforced by default, but can be optionally ignored.
Effective cross-file import
All relative imports between modules in a package must be parsed at release. The format of supported specifiers depends on whether to use package.json
.
Publish package to JSR
You can publish most JavaScript and TypeScript code written using ESM modules as JSR packages. The JSR package is published to jsr.io and can be imported from Deno, Node.js, and other tools.
Code written for the runtime using package.json
and code written for Deno can be published as packages to JSR. JSR supports and encourages the release of TypeScript source code instead of paired .js + .d.ts
files. This allows JSR to provide more useful automated documentation and helps provide improved autocomplete functionality in the editor.
包配置文件
你必须在你的包中添加一个 包配置文件,该文件名为 jsr.json
。文件中包含包括 包的元数据,如 包名、 版本号、入口点。Deno 用户还可以在其 deno.json
中包含所需的属性,以避免创建另一个文件。
{
"name": "@luca/greet",
"version": "1.0.0",
"exports": "./mod.ts"
}
Create scope and package
To package to JSR, you must first create a scope, and then create a package under this scope. scope is similar to NPM scope, scope starts with the @
symbol followed by the name, such as @luca
is a
You can create scope in jsr.io/new. The name length of scope must range from 2 to 32 characters. Only lowercase characters, hyphens and numbers are used. Only if the name is not used, names that are very similar to the existing scope are prohibited.
After creating the scope, you can create a package in this scope. The package name must be between 2 and 20 characters long. And can only contain lowercase letters, numbers and hyphens. Only if the name is not used, names that are very similar to existing packages are prohibited.
Verification package
To publish a package (including performing a trial run to confirm that the package meets all JSR rules), Requires jsr publish
or deno publish
. The syntax of these two commands is roughly the same. Depending on the different tools, the issuance command can be called as follows.
#deno
deno publish
# npm
npx jsr publish
# yarn
yarn dlx jsr publish
# pnpm
pnpm dlx jsr publish
You can run jsr publish
with the --dry-run
flag to perform all publish verifications that occur during the actual publishing period. This will print out the list of files to be published, but will not actually be published to the registry.
#deno
$ deno publish --dry-run
# npm
$ npx jsr publish --dry-run
# yarn
yarn dlx jsr publish --dry-run
# pnpm
pnpm dlx jsr publish --dry-run
Publish packages from this machine
Use the jsr publish
or deno publish
command to publish packages from your local computer.
Authentication is done through a browser, so there is no need to provide any authentication information to the JSR cli.
Go to the root directory of the package (including the jsr.json
/ deno.json
file), and run jsr publish
.
#deno
$ deno publish
# npm
$ npx jsr publish
# yarn
yarn dlx jsr publish
# pnpm
pnpm dlx jsr publish
During release, both the JSR CLI and the JSR server will run many checks on the package to ensure it is valid. If any of these checks fail, the CLI will output an error message. These errors must be fixed before attempting to publish again.
Publish packages from GitHub Actions
To publish a package from Github Actions, you must first link the package to Github Repo in the Setting
tab of the package page of the JSR.
Then, in the Github Repo, add a workflow configuration file, such as .github/workflows/publish.yml
.
name: Publish
on:
push:
branches:
- main
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # The OIDC ID token is used for authentication with JSR.
Steps:
- uses: actions/checkout@v4
- run: npx jsr publish
This workflow runs every time it is pushed to the main branch of the repository. It will publish your package to the JSR and automatically use the correct version number based on the version in the jsr.json
file. If the version specified in the jsr.json
file has been published to JSR, jsr publish
will not attempt to publish.
Filter files
jsr publish
will ignore the files listed in the .gitignore
file in the package root directory. Additionally, the include
and exclude
fields can be specified in the jsr.json
/ deno.json
file to include, Ignore or cancel gitignore specific operation files.
For example, to include only certain files selectively, you can use the include
option to specify a glob that matches all files:
{
"name": "@luca/greet",
"version": "1.0.0",
"exports": "./src/mod.ts",
// note: this will be collapsed down to just include in the future
"publish": {
"include": ["LICENSE", "README.md", "src/**/*.ts"]
}
}
You can also exclude certain files through the exclude option:
{
"name": "@luca/greet",
"version": "1.0.0",
"exports": "./src/mod.ts",
"publish": {
"include": ["LICENSE", "README.md", "src/**/*.ts"],
"exclude": ["src/tests"]
}
}
Cancel ignoring files when "include" is not used
You may have a package containing the .gitignore
file that contains the following:
.env
dist/
In this case, all files in the dist/
directory and any files named .env
are ignored when publishing.
However, if you want to publish the dist/
directory, this can be inconvenient because there is "exports" pointing to it (or its subdirectories). In this case, the dist/
directory can be unignited by using negative in the exclude
field in the jsr.json
/ deno.json
file.
{
"name": "@luca/greet",
"version": "1.0.0",
"exports": "./dist/mod.ts",
"publish": {
"exclude": ["!dist"]
}
}
In this case, the dist/
directory will be included when publishing, even if it is listed in the .gitignore
file.
jsr.json
file
A JSR package needs to include a configuration file that specifies the name, version, and export of the package. The file should be named jsr.json
or jsr.jsonc
. When using Deno, all properties of the jsr.json
configuration file can be placed in deno.json
.
{
"name": "@luca/greet",
"version": "1.0.0",
"exports": "./mod.ts"
}
name
The name field is the name of the package, prefixed with JSR scope.
version
The version field is the version of the package and it must be a valid version of SemVer. Each time a new version is released, the package version must be added.
exports
The exports
field tells the user of the JSR package which modules should be imported. The exports
field can be specified as a single string or as an object that maps the entry point name to the path in the package.
{
"name": "@luca/greet",
"version": "1.0.0",
"exports": {
".": "./mod.ts",
"./greet": "./greet.ts"
}
}
In the example above, the exports
field is an object. The .
entry point is the default entry point for the package. The ./greet
entry point is a named entry point. Through this entry point, you can import the greet.ts
module using import { greet } from "@luca/greet/greet";
, Use import { greet } from "@luca/greet";
to import the mod.ts
module.
You can also specify the exports
field as a single string. This is very useful if there is only one entry point in the package. It is semantically equivalent to specifying the default entry point in object form.
{
"name": "@luca/greet",
"version": "1.0.0",
"exports": {
".": "./mod.ts"
}
"exports": "./mod.ts"
}
include and exclude
You can also use the include
and exclude
options to include and exclude files during the publishing process. When using deno.json
, you can use publish.include
and publish.exclude
to include and exclude files that are only used for publication. Instead of using all Deno subcommands. Check out [Filter Files] (#Filter Files) to learn more.
Write a document
Writing a document is critical to the success of a package. JSR makes it easy for package authors to get excellent documentation because it generates documentation based on JSDoc annotations in package source code.
The generated document is displayed on the package page. The document will also be displayed to the user in the editor in the form of a complete and hover description.
There are two important parts of the document:
- symbol documentation: This is the documentation for each individual function, interface, constant or class exported by the package.
- module documentation: This is the documentation for each exported module in the package - it acts as an overview or summary of all symbols in the module.
symbol documentation
Add a JSDoc comment for each exported function, interface, constant, or class.
/** // [!code ++]
* This function takes two numbers as input, and then adds these numbers using
* floating point math.
*/
export function add(a: number, b: number): number {
return a + b
}
For functions, you can add a document to a specific parameter or return type:
/**
* Search the database with the given query.
*
* @param query This is the query to search with. It should be less than 50 chars to ensure good performance. // [!code ++]
* @param limit The number of items to return. If unspecified, defaults to 20. // [!code ++]
* @returns The array of matched items. // [!code ++]
*/
export function search(query: string, limit: number = 20): string[]
For more complex ones, it is usually best to include an example that demonstrates how to use the function:
/**
* Search the database with the given query.
*
* ```ts
* search("Alan") // ["Alan Turing", "Alan Kay", ...]
* ```
*/
export function search(query: string, limit: number = 20): string[]
interfaces can also be annotated using JSDoc. Their properties and methods can also be commented:
/** The options bag to pass to the {@link search} method. */
export interface SearchOptions {
/** The maximum number of items to return from the search. Defaults to 50 if
* unspecified. */
limit?: number
/** Skip the given number of items. This is helpful to implement pagination.
* Defaults to 0 (do not skip) if not specified. */
skip?: number
/** The function to call if the {@link search} function needs to show warnings
* to the user. If not specified, warnings will be silently swallowed. */
reportWarning?(message: string): void
}
Classes can be similarly annotated as interfaces and functions:
/**
* A class to represent a person.
*/
export class Person {
/** The name of the person. */
name: string
/** The age of the person. */
age: number
/**
* Create a new person with the given name and age.
* @param name The name of the person.
* @param age The age of the person. Must be non-negative.
*/
constructor(name: string, age: number) {
if (age < 0) {
throw new Error('Age cannot be negative')
}
this.name = name
this.age = age
}
/** Print a greeting to the console. */
greet() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`)
}
}
module documentation
Adding JSDoc comments to modules is useful for providing an overview of modules and their export symbols.
To record a module, add a JSDoc annotation at the top of the module file and include the @module
tag anywhere in the comment:
/**
* This module contains functions to search the database.
* @module
*/
/** The options bag to pass to the {@link search} method. */
export interface SearchOptions {}
/** Search the database with the given query. */
export function search(query: string, options?: SearchOptions): string[];
You can also include examples in the module documentation:
/**
* @module
*
* This module contains functions to search the database.
*
* ```ts
* import { search } from "@luca/search";
*
* search("Alan") // ["Alan Turing", "Alan Kay", ...]
* ```
*/
Slow Types
Many of the features of JSR analyze source code, especially TypeScript types in the source code. This is done to generate documents, generate type declarations for the npm compatibility layer, and to speed up type checking for Deno projects using packages in JSR.
For these functions to work, the TypeScript source must not export any function, class, interface, or variable or type alias that itself or reference "Slow Types". "Slow Types" are types that are not explicitly written, or are too complex to require a lot of reasoning to understand.
For these features, it is too expensive for JSR to perform such inferences, so the public API does not support these types.
Warning
If JSR finds "Slow Types" in the package, some features will not work or have degraded quality. include:
- Type checking of package users will be slower. For most packages, the speed will be reduced by at least 1.5-2 times. It could be much higher.
- This package will not be able to generate type declarations for the npm compatibility layer, or "Slow Types" in the generated type declaration will be omitted or replaced with any .
- The package will not be able to generate the document for the package, or the generated document will be omitted or details will be missing.
What is "Slow Types"?
"Slow Types" appears when functions, variables, or interfaces are exported from packages, and their types are not explicitly written, or are too complex to be simply inferred.
example:
// There is a problem with this function because the return type is not explicitly written, so it must be inferred from the function body.
export function foo() {
return Math.random().toString()
}
const foo = 'foo'
const bar = 'bar'
export class MyClass {
// This property is problematic because the type is not explicitly written, so it must be inferred from the initialization item.
prop = foo + ' ' + bar
}
Slow Types inside the package (i.e. not exported) will not have any problems with JSR and package consumers.
TypeScript Limitations
This section lists all the restrictions imposed by the "no slow types" policy on TypeScript code:
- All exported functions, classes, variables, and types must have explicit types. For example, a function should have an explicit return type and a class should have an explicit property type.
- Module enhancement and global enhancement shall not be used. This means that the package cannot use
declare global
,declare module
, orexport as namespace
to expand the global scope or other modules. - The CommonJS function must not be used. This means that the package cannot use
export =
orimport foo = require("foo")
. - All types in exported functions, classes, variables, and types must be simply inferred or explicit. If the expression is too complex to infer, its type should be explicitly assigned to the intermediate type.
- Deconstruction in export is not supported. Export each symbol separately, not deconstruct.
- Types must not refer to private fields of a class.
Explicit Type
All symbols exported from packages must explicitly specify the type. For example, a function should have an explicit return type:
export function add(a: number, b: number) {
export function add(a: number, b: number): number {
return a + b;
}
The attributes of a class should have clear types:
export class Person {
name
age
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
Global Enhancement
Module enhancements and global enhancements are not allowed. This means that the package cannot use declare global
to introduce new global variables, or declare module
to augment other modules.
Here are some unsupported code examples:
declare global {
const globalVariable: string
}
declare module 'some-module' {
const someModuleVariable: string
}
CommonJS Features
CommonJS features are not allowed. This means that the package cannot use export =
or import foo = require("foo")
.
Use ESM syntax instead:
export = 5
export default 5
import foo = require('foo')
import foo from 'foo'
Type must be simple inference or explicit
All types in exported functions, classes, variables, and types must be simply inferred or explicit. If the expression is too complex to infer, its type should be explicitly assigned to the intermediate type.
For example, the default exported type is too complex to infer, so it must be explicitly declared using an intermediate type:
class Class {}
export default {
test: new Class(),
}
const obj: { test: Class } = {
test: new Class(),
}
export default obj
Or use assertion:
class Class {}
export default {
test: new Class(),
};
} as { test: Class };
For superclass expressions, evaluate the expression and assign it to the intermediate type:
interface ISuperClass {}
function getSuperClass() {
return class SuperClass implements ISuperClass {}
}
export class MyClass extends getSuperClass() {}
const SuperClass: ISuperClass = getSuperClass()
export class MyClass extends SuperClass {}
Export without destruction
Deconstruction in export is not supported. Instead of using deconstruction, export each symbol separately:
export const { foo, bar } = { foo: 5, bar: 'world' }
const obj = { foo: 5, bar: 'world' }
export const foo: number = obj.foo
export const bar: string = obj.bar
Types must not refer to private fields of a class
During the inference process, the type must not refer to the private fields of the class.
In this example, the public field refers to the private field, which is not allowed.
export class MyClass {
prop!: typeof MyClass.prototype.myPrivateMember
private myPrivateMember!: string
prop!: MyPrivateMember
private myPrivateMember!: MyPrivateMember
}
type MyPrivateMember = string
package score
JSR scores are metrics that JSR automatically assigns to each package based on certain factors indicating package quality. This score is used to rank software packages in search results, helping users understand the quality of software packages at a glance.
The JSR score is a percentage between 0 and 100, calculated based on factors in 4 advanced categories:
- Document: There are readme files, module documents, and documents of common functions and types.
- Best Practice: Packages should not use SLow Types and should be published with the package source.
- Discoverability: The package should have a description to help users find the package through searches.
- Compatibility: The package should have at least one runtime marked as "compatible" in the Runtime Compatibility section of the package page. Additionally, packages are rewarded for having multiple compatible runtimes.
Each category has different specific factors that affect scores. Each factor has different weights.