jsr-pub

Utility to generate jsr.json files

MIT License

Stars
3

jsr-pub

Utility to update the deno.json file with the fields required to publish to JSR. Useful specially to generate exports and version fields automatically.

Usage

Run this code changing the PKG-NAME with the name of the package you want to publish:

deno run -A https://deno.land/x/[email protected]/mod.ts --name=PKG-NAME

It generates the fields name, version and exports in your deno.json file.

  • The name field is the same value passed as CLI argument.
  • The version value is detected automatically from the most recent tag in the
    git repository
  • The exports value is generated by scanning your code (more info below).

Then, you can run deno publish --allow-dirty to send the package to JSR.

How exports are generated?

The exports field of JSR allows to change the real files of your modules. This is used to provide extensionless specifiers. For example:

{
  "name": "@my/package",
  "version": "1.0.0",
  "exports": {
    ".": "./main.ts",
    "./first-module": "./first_module.ts",
    "./second-module": "./second_module.ts"
  }
}

Now, you can import these modules with @my/package/first-module which returns the file ./first_module.ts.

What's wrong with that?

This has several issues:

  • The package is not longer compatible with the file system. For example, you cannot use a local version of this package for debuging purposes, because the paths of the modules are different.

  • You don't know the real format of the file you're importing. This is specially critical for the new import attributes standard that allows to import different types of modules like JSON, CSS etc.

  • This is not compatible with web standards and makes more difficult to switch to HTTP imports in the future. It makes harder to create modules that work on Deno and browsers.

  • It's an antipattern that even Node (which originally started this trend) now recommends to avoid:

    With import maps now providing a standard for package resolution in browsers and other JavaScript runtimes, using the extensionless style can result in bloated import map definitions. Explicit file extensions can avoid this issue by enabling the import map to utilize a packages folder mapping to map multiple subpaths where possible instead of a separate map entry per package subpath export. This also mirrors the requirement of using the full specifier path in relative and absolute import specifiers.

  • It overlaps the import maps standard. For example, in Deno it's really painful to combine JSR and import maps.

  • For large projects, is hard to maintain the list of exports, specially if you want to let import all or almost all files. It's common to forget to add a new module to the exports list.

How to fix this?

This script fixes this problem by generating exports compatible with regular HTTP and file system imports:

  • Scans your code searching for files with the extensions .ts, .js, .tsx,
    .jsx and .mjs.
  • Files that meet any of the following conditions are ignored:
    • It's in the directory /test/, /docs/, /deps/ or /node_modules/.
    • The name starts with . or _ or is in a directory starting with . or
      _.
    • The name ends with .test.*, _test.*, .bench.*, _bench.*.
    • The name is deps.*.
  • It generates the exports field keeping the filename intact, without removing
    the extension or changing the name.

For example, the following project:

├── docs/
|   ├── one.js
|   └── two.ts
├── test/
|   ├── one.js
|   └── two.ts
├── mod.ts
├── one.js
├── one.bench.js
├── two.ts
└── two.bench.ts

Generate the following exports:

{
  "exports": {
    ".": "./mod.ts",
    "./one.js": "./one.js",
    "./two.ts": "./two.ts"
  }
}

The mod.* file is automatically detected as the main module.

Now the specifiers in JSR are compatible with the filesystem, so it's possible to switch to local imports or http imports easily at any time. For example, the following import:

import one from "my_package/one.js";

Is compatible with any origin type, by editing the import map:

{
  imports: {
    "my_package/": "jsr:@my/package/", // to use JSR
    "my_package/": "./vendor/my/package/", // to use local file system
    "my_package/": "https://deno.land/x/my_package/", // to use HTTP imports
  }
}

Example