Skip to content

Core | Bundle: Enable tree-shaken root bundle - #740

Open
onmax wants to merge 4 commits into
f5:mainfrom
onmax:fix/ts-treeshake-ssr-bloat
Open

Core | Bundle: Enable tree-shaken root bundle#740
onmax wants to merge 4 commits into
f5:mainfrom
onmax:fix/ts-treeshake-ssr-bloat

Conversation

@onmax

@onmax onmax commented Feb 13, 2026

Copy link
Copy Markdown

Summary

  • enable tree-shaking for @unovis/ts root build output instead of hard-disabling it
  • preserve intentional side effects only for styles/index modules

Why

When consumers bundle from @unovis/ts root entry, eager module loading can pull in graph/layout dependencies (elkjs, dagre stack) even for non-graph imports.

This change ensures non-graph imports (for example CurveType, Area, Line) do not include graph layout code, while Graph imports still include expected layout dependencies.

@github-actions

github-actions Bot commented Feb 13, 2026

Copy link
Copy Markdown

CLA Assistant Lite bot ✅ All required contributors have signed the F5 CLA for this PR. Thank you!

@onmax

onmax commented Feb 13, 2026

Copy link
Copy Markdown
Author

I have hereby read the F5 CLA and agree to its terms

@onmax
onmax force-pushed the fix/ts-treeshake-ssr-bloat branch from 6fdb730 to 41fbd06 Compare February 13, 2026 19:03
@rokotyan

Copy link
Copy Markdown
Contributor

@onmax Thanks for the contribution! Could you please tell more about this PR, was tree-shaking not working for you with the existing configuration?

Currently, Rollup creates individual modules for each TypeScript file in the code, and the actual tree-shaking is done by the user's bundler who uses Unovis. So I don't think this change will have any effect.

@rokotyan

Copy link
Copy Markdown
Contributor

Also your PR description mentions

add a CI-runnable regression check (packages/ts/scripts/check-tree-shake.mjs)

but I don't see anything like this in the code.

@onmax

onmax commented Feb 13, 2026

Copy link
Copy Markdown
Author

hey @rokotyan,

for context I am trying to deploy this PR: nimiq/validators-api#137

This PR uses LineChart component from Nuxt-Charts which uses unovis

In the cloudflare ci at build time I get this error:

Details

22:14:54.227 | FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
-- | --
22:14:54.227 | ----- Native stack trace -----
22:14:54.227 |  
22:14:54.246 | 1: 0xe13fde node::OOMErrorHandler(char const*, v8::OOMDetails const&) [node]
22:14:54.246 | 2: 0x11d5070 v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, v8::OOMDetails const&) [node]
22:14:54.247 | 3: 0x11d5347 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, v8::OOMDetails const&) [node]
22:14:54.248 | 4: 0x1402c05  [node]
22:14:54.249 | 5: 0x141c499 v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [node]
22:14:54.250 | 6: 0x13f0b48 v8::internal::HeapAllocator::AllocateRawWithLightRetrySlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [node]
22:14:54.250 | 7: 0x13f1a75 v8::internal::HeapAllocator::AllocateRawWithRetryOrFailSlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [node]
22:14:54.251 | 8: 0x13ca74e v8::internal::Factory::NewFillerObject(int, v8::internal::AllocationAlignment, v8::internal::AllocationType, v8::internal::AllocationOrigin) [node]
22:14:54.252 | 9: 0x182bef0 v8::internal::Runtime_AllocateInOldGeneration(int, unsigned long*, v8::internal::Isolate*) [node]
22:14:54.252 | 10: 0x7fd7c3eac476
22:14:55.673 | Aborted (core dumped)
22:14:55.740 | ELIFECYCLE  Command failed with exit code 134.
22:14:55.787 | Failed: error occurred while running build command

I can try to reproduce a minimal error if you like with just unovis

@onmax

onmax commented Feb 13, 2026

Copy link
Copy Markdown
Author
git clone --depth 1 --filter=blob:none --sparse https://github.com/onmax/repros.git
cd repros && git sparse-checkout set unovis-740
cd unovis-740 && pnpm i && pnpm build:oom

Importing only CurveType → 1498 modules transformed → OOM at 256 MB heap.

StackBlitz

@rokotyan

Copy link
Copy Markdown
Contributor

@onmax Thank for the context. Does the suggested change fix the problem?

Also, I wonder how it changes the built files in the dist folder of @unovis/ts. Did you get a chance to compare the outputs before and after?

@onmax

onmax commented Feb 13, 2026

Copy link
Copy Markdown
Author

Haven't tested the PR's fix against the repro yet — will do that and compare the dist outputs.

In the meantime I added a verify script that makes it easier to see the issue:

git clone --depth 1 --filter=blob:none --sparse https://github.com/onmax/repros.git
cd repros && git sparse-checkout set unovis-740
cd unovis-740 && pnpm i && pnpm verify

Output on @unovis/ts@1.6.4:

modules parsed from node_modules: 1492
bundle size: 15.6 KB

heavy deps pulled in for a single enum import: elkjs, three, leaflet

So just importing CurveType forces Vite to parse ~1500 modules from node_modules. The final bundle is small (Vite tree-shakes on its side), but the transform phase eats all the memory; that's what causes the OOM on Cloudflare Pages.

@rokotyan

Copy link
Copy Markdown
Contributor

@onmax Thanks, it would be great to get to the root of this and fix it if possible. Maybe we have circular dependencies somewhere. You can also do granular imports like import { CurveType } from '@unovis/ts/types/curve'

@onmax

onmax commented Feb 13, 2026

Copy link
Copy Markdown
Author

Tested the PR against the repro. Here are the results:

dist comparison (main vs PR)

main PR
Total dist size 12M 11M
JS files 219 203
Total JS bytes 8,272,475 8,254,337
d.ts files 210 210

The PR removes 16 intermediate barrle files and the side-effect import statements from index.js:

 // index.js (main)
-import './containers.js';
-import './components.js';
-import './data-models/index.js';
-import './types.js';
-import './utils/index.js';
 export { colors, colorsDark, ... } from './styles/colors.js';
 export { ContainerCore } from './core/container/index.js';
 // ...same named exports in both

Does it fix the OOM?

No. Both main and PR produce the same result when consumed:

main PR
Modules parsed (verify) 1492 1482
Heavy deps pulled in elkjs, three, leaflet elkjs, three, leaflet
build:oom (256MB) OOM OOM
Final bundle size 15.97 KB 15.97 KB

The PR removes the side-effect barrel imports, but index.js still has export { LeafletMap } from './components/leaflet-map/index.js' etc. Vite resolves ALL re-export targets during module resolution (before tree-shaking), so the same ~1500 modules are parsed and the OOM persists.

What does fix it

Granular/subpath imports bypass the barrel entirely:

-import { CurveType } from '@unovis/ts'
+import { CurveType } from '@unovis/ts/types/curve'
barrel granular
Modules parsed 1492 63
Heavy deps elkjs, three, leaflet none
build:oom OOM 98ms

Repro: unovis-740-fix

@rokotyan

Copy link
Copy Markdown
Contributor

@onmax So the problem is somewhere else: elkjs, three and maplibre (not Leaflet) are already lazy loaded.

Also we want to have the barrel exports, they are convenient. And they don't prevent users from using granular imports.

@rokotyan

Copy link
Copy Markdown
Contributor

If you could delve deeper and analyze why Vite loads the entire Unovis codebase while building, and whether this can be avoided, that would be great.

@onmax

onmax commented Feb 14, 2026

Copy link
Copy Markdown
Author

Update: keeping @unovis/ts barrels unchanged, but adding an additive path to avoid graph bloat.

The root cause:

  1. @unovis/ts root imports go through broad re-exports in src/index.ts and src/types.ts
  2. Vite expands this into a large transform graph during optimization
  3. this is why heavy chart/map deps get pulled even for enum-only usage

What’s added in fix/ts-treeshake-ssr-bloat:

  • packages/ts/scripts/check-vite-graph.mjs (+ docs) to measure transformed modules and heavy dep inclusion
  • additive entrypoint @unovis/ts/enums (src/enums.ts, no map/graph exports)

Result from local harness run:

  • root enum import: 1284 transformed node_modules
  • @unovis/ts/enums: 62
  • @unovis/ts/types/curve: 62
  • ~20.7x reduction, and heavy deps (elkjs, three, leaflet, maplibre-gl) are not pulled

No API migration required; root barrel stays intact.

@onmax
onmax force-pushed the fix/ts-treeshake-ssr-bloat branch from 3ce977a to 8f4cea5 Compare February 14, 2026 09:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants