|
| 1 | +# ADR-0041: ESM Module Graph First for JSR Consumer Builds |
| 2 | + |
| 3 | +> **Status**: ACCEPTED |
| 4 | +> **Date**: 2026-05-24 |
| 5 | +> **Applies to**: v0.21.9 |
| 6 | +> **Extends**: ADR-0033, ADR-0037, ADR-0038 |
| 7 | +
|
| 8 | +## Context |
| 9 | + |
| 10 | +LessJS v0.21.x is a Deno workspace published to JSR and consumed by generated |
| 11 | +applications through `deno run -A jsr:@lessjs/create`. The intended architecture |
| 12 | +is standards-first ESM: |
| 13 | + |
| 14 | +1. LessJS packages expose TypeScript ESM entrypoints through JSR exports. |
| 15 | +2. Application source imports stable bare specifiers such as `@lessjs/core`, |
| 16 | + `@lessjs/ui/less-card`, and `@lessjs/signals/framework`. |
| 17 | +3. Vite/Rolldown performs the final production bundle, tree-shaking, and code |
| 18 | + splitting. |
| 19 | + |
| 20 | +Recent JSR consumer smoke tests exposed a boundary leak. The adapter-vite SSG |
| 21 | +pipeline fetched JSR source files into virtual modules, then handed those |
| 22 | +modules to Vite. Source that was valid under Deno/JSR package resolution could |
| 23 | +still contain metadata-resolved specifiers such as: |
| 24 | + |
| 25 | +```text |
| 26 | +jsr:@lessjs/signals@^0.21/framework |
| 27 | +``` |
| 28 | + |
| 29 | +Vite/Rolldown does not natively own Deno's `jsr:` package scheme. When the SSG |
| 30 | +pipeline bypasses Deno's resolver and fetches source manually, LessJS must not |
| 31 | +leave Deno/JSR resolution semantics half-applied inside the Vite module graph. |
| 32 | + |
| 33 | +## Decision |
| 34 | + |
| 35 | +**LessJS v0.21.9 will treat the ESM module graph as the primary contract for |
| 36 | +published package consumption. Vite/Rolldown remains the final bundler only.** |
| 37 | + |
| 38 | +This means: |
| 39 | + |
| 40 | +1. Userland and generated app code import LessJS through stable ESM bare |
| 41 | + specifiers. |
| 42 | +2. Package `exports` define the public entrypoint graph. |
| 43 | +3. Package `imports` may point at `jsr:` for Deno/JSR resolution, but `jsr:` |
| 44 | + specifiers must not leak into any Vite/Rolldown bundle graph. |
| 45 | +4. adapter-vite must provide a centralized LessJS package graph bridge for SSG |
| 46 | + when it consumes published JSR source outside Deno's native resolver. |
| 47 | +5. The bridge must be package-aware, export-aware, version-aware, and tested |
| 48 | + against a generated consumer project. |
| 49 | + |
| 50 | +## Architecture Boundary |
| 51 | + |
| 52 | +``` |
| 53 | +Generated app source |
| 54 | + imports @lessjs/app, @lessjs/core, @lessjs/ui/* |
| 55 | + | |
| 56 | + v |
| 57 | +Deno / JSR package resolution |
| 58 | + resolves package exports and imports |
| 59 | + | |
| 60 | + v |
| 61 | +LessJS ESM module graph |
| 62 | + stable bare specifiers and virtual ids only |
| 63 | + | |
| 64 | + v |
| 65 | +adapter-vite SSG package graph bridge |
| 66 | + normalizes LessJS package entrypoints for Vite |
| 67 | + | |
| 68 | + v |
| 69 | +Vite / Rolldown |
| 70 | + bundle, tree-shake, split chunks |
| 71 | +``` |
| 72 | + |
| 73 | +Vite is not responsible for understanding Deno-specific package schemes. |
| 74 | +LessJS is responsible for ensuring the module graph handed to Vite is ordinary |
| 75 | +bundler-consumable ESM. |
| 76 | + |
| 77 | +## Required Invariants |
| 78 | + |
| 79 | +- Published LessJS package versions are unified for a release train. |
| 80 | +- Public package entrypoints are declared through `exports`. |
| 81 | +- Internal monorepo dependencies use stable LessJS bare specifiers in source. |
| 82 | +- `jsr:@lessjs/*` may appear in Deno package metadata, but not in generated |
| 83 | + Vite virtual entries or unresolved Vite/Rolldown logs. |
| 84 | +- adapter-vite SSG resolution must not be a list of one-off special cases. |
| 85 | +- Consumer smoke tests must exercise the JSR-published path, not only the local |
| 86 | + workspace path. |
| 87 | + |
| 88 | +## Non-Goals |
| 89 | + |
| 90 | +- Do not replace Vite/Rolldown. |
| 91 | +- Do not introduce a custom general-purpose bundler. |
| 92 | +- Do not force application authors to import `jsr:` specifiers in app source. |
| 93 | +- Do not add npm publishing as a workaround for the v0.21.x consumer path. |
| 94 | +- Do not widen v0.21.9 into Edge Full-Stack runtime work. |
| 95 | + |
| 96 | +## Consequences |
| 97 | + |
| 98 | +### Positive |
| 99 | + |
| 100 | +- The generated app path matches the product story: ESM first, standards first, |
| 101 | + Vite final bundling. |
| 102 | +- JSR package consumption becomes a release gate instead of an after-publish |
| 103 | + surprise. |
| 104 | +- The SSG pipeline gets a single ownership boundary for package graph |
| 105 | + normalization. |
| 106 | +- Future packages such as content, i18n, UI, and adapters can be checked against |
| 107 | + the same graph rules. |
| 108 | + |
| 109 | +### Negative |
| 110 | + |
| 111 | +- adapter-vite must own more package-resolution logic than a purely local |
| 112 | + workspace build needs. |
| 113 | +- Release validation now needs an external-style consumer smoke test. |
| 114 | +- Version drift across packages becomes a blocking release issue. |
| 115 | + |
| 116 | +## Acceptance Signals |
| 117 | + |
| 118 | +v0.21.9 is accepted only when: |
| 119 | + |
| 120 | +1. all `packages/*/deno.json` LessJS dependency imports are version-aligned; |
| 121 | +2. a generated JSR consumer app builds from outside the workspace; |
| 122 | +3. Vite/Rolldown receives no unresolved `jsr:@lessjs/*` specifier; |
| 123 | +4. the SSG package graph bridge is centralized and tested; |
| 124 | +5. release docs record the exact consumer validation command. |
| 125 | + |
| 126 | +## Related |
| 127 | + |
| 128 | +- ADR-0033: Architecture Positioning: SSG Islands |
| 129 | +- ADR-0037: DSD-First Strategic Boundary |
| 130 | +- ADR-0038: ISR + Edge KV Architecture |
| 131 | +- `docs/sop/v0.21.x/SOP-011-jsr-consumer-esm-graph.md` |
0 commit comments