Skip to content

fix(core): shrink thin-engine bundle (remove fileTools force-link + thin-closure stubs)#18656

Open
RaananW wants to merge 5 commits into
masterfrom
raananw-thin-engine-package-size-research
Open

fix(core): shrink thin-engine bundle (remove fileTools force-link + thin-closure stubs)#18656
RaananW wants to merge 5 commits into
masterfrom
raananw-thin-engine-package-size-research

Conversation

@RaananW

@RaananW RaananW commented Jul 3, 2026

Copy link
Copy Markdown
Member

Problem

The pure-barrel tree-shaking refactor (#18441) regressed the thin-engine bundle size. Two root causes:

  1. fileTools was force-linked into the ThinEngine import closure. Pure-barrel inlined the lazy-injection seams that previously kept fileTools out of a thin bundle, so a thin-engine-only consumer pulled the full file/image loading code even when unused.
  2. Side-effect stubs were emitted into thin-engine files (e.g. Effect, Observable), adding dead weight to the minimal closure.

The thin engine is meant to be the smallest possible entry point, so both regressions matter.

Changes

fileTools de-linking — restore lazy injection seams

  • abstractEngine: statics route through EngineFunctionContext (added loadImage); _LoadFile throws _WarnImport("FileTools") when no loader is injected instead of importing the implementation.
  • shaderProcessor.ProcessIncludes: calls the throwing stub _FunctionContainer.loadFile instead of importing LoadFile from fileTools.pure (this was the third, previously-missed seam).
  • RegisterFileTools() now injects EngineFunctionContext.loadFile/.loadImage and _FunctionContainer.loadFile, so the real implementation is wired up only when fileTools is actually imported.

Stub generator — auto-exclude the thin-engine closure

  • scripts/treeshaking/generateSideEffectStubs.mjs now computes the thinEngine.pure import closure (via esbuild) and excludes those files from side-effect stub emission. Effect/Observable and other thin-closure files no longer carry stubs. Heavier engine variants (Engine/WebGPU/Native) still keep their stubs via STUB_EXCLUDED_CLASSES.

Note on Constants

An earlier source-based measurement suggested the Constants god-object (~15 KB) was a further lever. It is notprepareEs6Build.ts already inlines every Constants.X to a literal and strips the import during the shipped ES6 build (prepare-es6-build -cf Engines/constants.js), so constants.js is dropped from real bundles. No Constants change was needed, and the numbers below are measured against the real inlined production build.

Results (real production ES6 build, constants inlined + /*#__PURE__*/ annotations, minified)

Thin-engine consumer bundle (import { ThinEngine } from "@babylonjs/core/Engines/thinEngine"), bundled with esbuild --minify:

min gzip
Baseline (master) 178,059 44,794
This PR 152,584 37,781
Savings −25,475 (−14.3%) −7,013 (−15.7%)

Validation

  • tsc -b (core production build) ✅
  • Prettier ✅
  • ESLint (0 errors) ✅
  • verifyTreeShaking --all-packages — 16/16 ✅ (manifest drift, side-effect import closure, pure barrels, side-effect stubs across core/gui/loaders/serializers)
  • Stub generator idempotent (generate:side-effect-stubs --check) ✅

Public API is unchanged; behavior is preserved because the file/image loaders are still wired up whenever fileTools is imported (as before the regression).

Opening as draft to let CI run the full suite.

…hin-closure stubs)

The pure-barrel refactor (#18441) regressed the thin-engine bundle by
force-linking fileTools into the ThinEngine closure and by emitting
side-effect stubs into thin-engine files.

fileTools de-linking (restores lazy injection seams):
- abstractEngine: statics route through EngineFunctionContext (loadImage
  added); _LoadFile throws _WarnImport when no loader is injected.
- shaderProcessor.ProcessIncludes: call _FunctionContainer.loadFile
  (throwing stub) instead of importing LoadFile from fileTools.pure.
- RegisterFileTools() now injects EngineFunctionContext.loadFile/.loadImage
  and _FunctionContainer.loadFile.

Stub generator:
- Automatically exclude the thinEngine.pure import closure (computed via
  esbuild) from side-effect stub emission, so Effect/Observable and other
  thin-closure files no longer carry stubs. Heavier engine variants keep
  their stubs via STUB_EXCLUDED_CLASSES.

Production ES6 build (constants inlined), thin-engine consumer bundle:
  min:  178,059 -> 152,584  (-25,475, -14.3%)
  gzip:  44,794 ->  37,781  ( -7,013, -15.7%)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Please make sure to label your PR with "bug", "new feature" or "breaking change" label(s).
To prevent this PR from going to the changelog marked it with the "skip changelog" label.

Comment thread scripts/treeshaking/generateSideEffectStubs.mjs Fixed
@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Snapshot stored with reference name:
refs/pull/18656/merge

Test environment:
https://snapshots-cvgtc2eugrd3cgfd.z01.azurefd.net/refs/pull/18656/merge/index.html

To test a playground add it to the URL, for example:

https://snapshots-cvgtc2eugrd3cgfd.z01.azurefd.net/refs/pull/18656/merge/index.html#WGZLGJ#4600

Links to test your changes to core in the published versions of the Babylon tools (does not contain changes you made to the tools themselves):

https://playground.babylonjs.com/?snapshot=refs/pull/18656/merge
https://sandbox.babylonjs.com/?snapshot=refs/pull/18656/merge
https://gui.babylonjs.com/?snapshot=refs/pull/18656/merge
https://nme.babylonjs.com/?snapshot=refs/pull/18656/merge

To test the snapshot in the playground with a playground ID add it after the snapshot query string:

https://playground.babylonjs.com/?snapshot=refs/pull/18656/merge#BCU1XR#0

If you made changes to the sandbox or playground in this PR, additional comments will be generated soon containing links to the dev versions of those tools.

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

🟢 Memory Leak Test Results

4 passed, 0 leaked out of 4 scenarios

🟢 All memory leak tests passed — no leaks detected.

Passed Scenarios (4)
Scenario Package
Core Playground #2FDQT5#1508 @babylonjs/core
Core Playground #T90MQ4#14 @babylonjs/core
Core Playground #8EDB5N#2 @babylonjs/core
Core Playground #LL5BIQ#636 @babylonjs/core

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

⚡ Performance Test Results

🟢 All performance tests passed — no regressions detected.

RaananW and others added 2 commits July 3, 2026 17:39
…ne closure

The previous approach computed the thin-engine closure by bundling
thinEngine.pure.ts with esbuild from source. That under-counted the real
closure by 12 modules (constants inlining + injected /*#__PURE__*/ annotations
make the shipped build tree-shake differently than a source-only pass), so a
stub still leaked into Buffers/buffer.pure.ts (VertexBuffer.effectiveByte*).

Replace the computed closure with an authoritative, committed list
(scripts/treeshaking/thin-engine-modules.json) of the 53 core modules pulled
in by `new ThinEngine(canvas)` (packages/tools/tests/src/thinEngineOnly.ts).
The stub generator now:
- loads the list and validates every module exists (a rename can never
  silently drop a file out of the exclusion set),
- excludes all of them from stub emission,
- drops the esbuild dependency and the async closure computation.

Result: buffer.pure VertexBuffer stub removed; all 53 thin-engine modules are
guaranteed stub-free, enforced by `generate:side-effect-stubs --check` in CI.
Production thin-engine bundle size is unchanged from the prior commit
(178,059 -> 152,584 min; 44,794 -> 37,781 gzip).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The `new Set()` initializer was always overwritten by loadThinEngineClosure()
before any read. Declare STUB_EXCLUDED_FILES uninitialized (assigned once at
module load, before Step 2 uses it) to remove the useless assignment.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

🟢 Memory Leak Test Results

4 passed, 0 leaked out of 4 scenarios

🟢 All memory leak tests passed — no leaks detected.

Passed Scenarios (4)
Scenario Package
Core Playground #2FDQT5#1508 @babylonjs/core
Core Playground #T90MQ4#14 @babylonjs/core
Core Playground #8EDB5N#2 @babylonjs/core
Core Playground #LL5BIQ#636 @babylonjs/core

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

…egistered

The thin-engine size work de-linked fileTools from the always-loaded engine
base classes and made LoadFile/LoadImage lazily injected via
EngineFunctionContext. The full-index (barrel) build still imports the
fileTools side effect, but deep imports ("@babylonjs/core/Engines/engine")
and pure imports (RegisterStandardEngineExtensions) no longer registered it,
so any scene that loaded an external asset threw _WarnImport("FileTools")
and never became ready. This broke the ES6 deep/pure visualization tests.

RegisterAbstractEngineLoadFile() is reached by both the full Engine
side-effect wrapper and RegisterStandardEngineExtensions, but not by the
minimal thin engine. Registering fileTools there restores file/image loading
for deep and pure consumers while keeping the thin engine free of fileTools.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

🟢 Memory Leak Test Results

4 passed, 0 leaked out of 4 scenarios

🟢 All memory leak tests passed — no leaks detected.

Passed Scenarios (4)
Scenario Package
Core Playground #2FDQT5#1508 @babylonjs/core
Core Playground #T90MQ4#14 @babylonjs/core
Core Playground #8EDB5N#2 @babylonjs/core
Core Playground #LL5BIQ#636 @babylonjs/core

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

⚡ Performance Test Results

🟢 All performance tests passed — no regressions detected.

@sebavan

sebavan commented Jul 3, 2026

Copy link
Copy Markdown
Member

@sebavan

sebavan commented Jul 3, 2026

Copy link
Copy Markdown
Member

Let s also kill the flags.json override at 20% in the repo ? and maybe lock down the thinEngineOnlyTest to 2k growth ?

…th at 2KB/PR

- Lower the global package-size error threshold from 20% to 10%
  (.build/flags.json errorThreshold 1.2 -> 1.1).
- Add an absolute per-case growth cap in generateFileSizes.js: the thin
  engine (thinEngineOnly) may not grow by more than 2 KB in a single PR,
  regardless of its percentage headroom. This keeps the minimal thin-engine
  bundle from creeping up over time.
- Fix the threshold comparison to use the resolved loadedMainSize instead of
  the raw CDN entry (which is an object for the new main/async format), so the
  percentage check is correct for both legacy and current size entries.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

🟢 Memory Leak Test Results

4 passed, 0 leaked out of 4 scenarios

🟢 All memory leak tests passed — no leaks detected.

Passed Scenarios (4)
Scenario Package
Core Playground #2FDQT5#1508 @babylonjs/core
Core Playground #T90MQ4#14 @babylonjs/core
Core Playground #8EDB5N#2 @babylonjs/core
Core Playground #LL5BIQ#636 @babylonjs/core

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

@RaananW

RaananW commented Jul 3, 2026

Copy link
Copy Markdown
Member Author

@sebavan Done

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

⚡ Performance Test Results

🟢 All performance tests passed — no regressions detected.

@RaananW RaananW marked this pull request as ready for review July 3, 2026 19:01
Copilot AI review requested due to automatic review settings July 3, 2026 19:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Reduces ThinEngine consumer bundle size by restoring lazy file/image-loading injection seams (so fileTools is not force-linked into the thin-engine import closure) and by preventing side-effect stub emission into always-loaded thin-engine closure files.

Changes:

  • Reworked file/image loading to route through EngineFunctionContext and _FunctionContainer, throwing _WarnImport("FileTools") when loaders aren’t registered (instead of importing fileTools implementations).
  • Updated the side-effect stub generator to exclude the thin-engine closure (using an authoritative module list) and to strip stale stub regions/imports cleanly.
  • Tightened bundle-size CI enforcement for the thin-engine case (absolute growth cap + stricter default error threshold).

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated no comments.

Show a summary per file
File Description
scripts/treeshaking/thin-engine-modules.json Adds authoritative thin-engine import-closure module list used to exclude files from stub emission.
scripts/treeshaking/generateSideEffectStubs.mjs Excludes thin-engine closure + always-loaded engine classes from stub generation; improves stale-stub removal and import cleanup.
packages/tools/tests/scripts/generateFileSizes.js Adds an absolute per-PR growth cap for thinEngineOnly and fixes threshold comparisons to use loaded baseline sizes.
.build/flags.json Tightens default errorThreshold to 1.1 for bundle-size checks.
packages/dev/core/src/Engines/abstractEngine.functions.ts Removes fileTools fallback; _LoadFile now relies on injected loader or throws _WarnImport("FileTools"); adds loadImage seam.
packages/dev/core/src/Engines/abstractEngine.pure.ts Routes file/image loading through EngineFunctionContext and throws when loaders aren’t registered; removes stub region.
packages/dev/core/src/Engines/Processors/shaderProcessor.ts Removes LoadFile import; include-loading now uses _FunctionContainer.loadFile stub/injection seam.
packages/dev/core/src/Misc/fileTools.pure.ts RegisterFileTools() now injects loadFile/loadImage into EngineFunctionContext and wires _FunctionContainer.loadFile.
packages/dev/core/src/Engines/AbstractEngine/abstractEngine.loadFile.pure.ts Ensures RegisterFileTools() runs when registering the engine load-file extension (so full engines still wire loaders).
packages/dev/core/src/Engines/thinEngine.pure.ts Removes generated side-effect stub region from thin-engine closure file.
packages/dev/core/src/Engines/engine.pure.ts Removes generated side-effect stub region from always-loaded Engine class file.
packages/dev/core/src/Materials/effect.pure.ts Removes generated side-effect stub region from thin-engine closure file.
packages/dev/core/src/Misc/observable.pure.ts Removes generated side-effect stub region from thin-engine closure file.
packages/dev/core/src/Buffers/buffer.pure.ts Removes generated side-effect stub region from thin-engine closure file.

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

🟢 Memory Leak Test Results

4 passed, 0 leaked out of 4 scenarios

🟢 All memory leak tests passed — no leaks detected.

Passed Scenarios (4)
Scenario Package
Core Playground #2FDQT5#1508 @babylonjs/core
Core Playground #T90MQ4#14 @babylonjs/core
Core Playground #8EDB5N#2 @babylonjs/core
Core Playground #LL5BIQ#636 @babylonjs/core

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

@bjsplat

bjsplat commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

⚡ Performance Test Results

🟢 All performance tests passed — no regressions detected.

@sebavan

sebavan commented Jul 3, 2026

Copy link
Copy Markdown
Member

Let s merge Monday so we do not have to monitor tomorrow :-)

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.

4 participants