Add tsdown example project; fix Rolldown sprite assembly and declaration output - #9
Conversation
The rolldown entry point existed but the sprite-assembly hooks (renderChunk/generateBundle) were only registered for vite and rollup, so Rolldown-based builds (including tsdown) left __SVG_JAR_SPRITE__<hash>__ placeholders unreplaced and never emitted sprite or ?file assets. Register the same hooks under the rolldown key. With tsdown's dts: true, SVG imports re-exported from a library entry polluted the declaration bundle with runtime JS: rolldown-plugin-dts resolves imports from virtual .d.ts twins, and the plugin resolved those SVG imports into the JS module graph. Resolve SVG imports from declaration importers to virtual .d.ts modules instead, loaded as per-target declaration code mirroring client/*.d.ts, so libraries get correctly typed exports (and the JS chunk graph stays unpolluted). Also: - test-projects/tsdown/library: icon library example exercising sprite, named sprite, inline, and file modes with declaration output; built by build:test-projects - Rolldown integration test mirroring the Rollup one, asserting placeholders are replaced and assets emitted - fix codegen.test.ts missing isEmbedded (typecheck was failing) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
I reviewed this closely and built the tsdown example locally to verify the claims. Both bugs are real and genuinely fixed: the built That said, I think we should step back on scope before merging, because the example steers toward a pattern I don't think we want to ship. Testing gapBug 1 has a proper regression test. Bug 2, the more subtle fix, only has isolated unit tests on I'd add an integration test that builds the example and asserts on The bigger issue: prebaked sprites from a distributed libraryUnder tsdown, More fundamentally, a sprite sheet is an application-level optimization. Its wins (app-wide dedup, per-symbol tree-shaking, one sheet, one deploy origin) only exist when a single build owns the whole graph. A library that prebakes a sheet assembles it from its own icon set, ships it as an opaque asset the app can't tree-shake or merge, and can't reliably serve. That's the costs of a sprite with none of the benefits. Shipping inline "fixes" the serving problem but throws away the entire reason svg-jar exists, so that isn't a defense either. The model that actually delivers our value through a library is: the library ships raw SVGs and the app runs svg-jar to assemble one sheet. The one case where this is legitimateAn internal monorepo library consumed by an app you also own, where you control the deploy origin, add an asset-copy step, and pin a known base. That's a real use case, but it's a constrained build artifact, not a general icon library. The distinction that matters is not library vs app, it's whether you control the deploy origin. Making the monorepo case workI checked what rolldown/tsdown actually expose: no user-facing
What I'd like to see on this PR
The dts codegen itself is correct and worth keeping regardless, since re-exporting an icon as real public API is a legitimate thing someone will do. My concern is the example teaching a shape that breaks in production, not the fix. |
|
Might also be worth having a rolldown as app bundler test project rather than just the library use case. |
… rolldown app project - Add a `base` plugin option (trailing slash normalized) seeding state.base outside Vite; in Vite the resolved config base is used unless the option is set explicitly. This makes the internal-package copy-step model workable under Rolldown/tsdown, where configResolved never runs. - Narrow the tsdown example to the supported scenario: renamed library -> internal-package, now a component using a sprite icon internally plus an inline re-export and one ?file export, built with base: '/vendor/icons/' and a README documenting the asset copy step and when prebaked sprites are (not) appropriate. - Add a tsdown integration test that builds the example via execa (rebuilding the plugin first so it never tests a stale dist) and asserts on dist/index.js, dist/assets, and dist/index.d.ts - the declaration file must contain the typed exports and no runtime JS or .js imports. This covers the real dts path end to end; tsdown exiting 0 was previously sufficient even with polluted declarations. - Add test-projects/rolldown/vanilla: Rolldown as the application bundler, using the Vite apps' dist/assets layout, served with vite preview and covered by a new playwright project (port 4207) with a committed linux baseline. - Extend the rolldown integration test with base-option URL assertions; unit tests for base resolution. - README: document the base option and add a scope note - sprites are an app-level optimization; public libraries should ship raw SVGs or inline components; tsdown sprite output targets internal packages where the app controls the deploy origin. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Thanks for the thorough review — the deploy-origin analysis was spot on, and you were right that 1. Example narrowed to the internal-monorepo case. To make that work I went with your first option — a 2. dts assertion test added ( 3. Scope decision, stated in the README (tsdown setup section, 4. Rolldown-as-app-bundler project (your second comment): added Local runs: 154 unit/integration, 136 e2e, lint/prettier/typecheck clean (root |
Adds an example tsdown project under
test-projects/tsdown/library, and fixes the two bugs that stopped it from working.Bug 1: Rolldown builds never assembled sprites
The
@svg-jar/plugin/rolldownentry point existed, but the sprite-assembly hooks (renderChunk/generateBundle) were only registered under theviteandrollupkeys in the unplugin factory. Under Rolldown (and anything built on it, like tsdown):__SVG_JAR_SPRITE__<hash>__placeholders survived into the output chunk unreplaced?fileimports exported an asset URL pointing at a file that was never emittedFix: register the same hooks under the
rolldownkey (unplugin ≥3 supports it).Bug 2:
dts: truepolluted declaration output with runtime JSWith tsdown's
dts: true, a library entry that re-exports SVG components produced a brokenindex.d.ts: rolldown-plugin-dts resolves imports from virtual.d.tstwins of the source modules, and this plugin resolved those SVG imports into the regular JS module graph. The "declarations" ended up being a copy of the JS re-export chunk (importing from a.jsfile), and the shared SVG modules also forced an unnecessary chunk split in the JS output.Fix: when the importer is a declaration module (
.d.ts/.d.mts/.d.cts),resolveIdnow resolves SVG imports to a virtual.d.tsmodule, andloadserves per-target declaration code mirroring the ambient types inclient/*.d.ts(componentshape per target,stringfor?file). Libraries built with tsdown now get correctly typed exports:This only activates for declaration importers, so Vite/Rollup behavior is unchanged.
Example project
test-projects/tsdown/libraryis an icon library exercising sprite mode, a named sprite,?unsafe-inline, and?file, withdts: true. It's wired intobuild:test-projectsso CI builds it alongside the Vite apps.Also along the way
plugintypecheck, which was failing onmain:codegen.test.ts'sBASE_CTXwas missing the requiredisEmbeddedfieldTesting
pnpm --filter @svg-jar/plugin test:unit— 145 passed (12 files)pnpm --filter @svg-jar/plugin test:e2e— 121 passedpnpm --filter @svg-jar/plugin typecheck— clean (was failing on main)pnpm lint/ prettier — cleanpnpm --filter @svg-jar/plugin build:test-projects— all Vite apps + the tsdown library build🤖 Generated with Claude Code