Is there a way to support ESM and node/NPM without an import map?

I’m working on a library that I publish as ESM. A primary goal is to allow it to be used without a build step, or even without using node/npm at all (just clone the repo and import). But I also realize most devs will want to use npm and consume the library that way.

It wouldn’t be a problem to support both workflows, except that my library depends on d3js. If I want ESM to work, I have to either vendor d3, or import it directly through URLs with a CDN. But this breaks NPM’s dependency resolution, ie if someone used my library and also used d3js directly, their app would include 2 separate copies of d3, even if it was the exact same version.

The best workaround I’ve found so far is to import d3 with a bare specifier (ie import * as d3 from 'd3') and use an import map with ESM to override it to import from jsdelivr (example below). This works, but I would love to ditch the import map. Is this possible?

Here’s an example (codepen):

<script type=importmap>
  {
    "imports": {
      "d3": "https://cdn.jsdelivr.net/npm/d3@7/+esm"
    }
  }
</script>

<script type='module' src="https://cdn.jsdelivr.net/npm/[email protected]/index.js"></script>

<iobio-data-broker
  url="https://s3.amazonaws.com/iobio/NA12878/NA12878.autsome.bam">
</iobio-data-broker>

<iobio-histogram
  broker-key="coverage_hist">
</iobio-histogram>