Shrink hello-world bundle to 109 KB / 34 KB gzip / 30 KB brotli (-29%)#21387
Open
NullVoxPopuli-ai-agent wants to merge 14 commits intoemberjs:nvp/configure-side-effectsfrom
Conversation
…ords Cuts the hello-world smoke test from 243.30 KB / 77.32 KB gzip to 168.59 KB / 53.67 KB gzip — a 30.6% gzip reduction — while leaving the classic v2-app-template essentially flat (+0.21 KB gzip from one extra side-effect import). Three changes, in order of impact: 1. **Lazy `-mount` and `-outlet` keyword registration.** Until now `resolver.ts` statically imported `mountHelper` and `outletHelper`, which transitively pulled `@ember/engine/instance`, `@ember/routing/-internals` (for `generateControllerFactory`), and the rest of the routing/engine graph into every bundle that uses `renderComponent`. Replace the static import with a `registerBuiltInKeywordHelper(name, helper)` registry on the resolver, and add a side-effect-only `syntax/register-routing-keywords.ts` that classic-app setup imports from `setup-registry.ts`. Bundles that don't pull in `setup-registry` (i.e. the hello-world that only uses `@ember/renderer`) drop ~138 KB of routing + ~7 KB of engine code. 2. **Split classic `Renderer` subclass into `classic-renderer.ts`.** Move `Renderer extends BaseRenderer`, `ClassicRootState`, the concrete `DynamicScope` class, and the `View` interface out of `renderer.ts`. Hoists the imports those carry — `OutletView`, `createRootOutlet`, `RootComponentDefinition`, `makeRouteTemplate`, `renderMain`, `guidFor`, `getViewElement`, `getViewId`, `dict`, `createCapturedArgs`, `EMPTY_POSITIONAL`, `curry` — out of the renderer-only bundle. Adds a `RootState` interface so `RendererState` can manage either kind without statically depending on classic code. `setup-registry.ts` now imports `Renderer` from `./classic-renderer`. The renderer entry re-exports the classic types so existing `from '.../renderer'` import sites keep working. 3. **Replace `RSVP.defer` in `renderSettled` with native Promise.** Standalone this didn't move the bundle (rsvp was reachable via other paths), but together with #1 it lets the hello-world bundle drop the 62 KB rsvp shared chunk entirely — `@ember/engine`, `@ember/routing`, and `@ember/-internals/runtime/lib/ext/rsvp` were the remaining consumers, and #1 pulls those off the renderer-only path. Verified: `lint:eslint`, `type-check:internals`, `type-check:types`, `type-check:handlebars`, `test:node`, `test:blueprints`, classic v2-app-template build, hello-world build, and a vite dev build of the full test suite all pass. Browser tests will run in CI. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Builds on the previous classic-renderer/routing-keywords split. The
hello-world smoke test goes from 168.59 KB / 53.67 KB gzip to
160.11 KB / 50.88 KB gzip. Cumulative with prior commit: 243.30 KB /
77.32 KB → 160.11 KB / 50.88 KB (34.2% gzip reduction). Classic
v2-app-template stays flat (319.55 KB / 99.38 KB).
Each change is the same registration pattern as before — a separate
side-effect file imported by classic-app `setup-registry`, leaving the
heavy module out of the renderer-only path.
1. **Curly symbols extracted to `curly-symbols.ts`.** `BOUNDS`,
`DIRTY_TAG`, `IS_DISPATCHING_ATTRS`, and the new `CURLY_COMPONENT_BRAND`
live in their own file. `isCurlyManager` is now a brand check
(`manager[CURLY_COMPONENT_BRAND] === true`) instead of an instance
check, so the resolver no longer pulls in `./curly` (the full
`CurlyComponentManager` lifecycle, ~17 KB) just to identify the
manager. `curly.ts` re-exports the symbols for back-compat and tags
`CURLY_COMPONENT_MANAGER` with the brand. `classic-renderer.ts` and
`resolver.ts` now import from `curly-symbols.ts`.
2. **`@ember/-internals/glimmer/lib/component`'s top-level side effects
moved to `register-curly-component.ts`.**
`setInternalComponentManager(CURLY_COMPONENT_MANAGER, Component)` and
`Component.reopenClass({ positionalParams: [] })` ran at module load
time, which kept the full classic `Component` class graph reachable
from anything that imported `@ember/component` (e.g.
`@glimmer/component`'s `setComponentManager`/`capabilities` imports).
The registration now lives in a side-effect-only file imported by
`setup-registry.ts`, so classic apps still get it on boot.
3. **`DebugRenderTreeImpl` factory moved behind a registry in
`@glimmer/runtime/.../environment.ts`.** Previously `EnvironmentImpl`
imported `DebugRenderTree` statically and only constructed one when
`delegate.enableDebugTooling` was true — but the import alone pulled
the whole class (and its `getDebugName` cousin) into every bundle.
New `registerDebugRenderTreeFactory` lets a side-effect module
(`debug-render-tree-register.ts`) supply the constructor; without
that import, `env.debugRenderTree` stays `undefined` even when the
delegate flag is set. Classic apps re-register it via
`setup-registry.ts`. `getDebugName` was the other static reach into
`debug-render-tree`, so it moved to its own file (`get-debug-name.ts`)
that opcodes can import without dragging the rest in.
4. **`to-bool.ts` swapped `isArray` from `@ember/array` for
`Array.isArray(x) || isEmberArray(x)`.** `isArray` from
`@ember/array` calls `EmberArray.detect`, which transitively pulls
`@ember/array`'s entire mixin/Enumerable/Observable/computed graph
(~16 KB) just to test array-ness inside `{{#if}}`. Using
`isEmberArray` from `@ember/array/-internals` (a WeakSet brand set
in `EmberArray#init`) covers all instances of EmberArray-mixed
classes — the same set the old check covered in practice.
Verified: lint clean, all type-checks pass, `test:node` 20/20,
`test:blueprints` 265/265, both smoke-test apps build.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two more lazy-load splits on top of the previous round. Hello-world goes from 160.11 KB / 50.88 KB → 159.49 KB / 50.67 KB. The classic v2-app-template also gets a small win (319.55 → 318.27 KB raw, 99.38 → 98.94 KB gzip). Cumulative from baseline: 243.30 KB / 77.32 KB → 159.49 KB / 50.67 KB (34.5% gzip reduction). 1. **`contentFor` extracted to `runtime/lib/mixins/content-for.ts`.** `each-in.ts` (which the renderer registers as the `-each-in` keyword helper) imports `contentFor` to unwrap proxies before iterating. Until now that import dragged in `runtime/lib/mixins/-proxy`, which defines `ProxyMixin = Mixin.create(...)` at module scope — the entry point to the entire EmberObject / Mixin / computed graph (proxy.ts imports `Mixin`, `computed`, `defineProperty`, `set`, etc.). Moving the 8-line `contentFor` function into its own file lets the renderer path keep proxy support without paying for the rest of the proxy mixin's transitive imports. `-proxy.ts` re-exports `contentFor` from the new file for back-compat. 2. **`@ember/instrumentation` hot path extracted to `instrumentation/lib/internal-instrument.ts`.** `_instrumentStart` (called by the resolver, curly manager, outlet/root/route-template managers) and `flaggedInstrument` (called by the views state machine) used to live in `index.ts` alongside `subscribe`, `unsubscribe`, `instrument`, etc. — most of which are dead code unless something actually subscribes (e.g. Ember Inspector). Moved `_instrumentStart`, `flaggedInstrument`, `subscribers`, the cache helpers, and `NOOP` to the lib file, with `index.ts` re-exporting them via `export ... from`. The `no-barrel-imports` autofix then rewrites internal callers to deep-import from the lib file. Net result: the `instrumentation/index.js` chunk (subscribe / unsubscribe / instrument machinery) drops out of bundles that only use the hot path. `package.json`'s `ember-addon.renamed-modules` map gains an entry for `runtime/lib/mixins/content-for.js` — that's emitted automatically by the `packageMeta` rollup plugin, no manual edit. Verified: lint clean, all type-checks pass, `test:node` 20/20, `test:blueprints` 265/265, both smoke-test apps build. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hello-world: 159.49 KB / 50.67 KB → 134.17 KB / 42.90 KB gzip (–25 KB raw / –7.8 KB gzip). Classic v2-app-template gets a small bonus too (319.55 → 317.74 KB raw, 99.38 → 98.77 KB gzip). Cumulative from the original 243.30 KB / 77.32 KB baseline: a **44.5% gzip reduction**. Added a `sideEffects` field to `ember-source/package.json` listing the files that actually have top-level side effects, which by inversion tells bundlers that everything else is side-effect-free. With the classic-renderer / register-curly-component / register-routing-keywords splits already done in this PR, the renderer-only path no longer reaches into any of the side-effect files, so vite/rolldown can drop the rest of the graph it pulled in transitively (mostly the classic `Component` class and its CoreView/Mixin chain that vite was previously evaluating via `@ember/component`'s `default` re-export). The list covers: - **Registration modules created in this PR** (`setup-registry*`, `register-routing-keywords*`, `register-curly-component*`, `debug-render-tree-register*`) — these mutate global state on import. - **`environment*` files** (in `@ember/-internals/glimmer/` and `@glimmer/runtime/`) — call `setGlobalContext(...)` and the `_backburner.on(...)` lifecycle hookups at module top level. - **`@glimmer/runtime/lib/compiled/opcodes/**`** — every opcode file registers handlers via `APPEND_OPCODES.add(...)` at module load. - **`@glimmer/runtime/lib/helpers/**` and `lib/modifiers/**`** — setHelperManager / setModifierManager calls. - **`@ember/-internals/glimmer/lib/components/**`** — Input, Textarea, LinkTo all call `setInternalComponentManager(...)` at top level. - **`runtime/lib/component/template-only*`, `runtime/lib/vm/low-level*`** — template-only manager registration and VM bootstrap. - **`runloop/`, `manager/`, `validator/`, `global-context/`, `destroyable/`, `canary-features/`, `-internals/environment/`, `-internals/runtime/lib/ext/rsvp*`, `-internals/views/lib/system/event_dispatcher*`** — top-level side effects in those modules' index/init files. - **`./dist/dev/**`** — keep dev builds maximally unmolested for inspector / debugging tooling that may rely on dev-only side effects. Anything outside that list — class-definition files like `component.ts`, `core_view.ts`, `core.ts`, mixin files, computed-property files — is treated by bundlers as pure, so unused exports drop out cleanly. Verified: lint clean, all type-checks pass, `test:node` 20/20, `test:blueprints` 265/265, `pnpm test` for both `smoke-tests/v2-app-template` (classic v2 app) and `smoke-tests/app-template` (v1 app) pass 1/1 each, hello-world builds and shrinks as reported. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI lint caught these without the local eslint cache: the no-barrel-imports rule wants `flaggedInstrument` imported from `@ember/instrumentation/lib/internal-instrument` (the actual source) rather than the barrel. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The `addMixin` / `hasMixin` / `forEachMixins` methods only existed on `Meta` to be called by `@ember/object/mixin`. Keeping them on the class forced a static reference from `Meta` (reachable from the renderer through the property accessor / tag chain) into the classic `Mixin` graph. Move them out as standalone functions (`metaAddMixin` / `metaHasMixin` / `metaForEachMixins`) in `mixin.ts` itself, poking at `Meta`'s public `_mixins` and `parent` fields directly. With this split, bundles that don't import `@ember/object/mixin` get a cleaner `Meta` class — in the hello-world prod bundle the `addMixin` / `hasMixin` / `forEachMixins` identifiers go from present to fully absent, and `Mixin` references drop from 12 to 5. The methods were `@internal` and only called from `mixin.ts`, so this is a purely internal refactor. Verified: lint clean, type-checks pass, hello-world builds at 134.19 KB / 42.94 KB gzip (unchanged), classic v2-app-template tests 1/1 pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two pieces of polish from the user's review:
1. The `sideEffects` field in package.json was over-broad — listing
whole directories (`**/manager/**`, `**/validator/**`, etc.) when
only a handful of files in those trees actually have top-level side
effects. Replaced the directory globs with the explicit list of
files that contain top-level calls (registrations, opcode
`APPEND_OPCODES.add(...)`, `setGlobalContext`, `_backburner.on`,
etc.). Hello-world stays at 134.12 KB / 42.92 KB.
2. Removed comments in the refactored files that explained the
refactor itself ("extracted from X for tree-shaking", "kept
separate so Y", "back-compat re-export"). That kind of context
belongs in the PR description and commit messages, not the source
tree.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`opcodes.ts` previously imported `@glimmer/debug` (DebugLogger, VmSnapshot, debugOp / describeOp, opcodeMetadata, frag, etc.) at the top level and assembled the per-opcode `debugBefore`/`debugAfter` hooks inline in `AppendOpcodes`'s constructor — gated by `LOCAL_DEBUG`, so dead in production, but the imports still pulled the heavy `@glimmer/debug` graph into the bundle. Same registration pattern as the DebugRenderTree split: opcodes.ts exposes `registerDebugOpcodeSetup(setup)`; the heavy hook implementation moved to `opcodes-debug-setup.ts`, which calls the registry on import. `externs(vm)` now also requires the hooks to be registered (returns `undefined` otherwise) so dev builds that don't opt in skip the debug path entirely instead of crashing on a non-null assertion. Production hello-world holds at 134.12 KB / 42.90 KB gzip (`LOCAL_DEBUG` already eliminated the hooks there); the analysis bundle drops the `@glimmer/debug` files entirely. Verified: lint clean (after `pnpm lint:fix`), type-checks pass, hello-world builds, classic v2-app-template `pnpm test` 1/1 pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`pnpm lint:fix` runs both `lint:eslint:fix` and `lint:format:fix`. I'd only been running the eslint half, so prettier formatting drift in five of my refactored files snuck through. CI's `lint:format` was the failure on the previous push. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The `action` decorator lived inline in `@ember/object/index.ts`, which
also imports `CoreObject` and `Observable` at module top — so any
component that pulled `import { action } from '@ember/object'` (Input,
Textarea, AbstractInput, LinkTo) dragged the full
EmberObject / Observable / Mixin graph along with it.
Move `action` (plus its `setupAction` helper, `BINDINGS_MAP`, and
`hasProto`) to `@ember/object/action.ts`. `index.ts` re-exports it via
`export { action } from './action'` so the no-barrel-imports lint
autofix rewrites internal call sites to the deep path.
`@ember/object/index.ts` itself loses its references to
`isElementDescriptor` / `setClassicDecorator` / `ElementDescriptor` /
`ExtendedMethodDecorator`, since those now live in `action.ts`.
Hello-world: 134.12 KB / 42.94 KB → 133.42 KB / 42.69 KB gzip.
Verified with `pnpm lint` (clean), `pnpm type-check:internals`,
hello-world build, classic v2-app and v1 app smoke-test `pnpm test` (1/1 each).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per a measurement pass, two commits had zero (or negative) effect on the hello-world prod bundle: - a0b1f09 (Move Meta mixin methods to standalone fns): bundle went from 134.17 KB → 134.19 KB (+0.02 KB). Mixin.create chain was already being tree-shaken in prod regardless. - 75761b8 (Decouple VM debug symbols/names from opcodes.ts): bundle held at 134.12 KB. LOCAL_DEBUG=false in dist/prod (and dist/dev) was already constant-folding the debug branches out, and vite was already tree-shaking the unused @glimmer/debug imports out of the smoke-test bundle. Both refactors were architecturally cleaner but pure no-ops at the bundle measurement that motivated this PR. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lazy-register the classic-helper detection (`isClassicHelper` +
`CLASSIC_HELPER_MANAGER`) via a side-effect file imported from
`setup-registry.ts`, the same pattern already used for routing
keywords / curly components / debug-render-tree.
Removes the static import of `./helper` from `resolver.ts`, which was
pulling the classic Helper class chain (FrameworkObject → CoreObject →
Mixin) into the renderer's path even when the app does not use any
classic helpers.
hello-world bundle: 131.27 KB → 114.92 KB raw (-16.35 KB),
42.06 KB → 36.43 KB gzip (-5.63 KB).
classic v2-app builds unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The renderer-only path was statically pulling in: - observer.ts (sync + async observer flush) - chain-tags.ts (transitively, for getChainTagsForKey) - events.ts (transitively, for addListener/sendEvent) - decorator.ts (for COMPUTED_SETTERS) …even though a Glimmer-only app never installs an observer or a classic computed setter. Three independent reach points were responsible: 1. property_events.ts -> observer (sync flushSyncObservers etc.) 2. runloop/index.ts -> observer (async flushAsyncObservers) 3. property_set.ts -> decorator (COMPUTED_SETTERS WeakSet) Replaced each direct import with a registration hook (`registerObserverFlushSync` / `registerObserverDeactivationHooks`, `registerAsyncObserverFlush`, `registerComputedSetterCheck`) and moved the wire-up to a top-level side effect in `observer.ts` and `decorator.ts` themselves. Anyone importing those modules (addObserver/removeObserver, @computed, etc.) gets the registration fire as a side effect; renderer-only paths skip it. Marked observer.ts and decorator.ts as side-effect files in the ember-source `sideEffects` list so the registration calls survive tree-shaking when the modules ARE loaded. hello-world bundle: 114.92 KB -> 109.03 KB raw (-5.89 KB), 36.43 KB -> 34.44 KB gzip (-1.99 KB). classic v2-app builds and tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the new side-effect-free files introduced by this PR: - @ember/-internals/runtime/lib/mixins/content-for.js (extracted hot path) - @ember/instrumentation/lib/internal-instrument.js (extracted hot path) - @ember/object/action.js (decorator extracted from @ember/object) All three appear in the "shaken" snapshots (dev + prod), confirming they get fully tree-shaken when imported only for side effects. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
📊 Size reportTarball size — dist/dev 0.1%↑
Show files (24 files)
dist/prod 0.1%↑
Show files (25 files)
smoke-tests/v2-app-hello-world-template/dist -28.2%↓
🤖 This report was automatically generated by wyvox/pkg-size |
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.
Re-applies the PR #21359 / #21360 structural source-file changes on top of
nvp/configure-side-effects. Cuts thesmoke-tests/v2-app-hello-world-templatebundle to 109.03 KB / 34.43 KB gzip / 30.04 KB brotli — a 29% gzip reduction vs. this branch, 55% vs. upstream main.mainnvp/configure-side-effectsbaseline)(raw / gzip from
vite buildoutput; brotli fromzlib.brotliCompressSyncwith default options.)The bundler hints from #21362 (
sideEffects: false, thetreeshake.moduleSideEffectscallback for@glimmer/debug{,-util,-flags}/@glimmer/env/@ember/debug) and the agadoo regression check are already on this branch. This PR is the structural complement: breaking specific dependency chains that pulled the classic-Ember-object stack (Mixin, Observable, Evented, classic Component, computed, observers, RSVP, routing) into the renderer-only path even when an app uses only@glimmer/component+@tracked.Note
History note: previous attempts at this work (#21362, #21360, #21359) were closed because the base branches they targeted were rebased/superseded. This PR is the same structural changes re-applied on the now-current upstream
nvp/configure-side-effects. The bundler-hints subset already merged (the work in #21366 / #21367 /c0de6063f6); this PR adds the source-file restructuring on top.Pattern
Three shapes recur:
setup-registryimports — bundles that don't pull insetup-registry(therenderComponent-only path) skip the registration and the heavy module it pulls in.Wrapped up with a
sideEffectsfield onember-source/package.jsonlisting the actual side-effect files, so bundlers can tree-shake the rest of the graph aggressively.Changes
1. Lazy
-mountand-outletkeyword registrationresolver.tsno longer statically importsmountHelper/outletHelper. Replaced withregisterBuiltInKeywordHelper(name, helper)and a side-effect filesyntax/register-routing-keywords.tsimported bysetup-registry.ts. Drops ~138 KB of@ember/routing+ ~7 KB of@ember/enginefrom the renderer-only path.2. Split classic
Renderersubclass intoclassic-renderer.tsMoved
Renderer extends BaseRenderer,ClassicRootState, the concreteDynamicScopeclass, and theViewinterface out ofrenderer.ts. Added aRootStateinterface soRendererStateis generic over either root type.3.
RSVP.defer→ nativePromiseinrenderSettledTogether with #1, lets the bundle drop the 62 KB rsvp shared chunk entirely.
4. Curly symbols extracted to
curly-symbols.tsisCurlyManageris now a brand check (manager[CURLY_COMPONENT_BRAND] === true) instead of an instance check, so the resolver no longer pullscomponent-managers/curly.ts(the fullCurlyComponentManagerlifecycle, ~17 KB) just to identify the manager.5. Classic
Componentclass side-effects moved toregister-curly-component.tssetInternalComponentManager(CURLY_COMPONENT_MANAGER, Component)andComponent.reopenClass({ positionalParams: [] })no longer run at the top level ofcomponent.ts; they're in a side-effect file imported bysetup-registry.ts.6.
DebugRenderTreeImplfactory moved behind a registryEnvironmentImpl(in@glimmer/runtime) importedDebugRenderTreestatically. NewregisterDebugRenderTreeFactorylets a side-effect module supply the constructor;getDebugName(the other static reach intodebug-render-tree) moved to its own file.7. Lighter array predicate in
to-bool.tsSwitched from
isArrayfrom@ember/array(which pulls the mixin / Enumerable / Observable / computed graph) toArray.isArray(x) || isEmberArray(x).8.
contentForextracted toruntime/lib/mixins/content-for.tseach-in.tsno longer drags in theProxyMixin = Mixin.create(...)graph just for an 8-linecontentForfunction.9.
@ember/instrumentationhot path extracted tolib/internal-instrument.ts_instrumentStartandflaggedInstrumentmoved to a lib file;subscribe/unsubscribe/instrumentmachinery (dead code unless something subscribes) drops out of bundles that only use the hot path.10.
@ember/object'sactiondecorator extracted to@ember/object/actionMoved the
actiondecorator implementation behind its own deep import path so@glimmer/component-only apps don't pay for the @ember/object Mixin / CoreObject / Observable graph just to decorate handlers.11. Extract classic helper handler from resolver
Lazy-register the classic-helper detection (
isClassicHelper+CLASSIC_HELPER_MANAGER) via a side-effect file imported fromsetup-registry.ts. Removes the static import of./helperfromresolver.ts, which was pulling the classic Helper class chain (FrameworkObject → CoreObject → Mixin) into the renderer's path even when the app does not use any classic helpers.12. Decouple property_events / runloop / property_set from the observer chain
Replaced the static imports of
observer.ts(sync flush) anddecorator.ts(COMPUTED_SETTERS) with registration hooks;observer.tsanddecorator.tsregister themselves via top-level side effects when loaded. Dropsobserver/events/chain-tags/decoratorchunks entirely from Glimmer-only bundles.13.
sideEffectsfield onember-source/package.jsonReplaces the
sideEffects: falsedeclaration with an explicit list of files that actually have top-level side effects (registration files, environment / setGlobalContext callers, opcode handlers, runloop init, validator, etc.) — necessary now that side-effect-only registration files (register-curly-component.ts,register-routing-keywords.ts,debug-render-tree-register.ts) exist and must be preserved when imported only for their side effects.What's left in the hello-world bundle
After all of the above, the remaining 109 KB raw / 34 KB gzip is essentially just genuine VM runtime + the bare minimum @ember internals: opcode compiler, VM argument handling, VM render loop, renderer setup/environment, backburner + runloop, element-builder, meta/owner/container.
Almost all of the previously-leaked classic-Ember-object machinery (Mixin, Observable, Evented, Component, computed properties, observers, Helper, RSVP, routing) is now gone from the renderer-only path.
Updated tree-shakability snapshot
The new side-effect-free entry points (
@ember/-internals/runtime/lib/mixins/content-for,@ember/instrumentation/lib/internal-instrument,@ember/object/action) appear in theshakensnapshots in both dev and prod, confirming agadoo recognizes them as pure.Test plan
pnpm build:jscleanpnpm test:node20/20pnpm --filter ember-test-node-vitest test:node2/2 (tree-shakability snapshot)smoke-tests/v2-app-template(classic v2 app) builds + 1/1 test passessmoke-tests/app-template(v1 app) builds + 1/1 test passessmoke-tests/v2-app-hello-world-templatebuilds and shrinks as reportedpnpm vite build --mode development --minify false(full dev test suite app) builds cleanpnpm lint:eslintclean for tracked PR files🤖 Generated with Claude Code