diff --git a/javascript/jsr-esbuild.md b/javascript/jsr-esbuild.md new file mode 100644 index 0000000000..67f8097152 --- /dev/null +++ b/javascript/jsr-esbuild.md @@ -0,0 +1,225 @@ +# Using packages from JSR with esbuild + +[JSR](https://jsr.io/) is a brand new package repository for "modern JavaScript and TypeScript", [launched on March 1st](https://deno.com/blog/jsr_open_beta) by the Deno team as a new alternative to [npm](https://www.npmjs.com/) + +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. + +## Installing yassify with npx jsr add + +[@kwhinnery/yassify](https://jsr.io/@kwhinnery/yassify) is the demo package created as part of that [introductory blog post](https://deno.com/blog/jsr_open_beta#publishing-to-jsr). The code itself is a tiny snippet of TypeScript: + +```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](https://jsr.io/@kwhinnery/yassify). That page includes `npm` instructions for using it that look like this: + +> ```bash +> npx jsr add @kwhinnery/yassify +> ``` +> ```javascript +> import * 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: + +```bash +mkdir /tmp/site +cd /tmp/site +``` +I used the `npx jsr add` command from the JSR documentation: +```bash +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: +```json +{ + "dependencies": { + "@kwhinnery/yassify": "npm:@jsr/kwhinnery__yassify@^1.0.1" + } +} +``` +`.npmrc` has the following, which is described in the JSR documentation about [npm compatibility](https://jsr.io/docs/npm-compatibility#advanced-setup): +``` +@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? + +## Building an index.js file with esbuild + +Here's a tiny JavaScript file that uses `yassify`: + +```javascript +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: +```bash +npx esbuild index.js --bundle +``` +That outputs directly to standard out: +```javascript +(() => { + // 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: +```bash +npx esbuild index.js --bundle --outfile=bundle.js +``` +Now here's an `index.html` file that makes use of this bundle: +```html + + + + + Yassify + + +

Yassify

+ + + +``` +And that works! You can open `index.html` directly in a browser and it loads and executes the bundled code. + +## Trying that with my own package - and failing to get it to work + +I have just one package on NPM at the moment: [datasette-table](https://www.npmjs.com/package/datasette-table), an experimental Web Component for rendering tables from [Datasette](https://datasette.io/) on a page. + +The source code for that is in [simonw/datasette-table](https://github.com/simonw/datasette-table) on GitHub. + +I followed the [JSR intro post](https://deno.com/blog/jsr_open_beta#publishing-to-jsr) 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: +```json +{ + "name": "@datasette/table", + "version": "0.1.0", + "exports": "./datasette-table.js" +} +``` +Then I ran this command: +```bash +npx jsr publish +``` +This opened my browser to authenticate and pushed the package to JSR: [jsr.io/@datasette/table](https://jsr.io/@datasette/table). Pretty smooth! + +Let's try using that package via the `esbuild` method described above: + +```bash +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: +```bash +npx esbuild index.js --bundle --outfile=bundle.js --external:lit +``` + +So I started from scratch: +```bash +mkdir /tmp/datasette-demo2 +cd /tmp/datasette-demo2 +``` +This time I used a mechanism I found in the [advanced setup](https://jsr.io/docs/npm-compatibility#advanced-setup) section of the npm compatibility documentation: +```bash +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: +```bash +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: +```bash +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 going to publish this TIL and see if anyone can get me unblocked!