Skip to content

Dual-publish to npm alongside JSR #32

@marekprochazka

Description

@marekprochazka

Summary

molstar-components is currently published only to JSR. JSR packages work great in Deno, but as demonstrated by the mol-view-stories integration, consuming them from Node.js / pnpm / Next.js projects requires a series of non-obvious workarounds that raise the barrier to adoption for any future Node-based consumer.

Current pain points

The primary consumer, mol-view-stories, currently requires a series of non-obvious workarounds to integrate the library:

  1. Import-rewriting webpack plugin — Deno uses bare specifiers prefixed with npm: (e.g., npm:react@19.2.3). Webpack doesn't understand these, so a NormalModuleReplacementPlugin must be added to strip the protocol prefix:

    // next.config.ts — boilerplate no consumer should need
    new webpack.NormalModuleReplacementPlugin(/^npm:/, (resource) => {
      resource.request = resource.request.slice(4).replace();
    });
  2. transpilePackages — because JSR ships TypeScript source, Next.js can't import it directly. Consumers must add transpilePackages: ["@jsr/molstar__molstar-components"] to next.config.ts.

  3. Tailwind CSS sourcing — Tailwind 4 needs to scan the library's source files for used class names, which requires pointing content paths into the node_modules/@jsr/… directory — fragile, version-path-dependent, and breaks on pnpm's virtual store layout.

  4. TypeScript path mappingsjsconfig.json / tsconfig.json often needs custom paths entries to resolve JSR package names to their on-disk locations.

None of these are hard to solve individually, but together they make "just add the library" a multi-step integration task and a recurring source of friction for any new Node-based consumer.

Goal

Publish an npm-compatible package to npmjs.com (as @molstar/molstar-components) in parallel with the existing JSR release. The npm package would ship pre-compiled .js + .d.ts files with standard bare specifiers — no npm: prefixes, no TS source — so it just works with any Node/bundler toolchain out of the box. JSR publishing stays unchanged for Deno consumers.

The tool: dnt

dnt (Deno-to-npm Transform), maintained by the Deno team, automates most of this. It:

  • Compiles TypeScript to ESM + CJS
  • Rewrites Deno-style specifiers (npm:, jsr:) to plain npm specifiers
  • Generates a package.json with proper exports map and type declarations
  • Runs the existing test suite against the npm output to verify correctness

A build-npm.ts script using dnt can be added alongside the existing build.ts and wired to a deno task build:npm command, then called from CI before npm publish.

Real-world precedent

dnt itself is the most direct example of this pattern — the tool is a Deno package that uses dnt to publish itself to npm. It is available as both jsr:@deno/dnt and npm:@deno/dnt, and Node consumers install it from npm with no Deno-specific glue required. If the tool that solves the problem uses itself to solve its own problem, that's a reasonable signal it works.

What changes for consumers

  • Install: pnpm add @molstar/molstar-components instead of the mangled pnpm add jsr:@molstar/molstar-components
  • No webpack plugin needed to rewrite npm: specifiers
  • No transpilePackages entry in next.config.ts
  • Tailwind content path becomes a standard node_modules path rather than a fragile version-dependent JSR store path
  • TypeScript works out of the box without custom paths entries
  • Deno consumers are unaffected — JSR publish stays unchanged

Scope

  • Add build-npm.ts using dnt
  • Add deno task build:npm and wire it into the release CI step
  • Publish to npmjs.com under @molstar/molstar-components
  • Keep existing JSR publish unchanged
  • Update README.md with the npm install path and remove the workaround instructions

Metadata

Metadata

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions