Skip to content

Split renderComponent out of renderer.ts to reduce import footprint#21286

Draft
NullVoxPopuli-ai-agent wants to merge 4 commits intoemberjs:mainfrom
NullVoxPopuli-ai-agent:nvp/split-render-component-from-renderer
Draft

Split renderComponent out of renderer.ts to reduce import footprint#21286
NullVoxPopuli-ai-agent wants to merge 4 commits intoemberjs:mainfrom
NullVoxPopuli-ai-agent:nvp/split-render-component-from-renderer

Conversation

@NullVoxPopuli-ai-agent
Copy link
Copy Markdown
Contributor

@NullVoxPopuli-ai-agent NullVoxPopuli-ai-agent commented Apr 3, 2026

Summary

Reduces the bundle footprint of import { renderComponent } from '@ember/renderer' by ~35% (58KB → 38KB gzipped) through three changes:

  1. Extract renderComponent/BaseRenderer to render-component.ts — Rollup groups code by source file into shared chunks. Splitting renderComponent out of renderer.ts produces a separate ~15KB chunk instead of sharing a ~183KB mega-chunk with the classic Renderer.

  2. Lazy Glimmer global context (glimmer-global-context.ts) — The setGlobalContext() call previously lived in environment.ts, which eagerly imports @ember/-internals/metal, @ember/array, and @ember/-internals/views. The new module provides lightweight fallbacks (simple obj[key], Boolean(), etc.) that are replaced with full Ember implementations when environment.ts loads during classic app boot.

  3. Direct import in @ember/renderer — Imports directly from render-component.ts instead of the @ember/-internals/glimmer barrel.

smol-est-ember-app results

Before After Change
Raw 181 KB 120 KB -34%
Gzipped 58 KB 38 KB -35%
Category Before After
routing 1.9 KB 0
@ember/object+metal 83.6 KB 12.5 KB
@ember/array 17.6 KB 0.2 KB
container 20 KB 0
views 10.1 KB 0
service 3.4 KB 0
rsvp present 0

Test plan

  • TypeScript type-check passes (same pre-existing errors as main)
  • Rollup build succeeds
  • ESLint + Prettier pass
  • smol-est-ember-app bundle verified
  • Full browser test suite (CI)

🤖 Generated with Claude Code

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.

do we have to import from the resolver?

can the non-renderComponent renderer be moved to a different file?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done — moved the classic Renderer (along with ClassicRootState, DynamicScope, View) into its own classic-renderer.ts.

renderer.ts is now a thin re-export shim with zero heavy imports. All internal consumers updated to import directly from the source files (render-component.ts or classic-renderer.ts).

@NullVoxPopuli-ai-agent NullVoxPopuli-ai-agent force-pushed the nvp/split-render-component-from-renderer branch from 135b116 to 015ece7 Compare April 4, 2026 04:18
@NullVoxPopuli-ai-agent
Copy link
Copy Markdown
Contributor Author

smol-est-ember-app results after this PR

Tested with NullVoxPopuli/smol-est-ember-app (renderComponent(<template>hi</template>, { into: document.body })):

Before: 181.02 KB raw / 57.83 KB gzip
After:  120.12 KB raw / 37.86 KB gzip  (-34% raw, -35% gzip)

Bundle breakdown after:

 355.3kb  glimmer VM + other core
  31.0kb  @glimmer/validator
  27.2kb  backburner
  17.2kb  @glimmer/* (other)
  12.5kb  @ember/object+metal (down from 83.6kb)
   0.2kb  @ember/array (down from 17.6kb)
   0.0kb  routing (was 1.9kb)
   0.0kb  container (was 20kb)
   0.0kb  views (was 10.1kb)
   0.0kb  service (was 3.4kb)
   0.0kb  rsvp (was present)

The PR was slimmed down to just the essential changes:

  • render-component.ts — extracted from renderer.ts so rollup produces a separate chunk
  • glimmer-global-context.ts — lazy setGlobalContext that doesn't eagerly import metal/array/views
  • environment.ts — registers the full Ember implementations during classic app boot
  • @ember/renderer/index.ts — imports directly from render-component.ts instead of the barrel
  • sideEffects: false in package.json (may already be added during publish)

NullVoxPopuli and others added 2 commits April 4, 2026 00:28
Replace all internal barrel imports (from `@ember/-internals/glimmer`,
`@ember/-internals/environment`, etc.) with direct imports from the
specific source files that define what is needed. Also replace all
`export *` patterns in `@ember` packages with explicit named exports.

- Update ~40 source files to import from specific lib paths instead of
  barrel `index.ts` files
- Replace `export *` with named exports in `@ember/-internals/environment`,
  `@ember/engine/parent`, `@ember/template-compiler/*`, `ember-template-compiler`,
  and `ember-testing`
- Replace `import * as environment` with named `{ hasDOM }` import in
  `@ember/application/instance`
- Add deep import path entries to `@ember/-internals/package.json` exports map
- Add `@glimmer/opcode-compiler` dependency to `ember-template-compiler`

Test files are intentionally left unchanged as they may use barrel imports.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three changes work together to let bundlers tree-shake heavy deps from
the renderComponent path:

1. Extract renderComponent/BaseRenderer to render-component.ts
   Rollup groups code by source file into shared chunks. Splitting
   renderComponent out of renderer.ts (which also has the classic
   Renderer with routing/outlet/curly deps) produces a separate, small
   shared chunk (~15KB vs ~183KB).

2. Lazy Glimmer global context (glimmer-global-context.ts)
   The setGlobalContext() call previously lived in environment.ts, which
   eagerly imports @ember/-internals/metal, @ember/array, and
   @ember/-internals/views. The new module provides lightweight fallback
   implementations (simple obj[key], Boolean(), etc.) that are replaced
   with full Ember implementations when environment.ts loads during
   classic Ember app boot.

3. Direct import in @ember/renderer
   The public @ember/renderer entry imports directly from
   render-component.ts instead of the @ember/-internals/glimmer barrel,
   so the barrel's other exports don't pull in their deps.

Tested with NullVoxPopuli/smol-est-ember-app (renderComponent only):
  Before: 181KB raw / 58KB gzip
  After:  120KB raw / 38KB gzip

Eliminated from bundle: routing, container, views, service, rsvp.
Reduced: @ember/object+metal 84KB→13KB, @ember/array 18KB→0.2KB.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@NullVoxPopuli-ai-agent NullVoxPopuli-ai-agent force-pushed the nvp/split-render-component-from-renderer branch from 015ece7 to d56d02a Compare April 4, 2026 05:04
The runloop eagerly imported flushAsyncObservers from @ember/-internals/metal,
pulling the entire observer/meta system (~42KB) into any bundle that uses the
runloop. Making it lazy (registered by environment.ts during full Ember boot)
breaks this dependency for standalone renderComponent.

smol-est-ember-app: 120KB/38KB → 112KB/35KB gzipped

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@NullVoxPopuli-ai-agent
Copy link
Copy Markdown
Contributor Author

Updated results after merging barrel removal + lazy observer flushing

Combined the barrel removal from #21295 and added lazy flushAsyncObservers in @ember/runloop (the runloop was eagerly importing the entire observer/meta system from @ember/-internals/metal).

smol-est-ember-app bundle size

Version Raw Gzipped
Canary baseline 181 KB 58 KB
After render-component split + lazy context 120 KB 38 KB
+ barrel removal + lazy observers 112 KB 35 KB

What's eliminated

routing, @ember/object, @ember/-internals/metal, @ember/-internals/meta, @ember/array, container, views, service, rsvp — all gone.

What remains (irreducible core)

 302kb  Glimmer VM shared chunks (opcodes, render runtime, opcode compiler, DOM builder)
  49kb  Runloop (backburner.js + @ember/runloop)
  48kb  @glimmer/* (validator, reference, destroyable, wire-format, global-context)

This is the core Glimmer rendering engine + Ember's runloop scheduler. Further reduction would require changes to the VM itself (e.g., replacing backburner with a lighter scheduler for standalone use), which is out of scope for this PR.

…lt as default }

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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