Skip to content

v3.14.0

Choose a tag to compare

@cldmv-bot cldmv-bot released this 21 Aug 05:58
· 1 commit to master since this release
v3.14.0
3eac962

release: v3.14.0 - resolve consumer-graph package exports in the… (#299)

Slothlet v3.14.0 Changelog

Release Date: August 2026
Release Type: Minor
Branch: release/3.14.0


Overview

Version 3.14.0 extends the browser importmap generator so it resolves
the exact exports subpaths of every package in the consumer's module
graph
, not just @cldmv/slothlet's own surface (#297).
generateBrowserAssets already emitted a complete importmap for
slothlet's own modules; other packages in the graph got only a prefix
map. Because import maps do plain prefix substitution and never consult
a package's exports, a subpath that exports redirects —
@scope/ext/errors where exports["./errors"] points at
./src/lib/errors.mjs — resolved to a literal …/ext/errors URL and
404'd in the browser, even though it loaded fine in Node. Consumers
worked around this by hand-maintaining per-dependency allowlists.

The generator now closes that gap by construction. No public API
signature changed and there are no breaking changes; the new behavior is
purely additive.


✨ Features

Browser importmap resolves consumer-graph package exports (#297)

generateBrowserAssets(apiDir, { slothletBase }) now covers a third
kind of module the page must resolve: the third-party packages the
registered API leaves import. As before, slothlet's own modules are
collected by the focused, unchanged collectSlothletSpecifiers and
rebased onto slothletBase. In addition, the generator scans the API
directory for the packages its leaves import, resolves each from the
consumer's node_modules tree, reads that package's package.json
exports, and emits the exact redirected subpath keys a prefix map
can never produce (@scope/ext/errors
…/@scope/ext/src/lib/errors.mjs). Without those keys a redirected
subpath 404s in the browser; with them a consumer can delete its
hand-maintained browser-dependency allowlists.

A new exported primitive, collectPackageSpecifiers(packageRoot),
does the package-agnostic half of the work: given any package's root
directory it reads that package's exports and returns the
bare-specifier → relative-target pairs the importmap needs. It handles
the same shapes the self-collector does, generalized — the package root
(.), flat (non-wildcard) subpaths, wildcard directories (./x/*
every module file under the declared target directory, including nested
files), and conditional exports (it selects the browser / import /
module / default condition and never a node/require-only target,
so a browser importmap can't be pointed at a CommonJS or Node-only
file). Only ES-module targets are emitted; a package with no exports
is left to the page's prefix map.

Discovery is driven by the registered API paths, so nothing new has to
be declared: the generator finds the packages the apiDir leaves
actually import. Sibling packages are served next to @cldmv/slothlet
under a base derived from slothletBase — its node_modules or CDN
parent (https://cdn/@cldmv/slothlet@3/https://cdn/), falling back
to the conventional /node_modules/ root when slothletBase isn't the
standard package layout — so no separate per-package configuration is
required for the common case. Every emitted entry is verified to exist
on disk, so a generated importmap never carries a URL that 404s by
construction. This also closes slothlet's own latent instance of the
same gap: the moment slothlet's browser source imports a third-party
subpath whose exports redirects, the generator now covers it. See
docs/BROWSER.md.


🧪 Tests

  • #297 — a staged consumer fixture whose registered API leaf imports
    a sibling extension package's exports-redirected subpaths reproduces
    the original 404 gap and asserts the exact keys now appear: flat
    redirects, the package root (.), wildcard directories including nested
    files, and the browser condition of a conditional export chosen over the
    node one. Skip paths are covered too — a node-only condition, a target
    missing on disk, and a non-module target are all omitted; relative,
    node:, package-internal (#), and unresolvable-bare specifiers
    contribute nothing; and the base is derived correctly for a
    node_modules-at-root layout, a CDN layout, and the "/" fallback. A
    direct unit suite exercises collectPackageSpecifiers across every
    exports shape — string sugar, conditions-only sugar, condition arrays,
    the module condition, null/non-module skips, and present/absent
    wildcard directories.
  • Full coverage gate green across node and browser arms.

📚 Documentation

  • NEW: docs/changelog/v3/v3.14.0.md — this
    changelog.
  • docs/BROWSER.md — the third kind of module the
    importmap resolves: consumer-graph package exports subpaths, why a
    prefix map 404s them, and how the sibling-package base is derived from
    slothletBase.
  • README — refreshed What's New.

🔧 Dependencies

No dependency updates.


Upgrade notes

  • No breaking changes. No public API signature changed.
    generateBrowserAssets accepts the same arguments and its importmap
    simply gains the exact exports-subpath keys for the third-party
    packages your API leaves import.
  • You can delete hand-maintained browser-dependency allowlists. If a
    consumer previously hand-listed a dependency's redirected subpaths (or
    added per-file importmap entries) to keep them from 404'ing in the
    browser, those entries are now produced automatically from the
    dependency's own exports — remove the workaround and regenerate.
  • Sibling packages are served under a base derived from
    slothletBase.
    By default that is slothletBase's node_modules/CDN
    parent; when slothletBase is served at a non-standard location (e.g.
    "/"), the generator falls back to the conventional /node_modules/
    root. If your extension packages are served somewhere else, serve them
    alongside @cldmv/slothlet under that derived root.

coverage

Metric Coverage
Statements 100.0%
Branches 100.0%
Functions 100.0%
Lines 100.0%

Avg: 100.0% · 46f9cb5 · Node lts/*


👥 Contributors