JSR is a brand new package repository for "modern JavaScript and TypeScript", launched on March 1st by the Deno team as a new alternative to npm
My JavaScript ecosystem fluency isn't great, so it took me a bit of work to figure out how to use packages from JSR in my browser.
@kwhinnery/yassify is the demo package created as part of that introductory blog post. The code itself is a tiny snippet of TypeScript:
/**
* Yassify a string of text by appending emoji
*
* @param str The string of text to yassify.
* @returns a string of text with emoji appended
*/
export function yassify(str: string): string {
return `${str} 💅✨👑`;
}
It's published to JSR here. That page includes npm
instructions for using it that look like this:
npx jsr add @kwhinnery/yassifyimport * as mod from "@kwhinnery/yassify";
For someone with limited JavaScript ecosystem fluency like myself, that is not enough information! I want to run this code in a browser.
Part of the problem is that there are a bewildering array of build tool options. I wanted the thing with the least number of steps - eventually I found this pattern using esbuild
that seems to work.
I already had npm
and npx
installed.
First I created myself a directory for my experiment:
mkdir /tmp/site
cd /tmp/site
I used the npx jsr add
command from the JSR documentation:
npx jsr add @kwhinnery/yassify
Output:
Setting up .npmrc...ok
Installing @kwhinnery/yassify...
$ npm install @kwhinnery/yassify@npm:@jsr/kwhinnery__yassify
added 1 package in 711ms
Completed in 824ms
This created a bunch of files. Running find .
reveals the following:
./node_modules
./node_modules/@kwhinnery
./node_modules/@kwhinnery/yassify
./node_modules/@kwhinnery/yassify/mod.d.ts
./node_modules/@kwhinnery/yassify/package.json
./node_modules/@kwhinnery/yassify/mod.js
./node_modules/.package-lock.json
./.npmrc
./package-lock.json
./package.json
The package.json
file contains:
{
"dependencies": {
"@kwhinnery/yassify": "npm:@jsr/kwhinnery__yassify@^1.0.1"
}
}
.npmrc
has the following, which is described in the JSR documentation about npm compatibility:
@jsr:registry=https://npm.jsr.io
The actual yassify
code lives in that ./node_modules/@kwhinnery/yassify/mod.js
file.
OK - so we've run a command and got ourselves a node_modules
directory with the yassify
code in it. How do we use that in a browser?
Here's a tiny JavaScript file that uses yassify
:
import { yassify } from "@kwhinnery/yassify";
const h1 = document.querySelector("h1");
h1.innerText = yassify(h1.innerText);
And the incantation to have esbuild
resolve that import and bundle all of the code together into a single file:
npx esbuild index.js --bundle
That outputs directly to standard out:
(() => {
// node_modules/@kwhinnery/yassify/mod.js
function yassify(str) {
return `${str} \u{1F485}\u2728\u{1F451}\u{1F984}`;
}
// index.js
var h1 = document.querySelector("h1");
h1.innerText = yassify(h1.innerText);
})();
Or you can add --outfile=bundle.js
to write it to a file:
npx esbuild index.js --bundle --outfile=bundle.js
Now here's an index.html
file that makes use of this bundle:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Yassify</title>
</head>
<body>
<h1>Yassify</h1>
<script src="bundle.js"></script>
</body>
</html>
And that works! You can open index.html
directly in a browser and it loads and executes the bundled code.
I have just one package on NPM at the moment: datasette-table, an experimental Web Component for rendering tables from Datasette on a page.
The source code for that is in simonw/datasette-table on GitHub.
I followed the JSR intro post and published it to GitHub.
I signed into JSR using my GitHub account and created a scope called @datasette
- all packages on JSR are published within a scope.
I added a jsr.json
file te the root of the repository:
{
"name": "@datasette/table",
"version": "0.1.0",
"exports": "./datasette-table.js"
}
Then I ran this command:
npx jsr publish
This opened my browser to authenticate and pushed the package to JSR: jsr.io/@datasette/table. Pretty smooth!
Let's try using that package via the esbuild
method described above:
mkdir /tmp/datasette-demo
cd /tmp/datasette-demo
npx jsr add @datasette/table
echo 'import * as mod from "jsr:@datasette/table";' > index.js
npx esbuild index.js --bundle --outfile=bundle.js
And I got this error:
✘ [ERROR] Could not resolve "npm:lit@^2.2.7"
node_modules/@datasette/table/datasette-table.js:1:36:
1 │ import {LitElement, html, css} from 'npm:lit@^2.2.7';
╵ ~~~~~~~~~~~~~~~~
You can mark the path "npm:lit@^2.2.7" as external to exclude it from the bundle, which will
remove this error and leave the unresolved path in the bundle.
1 error
lit
is the only dependency of my component. I checked node_modules
and it had all of the lit
files in it, so they had been installed correctly - but something wasn't working.
I tried adding --external:npm:lit
to the esbuild
command, but that didn't help.
This failed too:
npx esbuild index.js --bundle --outfile=bundle.js --external:lit
So I started from scratch:
mkdir /tmp/datasette-demo2
cd /tmp/datasette-demo2
This time I used a mechanism I found in the advanced setup section of the npm compatibility documentation:
echo '@jsr:registry=https://npm.jsr.io' > .npmrc
npm install @jsr/datasette__table
find .
showed me that it had installed the Lit packages, but I also noticed this:
find . | grep datasette
./node_modules/@jsr/datasette__table
./node_modules/@jsr/datasette__table/datasette-table.js
./node_modules/@jsr/datasette__table/package.json
So the JSR @datasette/table
package is in a slightly different shape.
Now I tried the esbuild
command again:
echo 'import * as mod from "@jsr/datasette__table";' > index.js
npx esbuild index.js --bundle --outfile=bundle.js
But I got the same error again:
✘ [ERROR] Could not resolve "npm:lit@^2.2.7"
node_modules/@jsr/datasette__table/datasette-table.js:1:36:
1 │ import {LitElement, html, css} from 'npm:lit@^2.2.7';
╵ ~~~~~~~~~~~~~~~~
You can mark the path "npm:lit@^2.2.7" as external to exclude it from the bundle, which will
remove this error and leave the unresolved path in the bundle.
1 error
And at this point... I gave up. I'm still seeking a solution - progress so far:
Update 5th March 2024: the JSR team shipped this fix and now the following recipe works exactly as I want it to:
mkdir /tmp/datasette-demo3
cd /tmp/datasette-demo3
echo '@jsr:registry=https://npm.jsr.io' > .npmrc
npm install @jsr/datasette__table
echo 'import * as mod from "@jsr/datasette__table";' > index.js
npx esbuild index.js --bundle --outfile=bundle.js
echo '<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Datasette News</title>
</head>
<body>
<datasette-table
url="https://datasette.io/content/news.json"
></datasette-table>
<script src="bundle.js"></script>
</body>
</html>' > index.html
Now open index.html
in a browser and:
Created 2024-03-02T12:03:04-08:00, updated 2024-03-05T17:28:59-08:00 · History · Edit