diff --git a/apps/repl/app/components/verify-danger.gts b/apps/repl/app/components/verify-danger.gts
index bb32a5562..302378c50 100644
--- a/apps/repl/app/components/verify-danger.gts
+++ b/apps/repl/app/components/verify-danger.gts
@@ -17,6 +17,7 @@ const ALLOWED = Object.freeze([]);
const MARKUP_FORMATS = new Set([
'js',
'gjs',
+ 'gjs|codegen',
'gts',
'vue',
'jsx',
@@ -27,7 +28,16 @@ const MARKUP_FORMATS = new Set([
'hbs',
'hbs|ember',
]);
-const SCRIPT_FORMATS = new Set(['js', 'gjs', 'gts', 'vue', 'jsx', 'jsx|react', 'svelte']);
+const SCRIPT_FORMATS = new Set([
+ 'js',
+ 'gjs',
+ 'gjs|codegen',
+ 'gts',
+ 'vue',
+ 'jsx',
+ 'jsx|react',
+ 'svelte',
+]);
function couldHaveMarkup(format: Format) {
return MARKUP_FORMATS.has(format);
diff --git a/apps/repl/app/languages.gts b/apps/repl/app/languages.gts
index aef90fd88..5d70f067c 100644
--- a/apps/repl/app/languages.gts
+++ b/apps/repl/app/languages.gts
@@ -52,6 +52,13 @@ const languages = {
,
},
+ 'gjs|codegen': {
+ name: 'Glimmer JS (codegen, experimental)',
+ ext: 'gjs',
+ icon:
+ {{{FileGlimmer}}}
+ ,
+ },
// an alias
get hbs() {
return languages['hbs|ember'];
diff --git a/apps/repl/app/snippets.ts b/apps/repl/app/snippets.ts
index 0f500da75..01812f543 100644
--- a/apps/repl/app/snippets.ts
+++ b/apps/repl/app/snippets.ts
@@ -130,6 +130,11 @@ export const ALL = [
path: '/samples/nested-ember-routing-demo.gjs',
},
{ format: 'gts', label: 'Ember GTS', path: '/samples/gts-demo.gts' },
+ {
+ format: 'gjs|codegen',
+ label: 'Ember GJS (codegen, experimental)',
+ path: '/samples/gjs-codegen-demo.gjs',
+ },
{ format: 'svelte', label: 'Svelte', path: '/samples/svelte-demo.svelte' },
{ format: 'js', label: 'Vanilla JS', path: '/samples/js-demo.js' },
// Yaml
@@ -195,7 +200,7 @@ export async function getFromLabel(label: string): Promise {
}
export function defaultSnippetForFormat(format: string) {
- if (format === 'gjs') {
+ if (format === 'gjs' || format === 'gjs|codegen') {
return DEFAULT_GJS;
}
diff --git a/apps/repl/app/templates/edit/format-menu.gts b/apps/repl/app/templates/edit/format-menu.gts
index eb30df2c4..0a5d9bb39 100644
--- a/apps/repl/app/templates/edit/format-menu.gts
+++ b/apps/repl/app/templates/edit/format-menu.gts
@@ -106,6 +106,7 @@ export class FormatMenu extends Component<{ Element: HTMLButtonElement }> {
+
diff --git a/apps/repl/app/templates/edit/layout/controls/formatting.ts b/apps/repl/app/templates/edit/layout/controls/formatting.ts
index 8df76e769..5a1a5b3d1 100644
--- a/apps/repl/app/templates/edit/layout/controls/formatting.ts
+++ b/apps/repl/app/templates/edit/layout/controls/formatting.ts
@@ -49,6 +49,10 @@ const FORMATTERS: Records directly to DOM operations
+// (no wire format, no VM). Strict mode only.
+import Component from '@glimmer/component';
+import { tracked } from '@glimmer/tracking';
+import { trackedArray } from '@ember/reactive/collections';
+import { on } from '@ember/modifier';
+import { fn } from '@ember/helper';
+// A component compiled for the VM (from an addon): rendered by the VM,
+// with the attributes and block from this compiled template.
+import { ExternalLink } from 'ember-primitives';
+
+const shout = (text) => `${text.toUpperCase()}!`;
+
+const Card =
+
+ {{yield to="title"}}
+ {{#if (has-block "body")}}
+ {{yield to="body"}}
+ {{else}}
+ (no body)
+ {{/if}}
+
+;
+
+class Counter extends Component {
+ @tracked count = 0;
+
+ increment = (by) => (this.count += by);
+
+
+
+
+ {{#if (eq this.count 0)}}
+ Click it!
+ {{else if (gt this.count 10)}}
+ {{shout "that's a lot"}}
+ {{/if}}
+
+}
+
+class Todos extends Component {
+ todos = trackedArray([
+ { id: 1, title: 'Compile templates to JS' },
+ { id: 2, title: 'Delete the VM?' },
+ { id: 3, title: 'Measure it' },
+ ]);
+
+ reverse = () => this.todos.reverse();
+ remove = (todo) => this.todos.splice(this.todos.indexOf(todo), 1);
+
+
+
+
+ {{#each this.todos key="id" as |todo i|}}
+ -
+ {{i}}: {{todo.title}}
+
+
+ {{else}}
+ - All done
+ {{/each}}
+
+
+}
+
+
+
+ <:title>Counter
+ <:body>
+
+
+
+ <:title>Todos
+ <:body>
+
+
+ <:title>Empty
+
+
+ <:title>From an addon
+ <:body>
+
+ The ember.js PR
+
+
+
+
+
+
diff --git a/packages/ember-repl/src/services/known-modules.ts b/packages/ember-repl/src/services/known-modules.ts
index 008964c8d..e27ae4cdf 100644
--- a/packages/ember-repl/src/services/known-modules.ts
+++ b/packages/ember-repl/src/services/known-modules.ts
@@ -40,6 +40,15 @@ const frameworkModules = {
'@glimmer/tracking': () => import('@glimmer/tracking'),
'@glimmer/tracking/primitives/cache': () =>
import('@glimmer/tracking/primitives/cache'),
+ /**
+ * EXPERIMENTAL: the runtime for the `gjs|codegen` format, where templates
+ * are compiled directly to DOM operations (no wire format, no VM).
+ * Vendored from a branch of ember.js, see the file's header.
+ */
+ // @ts-ignore
+ '@glimmer/dom': () => import('../vendor/glimmer-dom/index.js'),
+ // @ts-ignore
+ '@glimmer/dom/vm': () => import('../vendor/glimmer-dom/vm.js'),
};
const coreLibraries = {
diff --git a/packages/ember-repl/src/vendor/glimmer-dom/index.js b/packages/ember-repl/src/vendor/glimmer-dom/index.js
new file mode 100644
index 000000000..6513794b5
--- /dev/null
+++ b/packages/ember-repl/src/vendor/glimmer-dom/index.js
@@ -0,0 +1,552 @@
+// @ts-nocheck
+/* eslint-disable */
+/**
+ * VENDORED -- do not edit.
+ *
+ * The runtime for compiled templates (`@glimmer/dom`, `@glimmer/dom/vm`).
+ * Built from https://github.com/NullVoxPopuli-ai-agent/ember.js/tree/claude/gallant-heisenberg-aovzyr (af3aef79),
+ * see https://github.com/emberjs/ember.js/pull/21649
+ *
+ * This is an experiment: strict-mode templates compiled directly to DOM
+ * operations, with no wire format and no VM.
+ */
+import {
+ Template,
+ at,
+ clone,
+ consumeTag,
+ createCache,
+ createComputeRef,
+ createTag,
+ curry,
+ dirtyTag,
+ getInternalHelperManager,
+ getValue,
+ helper,
+ helperDyn,
+ html,
+ instantiate,
+ invoke,
+ invokeDyn,
+ isComponentDefinition,
+ isConst,
+ marker,
+ modifier,
+ mount,
+ moveRange,
+ removeRange,
+ renderComponent,
+ setTemplate,
+ setVMFallback,
+ template,
+ unmount,
+ valueForRef
+} from "./shared.js";
+
+// packages/@glimmer/dom/lib/attributes.ts
+import { registerDestructor } from "@glimmer/destroyable";
+var XHTML = "http://www.w3.org/1999/xhtml";
+var PROPERTIES = /* @__PURE__ */ new Set(["value", "checked", "selected", "indeterminate", "muted"]);
+var SANITIZED_TAGS = /* @__PURE__ */ new Set(["A", "BODY", "LINK", "IMG", "IFRAME", "BASE", "FORM"]);
+var SANITIZED_ATTRS = /* @__PURE__ */ new Set(["href", "src", "background", "action"]);
+var BAD_PROTOCOLS = /* @__PURE__ */ new Set(["javascript:", "vbscript:"]);
+function normalizeAttrValue(value) {
+ if (value === false || value === void 0 || value === null) return null;
+ if (value === true) return "";
+ if (typeof value === "function") return null;
+ if (typeof value.toString !== "function") return null;
+ return String(value);
+}
+function sanitize(element2, name, value) {
+ if (value === null || !SANITIZED_TAGS.has(element2.tagName) || !SANITIZED_ATTRS.has(name)) {
+ return value;
+ }
+ let protocol;
+ try {
+ protocol = new URL(value, "http://localhost").protocol;
+ } catch {
+ return value;
+ }
+ return BAD_PROTOCOLS.has(protocol) ? `unsafe:${value}` : value;
+}
+function setterFor(element2, name) {
+ if (element2.namespaceURI === XHTML && PROPERTIES.has(name) && name in element2) {
+ return (value) => {
+ element2[name] = value === null || value === void 0 ? name === "value" ? "" : false : value;
+ };
+ }
+ return (value) => {
+ let normalized = sanitize(element2, name, normalizeAttrValue(value));
+ if (normalized === null) {
+ element2.removeAttribute(name);
+ } else {
+ element2.setAttribute(name, normalized);
+ }
+ };
+}
+function bind(b, value, setter) {
+ if (typeof value !== "function") {
+ setter(value);
+ return;
+ }
+ let cache = createCache(value);
+ let current = getValue(cache);
+ setter(current);
+ if (isConst(cache)) return;
+ b.updaters.push(() => {
+ let next = getValue(cache);
+ if (next !== current) {
+ current = next;
+ setter(next);
+ }
+ });
+}
+var CLASSES = /* @__PURE__ */ new WeakMap();
+function classPart(b, element2, value) {
+ let state = CLASSES.get(element2);
+ if (!state) {
+ state = { parts: [] };
+ let existing = element2.getAttribute("class");
+ if (existing) state.parts.push(existing);
+ CLASSES.set(element2, state);
+ }
+ let { parts } = state;
+ let index = parts.length;
+ parts.push("");
+ bind(b, value, (next) => {
+ parts[index] = normalizeAttrValue(next) ?? "";
+ let className = parts.filter(Boolean).join(" ");
+ if (className) {
+ element2.setAttribute("class", className);
+ } else {
+ element2.removeAttribute("class");
+ }
+ });
+}
+function attr(b, element2, name, value) {
+ if (name === "class") {
+ classPart(b, element2, value);
+ } else {
+ bind(b, value, setterFor(element2, name));
+ }
+}
+function splat(b, element2, ctx) {
+ ctx.attrs?.(b, element2);
+}
+function describeElement(element2) {
+ return element2.tagName.toLowerCase() + (element2.id ? `#${element2.id}` : "") + Array.from(element2.classList).map((c) => `.${c}`).join("");
+}
+function assertHandler(fn, element2) {
+ if (typeof fn !== "function") {
+ throw new Error(
+ `You must pass a function as the second argument to the \`on\` modifier; you passed ${fn === null ? "null" : typeof fn}. While rendering:
+
+(unknown) on ${describeElement(element2)}`
+ );
+ }
+}
+function listen(b, element2, eventName, handler, options) {
+ let cache = createCache(handler);
+ let listener = (event) => {
+ let fn = getValue(cache);
+ assertHandler(fn, element2);
+ return fn(event);
+ };
+ b.root.schedule(() => {
+ if (typeof eventName !== "string" || !eventName) {
+ throw new Error(
+ `You must pass a valid DOM event name as the first argument to the \`on\` modifier on ${describeElement(element2)}`
+ );
+ }
+ assertHandler(getValue(cache), element2);
+ element2.addEventListener(eventName, listener, options);
+ registerDestructor(b, () => {
+ element2.removeEventListener(eventName, listener, options);
+ });
+ });
+}
+
+// packages/@glimmer/dom/lib/values.ts
+import { getPath as globalGetPath, getProp, toBool } from "@glimmer/global-context";
+function get(obj, ...keys) {
+ for (let key of keys) {
+ if (obj === null || obj === void 0) return void 0;
+ obj = getProp(obj, key);
+ }
+ return obj;
+}
+function getPath(obj, path) {
+ if (obj === null || obj === void 0 || path === null || path === void 0) return void 0;
+ return globalGetPath(obj, String(path));
+}
+function bool(value) {
+ return toBool(value);
+}
+function isEmpty(value) {
+ return value === null || value === void 0 || typeof value.toString !== "function";
+}
+function str(value) {
+ return isEmpty(value) ? "" : String(value);
+}
+function and(...values) {
+ let last;
+ for (let value of values) {
+ last = value();
+ if (!toBool(last)) return last;
+ }
+ return last;
+}
+function or(...values) {
+ let last;
+ for (let value of values) {
+ last = value();
+ if (toBool(last)) return last;
+ }
+ return last;
+}
+function memo(fn) {
+ let cache = createCache(fn);
+ return () => getValue(cache);
+}
+function log(...values) {
+ console.log(...values);
+ return void 0;
+}
+function entries(obj) {
+ if (obj === null || obj === void 0) return [];
+ if (obj instanceof Map) return Array.from(obj.entries());
+ return Object.keys(obj).map((key) => [key, get(obj, key)]);
+}
+function hasBlock(ctx, name = "default") {
+ return Boolean(ctx.blocks?.[name]);
+}
+function hasBlockParams(ctx, name = "default") {
+ let block2 = ctx.blocks?.[name];
+ return block2 ? block2.length > 1 : false;
+}
+
+// packages/@glimmer/dom/lib/content.ts
+var MAY_CALL = 1;
+var TRUSTING = 2;
+function isSafeString(value) {
+ return typeof value === "object" && value !== null && typeof value.toHTML === "function";
+}
+function isNode(value) {
+ return typeof value === "object" && value !== null && typeof value.nodeType === "number";
+}
+function kindOf(value, flags) {
+ if (typeof value === "string") return flags & TRUSTING ? 1 /* html */ : 0 /* text */;
+ if (value === null || value === void 0) return 0 /* text */;
+ if (typeof value !== "object" && typeof value !== "function") return 0 /* text */;
+ if (isSafeString(value)) return 1 /* html */;
+ if (isNode(value)) return 2 /* node */;
+ if (isComponentDefinition(value)) return 3 /* component */;
+ return 0 /* text */;
+}
+function isHelperLike(value) {
+ if (isComponentDefinition(value)) return false;
+ if (typeof value === "function") return true;
+ return typeof value === "object" && value !== null && getInternalHelperManager(value, true) !== null;
+}
+function insertNodes(parent, before, nodes) {
+ let doc = parent.ownerDocument ?? parent;
+ if (nodes.nodeType === 11) {
+ if (!nodes.firstChild) nodes.appendChild(doc.createTextNode(""));
+ let first = nodes.firstChild;
+ let last = nodes.lastChild;
+ parent.insertBefore(nodes, before);
+ return { first, last, text: null, mounted: null };
+ }
+ parent.insertBefore(nodes, before);
+ return { first: nodes, last: nodes, text: null, mounted: null };
+}
+function render(b, parent, before, kind, value, flags) {
+ let doc = b.root.document;
+ switch (kind) {
+ case 0 /* text */: {
+ let text = doc.createTextNode(str(value));
+ parent.insertBefore(text, before);
+ return { first: text, last: text, text, mounted: null };
+ }
+ case 1 /* html */: {
+ let t = doc.createElement("template");
+ t.innerHTML = flags & TRUSTING && !isSafeString(value) ? str(value) : value.toHTML();
+ return insertNodes(parent, before, t.content);
+ }
+ case 2 /* node */:
+ return insertNodes(parent, before, value);
+ case 3 /* component */: {
+ let mounted = mount(
+ b,
+ before,
+ (child) => instantiate(child, value, {}, null, null)
+ );
+ return { first: mounted.start, last: mounted.end, text: null, mounted };
+ }
+ }
+}
+function clear(slot) {
+ if (slot.mounted) {
+ unmount(slot.mounted);
+ } else {
+ removeRange(slot.first, slot.last);
+ }
+}
+function content(b, anchor, fn, flags = 0) {
+ if (flags & MAY_CALL) {
+ let definition = fn();
+ if (isHelperLike(definition)) fn = helper(b, definition, null, null);
+ }
+ let cache = createCache(fn);
+ let value = getValue(cache);
+ let kind = kindOf(value, flags);
+ let parent = anchor.parentNode;
+ let slot = render(b, parent, anchor, kind, value, flags);
+ parent.removeChild(anchor);
+ if (isConst(cache) && kind !== 3 /* component */) return;
+ b.updaters.push(() => {
+ let next = getValue(cache);
+ if (next === value) {
+ slot.mounted?.b.update();
+ return;
+ }
+ let nextKind = kindOf(next, flags);
+ if (kind === 0 /* text */ && nextKind === 0 /* text */ && slot.text) {
+ slot.text.data = str(next);
+ value = next;
+ return;
+ }
+ let nextSlot = render(b, slot.first.parentNode, slot.first, nextKind, next, flags);
+ clear(slot);
+ slot = nextSlot;
+ kind = nextKind;
+ value = next;
+ });
+}
+function appendCall(b, anchor, definition, isStatic, positional, named, flags = 0) {
+ let value = isStatic ? definition : definition();
+ if (isComponentDefinition(value)) {
+ if (positional) {
+ throw new Error("positional arguments to components are not supported yet");
+ }
+ if (isStatic) {
+ invoke(b, anchor, value, named ?? {}, null, null);
+ } else {
+ invokeDyn(b, anchor, definition, named ?? {}, null, null);
+ }
+ return;
+ }
+ let result = isStatic ? helper(b, value, positional, named) : helperDyn(b, definition, positional, named);
+ content(b, anchor, result, flags);
+}
+
+// shim:@glimmer/reference/lib/iterable
+import { createIteratorRef } from "@glimmer/reference";
+
+// shim:@glimmer/validator/lib/meta
+import { tagFor } from "@glimmer/validator";
+
+// packages/@glimmer/dom/lib/control-flow.ts
+function when(b, anchor, condition, consequent, alternate) {
+ let cache = createCache(condition);
+ let current = Boolean(getValue(cache));
+ let render2 = (value) => {
+ let fn = value ? consequent : alternate;
+ return fn ? mount(b, anchor, fn) : null;
+ };
+ let m = render2(current);
+ b.updaters.push(() => {
+ let next = Boolean(getValue(cache));
+ if (next === current) {
+ m?.b.update();
+ return;
+ }
+ if (m) unmount(m);
+ current = next;
+ m = render2(next);
+ });
+}
+function cell(initial) {
+ let tag = createTag();
+ let value = initial;
+ return {
+ read: () => {
+ consumeTag(tag);
+ return value;
+ },
+ set(next) {
+ if (next !== value) {
+ value = next;
+ dirtyTag(tag);
+ }
+ }
+ };
+}
+function each(b, anchor, list, key, item, inverse) {
+ let listRef = createComputeRef(() => {
+ let value = list();
+ if (typeof value === "object" && value !== null) consumeTag(tagFor(value, "[]"));
+ return value;
+ });
+ let iteratorRef = createIteratorRef(listRef, key ?? "@identity");
+ let rows = [];
+ let byKey = /* @__PURE__ */ new Map();
+ let inverseMounted = null;
+ let lastIterator = null;
+ let sync = () => {
+ let iterator = valueForRef(iteratorRef);
+ if (iterator === lastIterator) {
+ for (let row of rows) row.m?.b.update();
+ inverseMounted?.b.update();
+ return;
+ }
+ lastIterator = iterator;
+ let nextRows = [];
+ let nextByKey = /* @__PURE__ */ new Map();
+ for (let entry = iterator.next(); entry !== null; entry = iterator.next()) {
+ let row = byKey.get(entry.key);
+ if (row) {
+ row.value.set(entry.value);
+ row.index.set(entry.memo);
+ } else {
+ row = { value: cell(entry.value), index: cell(entry.memo), m: null };
+ }
+ nextByKey.set(entry.key, row);
+ nextRows.push(row);
+ }
+ for (let [k, row] of byKey) {
+ if (nextByKey.get(k) !== row && row.m) unmount(row.m);
+ }
+ let before = anchor;
+ for (let i = nextRows.length - 1; i >= 0; i--) {
+ let row = nextRows[i];
+ if (row.m === null) {
+ let { value, index } = row;
+ row.m = mount(b, before, (child) => item(child, value.read, index.read));
+ } else {
+ if (row.m.end.nextSibling !== before) moveRange(row.m, before);
+ row.m.b.update();
+ }
+ before = row.m.start;
+ }
+ rows = nextRows;
+ byKey = nextByKey;
+ if (rows.length === 0 && inverse && !inverseMounted) {
+ inverseMounted = mount(b, anchor, inverse);
+ } else if (rows.length > 0 && inverseMounted) {
+ unmount(inverseMounted);
+ inverseMounted = null;
+ }
+ };
+ sync();
+ b.updaters.push(sync);
+}
+function block(b, anchor, fn, params) {
+ let m = mount(b, anchor, (child) => fn(child, ...params));
+ b.updaters.push(() => {
+ m.b.update();
+ });
+}
+function yieldTo(b, anchor, ctx, name, params) {
+ let fn = ctx.blocks?.[name] ?? (name === "else" ? ctx.blocks?.["inverse"] : void 0);
+ if (fn) block(b, anchor, fn, params);
+}
+function scope(b, anchor, fn) {
+ block(b, anchor, fn, []);
+}
+function inElement(b, destination, append, fn) {
+ let cache = createCache(destination);
+ let current = null;
+ let m = null;
+ let end = null;
+ let render2 = (target) => {
+ current = target;
+ if (!target) return;
+ if (!append) target.innerHTML = "";
+ end = marker(b.root);
+ target.appendChild(end);
+ m = mount(b, end, fn);
+ };
+ render2(getValue(cache));
+ b.updaters.push(() => {
+ let next = getValue(cache);
+ if (next === current) {
+ m?.b.update();
+ return;
+ }
+ if (m) unmount(m);
+ if (end) removeRange(end, end);
+ m = null;
+ end = null;
+ render2(next);
+ });
+}
+
+// packages/@glimmer/dom/lib/element.ts
+var ELEMENTS = /* @__PURE__ */ new Map();
+function element(tagName) {
+ if (typeof tagName !== "string") {
+ throw new Error(
+ `The argument passed to the \`element\` helper must be a string (you passed \`${String(tagName)}\`)`
+ );
+ }
+ let existing = ELEMENTS.get(tagName);
+ if (existing) return existing;
+ let created = template((b, ctx) => {
+ let doc = b.root.document;
+ let fragment = doc.createDocumentFragment();
+ let anchor = marker(b.root);
+ if (tagName === "") {
+ fragment.appendChild(anchor);
+ } else {
+ let el = doc.createElement(tagName);
+ splat(b, el, ctx);
+ el.appendChild(anchor);
+ fragment.appendChild(el);
+ }
+ yieldTo(b, anchor, ctx, "default", []);
+ return fragment;
+ });
+ ELEMENTS.set(tagName, created);
+ return created;
+}
+export {
+ MAY_CALL,
+ TRUSTING,
+ Template,
+ and,
+ appendCall,
+ at,
+ attr,
+ bool,
+ clone,
+ content,
+ curry,
+ each,
+ element,
+ entries,
+ get,
+ getPath,
+ hasBlock,
+ hasBlockParams,
+ helper,
+ helperDyn,
+ html,
+ inElement,
+ invoke,
+ invokeDyn,
+ listen,
+ log,
+ memo,
+ modifier,
+ or,
+ renderComponent,
+ scope,
+ setTemplate,
+ setVMFallback,
+ splat,
+ str,
+ template,
+ when,
+ yieldTo
+};
diff --git a/packages/ember-repl/src/vendor/glimmer-dom/shared.js b/packages/ember-repl/src/vendor/glimmer-dom/shared.js
new file mode 100644
index 000000000..c1cd5b137
--- /dev/null
+++ b/packages/ember-repl/src/vendor/glimmer-dom/shared.js
@@ -0,0 +1,496 @@
+// @ts-nocheck
+/* eslint-disable */
+/**
+ * VENDORED -- do not edit.
+ *
+ * The runtime for compiled templates (`@glimmer/dom`, `@glimmer/dom/vm`).
+ * Built from https://github.com/NullVoxPopuli-ai-agent/ember.js/tree/claude/gallant-heisenberg-aovzyr (af3aef79),
+ * see https://github.com/emberjs/ember.js/pull/21649
+ *
+ * This is an experiment: strict-mode templates compiled directly to DOM
+ * operations, with no wire format and no VM.
+ */
+
+// packages/@glimmer/dom/lib/core.ts
+import { associateDestroyableChild, destroy } from "@glimmer/destroyable";
+var Root = class {
+ constructor(owner, document) {
+ this.owner = owner;
+ this.document = document;
+ }
+ owner;
+ document;
+ #queue = [];
+ /**
+ * Where `{{outlet}}`s go, when the VM renders this tree as a route
+ * template (see `@glimmer/dom/vm`).
+ */
+ outlets = null;
+ /**
+ * Schedule work that must happen after DOM is inserted
+ * (modifier install/update, component didCreate hooks).
+ */
+ schedule(fn) {
+ this.#queue.push(fn);
+ }
+ flush() {
+ while (this.#queue.length > 0) {
+ let queue = this.#queue;
+ this.#queue = [];
+ for (let fn of queue) fn();
+ }
+ }
+};
+var Block = class {
+ constructor(root, parent) {
+ this.root = root;
+ if (parent) associateDestroyableChild(parent, this);
+ }
+ root;
+ updaters = [];
+ update() {
+ let { updaters } = this;
+ for (let i = 0; i < updaters.length; i++) {
+ updaters[i]();
+ }
+ }
+};
+function marker(root) {
+ return root.document.createTextNode("");
+}
+function mount(parent, before, render) {
+ let b = new Block(parent.root, parent);
+ let start = marker(parent.root);
+ let end = marker(parent.root);
+ let target = before.parentNode;
+ target.insertBefore(start, before);
+ target.insertBefore(render(b), before);
+ target.insertBefore(end, before);
+ return { b, start, end };
+}
+function unmount(m) {
+ removeRange(m.start, m.end);
+ destroy(m.b);
+}
+function removeRange(first, last) {
+ let parent = first.parentNode;
+ if (!parent) return;
+ let node = first;
+ while (node) {
+ let next = node === last ? null : node.nextSibling;
+ parent.removeChild(node);
+ node = next;
+ }
+}
+function moveRange(m, before) {
+ let parent = before.parentNode;
+ let node = m.start;
+ while (node) {
+ let next = node === m.end ? null : node.nextSibling;
+ parent.insertBefore(node, before);
+ node = next;
+ }
+}
+function html(markup) {
+ return { markup, parsed: null };
+}
+function clone(b, t) {
+ let parsed = t.parsed;
+ if (parsed === null) {
+ parsed = t.parsed = b.root.document.createElement("template");
+ parsed.innerHTML = t.markup;
+ }
+ return b.root.document.importNode(parsed.content, true);
+}
+function at(node, ...path) {
+ for (let i of path) {
+ node = node.childNodes[i];
+ }
+ return node;
+}
+var Template = class {
+ constructor(render) {
+ this.render = render;
+ }
+ render;
+ toString() {
+ return "(compiled template)";
+ }
+};
+function template(render) {
+ return new Template(render);
+}
+var TEMPLATES = /* @__PURE__ */ new WeakMap();
+function setTemplate(definition, t) {
+ TEMPLATES.set(definition, t);
+ return definition;
+}
+function templateFor(definition) {
+ if (definition instanceof Template) return definition;
+ let pointer = definition;
+ while (pointer !== null && pointer !== Function.prototype && pointer !== Object.prototype) {
+ let found = TEMPLATES.get(pointer);
+ if (found) return found;
+ pointer = Object.getPrototypeOf(pointer);
+ }
+ return void 0;
+}
+
+// packages/@glimmer/dom/lib/managers.ts
+import { associateDestroyableChild as associateDestroyableChild2, destroy as destroy2 } from "@glimmer/destroyable";
+
+// shim:@glimmer/manager/lib/internal/api
+import { getInternalComponentManager, getInternalHelperManager, getInternalModifierManager } from "@glimmer/manager";
+
+// shim:@glimmer/reference/lib/reference
+import { createComputeRef, valueForRef } from "@glimmer/reference";
+
+// shim:@glimmer/validator/lib/tracking
+import { consumeTag, createCache, getValue, isConst, track, untrack } from "@glimmer/validator";
+
+// shim:@glimmer/validator/lib/validators
+import { createTag, dirtyTag, updateTag, validateTag, valueForTag } from "@glimmer/validator";
+
+// packages/@glimmer/dom/lib/managers.ts
+function capture(named, positional) {
+ let capturedNamed = /* @__PURE__ */ Object.create(null);
+ if (named) {
+ for (let key of Object.keys(named)) {
+ capturedNamed[key] = createComputeRef(named[key]);
+ }
+ }
+ let capturedPositional = (positional ?? []).map((thunk) => createComputeRef(thunk));
+ return {
+ named: capturedNamed,
+ positional: capturedPositional
+ };
+}
+function resolveHelper(definition) {
+ let manager = getInternalHelperManager(definition);
+ return typeof manager === "function" ? manager : manager.getHelper(definition);
+}
+function instantiateHelper(b, definition, args) {
+ let ref = resolveHelper(definition)(args, b.root.owner);
+ associateDestroyableChild2(b, ref);
+ return ref;
+}
+function helper(b, definition, positional, named) {
+ let ref = instantiateHelper(b, definition, capture(named, positional));
+ return () => valueForRef(ref);
+}
+function helperDyn(b, definition, positional, named) {
+ let args = capture(named, positional);
+ let definitionCache = createCache(definition);
+ let current;
+ let ref = null;
+ return () => {
+ let next = getValue(definitionCache);
+ if (next !== current || ref === null) {
+ if (ref !== null) destroy2(ref);
+ current = next;
+ ref = instantiateHelper(b, next, args);
+ }
+ return valueForRef(ref);
+ };
+}
+function modifier(b, element, definition, positional, named) {
+ let manager = getInternalModifierManager(definition);
+ let state = manager.create(
+ b.root.owner,
+ element,
+ definition,
+ capture(named, positional)
+ );
+ let destroyable = manager.getDestroyable(state);
+ if (destroyable) associateDestroyableChild2(b, destroyable);
+ let tag = manager.getTag(state);
+ if (tag === null) {
+ b.root.schedule(() => {
+ manager.install(state);
+ });
+ return;
+ }
+ let lastRevision = null;
+ let run = (hook) => {
+ updateTag(
+ tag,
+ track(() => {
+ hook(state);
+ })
+ );
+ consumeTag(tag);
+ lastRevision = valueForTag(tag);
+ };
+ b.root.schedule(() => {
+ run((s) => {
+ manager.install(s);
+ });
+ });
+ b.updaters.push(() => {
+ consumeTag(tag);
+ if (lastRevision === null || validateTag(tag, lastRevision)) return;
+ lastRevision = null;
+ b.root.schedule(() => {
+ run((s) => {
+ manager.update(s);
+ });
+ });
+ });
+}
+
+// packages/@glimmer/dom/lib/invoke.ts
+import { associateDestroyableChild as associateDestroyableChild3 } from "@glimmer/destroyable";
+
+// shim:@glimmer/manager/lib/public/template
+import { getComponentTemplate, setComponentTemplate } from "@glimmer/manager";
+
+// packages/@glimmer/dom/lib/invoke.ts
+var Curried = class {
+ constructor(definition, named) {
+ this.definition = definition;
+ this.named = named;
+ }
+ definition;
+ named;
+};
+function curry(definition, named) {
+ if (!definition) return null;
+ return new Curried(definition, named ?? {});
+}
+function isComponentDefinition(value) {
+ if (value instanceof Template || value instanceof Curried) return true;
+ if (typeof value !== "object" && typeof value !== "function" || value === null) return false;
+ if (templateFor(value)) return true;
+ return getInternalComponentManager(value, true) !== null;
+}
+var vmFallback = null;
+function setVMFallback(fallback) {
+ vmFallback = fallback;
+}
+function describe(definition) {
+ return typeof definition === "function" ? definition.name || "(anonymous component)" : Object.prototype.toString.call(definition);
+}
+function argsFor(captured) {
+ let args = /* @__PURE__ */ Object.create(null);
+ for (let key of Object.keys(captured.named)) {
+ let ref = captured.named[key];
+ Object.defineProperty(args, key, { enumerable: true, get: () => valueForRef(ref) });
+ }
+ return args;
+}
+function island(b, definition, named, blocks, attrs, current) {
+ if (!vmFallback) {
+ throw new Error(
+ `${describe(definition)} does not have a compiled template. To render it with the VM, import '@glimmer/dom/vm' (or compile with \`vmInterop\`).`
+ );
+ }
+ let element = b.root.document.createElement("glimmer-island");
+ element.style.display = "contents";
+ associateDestroyableChild3(
+ b,
+ vmFallback.render(current ?? (() => definition), element, b.root.owner, {
+ named,
+ blocks,
+ attrs
+ })
+ );
+ return element;
+}
+function instantiate(b, definition, named, blocks, attrs, current) {
+ while (definition instanceof Curried) {
+ named = { ...definition.named, ...named };
+ definition = definition.definition;
+ }
+ let compiled = templateFor(definition);
+ let templateless = !compiled && !getComponentTemplate(definition) && "getDelegateFor" in (getInternalComponentManager(definition, true) ?? {});
+ if (!compiled && !templateless) return island(b, definition, named, blocks, attrs, current);
+ let captured = capture(named, null);
+ let args = argsFor(captured);
+ if (compiled === definition) {
+ return compiled.render(b, { self: void 0, args, blocks, attrs });
+ }
+ let manager = getInternalComponentManager(definition);
+ if (!("getDelegateFor" in manager)) {
+ throw new Error(
+ `Compiled templates can only be used with components that have a custom component manager (like @glimmer/component). ${describe(definition)} does not.`
+ );
+ }
+ let state = manager.create(b.root.owner, definition, { capture: () => captured });
+ let self = valueForRef(manager.getSelf(state));
+ let destroyable = manager.getDestroyable(state);
+ if (destroyable) associateDestroyableChild3(b, destroyable);
+ b.root.schedule(() => {
+ manager.didCreate(state);
+ });
+ if (!compiled) return b.root.document.createDocumentFragment();
+ return compiled.render(b, { self, args, blocks, attrs });
+}
+function invoke(b, anchor, definition, named, blocks, attrs) {
+ let m = mount(b, anchor, (child) => instantiate(child, definition, named, blocks, attrs));
+ b.updaters.push(() => {
+ m.b.update();
+ });
+}
+function invokeDyn(b, anchor, definition, named, blocks, attrs) {
+ let cache = createCache(definition);
+ let current = getValue(cache);
+ let tag = createTag();
+ let read = () => {
+ consumeTag(tag);
+ return current;
+ };
+ let render = (value) => value ? mount(b, anchor, (child) => instantiate(child, value, named, blocks, attrs, read)) : null;
+ let m = render(current);
+ b.updaters.push(() => {
+ let next = getValue(cache);
+ if (next === current) {
+ m?.b.update();
+ return;
+ }
+ if (m && vmFallback?.canUpdate(current, next)) {
+ current = next;
+ dirtyTag(tag);
+ m.b.update();
+ return;
+ }
+ if (m) unmount(m);
+ current = next;
+ m = render(next);
+ });
+}
+
+// packages/@glimmer/dom/lib/render.ts
+import {
+ associateDestroyableChild as associateDestroyableChild4,
+ destroy as destroy3,
+ isDestroying,
+ registerDestructor
+} from "@glimmer/destroyable";
+import { _backburner } from "@ember/runloop";
+function startRoot(owner, document, outlets, build, cleanup) {
+ let root = new Root(owner, document);
+ root.outlets = outlets ?? null;
+ let b = new Block(root, null);
+ let tag;
+ let revision;
+ let destroyed = false;
+ let pass = (fn) => {
+ tag = track(() => {
+ fn();
+ root.flush();
+ });
+ revision = valueForTag(tag);
+ };
+ pass(() => {
+ build(b);
+ });
+ let result;
+ let rerender = () => {
+ if (destroyed || isDestroying(result) || validateTag(tag, revision)) return;
+ pass(() => {
+ b.update();
+ });
+ };
+ _backburner.on("end", rerender);
+ result = {
+ rerender,
+ destroy() {
+ if (destroyed) return;
+ destroyed = true;
+ _backburner.off("end", rerender);
+ cleanup();
+ if (!isDestroying(result)) destroy3(result);
+ }
+ };
+ associateDestroyableChild4(result, b);
+ registerDestructor(result, () => {
+ result.destroy();
+ });
+ return result;
+}
+function renderRoot({ into, owner = {}, outlets }, render) {
+ into.innerHTML = "";
+ let end = into.ownerDocument.createTextNode("");
+ into.appendChild(end);
+ return startRoot(
+ owner,
+ into.ownerDocument,
+ outlets,
+ (b) => {
+ let m = mount(b, end, render);
+ b.updaters.push(() => {
+ m.b.update();
+ });
+ },
+ () => {
+ removeRange(into.firstChild, end);
+ }
+ );
+}
+function attach(element, owner, build) {
+ return startRoot(owner, element.ownerDocument, void 0, build, () => {
+ });
+}
+function renderBlock(block, params, { into, owner }) {
+ return renderRoot({ into, owner }, (child) => block(child, ...params));
+}
+function renderComponent(component, {
+ into,
+ owner,
+ args
+}) {
+ let named = {};
+ if (args) {
+ for (let key of Object.keys(args)) named[key] = () => args[key];
+ }
+ return renderRoot(
+ owner ? { into, owner } : { into },
+ (child) => instantiate(child, component, named, null, null)
+ );
+}
+function renderTemplate(template2, options) {
+ return renderRoot(
+ options,
+ (child) => template2.render(child, { self: options.self, args: options.args, blocks: null, attrs: null })
+ );
+}
+
+export {
+ marker,
+ mount,
+ unmount,
+ removeRange,
+ moveRange,
+ html,
+ clone,
+ at,
+ Template,
+ template,
+ setTemplate,
+ templateFor,
+ consumeTag,
+ createCache,
+ getValue,
+ isConst,
+ untrack,
+ getInternalHelperManager,
+ setComponentTemplate,
+ createComputeRef,
+ valueForRef,
+ createTag,
+ dirtyTag,
+ capture,
+ helper,
+ helperDyn,
+ modifier,
+ curry,
+ isComponentDefinition,
+ setVMFallback,
+ instantiate,
+ invoke,
+ invokeDyn,
+ attach,
+ renderBlock,
+ renderComponent,
+ renderTemplate
+};
diff --git a/packages/ember-repl/src/vendor/glimmer-dom/vm.js b/packages/ember-repl/src/vendor/glimmer-dom/vm.js
new file mode 100644
index 000000000..40a1cdb49
--- /dev/null
+++ b/packages/ember-repl/src/vendor/glimmer-dom/vm.js
@@ -0,0 +1,314 @@
+// @ts-nocheck
+/* eslint-disable */
+/**
+ * VENDORED -- do not edit.
+ *
+ * The runtime for compiled templates (`@glimmer/dom`, `@glimmer/dom/vm`).
+ * Built from https://github.com/NullVoxPopuli-ai-agent/ember.js/tree/claude/gallant-heisenberg-aovzyr (af3aef79),
+ * see https://github.com/emberjs/ember.js/pull/21649
+ *
+ * This is an experiment: strict-mode templates compiled directly to DOM
+ * operations, with no wire format and no VM.
+ */
+import {
+ attach,
+ capture,
+ renderBlock,
+ renderTemplate,
+ setComponentTemplate,
+ setTemplate,
+ setVMFallback,
+ template,
+ templateFor,
+ untrack
+} from "./shared.js";
+
+// packages/@glimmer/dom/lib/vm.ts
+import { registerDestructor as registerDestructor2 } from "@glimmer/destroyable";
+
+// shim:@glimmer/manager/lib/public/component
+import { componentCapabilities } from "@glimmer/manager";
+
+// shim:@glimmer/manager/lib/public/api
+import { setComponentManager, setHelperManager, setModifierManager } from "@glimmer/manager";
+
+// shim:@glimmer/manager/lib/public/modifier
+import { modifierCapabilities } from "@glimmer/manager";
+
+// shim:@glimmer/manager/lib/public/helper
+import { helperCapabilities } from "@glimmer/manager";
+
+// packages/@glimmer/dom/lib/vm.ts
+import { getOwner, setOwner } from "@glimmer/owner";
+
+// shim:@glimmer/constants/lib/curried
+var CURRIED_COMPONENT = 0;
+
+// shim:@glimmer/runtime/lib/curried-value
+import { CurriedValue } from "@glimmer/runtime";
+import { curry } from "@glimmer/runtime";
+var isCurriedValue = (value) => value instanceof CurriedValue;
+
+// shim:@glimmer/validator/lib/collections/array
+import { trackedArray } from "@glimmer/validator";
+
+// shim:@ember/-internals/glimmer/lib/base-renderer
+import { renderComponent } from "@ember/renderer";
+import { registerDestructor } from "@glimmer/destroyable";
+var BaseRenderer = class _BaseRenderer {
+ static strict(owner) {
+ return new _BaseRenderer(owner);
+ }
+ constructor(owner) {
+ this.owner = owner;
+ }
+ render(component, { into, args }) {
+ let result = renderComponent(component, { into, owner: this.owner, args });
+ let destroyable = {};
+ registerDestructor(destroyable, () => result.destroy());
+ return destroyable;
+ }
+};
+
+// packages/@glimmer/dom/lib/vm.ts
+import templateOnly from "@ember/component/template-only";
+import { createTemplateFactory } from "@ember/template-factory";
+var RENDERERS = /* @__PURE__ */ new WeakMap();
+function rendererFor(owner) {
+ let renderer = RENDERERS.get(owner);
+ if (!renderer) {
+ renderer = BaseRenderer.strict(owner, document, {
+ isInteractive: true,
+ hasDOM: true
+ });
+ RENDERERS.set(owner, renderer);
+ }
+ return renderer;
+}
+var ApplyAttrsManager = class {
+ capabilities = modifierCapabilities("3.22");
+ constructor(owner) {
+ this.owner = owner;
+ }
+ createModifier(_definition, args) {
+ return {
+ attrs: args.positional[0],
+ result: null
+ };
+ }
+ installModifier(state, element) {
+ let {
+ attrs
+ } = state;
+ if (attrs) {
+ state.result = attach(element, this.owner, (b) => {
+ attrs(b, element);
+ });
+ }
+ }
+ updateModifier() {
+ }
+ destroyModifier(state) {
+ state.result?.destroy();
+ }
+};
+var applyAttrs = setModifierManager((owner) => new ApplyAttrsManager(owner), {});
+var BlockIslandManager = class {
+ capabilities = helperCapabilities("3.23", {
+ hasValue: true,
+ hasDestroyable: true
+ });
+ constructor(owner) {
+ this.owner = owner;
+ }
+ createHelper(_definition, args) {
+ let block = args.positional[0];
+ let params = [1, 2, 3].map((i) => () => args.positional[i]);
+ return untrack(() => {
+ let element = document.createElement("glimmer-island");
+ element.style.display = "contents";
+ let result = renderBlock(block, params, {
+ into: element,
+ owner: this.owner
+ });
+ return {
+ element,
+ result
+ };
+ });
+ }
+ getValue(island2) {
+ return island2.element;
+ }
+ getDestroyable(island2) {
+ return island2.result;
+ }
+};
+var blockIsland = setHelperManager((owner) => new BlockIslandManager(owner ?? {}), {});
+var INVOKE = setComponentTemplate(createTemplateFactory(
+ /*
+ {{#if @block}}<@definition {{applyAttrs @attrs}} as |a b c|>{{blockIsland @block a b c}}@definition>{{else}}<@definition {{applyAttrs @attrs}} />{{/if}}
+ */
+ {
+ "id": null,
+ "block": '[[[41,[30,1],[[[8,[30,2],[[4,[32,0],[[30,3]],null]],null,[["default"],[[[[1,[28,[32,1],[[30,1],[30,4],[30,5],[30,6]],null]]],[4,5,6]]]]]],[]],[[[8,[30,2],[[4,[32,0],[[30,3]],null]],null,null]],[]]]],["@block","@definition","@attrs","a","b","c"],["if"]]',
+ "moduleName": "packages/@glimmer/dom/lib/invoke.hbs",
+ "scope": () => ({
+ applyAttrs,
+ blockIsland
+ }),
+ "isStrictMode": true
+ }
+), templateOnly());
+setVMFallback({
+ render(definition, into, owner, {
+ named,
+ blocks,
+ attrs
+ }) {
+ let args = capture(named, null);
+ let last;
+ let curried;
+ return rendererFor(owner).render(INVOKE, {
+ into,
+ args: {
+ get definition() {
+ let value = definition();
+ if (value !== last || !curried) {
+ last = value;
+ curried = curry(CURRIED_COMPONENT, value, owner, args);
+ }
+ return curried;
+ },
+ attrs,
+ block: blocks?.["default"] ?? null
+ }
+ });
+ },
+ canUpdate(from, to) {
+ return isCurriedValue(from) && isCurriedValue(to);
+ }
+});
+var TemplateOnlyState = class {
+ constructor(template3, args) {
+ this.template = template3;
+ this.args = args;
+ }
+};
+function createIsland(context) {
+ return untrack(() => {
+ let template3;
+ let self;
+ let args;
+ if (context instanceof TemplateOnlyState) {
+ template3 = context.template;
+ self = void 0;
+ args = context.args;
+ } else {
+ template3 = templateFor(context.constructor);
+ self = context;
+ args = context.args ?? {};
+ }
+ if (!template3) {
+ throw new Error("BUG: rendering a compiled component without a compiled template");
+ }
+ let element = document.createElement("glimmer-island");
+ element.style.display = "contents";
+ let outlets = trackedArray([]);
+ let result = renderTemplate(template3, {
+ into: element,
+ owner: getOwner(context) ?? {},
+ self,
+ args,
+ outlets: {
+ add(outlet2) {
+ outlets.push(outlet2);
+ return () => {
+ let index = outlets.indexOf(outlet2);
+ if (index !== -1) outlets.splice(index, 1);
+ };
+ }
+ }
+ });
+ return {
+ element,
+ outlets,
+ result
+ };
+ });
+}
+var IslandHelperManager = class {
+ capabilities = helperCapabilities("3.23", {
+ hasValue: true,
+ hasDestroyable: true
+ });
+ createHelper(_definition, args) {
+ return createIsland(args.positional[0]);
+ }
+ getValue(island2) {
+ return island2;
+ }
+ getDestroyable(island2) {
+ return island2.result;
+ }
+};
+var island = setHelperManager(() => new IslandHelperManager(), {});
+var BRIDGE = createTemplateFactory(
+ /*
+ {{#let (island this) as |i|}}{{i.element}}{{#each i.outlets as |el|}}{{#in-element el insertBefore=null}}<@outlet />{{/in-element}}{{/each}}{{/let}}
+ */
+ {
+ "id": null,
+ "block": '[[[44,[[28,[32,0],[[30,0]],null]],[[[1,[30,1,["element"]]],[42,[28,[31,2],[[28,[31,2],[[30,1,["outlets"]]],null]],null],null,[[[40,[[[8,[30,3],null,null,null]],[]],"%cursor:0%",[28,[31,4],[[30,2]],null],null]],[2]],null]],[1]]]],["i","el","@outlet"],["let","each","-track-array","in-element","-in-el-null"]]',
+ "moduleName": "packages/@glimmer/dom/lib/vm.hbs",
+ "scope": () => ({
+ island
+ }),
+ "isStrictMode": true
+ }
+);
+var CAPABILITIES = componentCapabilities("3.13", {});
+var TemplateOnlyManager = class {
+ capabilities = CAPABILITIES;
+ constructor(owner) {
+ this.owner = owner;
+ }
+ createComponent(definition, args) {
+ let state = new TemplateOnlyState(definition, args.named);
+ setOwner(state, this.owner);
+ return state;
+ }
+ getContext(state) {
+ return state;
+ }
+};
+function vmCompatible(t) {
+ setComponentManager((owner) => new TemplateOnlyManager(owner), t);
+ setComponentTemplate(BRIDGE, t);
+ return t;
+}
+function template2(render) {
+ return vmCompatible(template(render));
+}
+function setTemplate2(definition, t) {
+ setTemplate(definition, t);
+ setComponentTemplate(BRIDGE, definition);
+ return definition;
+}
+function outlet(b, anchor) {
+ let sink = b.root.outlets;
+ if (!sink) {
+ throw new Error("{{outlet}} can only be used in a template that the router renders (a route template)");
+ }
+ let element = b.root.document.createElement("glimmer-outlet");
+ element.style.display = "contents";
+ anchor.parentNode.insertBefore(element, anchor);
+ let remove = sink.add(element);
+ registerDestructor2(b, remove);
+}
+export {
+ outlet,
+ setTemplate2 as setTemplate,
+ template2 as template,
+ vmCompatible
+};
diff --git a/packages/repl-sdk/src/compilers/ember.js b/packages/repl-sdk/src/compilers/ember.js
index cdb0b704b..672d8867a 100644
--- a/packages/repl-sdk/src/compilers/ember.js
+++ b/packages/repl-sdk/src/compilers/ember.js
@@ -107,24 +107,45 @@ function onUnhandled(e, handle) {
handle(reason);
}
+const gjsCodemirror = {
+ lang: async () => {
+ const { gjs } = await import('codemirror-lang-glimmer-js');
+
+ return gjs();
+ },
+};
+
/**
- * @type {CompilerConfig}
+ * `gjs` doubles as the config for its flavors (`gjs|codegen`).
+ *
+ * @type {CompilerConfig & { codegen: CompilerConfig }}
*/
export const gjs = {
resolve,
onUnhandled,
- codemirror: {
- lang: async () => {
- const { gjs } = await import('codemirror-lang-glimmer-js');
-
- return gjs();
- },
- },
+ codemirror: gjsCodemirror,
compiler: async (...args) => {
const gjs = await import('./ember/gjs.js');
return gjs.compiler(...args);
},
+
+ /**
+ * EXPERIMENTAL: strict-mode templates compiled directly to DOM operations,
+ * with no wire format and no VM.
+ *
+ * The host must provide `@glimmer/dom` (ember-repl does).
+ */
+ codegen: {
+ resolve,
+ onUnhandled,
+ codemirror: gjsCodemirror,
+ compiler: async (config, api) => {
+ const gjs = await import('./ember/gjs.js');
+
+ return gjs.compiler(config, api, { codegen: true });
+ },
+ },
};
/**
diff --git a/packages/repl-sdk/src/compilers/ember/gjs.js b/packages/repl-sdk/src/compilers/ember/gjs.js
index c3a350ee7..a0876ecfd 100644
--- a/packages/repl-sdk/src/compilers/ember/gjs.js
+++ b/packages/repl-sdk/src/compilers/ember/gjs.js
@@ -55,12 +55,17 @@ const buildDependencies = [
* gjs and gts share one pipeline.
* gts adds the babel typescript plugin, which only strips types.
*
+ * `codegen` is EXPERIMENTAL: templates are compiled directly to DOM operations
+ * (no wire format, no VM), and rendered with the runtime from `@glimmer/dom`.
+ * See https://github.com/NullVoxPopuli-ai-agent/ember.js/tree/claude/gallant-heisenberg-aovzyr/packages/@glimmer/dom
+ *
* @param {Parameters[0]} config
* @param {Parameters[1]} api
- * @param {{ typescript?: boolean }} [flags]
+ * @param {{ typescript?: boolean; codegen?: boolean }} [flags]
*/
export async function compiler(config, api, flags = {}) {
const typescript = flags.typescript ?? false;
+ const codegen = flags.codegen ?? false;
const ext = typescript ? 'ts' : 'js';
const filename = `dynamic-repl.${ext}`;
@@ -85,6 +90,33 @@ export async function compiler(config, api, flags = {}) {
const babel = 'availablePlugins' in _babel ? _babel : _babel.default;
+ const codegenCompiler = codegen ? await import('./vendor/codegen-compiler.js') : null;
+ /**
+ * @type {unknown[]}
+ */
+ const templatePlugin = codegenCompiler
+ ? [
+ codegenCompiler.codegenBabelPlugin,
+ {
+ // the parser that ships with ember-source
+ preprocess: compiler._preprocess,
+ // compiled components can be rendered by the VM too (and VM-only
+ // components, e.g. from addons, by compiled templates)
+ vmInterop: true,
+ isGlobal: codegenCompiler.isAllowedGlobal,
+ },
+ ]
+ : [
+ emberTemplateCompilation,
+ {
+ compiler,
+ transforms: [
+ // ...macros.templateMacros
+ ],
+ targetFormat: 'wire',
+ },
+ ];
+
// let macros = embroiderMacros.buildMacros();
/**
@@ -111,16 +143,7 @@ export async function compiler(config, api, flags = {}) {
return babel.transformAsync(text, {
filename,
plugins: typePlugins.concat([
- [
- emberTemplateCompilation,
- {
- compiler,
- transforms: [
- // ...macros.templateMacros
- ],
- targetFormat: 'wire',
- },
- ],
+ templatePlugin,
[
// @ts-ignore - we don't care about types here..
decoratorTransforms,
@@ -200,7 +223,9 @@ export async function compiler(config, api, flags = {}) {
element.setAttribute(attribute, '');
- const { renderComponent } = await compiler.tryResolve('@ember/renderer');
+ const { renderComponent } = await compiler.tryResolve(
+ codegen ? '@glimmer/dom' : '@ember/renderer'
+ );
const owner = makeOwner(config.owner);
const args = /** @type {Record | undefined} */ (
diff --git a/packages/repl-sdk/src/compilers/ember/vendor/codegen-compiler.js b/packages/repl-sdk/src/compilers/ember/vendor/codegen-compiler.js
new file mode 100644
index 000000000..499593f4d
--- /dev/null
+++ b/packages/repl-sdk/src/compilers/ember/vendor/codegen-compiler.js
@@ -0,0 +1,981 @@
+// @ts-nocheck
+/* eslint-disable */
+/**
+ * VENDORED -- do not edit.
+ *
+ * The template compiler for the codegen flavor (babel plugin + code generator).
+ * Built from https://github.com/NullVoxPopuli-ai-agent/ember.js/tree/claude/gallant-heisenberg-aovzyr (af3aef79),
+ * see https://github.com/emberjs/ember.js/pull/21649
+ *
+ * This is an experiment: strict-mode templates compiled directly to DOM
+ * operations, with no wire format and no VM.
+ */
+
+// packages/@glimmer/compiler/lib/codegen/generate.ts
+var CodegenError = class extends Error {
+ constructor(message, node) {
+ let loc = node?.loc;
+ let where = loc?.startPosition ? ` (line ${loc.startPosition.line}, column ${loc.startPosition.column})` : "";
+ super(`${message}${where}`);
+ this.name = "CodegenError";
+ }
+};
+var VOID_ELEMENTS = /* @__PURE__ */ new Set([
+ "area",
+ "base",
+ "br",
+ "col",
+ "command",
+ "embed",
+ "hr",
+ "img",
+ "input",
+ "keygen",
+ "link",
+ "meta",
+ "param",
+ "source",
+ "track",
+ "wbr"
+]);
+var KEYWORDS = /* @__PURE__ */ new Set([
+ "action",
+ "component",
+ "debugger",
+ "each",
+ "each-in",
+ "has-block",
+ "has-block-params",
+ "helper",
+ "if",
+ "in-element",
+ "let",
+ "log",
+ "modifier",
+ "mount",
+ "mut",
+ "outlet",
+ "readonly",
+ "unbound",
+ "unless",
+ "yield"
+]);
+var AUTO_IMPORTED = {
+ and: "@ember/helper",
+ array: "@ember/helper",
+ element: "@ember/helper",
+ eq: "@ember/helper",
+ fn: "@ember/helper",
+ gt: "@ember/helper",
+ gte: "@ember/helper",
+ hash: "@ember/helper",
+ lt: "@ember/helper",
+ lte: "@ember/helper",
+ neq: "@ember/helper",
+ not: "@ember/helper",
+ on: "@ember/modifier",
+ or: "@ember/helper"
+};
+var SPECIALIZED = {
+ "@ember/helper": /* @__PURE__ */ new Set([
+ "and",
+ "array",
+ "concat",
+ "element",
+ "eq",
+ "fn",
+ "get",
+ "gt",
+ "gte",
+ "hash",
+ "lt",
+ "lte",
+ "neq",
+ "not",
+ "or"
+ ]),
+ "@ember/modifier": /* @__PURE__ */ new Set(["on"])
+};
+var COMPARISONS = {
+ eq: "===",
+ neq: "!==",
+ gt: ">",
+ gte: ">=",
+ lt: "<",
+ lte: "<="
+};
+var IDENTIFIER = /^[A-Za-z_$][\w$]*$/u;
+var q = (value) => JSON.stringify(value);
+function key(name) {
+ return IDENTIFIER.test(name) ? name : q(name);
+}
+function prop(name) {
+ return IDENTIFIER.test(name) ? `.${name}` : `[${q(name)}]`;
+}
+function escapeText(text) {
+ return text.replace(/&/gu, "&").replace(//gu, ">");
+}
+function escapeAttr(text) {
+ return text.replace(/&/gu, "&").replace(/"/gu, """);
+}
+var Scope = class _Scope {
+ constructor(parent) {
+ this.parent = parent;
+ }
+ parent;
+ #names = /* @__PURE__ */ new Map();
+ bind(name, js) {
+ this.#names.set(name, js);
+ }
+ lookup(name) {
+ return this.#names.get(name) ?? this.parent?.lookup(name);
+ }
+ child() {
+ return new _Scope(this);
+ }
+};
+var Generator = class {
+ constructor(options) {
+ this.options = options;
+ }
+ options;
+ #uid = 0;
+ #hoisted = [];
+ #html = /* @__PURE__ */ new Map();
+ get hoisted() {
+ return this.#hoisted;
+ }
+ uid(prefix) {
+ return `$_${this.options.prefix ?? ""}${prefix}${++this.#uid}`;
+ }
+ rt(name) {
+ return this.options.runtime(name);
+ }
+ template(ast) {
+ let scope = new Scope(null);
+ let body = this.body(ast.body, scope);
+ return `${this.rt("template")}(($_b, $_ctx) => {
+const $_s = $_ctx.self, $_a = $_ctx.args;
+${body}
+})`;
+ }
+ /////////////////////////////////////////////////////////////////////////
+ // Blocks of content
+ /////////////////////////////////////////////////////////////////////////
+ /**
+ * Generate a function body that renders `statements`, and returns the DOM.
+ */
+ body(statements, scope) {
+ let body = { lines: [], scope };
+ let ops = [];
+ let markup = this.html(statements, [], ops, body);
+ let root = this.uid("r");
+ body.lines.push(`const ${root} = ${this.rt("clone")}($_b, ${this.hoistHTML(markup)});`);
+ let refs = ops.map((op) => {
+ let ref = this.uid("n");
+ body.lines.push(`const ${ref} = ${this.rt("at")}(${[root, ...op.path].join(", ")});`);
+ return ref;
+ });
+ ops.forEach((op, i) => {
+ let ref = refs[i];
+ if (op.kind === "element") {
+ this.elementOps(op.node, ref, body);
+ } else {
+ this.slot(op.node, ref, body);
+ }
+ });
+ body.lines.push(`return ${root};`);
+ return body.lines.join("\n");
+ }
+ blockFn(block, scope) {
+ if (!block) return "undefined";
+ return this.blockFnWith(block.body, block.blockParams, scope);
+ }
+ blockFnWith(statements, params, scope, bindParam) {
+ let inner = scope.child();
+ let names = params.map((name, i) => {
+ let id = this.uid("p");
+ if (bindParam) {
+ bindParam(id, i, inner);
+ } else {
+ inner.bind(name, `${id}()`);
+ }
+ return id;
+ });
+ return `($_b${names.map((n) => `, ${n}`).join("")}) => {
+${this.body(statements, inner)}
+}`;
+ }
+ hoistHTML(markup) {
+ let existing = this.#html.get(markup);
+ if (existing) return existing;
+ let id = this.uid("t");
+ this.#hoisted.push(`const ${id} = ${this.rt("html")}(${q(markup)});`);
+ this.#html.set(markup, id);
+ return id;
+ }
+ /**
+ * Build the static HTML for a list of statements, recording the dynamic
+ * parts (and the child-index path to their node) in `ops`.
+ */
+ html(statements, path, ops, body) {
+ let out = "";
+ let index = 0;
+ let text = null;
+ let flushText = () => {
+ if (text) {
+ out += escapeText(text);
+ index++;
+ }
+ text = null;
+ };
+ for (let statement of statements) {
+ switch (statement.type) {
+ case "TextNode":
+ text = (text ?? "") + statement.chars;
+ break;
+ case "MustacheCommentStatement":
+ break;
+ case "CommentStatement":
+ flushText();
+ out += ``;
+ index++;
+ break;
+ case "MustacheStatement": {
+ let literal = staticText(statement);
+ if (literal !== null) {
+ text = (text ?? "") + literal;
+ break;
+ }
+ flushText();
+ out += "";
+ ops.push({ kind: "slot", path: [...path, index++], node: statement });
+ break;
+ }
+ case "BlockStatement":
+ flushText();
+ out += "";
+ ops.push({ kind: "slot", path: [...path, index++], node: statement });
+ break;
+ case "ElementNode":
+ flushText();
+ if (this.isComponent(statement, body.scope)) {
+ out += "";
+ ops.push({ kind: "slot", path: [...path, index++], node: statement });
+ } else {
+ out += this.element(statement, [...path, index++], ops, body);
+ }
+ break;
+ }
+ }
+ flushText();
+ return out;
+ }
+ element(node, path, ops, body) {
+ let { tag } = node;
+ let out = `<${tag}`;
+ let dynamic = node.modifiers.length > 0;
+ let splatted = false;
+ for (let attr of node.attributes) {
+ if (attr.name === "...attributes") {
+ splatted = dynamic = true;
+ } else if (attr.value.type === "TextNode" && !splatted) {
+ out += attr.value.chars === "" ? ` ${attr.name}` : ` ${attr.name}="${escapeAttr(attr.value.chars)}"`;
+ } else {
+ dynamic = true;
+ }
+ }
+ out += ">";
+ if (dynamic) ops.push({ kind: "element", path, node });
+ if (VOID_ELEMENTS.has(tag)) return out;
+ let children = this.html(node.children, path, ops, body);
+ if ((tag === "pre" || tag === "textarea" || tag === "listing") && children.startsWith("\n")) {
+ children = `
+${children}`;
+ }
+ return `${out}${children}${tag}>`;
+ }
+ isComponent(node, scope) {
+ let { head, tail } = node.path;
+ if (head.type !== "VarHead") return true;
+ if (tail.length > 0) return true;
+ if (scope.lookup(head.name) !== void 0) return true;
+ if (this.options.isLexical(head.name)) return true;
+ return /^[A-Z]/u.test(head.name);
+ }
+ /////////////////////////////////////////////////////////////////////////
+ // Elements
+ /////////////////////////////////////////////////////////////////////////
+ elementOps(node, element, body) {
+ let splatted = false;
+ for (let attr of node.attributes) {
+ if (attr.name === "...attributes") {
+ splatted = true;
+ body.lines.push(`${this.rt("splat")}($_b, ${element}, $_ctx);`);
+ } else if (attr.value.type === "TextNode") {
+ if (splatted) {
+ body.lines.push(
+ `${this.rt("attr")}($_b, ${element}, ${q(attr.name)}, ${q(attr.value.chars)});`
+ );
+ }
+ } else {
+ body.lines.push(
+ `${this.rt("attr")}($_b, ${element}, ${q(attr.name)}, () => ${this.attrValue(attr.value, body)});`
+ );
+ }
+ }
+ for (let modifier of node.modifiers) {
+ this.modifier(modifier, element, body);
+ }
+ }
+ attrValue(value, body) {
+ switch (value.type) {
+ case "TextNode":
+ return q(value.chars);
+ case "MustacheStatement":
+ return this.mustacheValue(value, body);
+ case "ConcatStatement":
+ return `(${[
+ '""',
+ ...value.parts.map(
+ (part) => part.type === "TextNode" ? q(part.chars) : `${this.rt("str")}(${this.mustacheValue(part, body)})`
+ )
+ ].join(" + ")})`;
+ }
+ }
+ modifier(node, element, body) {
+ let builtin = this.builtin(node.path, body.scope);
+ if (builtin === "on") {
+ let [event, handler] = node.params;
+ let options = node.hash.pairs.length ? `, { ${node.hash.pairs.map((p) => `${key(p.key)}: ${this.expr(p.value, body)}`).join(", ")} }` : "";
+ body.lines.push(
+ `${this.rt("listen")}($_b, ${element}, ${event ? this.expr(event, body) : "undefined"}, () => ${handler ? this.expr(handler, body) : "undefined"}${options});`
+ );
+ return;
+ }
+ if (builtin !== null) {
+ throw new CodegenError(`\`${builtin}\` cannot be used as a modifier`, node);
+ }
+ body.lines.push(
+ `${this.rt("modifier")}($_b, ${element}, ${this.expr(node.path, body)}, ${this.positional(
+ node.params,
+ body
+ )}, ${this.named(node.hash, body)});`
+ );
+ }
+ /////////////////////////////////////////////////////////////////////////
+ // Dynamic content
+ /////////////////////////////////////////////////////////////////////////
+ slot(node, anchor, body) {
+ switch (node.type) {
+ case "ElementNode": {
+ this.componentElement(node, anchor, body);
+ return;
+ }
+ case "MustacheStatement": {
+ this.append(node, anchor, body);
+ return;
+ }
+ case "BlockStatement": {
+ this.block(node, anchor, body);
+ return;
+ }
+ }
+ }
+ append(node, anchor, body) {
+ let { path, params, hash } = node;
+ let flags = node.trusting ? 2 : 0;
+ if (path.type !== "PathExpression") {
+ body.lines.push(
+ `${this.rt("content")}($_b, ${anchor}, () => ${this.mustacheValue(node, body)}, ${flags});`
+ );
+ return;
+ }
+ let builtin = this.builtin(path, body.scope);
+ switch (builtin) {
+ case "yield": {
+ let to = hash.pairs.find((p) => p.key === "to");
+ let name = to ? this.staticString(to.value, "yield to=") : "default";
+ body.lines.push(
+ `${this.rt("yieldTo")}($_b, ${anchor}, $_ctx, ${q(name === "inverse" ? "else" : name)}, [${params.map((p) => `() => ${this.expr(p, body)}`).join(", ")}]);`
+ );
+ return;
+ }
+ case "debugger":
+ body.lines.push("debugger;");
+ return;
+ case "outlet":
+ if (this.options.vmInterop) {
+ body.lines.push(`${this.rt("outlet")}($_b, ${anchor});`);
+ } else {
+ body.lines.push(
+ `${this.rt("invokeDyn")}($_b, ${anchor}, () => $_a.outlet, {}, null, null);`
+ );
+ }
+ return;
+ case "component": {
+ let [definition, ...rest] = params;
+ if (!definition) throw new CodegenError("`{{component}}` requires a component", node);
+ if (rest.length)
+ throw new CodegenError("positional arguments to components are not supported yet", node);
+ body.lines.push(
+ `${this.rt("invokeDyn")}($_b, ${anchor}, () => ${this.expr(definition, body)}, ${this.named(hash, body) ?? "{}"}, null, null);`
+ );
+ return;
+ }
+ }
+ if (builtin === null && (params.length > 0 || hash.pairs.length > 0)) {
+ let isStatic = path.head.type === "VarHead" && path.tail.length === 0 && body.scope.lookup(path.head.name) === void 0;
+ let definition = this.expr(path, body);
+ body.lines.push(
+ `${this.rt("appendCall")}($_b, ${anchor}, ${isStatic ? definition : `() => ${definition}`}, ${isStatic}, ${this.positional(
+ params,
+ body
+ )}, ${this.named(hash, body)}, ${flags});`
+ );
+ return;
+ }
+ if (params.length === 0 && hash.pairs.length === 0 && builtin === null) {
+ if (path.head.type === "VarHead" && path.tail.length === 0 && body.scope.lookup(path.head.name) === void 0) {
+ flags |= 1;
+ }
+ }
+ body.lines.push(
+ `${this.rt("content")}($_b, ${anchor}, () => ${this.mustacheValue(node, body)}, ${flags});`
+ );
+ }
+ /**
+ * The value of `{{path params hash}}`.
+ */
+ mustacheValue(node, body) {
+ if (node.params.length === 0 && node.hash.pairs.length === 0) {
+ if (node.path.type === "PathExpression" && this.builtin(node.path, body.scope) === null) {
+ return this.expr(node.path, body);
+ }
+ if (node.path.type !== "PathExpression" && node.path.type !== "SubExpression") {
+ return this.expr(node.path, body);
+ }
+ }
+ return this.call(node.path, node.params, node.hash, node, body);
+ }
+ block(node, anchor, body) {
+ let { path, params, hash, program, inverse } = node;
+ let builtin = path.type === "PathExpression" ? this.builtin(path, body.scope) : null;
+ let { scope } = body;
+ switch (builtin) {
+ case "if":
+ case "unless": {
+ let [condition] = params;
+ if (!condition) throw new CodegenError(`\`${builtin}\` requires a condition`, node);
+ let test = `${this.rt("bool")}(${this.expr(condition, body)})`;
+ body.lines.push(
+ `${this.rt("when")}($_b, ${anchor}, () => ${builtin === "unless" ? `!${test}` : test}, ${this.blockFn(
+ program,
+ scope
+ )}, ${this.blockFn(inverse, scope)});`
+ );
+ return;
+ }
+ case "each": {
+ let [list] = params;
+ if (!list) throw new CodegenError("`each` requires a list", node);
+ let keyPair = hash.pairs.find((p) => p.key === "key");
+ body.lines.push(
+ `${this.rt("each")}($_b, ${anchor}, () => ${this.expr(list, body)}, ${keyPair ? this.expr(keyPair.value, body) : "null"}, ${this.blockFn(program, scope)}, ${this.blockFn(inverse, scope)});`
+ );
+ return;
+ }
+ case "each-in": {
+ let [object] = params;
+ if (!object) throw new CodegenError("`each-in` requires an object", node);
+ let item = this.blockFnWith(program.body, ["entry"], scope, (id, _i, inner) => {
+ let [k, v] = program.blockParams;
+ if (k) inner.bind(k, `${id}()[0]`);
+ if (v) inner.bind(v, `${id}()[1]`);
+ });
+ body.lines.push(
+ `${this.rt("each")}($_b, ${anchor}, () => ${this.rt("entries")}(${this.expr(
+ object,
+ body
+ )}), "0", ${item}, ${this.blockFn(inverse, scope)});`
+ );
+ return;
+ }
+ case "let": {
+ let inner = scope.child();
+ program.blockParams.forEach((name, i) => {
+ let value = params[i];
+ let id = this.uid("l");
+ body.lines.push(
+ `const ${id} = ${this.rt("memo")}(() => ${value ? this.expr(value, body) : "undefined"});`
+ );
+ inner.bind(name, `${id}()`);
+ });
+ body.lines.push(
+ `${this.rt("scope")}($_b, ${anchor}, ($_b) => {
+${this.body(program.body, inner)}
+});`
+ );
+ return;
+ }
+ case "in-element": {
+ let [destination] = params;
+ if (!destination) throw new CodegenError("`in-element` requires a destination", node);
+ let append = hash.pairs.some((p) => p.key === "insertBefore");
+ body.lines.push(
+ `${this.rt("inElement")}($_b, () => ${this.expr(destination, body)}, ${append}, ${this.blockFn(
+ program,
+ scope
+ )});`
+ );
+ return;
+ }
+ case "component": {
+ let [definition] = params;
+ if (!definition) throw new CodegenError("`{{#component}}` requires a component", node);
+ body.lines.push(
+ `${this.rt("invokeDyn")}($_b, ${anchor}, () => ${this.expr(definition, body)}, ${this.named(hash, body) ?? "{}"}, ${this.blocksObject(program, inverse, scope)}, null);`
+ );
+ return;
+ }
+ case null:
+ break;
+ default:
+ throw new CodegenError(
+ `\`{{#${builtin}}}\` is not supported by the codegen compiler yet`,
+ node
+ );
+ }
+ if (params.length > 0) {
+ throw new CodegenError("positional arguments to components are not supported yet", node);
+ }
+ this.invocation(
+ path,
+ anchor,
+ this.named(hash, body) ?? "{}",
+ this.blocksObject(program, inverse, scope),
+ "null",
+ body
+ );
+ }
+ blocksObject(program, inverse, scope) {
+ let entries = [`default: ${this.blockFn(program, scope)}`];
+ if (inverse) entries.push(`else: ${this.blockFn(inverse, scope)}`);
+ return `{ ${entries.join(", ")} }`;
+ }
+ componentElement(node, anchor, body) {
+ let named = [];
+ let attrs = [];
+ for (let attr of node.attributes) {
+ if (attr.name.startsWith("@")) {
+ named.push(`${key(attr.name.slice(1))}: () => ${this.attrValue(attr.value, body)}`);
+ } else {
+ attrs.push(attr);
+ }
+ }
+ let attrsFn = "null";
+ if (attrs.length > 0 || node.modifiers.length > 0) {
+ let inner = { lines: [], scope: body.scope };
+ for (let attr of attrs) {
+ if (attr.name === "...attributes") {
+ inner.lines.push(`${this.rt("splat")}($_b, $_el, $_ctx);`);
+ } else {
+ let value = attr.value.type === "TextNode" ? q(attr.value.chars) : `() => ${this.attrValue(attr.value, inner)}`;
+ inner.lines.push(`${this.rt("attr")}($_b, $_el, ${q(attr.name)}, ${value});`);
+ }
+ }
+ for (let modifier of node.modifiers) {
+ this.modifier(modifier, "$_el", inner);
+ }
+ attrsFn = `($_b, $_el) => {
+${inner.lines.join("\n")}
+}`;
+ }
+ let blocks = "null";
+ let namedBlocks = node.children.filter(
+ (c) => c.type === "ElementNode" && c.tag.startsWith(":")
+ );
+ if (namedBlocks.length > 0) {
+ blocks = `{ ${namedBlocks.map(
+ (b) => `${key(b.tag.slice(1))}: ${this.blockFnWith(
+ b.children,
+ b.params.map((p) => p.name),
+ body.scope
+ )}`
+ ).join(", ")} }`;
+ } else if (node.children.length > 0) {
+ blocks = `{ default: ${this.blockFnWith(
+ node.children,
+ node.params.map((p) => p.name),
+ body.scope
+ )} }`;
+ }
+ this.invocation(node.path, anchor, `{ ${named.join(", ")} }`, blocks, attrsFn, body);
+ }
+ invocation(path, anchor, named, blocks, attrs, body) {
+ let { head, tail } = path;
+ let isStatic = head.type === "VarHead" && tail.length === 0 && body.scope.lookup(head.name) === void 0 && this.options.isLexical(head.name);
+ if (isStatic) {
+ body.lines.push(
+ `${this.rt("invoke")}($_b, ${anchor}, ${this.expr(path, body)}, ${named}, ${blocks}, ${attrs});`
+ );
+ } else {
+ body.lines.push(
+ `${this.rt("invokeDyn")}($_b, ${anchor}, () => ${this.expr(path, body)}, ${named}, ${blocks}, ${attrs});`
+ );
+ }
+ }
+ /////////////////////////////////////////////////////////////////////////
+ // Expressions
+ /////////////////////////////////////////////////////////////////////////
+ /**
+ * Which built-in (keyword, or well-known import) does `path` refer to?
+ */
+ builtin(path, scope) {
+ if (path.type !== "PathExpression") return null;
+ let { head, tail } = path;
+ if (head.type !== "VarHead" || tail.length > 0) return null;
+ let { name } = head;
+ if (scope.lookup(name) !== void 0) return null;
+ if (KEYWORDS.has(name)) return name;
+ if (this.options.isLexical(name)) {
+ let info = this.options.importOf?.(name);
+ if (info && SPECIALIZED[info.module]?.has(info.name)) return info.name;
+ return null;
+ }
+ return name in AUTO_IMPORTED ? name : null;
+ }
+ expr(node, body) {
+ switch (node.type) {
+ case "StringLiteral":
+ return q(node.value);
+ case "NumberLiteral":
+ return String(node.value);
+ case "BooleanLiteral":
+ return String(node.value);
+ case "NullLiteral":
+ return "null";
+ case "UndefinedLiteral":
+ return "undefined";
+ case "SubExpression":
+ return this.call(node.path, node.params, node.hash, node, body);
+ case "PathExpression":
+ return this.path(node, body);
+ }
+ }
+ path(node, body) {
+ let { head, tail } = node;
+ let base;
+ switch (head.type) {
+ case "ThisHead":
+ base = "$_s";
+ break;
+ case "AtHead":
+ base = `$_a${prop(head.name.replace(/^@/u, ""))}`;
+ break;
+ case "VarHead": {
+ let local = body.scope.lookup(head.name);
+ if (local !== void 0) {
+ base = local;
+ } else if (this.options.isLexical(head.name)) {
+ base = head.name;
+ } else if (head.name in AUTO_IMPORTED) {
+ base = this.options.importBinding(AUTO_IMPORTED[head.name], head.name);
+ } else if (this.options.isGlobal?.(head.name)) {
+ base = head.name;
+ } else {
+ throw new CodegenError(
+ `Attempted to use \`${head.name}\`, but it is not in scope. In strict mode, values must be imported or defined in JavaScript`,
+ node
+ );
+ }
+ break;
+ }
+ }
+ return tail.length > 0 ? `${this.rt("get")}(${[base, ...tail.map(q)].join(", ")})` : base;
+ }
+ positional(params, body) {
+ if (params.length === 0) return "null";
+ return `[${params.map((p) => `() => ${this.expr(p, body)}`).join(", ")}]`;
+ }
+ named(hash, body) {
+ if (hash.pairs.length === 0) return null;
+ return `{ ${hash.pairs.map((p) => `${key(p.key)}: () => ${this.expr(p.value, body)}`).join(", ")} }`;
+ }
+ staticString(node, what) {
+ if (node.type !== "StringLiteral")
+ throw new CodegenError(`${what} must be a string literal`, node);
+ return node.value;
+ }
+ /**
+ * `(path params hash)` (or `{{path params hash}}`).
+ */
+ call(path, params, hash, node, body) {
+ let builtin = this.builtin(path, body.scope);
+ let arg = (i) => {
+ let param = params[i];
+ return param ? this.expr(param, body) : "undefined";
+ };
+ let all = () => params.map((p) => this.expr(p, body));
+ switch (builtin) {
+ case null:
+ break;
+ case "if":
+ return `(${this.rt("bool")}(${arg(0)}) ? ${arg(1)} : ${arg(2)})`;
+ case "unless":
+ return `(${this.rt("bool")}(${arg(0)}) ? ${arg(2)} : ${arg(1)})`;
+ case "concat":
+ return `(${['""', ...all().map((p) => `${this.rt("str")}(${p})`)].join(" + ")})`;
+ case "fn":
+ return `((...$_r) => (${arg(0)})(${[...all().slice(1), "...$_r"].join(", ")}))`;
+ case "hash":
+ return `({ ${hash.pairs.map((p) => `get ${key(p.key)}() { return ${this.expr(p.value, body)}; }`).join(", ")} })`;
+ case "array":
+ return `[${all().join(", ")}]`;
+ case "get":
+ return `${this.rt("getPath")}(${arg(0)}, ${arg(1)})`;
+ case "eq":
+ case "neq":
+ case "gt":
+ case "gte":
+ case "lt":
+ case "lte":
+ return `(${arg(0)} ${COMPARISONS[builtin]} ${arg(1)})`;
+ case "not":
+ return `!${this.rt("bool")}(${arg(0)})`;
+ case "and":
+ case "or":
+ return `${this.rt(builtin)}(${all().map((p) => `() => ${p}`).join(", ")})`;
+ case "has-block":
+ case "has-block-params":
+ return `${this.rt(builtin === "has-block" ? "hasBlock" : "hasBlockParams")}($_ctx${params.length ? `, ${arg(0)}` : ""})`;
+ case "component":
+ return `${this.rt("curry")}(${arg(0)}, ${this.named(hash, body) ?? "null"})`;
+ case "log":
+ return `${this.rt("log")}(${all().join(", ")})`;
+ case "element":
+ return `${this.rt("element")}(${arg(0)})`;
+ default:
+ throw new CodegenError(`\`${builtin}\` is not supported by the codegen compiler yet`, node);
+ }
+ let positional = this.positional(params, body);
+ let named = this.named(hash, body);
+ let id = this.uid("h");
+ let isStatic = path.type === "PathExpression" && path.head.type === "VarHead" && path.tail.length === 0 && body.scope.lookup(path.head.name) === void 0;
+ if (isStatic) {
+ body.lines.push(
+ `const ${id} = ${this.rt("helper")}($_b, ${this.expr(path, body)}, ${positional}, ${named});`
+ );
+ } else {
+ body.lines.push(
+ `const ${id} = ${this.rt("helperDyn")}($_b, () => ${this.expr(path, body)}, ${positional}, ${named});`
+ );
+ }
+ return `${id}()`;
+ }
+};
+function staticText(node) {
+ if (node.trusting || node.params.length > 0 || node.hash.pairs.length > 0) return null;
+ let { path } = node;
+ switch (path.type) {
+ case "StringLiteral":
+ return path.value;
+ case "NumberLiteral":
+ case "BooleanLiteral":
+ return String(path.value);
+ case "NullLiteral":
+ case "UndefinedLiteral":
+ return "";
+ default:
+ return null;
+ }
+}
+function generate(ast, options) {
+ let generator = new Generator(options);
+ let expression = generator.template(ast);
+ return { hoisted: generator.hoisted, expression };
+}
+
+// packages/@glimmer/compiler/lib/codegen/babel-plugin.ts
+var VM_RUNTIME = /* @__PURE__ */ new Set(["outlet", "setTemplate", "template"]);
+var TEMPLATE_MODULES = /* @__PURE__ */ new Set(["@ember/template-compiler", "@ember/template-compiler/runtime"]);
+function importedName(specifier) {
+ return specifier.imported.type === "StringLiteral" ? specifier.imported.value : specifier.imported.name;
+}
+function codegenBabelPlugin(babel, options) {
+ let t = babel.types;
+ let runtimeModule = options.runtimeModule ?? "@glimmer/dom";
+ let vmInterop = options.vmInterop ?? false;
+ let moduleFor = (name) => vmInterop && VM_RUNTIME.has(name) ? `${runtimeModule}/vm` : runtimeModule;
+ let compileTemplate = (path, state) => {
+ let callee = path.get("callee");
+ if (!callee.isIdentifier()) return;
+ let binding = path.scope.getBinding(callee.node.name);
+ if (!binding || binding.kind !== "module" || !binding.path.isImportSpecifier()) return;
+ if (!TEMPLATE_MODULES.has(binding.path.parent.source.value)) return;
+ if (importedName(binding.path.node) !== "template") return;
+ let [source, templateOptions] = path.node.arguments;
+ let text;
+ if (t.isTemplateLiteral(source) && source.expressions.length === 0) {
+ text = source.quasis[0].value.cooked;
+ } else if (t.isStringLiteral(source)) {
+ text = source.value;
+ } else {
+ throw path.buildCodeFrameError("template() must be called with a static string");
+ }
+ let component = null;
+ if (templateOptions && t.isObjectExpression(templateOptions)) {
+ for (let property of templateOptions.properties) {
+ if (t.isObjectProperty(property) && t.isIdentifier(property.key, { name: "component" })) {
+ component = property.value;
+ }
+ }
+ }
+ let program = path.scope.getProgramParent();
+ let { imports } = state.codegen;
+ let local = (module, name) => {
+ let names = imports.get(module);
+ if (!names) imports.set(module, names = /* @__PURE__ */ new Map());
+ let existing = names.get(name);
+ if (existing) return existing;
+ let id = program.generateUid(name.replace(/\W/gu, "_"));
+ names.set(name, id);
+ return id;
+ };
+ let importOf = (name) => {
+ let found = path.scope.getBinding(name);
+ if (!found || found.kind !== "module") return void 0;
+ let module = found.path.parent.source.value;
+ if (found.path.isImportSpecifier()) return { module, name: importedName(found.path.node) };
+ if (found.path.isImportDefaultSpecifier()) return { module, name: "default" };
+ return void 0;
+ };
+ let ast = options.preprocess(text, { strictMode: true });
+ let { hoisted, expression } = generate(ast, {
+ isLexical: (name) => Boolean(path.scope.getBinding(name)),
+ importOf,
+ runtime: (name) => local(moduleFor(name), name),
+ importBinding: local,
+ prefix: `${state.codegen.templates++}_`,
+ vmInterop,
+ ...options.isGlobal ? { isGlobal: options.isGlobal } : {}
+ });
+ state.codegen.hoisted.push(...hoisted);
+ let replacement = babel.template.expression.ast(expression, { placeholderPattern: false });
+ if (component) {
+ replacement = t.callExpression(t.identifier(local(moduleFor("setTemplate"), "setTemplate")), [
+ component,
+ replacement
+ ]);
+ }
+ path.replaceWith(replacement);
+ };
+ let addImports = (path, state) => {
+ let { imports, hoisted } = state.codegen;
+ if (imports.size === 0) return;
+ let declarations = [...imports].map(
+ ([module, names]) => t.importDeclaration(
+ [...names].map(
+ ([name, local]) => t.importSpecifier(t.identifier(local), t.identifier(name))
+ ),
+ t.stringLiteral(module)
+ )
+ );
+ let statements = hoisted.flatMap(
+ (code) => babel.template.statements.ast(code, { placeholderPattern: false })
+ );
+ path.unshiftContainer("body", [...declarations, ...statements]);
+ path.scope.crawl();
+ for (let statement of path.get("body")) {
+ if (!statement.isImportDeclaration()) continue;
+ if (!TEMPLATE_MODULES.has(statement.node.source.value)) continue;
+ for (let specifier of statement.get("specifiers")) {
+ let binding = path.scope.getBinding(specifier.node.local.name);
+ if (binding && !binding.referenced) specifier.remove();
+ }
+ if (statement.node.specifiers.length === 0) statement.remove();
+ }
+ };
+ return {
+ name: "glimmer-codegen",
+ // Everything happens in `pre` (rather than in a visitor), so that this
+ // plugin compiles `template()` before other template plugins see it
+ // (babel-plugin-ember-template-compilation also does its work in `pre`).
+ // List this plugin first.
+ pre(file) {
+ this.codegen = { imports: /* @__PURE__ */ new Map(), hoisted: [], templates: 0 };
+ file.path.traverse(
+ {
+ CallExpression: (call) => {
+ compileTemplate(call, this);
+ }
+ },
+ this
+ );
+ addImports(file.path, this);
+ },
+ visitor: {}
+ };
+}
+
+// packages/@ember/template-compiler/lib/plugins/allowed-globals.ts
+var ALLOWED_GLOBALS = /* @__PURE__ */ new Set([
+ // ////////////////
+ // namespaces
+ // ////////////////
+ // TC39
+ "globalThis",
+ "Atomics",
+ "JSON",
+ "Math",
+ "Reflect",
+ // WHATWG
+ "localStorage",
+ "sessionStorage",
+ "URL",
+ // ////////////////
+ // functions / utilities
+ // ////////////////
+ // TC39
+ "isNaN",
+ "isFinite",
+ "parseInt",
+ "parseFloat",
+ "decodeURI",
+ "decodeURIComponent",
+ "encodeURI",
+ "encodeURIComponent",
+ // WHATWG
+ "postMessage",
+ "structuredClone",
+ // ////////////////
+ // new-less Constructors (still functions)
+ // ////////////////
+ // TC39
+ "Array",
+ // different behavior from (array)
+ "BigInt",
+ "Boolean",
+ "Date",
+ "Number",
+ "Object",
+ // different behavior from (hash)
+ "String",
+ // ////////////////
+ // Values
+ // ////////////////
+ // TC39
+ "Infinity",
+ "NaN",
+ // WHATWG
+ "isSecureContext"
+]);
+
+//
+var isAllowedGlobal = (name) => ALLOWED_GLOBALS.has(name);
+export {
+ CodegenError,
+ codegenBabelPlugin,
+ generate,
+ isAllowedGlobal
+};