Experiment: compile strict-mode templates directly to DOM operations - #21649
Draft
NullVoxPopuli wants to merge 3 commits into
Draft
NullVoxPopuli wants to merge 3 commits into
NullVoxPopuli wants to merge 3 commits into
Conversation
Adds a prototype of compiling strict-mode templates straight to JavaScript, with no wire format, opcode compiler, or VMs: - @glimmer/compiler: `generateDOM` / `compileToDOM` (ASTv1 -> JS) and `codegenBabelPlugin`, which compiles `template()` calls from gjs/gts in place, so templates close over the module's JS bindings. - @glimmer/dom: the runtime helpers that compiled templates call (static HTML cloning, text/attributes/content, if/each/yield/let/ in-element, component/helper/modifier invocation through the existing managers, and a VM fallback for components without a compiled template). Well-known built-ins (on, fn, hash, array, concat, get, eq, not, and, or, if, ...) are compiled inline. Tracking uses @glimmer/validator caches, so autotracking works unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H2cNyDMVxZb1xQB6oCzX89
Switches both apps that the package-size report measures to the codegen
compiler:
- v2-app-hello-world-template: 132,266 -> 26,285 bytes of JS
(41,801 -> 9,304 gzipped). Nothing from the VM is loaded.
- v2-app-template: 349,060 -> 356,809 bytes (110,323 -> 113,006 gzipped).
It still needs the VM for the router and `{{outlet}}`.
To make that possible:
- ember-source now builds `@glimmer/dom` and `@glimmer/dom/vm`, and the
ember-template-compiler entry exports `_codegenBabelPlugin` (runs in
`pre`, so it goes before babel-plugin-ember-template-compilation).
- `@glimmer/dom` no longer imports the VM; it only sets up Ember's global
context.
- New `@glimmer/dom/vm` (enabled with `vmInterop`): the VM can render
compiled components (route templates, `{{outlet}}` via in-element so the
outlet keeps its dynamic scope), and compiled templates can render
VM-only components.
- The compiler supports Ember's allowed globals (RFC 1070), `(element)`,
and reports `{{on}}` misuse at runtime with the same messages as the VM.
Smoke scenarios that use v2-app-template pass, except the
captureRenderTree test: compiled components don't report to the debug
render tree yet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H2cNyDMVxZb1xQB6oCzX89
Upstream now passes route templates their outlet as `@outlet` (with
`{{outlet}}` compiled to `<@outlet />`), and tears routes down differently,
which the smoke-test scenarios exercise.
- `{{outlet}}` in a compiled route template (with `vmInterop`) is an
element that the bridge's VM template renders `<@outlet />` into (with
in-element), so the outlet is in the same render tree as the route.
Without `vmInterop`, it invokes `@outlet`.
- The island that a compiled component renders into is created by a helper
with a destroyable, so the VM destroys it in the same pass as its own
components (`willDestroy` used to run a scheduling round late).
- VM-only components (e.g. `<LinkTo>`) rendered from compiled templates get
named args (curried), attributes and modifiers (forwarded to
`...attributes`), and the default block. Their VM render is a destroyable
child of the compiled block, and a new curried value (like a new
`@outlet`) updates the VM render instead of replacing it.
- Components with no template at all don't need the VM.
All smoke scenarios based on v2-app-template pass, except the
captureRenderTree test (compiled components don't report to the debug
render tree yet).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H2cNyDMVxZb1xQB6oCzX89
NullVoxPopuli-ai-agent
force-pushed
the
claude/gallant-heisenberg-aovzyr
branch
from
October 2, 2026 22:01
7f3785c to
af3aef7
Compare
Contributor
Author
📊 Size reportTarball size — dist/dev 4%↑
dist/prod 4%↑
smoke-tests/v2-app-template/dist 3%↑
smoke-tests/v2-app-hello-world-template/dist -77.8%↓
🤖 This report was automatically generated by wyvox/pkg-size |
Contributor
Author
|
red is because of missing debug render tree with the new output format |
NullVoxPopuli-ai-agent
pushed a commit
to NullVoxPopuli-ai-agent/limber
that referenced
this pull request
Oct 2, 2026
Vendors @glimmer/dom and the compiler from emberjs/ember.js#21649: - compile with `vmInterop`, so compiled templates can render components that only have a VM template (e.g. from addons), and the other way around - `@glimmer/dom/vm` is a known module (the VM interop runtime) - allowed globals (RFC 1070) resolve in compiled templates - the demo renders ember-primitives' <ExternalLink> from a compiled template Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H2cNyDMVxZb1xQB6oCzX89
Contributor
Author
|
Bundle size check: limber's REPL with this experiment vendored in (
Most of the growth is in three new lazily loaded chunks, about 9.4 KiB brotli in total: without repl-sdk:
|
Contributor
Author
|
on: NullVoxPopuli/limber#2287 (comment) output looks like (in the repl): import { clone as _clone, html as _html, at as _at, content as _content, get as _get, listen as _listen } from /*"@glimmer/dom"*/'blob:https://nvp-codegen-gjs-flavor.limber-glimdown.pages.dev/190a098e-aaca-4511-8cf2-3e3dcf49710a';
import { template as _template, setTemplate as _setTemplate } from /*"@glimmer/dom/vm"*/'blob:https://nvp-codegen-gjs-flavor.limber-glimdown.pages.dev/55ca1836-bc27-4f34-abe1-e39935d3307f';
const $_0_t2 = _html("<p>You have clicked the button <!> times.</p>\n\n<button type=\"button\">Click</button>");
import Component from /*'@glimmer/component'*/'blob:https://nvp-codegen-gjs-flavor.limber-glimdown.pages.dev/b77d5391-da1e-4779-a569-0b244523d07c';
import { tracked } from /*'@glimmer/tracking'*/'blob:https://nvp-codegen-gjs-flavor.limber-glimdown.pages.dev/62ba06ac-9fe4-4a2d-8221-c4d4c2d23003';
import { on } from /*'@ember/modifier'*/'blob:https://nvp-codegen-gjs-flavor.limber-glimdown.pages.dev/c6d78410-ddf1-4133-89ec-6c48eca9b3bc';
import { g, i } from /*"decorator-transforms/runtime-esm"*/'blob:https://nvp-codegen-gjs-flavor.limber-glimdown.pages.dev/14686cdd-3aa5-464e-9221-35a1f369de76';
export default class HelloWorld extends Component {
static {
g(this.prototype, "count", [tracked], function () {
return 0;
});
}
#count = (i(this, "count"), void 0);
increment = () => this.count += 1;
static {
_setTemplate(this, _template(($_b, $_ctx) => {
const $_s = $_ctx.self,
$_a = $_ctx.args;
const $_0_r1 = _clone($_b, $_0_t2);
const $_0_n3 = _at($_0_r1, 0, 1);
const $_0_n4 = _at($_0_r1, 2);
_content($_b, $_0_n3, () => _get($_s, "count"), 0);
_listen($_b, $_0_n4, "click", () => _get($_s, "increment"));
return $_0_r1;
}));
}
}
//# sourceURL=file:///src/index.gjs?v=4 |
This branch has not been deployed
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.
Adds a prototype of compiling strict-mode templates straight to JavaScript, with no wire format, opcode compiler, or VMs:
generateDOM/compileToDOM(ASTv1 -> JS) andcodegenBabelPlugin, which compilestemplate()calls from gjs/gts in place, so templates close over the module's JS bindings.Well-known built-ins (on, fn, hash, array, concat, get, eq, not, and, or, if, ...) are compiled inline. Tracking uses @glimmer/validator caches, so autotracking works unchanged.
Claude-Session: https://claude.ai/code/session_01H2cNyDMVxZb1xQB6oCzX89