Make DebugRenderTree tree-shakeable in production app builds#21483
Draft
NullVoxPopuli-ai-agent wants to merge 1 commit into
Draft
Make DebugRenderTree tree-shakeable in production app builds#21483NullVoxPopuli-ai-agent wants to merge 1 commit into
NullVoxPopuli-ai-agent wants to merge 1 commit into
Conversation
Previously, glimmer's EnvironmentImpl statically imported the DebugRenderTree implementation and instantiated it behind the runtime flag EmberENV._DEBUG_RENDER_TREE. Because that flag can be flipped at runtime (Ember Inspector's production opt-in), no bundler could ever prove the implementation unused, so every production app shipped the render-tree bookkeeping code even when nothing could ever use it. This inverts the dependency: the environment no longer constructs the tree; its delegate supplies one. The implementation is registered via a small factory seam in @ember/-internals/glimmer: - In debug builds, registration happens eagerly (stripped from production builds), preserving _DEBUG_RENDER_TREE defaulting to on in development. - In production builds, registration happens as a top-level side effect of importing @ember/debug/lib/capture-render-tree — the very module Ember Inspector's EmberENV._DEBUG_RENDER_TREE opt-in exists to serve. Apps that include captureRenderTree (e.g. via the ember barrel) keep the documented production opt-in. Production apps whose module graph never reaches captureRenderTree pay neither the bytes nor the bookkeeping. For the v2-app-hello-world smoke test app, DebugRenderTreeImpl and friends are now fully tree-shaken: 152.75 kB -> 150.99 kB (48.49 -> 47.92 kB brotli/gzip). EnvironmentDelegate.enableDebugTooling (boolean) is replaced by an optional debugRenderTree property; isArgumentCaptureError now keys off the presence of the tree, matching the previous behavior. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Makes the
DebugRenderTreeimplementation tree-shakeable, so production builds of apps that never use it — like thev2-app-hello-world-templatesmoke-test app — no longer ship it.Result for
smoke-tests/v2-app-hello-world-template(vite build):DebugRenderTreeImplin bundlerender-node:,captureRefs, … present)Findings: why it always shipped
The hello-world app's only import is
renderComponentfrom@ember/renderer. That path reaches glimmer'sEnvironmentImpl, which did:with
enableDebugToolingcoming fromENV._DEBUG_RENDER_TREE. SinceEmberENV._DEBUG_RENDER_TREEis a runtime flag (documented as Ember Inspector's production opt-in, set viawindow.EmberENVbefore Ember evaluates), no bundler can ever prove the branch dead — so the implementation was retained in every production build, along with its bookkeeping cost being oneundefined-check away on every render path.The bytes themselves can't be conditionally present at runtime, so "don't ship it" necessarily means the production opt-in has to become graph-dependent: available when something in the app's module graph actually pulls in the debug tooling, absent otherwise.
What this PR does
Inverts the dependency: the environment no longer constructs the tree — its delegate supplies one (
EnvironmentDelegate.enableDebugTooling: boolean→debugRenderTree?: DebugRenderTree). The implementation is registered through a tiny factory seam in@ember/-internals/glimmer:@ember/-internals/glimmer/lib/environment.ts, insideif (DEBUG), which is stripped fromdist/prod)._DEBUG_RENDER_TREEstays "always on in development" exactly as documented.@ember/debug/lib/capture-render-tree— the module theEmberENV._DEBUG_RENDER_TREEopt-in exists to serve (Ember Inspector callscaptureRenderTree). Apps whose graph includes it (e.g. anything importing the@ember/debugbarrel, including classic apps via theemberbarrel) keep the documented production opt-in unchanged.Apps whose production graph never reaches
captureRenderTree— like the hello-world app — pay neither the bytes nor the bookkeeping.isArgumentCaptureErrornow keys off the presence of the tree, which matches the previousenableDebugToolingbehavior.Behavior change (the one caveat)
In a production build that tree-shakes
captureRenderTreeaway, settingwindow.EmberENV = { _DEBUG_RENDER_TREE: true }no longer does anything — the code simply isn't there. That combination previously "worked" in the sense that the tree recorded, but such an app has no code path that could ever read it (captureRenderTreeis the only consumer), so nothing observable is lost. Classic/barrel apps are unaffected.Verification
pnpm build+pnpm type-check:internalspass; eslint + prettier clean on changed files.main). Also verified with focused runs of the affected suites (filter=render tree23/23 — covers bothApplication test: debug render treeand glimmer'sDebug Render Treesuite;[integration] env;renderComponent41/41).dist/dev: eager registration confirmed present;dist/prod:@ember/-internals/glimmerno longer references the implementation,capture-render-tree.jsretains the registration call (rollup'smoduleSideEffects: falsefor@ember/debugdoes not strip it, andember-sourcepublishes nosideEffects: false, so app bundlers keep it too).vite preview:<body>hi </body>), zero render-tree markers in the bundle;captureRenderTreeand setsEmberENV._DEBUG_RENDER_TREE = trueinindex.htmlrenders correctly with the implementation shipped and live (152.83 kB — the cost returns only when actually opted in).The glimmer-workspace
DebugRenderTreetest suite now provides the tree via a getter on the test env delegate (fresh instance per environment, mirroringEmberEnvironmentDelegate).🤖 Generated with Claude Code