Understanding `bin/importmap pin` urls

I’m not primarily a front-end developer, been slowly learning how to deal with the brave new importmaps world.

I learned the hard way what it means that a js library is “fully bundled” and also “esm” (it usually has to be both to work correctly with Rails importmaps). I also understood that some library authors don’t provide fully-bundled esm out of the box, but jsdelivr CDN can auto-bundle them for me if I call bin/importmap with the --from jsdelivr option, and use /+esm at the end of the link.

Additionally, I understood that even if I use --from jsdelivr the bin/importmap still uses jspm API to figure out what packages and dependencies are available and needed, and only goes to jsdelivr for the final download.

However, I still fail to understand the following 3 things:

1. Why is it that some packages on jsdelivr have /+esm and some don’t?

For example:

> bin/importmap pin chartjs-plugin-annotation/+esm --from jsdelivr
Couldn't find any packages in ["chartjs-plugin-annotation/+esm"] on jsdelivr

But if I run this for popperjs/core it works:

> bin/importmap pin @popperjs/core/+esm --from jsdelivr
[downloads correctly]

NOTE: I know that for this specific Chart.js plugin package you don’t need “esm”, but rather “umd”. But my question is not about that, I’m just using it as an example. :upside_down_face:

2. What is it specifically about the package that determines whether /+esm is available?

I’ve been staring at these 2 package.json files for a long time:

  1. chartjs-plugin-annotation/package.json at master · chartjs/chartjs-plugin-annotation · GitHub
  2. popper-core/package.json at master · intercom/popper-core · GitHub

And I can’t figure out or find documentation anywhere that explains how to tell from their code what file paths or “magical” /+esm type links they expose once hosted on a CDN.

3. How can you tell which urls are available at all?

For some libraries I can pin what seems to be any file, but for others, no matter what I try, I get the “not found” error from bin/importmap. At the same time, the same url works if I use it in the browser.

For example, this file is available directly: https://cdn.jsdelivr.net/npm/chartjs-plugin-annotation/dist/chartjs-plugin-annotation.cjs

But no matter what I try, I cannot pin it with bin/importmaps:

bin/importmap pin chartjs-plugin-annotation/dist/chartjs-plugin-annotation.cjs --from jsdelivr

I also tried like this:

bin/importmap pin /npm/chartjs-plugin-annotation/dist/chartjs-plugin-annotation.cjs --from jsdelivr

and like this:

bin/importmap pin @chartjs/chartjs-plugin-annotation/dist/chartjs-plugin-annotation.cjs --from jsdelivr

And a few other ways (like omitting /dist/, etc). Why are they not working? How do I determine the valid pin-compatible urls, where do I get that information from?

Any explanation would be very much appreciated!

  1. Why is it that some packages on jsdelivr have /+esm and some don’t?

This seems like a bug in the implementation. Whether its a bug from the JSPM API, or from importmap-rails itself, im not sure. I would file an issue.

The other option is to use esm.sh as the provider which is roughly how those /+esm routes are produced.

bin/importmap pin chartjs-plugin-annotation --from esm.sh

2. What is it specifically about the package that determines whether /+esm is available?

/+esm is just a way to tell JSPM you want an esm compatible file back. Whether or not the file is available is determined by the exports or files fields in the package.json. (See: Modules: Packages | Node.js v23.6.0 Documentation )

  1. How can you tell which urls are available at all?

Without directly reading the package.json, the easiest way to see what files are available is by going to the JSPM Sandbox Generator and and checking in the package export dropdown.

Link below of the generator for the 2 packages you mentioned.

I see that url not working quite often, so seemed unlikely to be unintentional, but at this early stage anything’s possible. :man_shrugging:

Is /+esm processed by jspm, not jsdelivr? I thought it was a special feature of jsdelivr where it dynamically builds a fully-bundled esm by inlining the imports listed in the “main” esm file. Is that not what’s happening?

So if I only see . [main entry], that means none of the other files are available? How would it explain the example of chartjs-plugin-annotation which shows only main entry in that dropdown, but this non-main file is still available? https://cdn.jsdelivr.net/npm/chartjs-plugin-annotation/dist/chartjs-plugin-annotation.cjs

Is /+esm processed by jspm, not jsdelivr? I thought it was a special feature of jsdelivr where it dynamically builds a fully-bundled esm by inlining the imports listed in the “main” esm file. Is that not what’s happening?

sigh typo. Need to re-read before I post. Yes its a feature of jsdelivr only, not part of JSPM at all.

So the reasoning is a little complicated if you’ve never looked at package.json exports, but the TLDR is that this package implicitly decides what to give you based on how what environment its loaded in.

So for JSPM, it will automatically load the .esm.js version.

These keys essentially define the “publicly importable files”

so the .cjs version will only work when using node const chart = require("chartjs-plugin-annotation") for example.

Dont know if any of this helps, but its a wide chasm understanding the authoring keys for NPM packages. And also, if youre using importmaps, it expects everything to be ESM importable files because thats how the spec for importmaps module resolution was designed :confused:

Yeah, in general terms I can understand that some sort of resolution based on various package.json keys is going on, and am hoping to understand very specifically what resolution is going on, and by which logic. At least for my examples.

Jsdelivr does make chartjs-plugin-annotation.cjs available. And in some cases importmaps seems capable to just pin any file available on a CDN, in others it isn’t. It’s unlikely that any intermediary parses the actual contents of js files to check if they are esm or not, so it must be purely package.json-based (and also based on file paths included with the package).

I appreciate that you’re sharing the parts you understand. It helps put the full picture together.

The extra intermediary layer you’re hitting is JSPM.

JSPM maintains a list of “entry point” files you can import and this is what importmaps-rails uses to determine valid entry points.

I hit this issue with JSPM in one of my own packages outside of rails importmaps.

So even though you’re using jsdelivr, JSPM is still decided what files you’re allowed to use based on its guess reading the package.json

1 Like