diff --git a/package.json b/package.json index e59028634c6..e5701d3fc08 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "keywords": [ "ember-addon" ], + "sideEffects": false, "exports": { "./*": { "development": "./dist/dev/packages/*", diff --git a/packages/@ember/-internals/deprecations/index.ts b/packages/@ember/-internals/deprecations/index.ts index b68b84b616e..44124f88990 100644 --- a/packages/@ember/-internals/deprecations/index.ts +++ b/packages/@ember/-internals/deprecations/index.ts @@ -1,5 +1,5 @@ import type { DeprecationOptions } from '@ember/debug'; -import { ENV } from '@ember/-internals/environment'; +import { ENV } from '@ember/-internals/environment/lib/env'; import { VERSION } from '@ember/version'; import { deprecate, assert } from '@ember/debug'; import { dasherize } from '../string/index'; diff --git a/packages/@ember/-internals/environment/index.ts b/packages/@ember/-internals/environment/index.ts index 27e1530a895..cb100660e78 100644 --- a/packages/@ember/-internals/environment/index.ts +++ b/packages/@ember/-internals/environment/index.ts @@ -1,2 +1,2 @@ -export * from './lib/context'; -export * from './lib/env'; +export { context, getLookup, setLookup, type GlobalContext } from './lib/context'; +export { ENV, getENV } from './lib/env'; diff --git a/packages/@ember/-internals/glimmer/index.ts b/packages/@ember/-internals/glimmer/index.ts index 4cf8bd70367..dc219ac8373 100644 --- a/packages/@ember/-internals/glimmer/index.ts +++ b/packages/@ember/-internals/glimmer/index.ts @@ -467,13 +467,8 @@ export { htmlSafe, isHTMLSafe, } from './lib/utils/string'; -export { - Renderer, - _resetRenderers, - renderSettled, - renderComponent, - type View, -} from './lib/renderer'; +export { Renderer, type View } from './lib/renderer'; +export { _resetRenderers, renderSettled, renderComponent } from './lib/render-component'; export { getTemplate, setTemplate, diff --git a/packages/@ember/-internals/glimmer/lib/component.ts b/packages/@ember/-internals/glimmer/lib/component.ts index d66842da331..e58353640d0 100644 --- a/packages/@ember/-internals/glimmer/lib/component.ts +++ b/packages/@ember/-internals/glimmer/lib/component.ts @@ -1,4 +1,4 @@ -import type { View } from '@ember/-internals/glimmer'; +import type { View } from './renderer'; import { descriptorForProperty, get, diff --git a/packages/@ember/-internals/glimmer/lib/environment.ts b/packages/@ember/-internals/glimmer/lib/environment.ts index 5abd58c2bc6..a97928f0e23 100644 --- a/packages/@ember/-internals/glimmer/lib/environment.ts +++ b/packages/@ember/-internals/glimmer/lib/environment.ts @@ -1,96 +1,41 @@ -import { ENV } from '@ember/-internals/environment'; +/** + * Full Ember-specific global context registration. + * + * This module registers the heavy Ember implementations (metal get/set, + * EmberArray iteration, proxy truthiness, HTMLSafe style checking, etc.) + * into the lightweight global context set up by glimmer-global-context.ts. + * + * It is imported by setup-registry.ts (full Ember app boot path) but NOT + * by the standalone renderComponent path, enabling tree-shaking of metal, + * @ember/array, and @ember/-internals/views for apps that only use + * renderComponent. + */ import { get, set, _getProp, _setProp } from '@ember/-internals/metal'; -import type { InternalOwner } from '@ember/-internals/owner'; import { getDebugName } from '@ember/-internals/utils'; import { constructStyleDeprecationMessage } from '@ember/-internals/views'; -import { assert, deprecate, warn } from '@ember/debug'; -import type { DeprecationOptions } from '@ember/debug'; -import { schedule, _backburner } from '@ember/runloop'; import { DEBUG } from '@glimmer/env'; -import setGlobalContext from '@glimmer/global-context'; -import type { EnvironmentDelegate } from '@glimmer/runtime'; import { debug } from '@glimmer/validator'; import toIterator from './utils/iterator'; import { isHTMLSafe } from './utils/string'; import toBool from './utils/to-bool'; -/////////// +import { flushAsyncObservers } from '@ember/-internals/metal'; +import { registerFlushAsyncObservers } from '@ember/runloop'; +import { registerEmberGlobalContextImplementations } from './glimmer-global-context'; -// Setup global context - -setGlobalContext({ - FEATURES: { - DEFAULT_HELPER_MANAGER: true, - }, - - scheduleRevalidate() { - _backburner.ensureInstance(); - }, +registerFlushAsyncObservers(flushAsyncObservers); +registerEmberGlobalContextImplementations({ + _getProp, + _setProp, + get, + set, toBool, toIterator, - - getProp: _getProp, - setProp: _setProp, - getPath: get, - setPath: set, - - scheduleDestroy(destroyable, destructor) { - schedule('actions', null, destructor, destroyable); - }, - - scheduleDestroyed(finalizeDestructor) { - schedule('destroy', null, finalizeDestructor); - }, - - warnIfStyleNotTrusted(value: unknown) { - warn( - constructStyleDeprecationMessage(String(value)), - (() => { - if (value === null || value === undefined || isHTMLSafe(value)) { - return true; - } - return false; - })(), - { id: 'ember-htmlbars.style-xss-warning' } - ); - }, - - assert(test: unknown, msg: string, options?: { id: string }) { - if (DEBUG) { - let id = options?.id; - - let override = VM_ASSERTION_OVERRIDES.filter((o) => o.id === id)[0]; - - assert(override?.message ?? msg, test); - } - }, - - deprecate(msg: string, test: unknown, options: { id: string }) { - if (DEBUG) { - let { id } = options; - - if (id === 'argument-less-helper-paren-less-invocation') { - throw new Error( - `A resolved helper cannot be passed as a named argument as the syntax is ` + - `ambiguously a pass-by-reference or invocation. Use the ` + - `\`{{helper 'foo-helper}}\` helper to pass by reference or explicitly ` + - `invoke the helper with parens: \`{{(fooHelper)}}\`.` - ); - } - - let override = VM_DEPRECATION_OVERRIDES.filter((o) => o.id === id)[0]; - - if (!override) throw new Error(`deprecation override for ${id} not found`); - - // allow deprecations to be disabled in the VM_DEPRECATION_OVERRIDES array below - if (!override.disabled) { - deprecate(override.message ?? msg, Boolean(test), override); - } - } - }, + isHTMLSafe, }); +// Override the debug tracking message with the richer getDebugName if (DEBUG) { debug?.setTrackingTransactionEnv?.({ debugMessage(obj, keyName) { @@ -103,38 +48,5 @@ if (DEBUG) { }); } -/////////// - -// VM Assertion/Deprecation overrides - -const VM_DEPRECATION_OVERRIDES: (DeprecationOptions & { - disabled?: boolean; - message?: string; -})[] = [ - { - id: 'setting-on-hash', - until: '4.4.0', - for: 'ember-source', - since: { - available: '3.28.0', - enabled: '3.28.0', - }, - }, -]; - -const VM_ASSERTION_OVERRIDES: { id: string; message: string }[] = []; - -/////////// - -// Define environment delegate - -export class EmberEnvironmentDelegate implements EnvironmentDelegate { - public enableDebugTooling: boolean = ENV._DEBUG_RENDER_TREE; - - constructor( - public owner: InternalOwner, - public isInteractive: boolean - ) {} - - onTransactionCommit(): void {} -} +// Re-export warnIfStyleNotTrusted using the full implementation +export { constructStyleDeprecationMessage }; diff --git a/packages/@ember/-internals/glimmer/lib/glimmer-global-context.ts b/packages/@ember/-internals/glimmer/lib/glimmer-global-context.ts new file mode 100644 index 00000000000..3acc355d7fe --- /dev/null +++ b/packages/@ember/-internals/glimmer/lib/glimmer-global-context.ts @@ -0,0 +1,154 @@ +/** + * Lightweight Glimmer VM global context setup. + * + * The heavy Ember-specific implementations (metal get/set, EmberArray iteration, + * proxy truthiness, HTMLSafe style checking) are registered lazily by + * environment.ts during full Ember app boot. For standalone renderComponent + * usage, the simple fallbacks here are sufficient. + */ +import { assert, deprecate, warn } from '@ember/debug'; +import type { DeprecationOptions } from '@ember/debug'; +import { schedule, _backburner } from '@ember/runloop'; +import { DEBUG } from '@glimmer/env'; +import setGlobalContext from '@glimmer/global-context'; +import { debug } from '@glimmer/validator'; + +/////////// + +// Lazy integration for heavy Ember-specific implementations. +// Simple fallbacks are used until registerEmberGlobalContextImplementations() +// is called by environment.ts (only loaded in full Ember apps). + +let _getProp: (obj: object, key: string) => unknown = (obj, key) => + (obj as Record)[key]; +let _setProp: (obj: object, key: string, value: unknown) => void = (obj, key, value) => { + (obj as Record)[key] = value; +}; +let _getPath: (obj: object, key: string) => unknown = (obj, key) => + (obj as Record)[key]; +let _setPath: (obj: object, key: string, value: unknown) => unknown = (obj, key, value) => { + (obj as Record)[key] = value; + return value; +}; +let _toBool: (value: unknown) => boolean = Boolean; +let _toIterator: (value: unknown) => any = () => null; +let _isHTMLSafe: (value: unknown) => boolean = () => false; + +export function registerEmberGlobalContextImplementations(impls: { + _getProp: typeof _getProp; + _setProp: typeof _setProp; + get: typeof _getPath; + set: typeof _setPath; + toBool: typeof _toBool; + toIterator: typeof _toIterator; + isHTMLSafe: typeof _isHTMLSafe; +}) { + _getProp = impls._getProp; + _setProp = impls._setProp; + _getPath = impls.get; + _setPath = impls.set; + _toBool = impls.toBool; + _toIterator = impls.toIterator; + _isHTMLSafe = impls.isHTMLSafe; +} + +/////////// + +const VM_DEPRECATION_OVERRIDES: (DeprecationOptions & { + disabled?: boolean; + message?: string; +})[] = [ + { + id: 'setting-on-hash', + until: '4.4.0', + for: 'ember-source', + since: { + available: '3.28.0', + enabled: '3.28.0', + }, + }, +]; + +const VM_ASSERTION_OVERRIDES: { id: string; message: string }[] = []; + +/////////// + +setGlobalContext({ + FEATURES: { + DEFAULT_HELPER_MANAGER: true, + }, + + scheduleRevalidate() { + _backburner.ensureInstance(); + }, + + toBool: (value: unknown) => _toBool(value), + toIterator: (value: unknown) => _toIterator(value), + + getProp: (obj: object, key: string) => _getProp(obj, key), + setProp: (obj: object, key: string, value: unknown) => _setProp(obj, key, value), + getPath: (obj: object, key: string) => _getPath(obj, key), + setPath: (obj: object, key: string, value: unknown) => _setPath(obj, key, value), + + scheduleDestroy(destroyable, destructor) { + schedule('actions', null, destructor, destroyable); + }, + + scheduleDestroyed(finalizeDestructor) { + schedule('destroy', null, finalizeDestructor); + }, + + warnIfStyleNotTrusted(value: unknown) { + warn( + `Binding style attributes may introduce cross-site scripting vulnerabilities; please ensure that the \`${String(value)}\` string is trusted.`, + (() => { + if (value === null || value === undefined || _isHTMLSafe(value)) { + return true; + } + return false; + })(), + { id: 'ember-htmlbars.style-xss-warning' } + ); + }, + + assert(test: unknown, msg: string, options?: { id: string }) { + if (DEBUG) { + let id = options?.id; + let override = VM_ASSERTION_OVERRIDES.filter((o) => o.id === id)[0]; + assert(override?.message ?? msg, test); + } + }, + + deprecate(msg: string, test: unknown, options: { id: string }) { + if (DEBUG) { + let { id } = options; + + if (id === 'argument-less-helper-paren-less-invocation') { + throw new Error( + `A resolved helper cannot be passed as a named argument as the syntax is ` + + `ambiguously a pass-by-reference or invocation. Use the ` + + `\`{{helper 'foo-helper}}\` helper to pass by reference or explicitly ` + + `invoke the helper with parens: \`{{(fooHelper)}}\`.` + ); + } + + let override = VM_DEPRECATION_OVERRIDES.filter((o) => o.id === id)[0]; + + if (!override) throw new Error(`deprecation override for ${id} not found`); + + if (!override.disabled) { + deprecate(override.message ?? msg, Boolean(test), override); + } + } + }, +}); + +if (DEBUG) { + debug?.setTrackingTransactionEnv?.({ + debugMessage(obj, keyName) { + let dirtyString = keyName ? `\`${keyName}\` on \`${String(obj)}\`` : `\`${String(obj)}\``; + + return `You attempted to update ${dirtyString}, but it had already been used previously in the same computation. Attempting to update a value after using it in a computation can cause logical errors, infinite revalidation bugs, and performance issues, and is not supported.`; + }, + }); +} diff --git a/packages/@ember/-internals/glimmer/lib/render-component.ts b/packages/@ember/-internals/glimmer/lib/render-component.ts new file mode 100644 index 00000000000..12a8a7286c3 --- /dev/null +++ b/packages/@ember/-internals/glimmer/lib/render-component.ts @@ -0,0 +1,669 @@ +import { ENV } from '@ember/-internals/environment'; +import { assert } from '@ember/debug'; +import { _backburner, _getCurrentRunLoop } from '@ember/runloop'; +import { + associateDestroyableChild, + destroy, + isDestroyed, + isDestroying, + registerDestructor, +} from '@glimmer/destroyable'; +import { DEBUG } from '@glimmer/env'; +import type { + Bounds, + Cursor, + DebugRenderTree, + Environment, + RenderResult as GlimmerRenderResult, + EvaluationContext, + TreeBuilder, + ClassicResolver, +} from '@glimmer/interfaces'; + +import { artifacts, RuntimeOpImpl } from '@glimmer/program'; +import { + clientBuilder, + inTransaction, + renderComponent as glimmerRenderComponent, + runtimeOptions, +} from '@glimmer/runtime'; +import type { EnvironmentDelegate } from '@glimmer/runtime'; +import { CURRENT_TAG, validateTag, valueForTag } from '@glimmer/validator'; +import type { SimpleDocument, SimpleElement } from '@simple-dom/interface'; +import RSVP from 'rsvp'; +import { hasDOM } from '../../browser-environment'; +import { EvaluationContextImpl } from '@glimmer/opcode-compiler'; + +// Side-effect: sets up the lightweight Glimmer global context +import './glimmer-global-context'; + +export type IBuilder = (env: Environment, cursor: Cursor) => TreeBuilder; + +export interface View { + parentView: unknown; + renderer: unknown; + tagName: string | null; + elementId: string | null; + isDestroying: boolean; + isDestroyed: boolean; + [key: symbol]: Bounds | null; +} + +const NO_OP = () => {}; + +// This wrapper logic prevents us from rerendering in case of a hard failure +// during render. This prevents infinite revalidation type loops from occuring, +// and ensures that errors are not swallowed by subsequent follow on failures. +export function errorLoopTransaction(fn: () => void) { + if (DEBUG) { + return () => { + let didError = true; + + try { + fn(); + didError = false; + } finally { + if (didError) { + // Noop the function so that we won't keep calling it and causing + // infinite looping failures; + fn = () => { + // eslint-disable-next-line no-console + console.warn( + 'Attempted to rerender, but the Ember application has had an unrecoverable error occur during render. You should reload the application after fixing the cause of the error.' + ); + }; + } + } + }; + } else { + return fn; + } +} + +export interface RootState { + readonly type: string; + destroyed: boolean; + result: GlimmerRenderResult | undefined; + isFor(component: unknown): boolean; + render(): void; + destroy(): void; +} + +export class ComponentRootState implements RootState { + readonly type = 'component'; + + #result: GlimmerRenderResult | undefined; + #render: () => void; + + constructor( + state: RendererState, + definition: object, + options: { into: Cursor; args?: Record } + ) { + this.#render = errorLoopTransaction(() => { + let iterator = glimmerRenderComponent( + state.context, + state.builder(state.env, options.into), + state.owner, + definition, + options?.args + ); + + let result = (this.#result = iterator.sync()); + + associateDestroyableChild(this, this.#result); + + this.#render = errorLoopTransaction(() => { + if (isDestroying(result) || isDestroyed(result)) return; + + return result.rerender({ + alwaysRevalidate: false, + }); + }); + }); + } + + isFor(_component: unknown): boolean { + return false; + } + + render(): void { + this.#render(); + } + + destroy(): void { + destroy(this); + } + + get destroyed(): boolean { + return isDestroyed(this); + } + + get result(): GlimmerRenderResult | undefined { + return this.#result; + } +} + +const renderers: BaseRenderer[] = []; + +export function _resetRenderers() { + renderers.length = 0; +} + +export function register(renderer: BaseRenderer): void { + assert('Cannot register the same renderer twice', renderers.indexOf(renderer) === -1); + renderers.push(renderer); +} + +export function deregister(renderer: BaseRenderer): void { + let index = renderers.indexOf(renderer); + assert('Cannot deregister unknown unregistered renderer', index !== -1); + renderers.splice(index, 1); +} + +function loopBegin(): void { + for (let renderer of renderers) { + renderer.rerender(); + } +} + +let renderSettledDeferred: RSVP.Deferred | null = null; +/* + Returns a promise which will resolve when rendering has settled. Settled in + this context is defined as when all of the tags in use are "current" (e.g. + `renderers.every(r => r._isValid())`). When this is checked at the _end_ of + the run loop, this essentially guarantees that all rendering is completed. + + @method renderSettled + @returns {Promise} a promise which fulfills when rendering has settled +*/ +export function renderSettled() { + if (renderSettledDeferred === null) { + renderSettledDeferred = RSVP.defer(); + // if there is no current runloop, the promise created above will not have + // a chance to resolve (because its resolved in backburner's "end" event) + if (!_getCurrentRunLoop()) { + // ensure a runloop has been kicked off + _backburner.schedule('actions', null, NO_OP); + } + } + + return renderSettledDeferred.promise; +} + +function resolveRenderPromise() { + if (renderSettledDeferred !== null) { + let resolve = renderSettledDeferred.resolve; + renderSettledDeferred = null; + + _backburner.join(null, resolve); + } +} + +let loops = 0; +function loopEnd() { + for (let renderer of renderers) { + if (!renderer.isValid()) { + if (loops > ENV._RERENDER_LOOP_LIMIT) { + loops = 0; + // TODO: do something better + renderer.destroy(); + throw new Error('infinite rendering invalidation detected'); + } + loops++; + return _backburner.join(null, NO_OP); + } + } + loops = 0; + resolveRenderPromise(); +} + +_backburner.on('begin', loopBegin); +_backburner.on('end', loopEnd); + +export interface ViewRegistry { + [viewId: string]: unknown; +} + +interface RendererData { + owner: object; + context: EvaluationContext; + builder: IBuilder; +} + +export class RendererState { + static create(data: RendererData, renderer: BaseRenderer): RendererState { + const state = new RendererState(data, renderer); + associateDestroyableChild(renderer, state); + return state; + } + + readonly #data: RendererData; + #lastRevision = -1; + #inRenderTransaction = false; + #destroyed = false; + #roots: RootState[] = []; + #removedRoots: RootState[] = []; + + private constructor(data: RendererData, renderer: BaseRenderer) { + this.#data = data; + + registerDestructor(this, () => { + this.clearAllRoots(renderer); + }); + } + + get debug() { + return { + roots: this.#roots, + inRenderTransaction: this.#inRenderTransaction, + isInteractive: this.isInteractive, + }; + } + + get roots() { + return this.#roots; + } + + get owner(): object { + return this.#data.owner; + } + + get builder(): IBuilder { + return this.#data.builder; + } + + get context(): EvaluationContext { + return this.#data.context; + } + + get env(): Environment { + return this.context.env; + } + + get isInteractive(): boolean { + return this.#data.context.env.isInteractive; + } + + renderRoot(root: RootState, renderer: BaseRenderer): RootState { + let roots = this.#roots; + + roots.push(root); + associateDestroyableChild(this, root); + + if (roots.length === 1) { + register(renderer); + } + + this.#renderRootsTransaction(renderer); + + return root; + } + + #renderRootsTransaction(renderer: BaseRenderer): void { + if (this.#inRenderTransaction) { + // currently rendering roots, a new root was added and will + // be processed by the existing _renderRoots invocation + return; + } + + // used to prevent calling _renderRoots again (see above) + // while we are actively rendering roots + this.#inRenderTransaction = true; + + let completedWithoutError = false; + try { + this.renderRoots(renderer); + completedWithoutError = true; + } finally { + if (!completedWithoutError) { + this.#lastRevision = valueForTag(CURRENT_TAG); + } + this.#inRenderTransaction = false; + } + } + + renderRoots(renderer: BaseRenderer): void { + let roots = this.#roots; + let removedRoots = this.#removedRoots; + let initialRootsLength: number; + + do { + initialRootsLength = roots.length; + + inTransaction(this.context.env, () => { + // ensure that for the first iteration of the loop + // each root is processed + for (let i = 0; i < roots.length; i++) { + let root = roots[i]; + assert('has root', root); + + if (root.destroyed) { + // add to the list of roots to be removed + // they will be removed from `this._roots` later + removedRoots.push(root); + + // skip over roots that have been marked as destroyed + continue; + } + + // when processing non-initial reflush loops, + // do not process more roots than needed + if (i >= initialRootsLength) { + continue; + } + + root.render(); + } + + this.#lastRevision = valueForTag(CURRENT_TAG); + }); + } while (roots.length > initialRootsLength); + + // remove any roots that were destroyed during this transaction + while (removedRoots.length) { + let root = removedRoots.pop(); + + let rootIndex = roots.indexOf(root!); + roots.splice(rootIndex, 1); + } + + if (this.#roots.length === 0) { + deregister(renderer); + } + } + + scheduleRevalidate(renderer: BaseRenderer): void { + _backburner.scheduleOnce('render', this, this.revalidate, renderer); + } + + isValid(): boolean { + return ( + this.#destroyed || this.#roots.length === 0 || validateTag(CURRENT_TAG, this.#lastRevision) + ); + } + + revalidate(renderer: BaseRenderer): void { + if (this.isValid()) { + return; + } + this.#renderRootsTransaction(renderer); + } + + clearAllRoots(renderer: BaseRenderer): void { + let roots = this.#roots; + for (let root of roots) { + destroy(root); + } + + this.#removedRoots.length = 0; + this.#roots = []; + + // if roots were present before destroying + // deregister this renderer instance + if (roots.length) { + deregister(renderer); + } + } +} + +type IntoTarget = Cursor | Element | SimpleElement; + +/** + * The returned object from `renderComponent` + * @public + * @module @ember/renderer + */ +export interface RenderResult { + /** + * Destroys the render tree and removes all rendered content from the element rendered into + */ + destroy(): void; +} + +interface RenderCacheEntry { + result: RenderResult; + /** + * The GlimmerRenderResult from the last render. Used to get positional + * information (firstNode) when a re-render replaces the content, so + * that the new content is placed at the same DOM position. + */ + glimmerResult: GlimmerRenderResult | undefined; +} + +function intoTarget(into: IntoTarget): Cursor { + if ('element' in into) { + return into; + } else { + return { element: into as SimpleElement, nextSibling: null }; + } +} + +/** + * Render a component into a DOM element. + * + * @method renderComponent + * @static + * @for @ember/renderer + * @param {Object} component The component to render. + * @param {Object} options + * @param {Element} options.into Where to render the component in to. + * @param {Object} [options.owner] Optionally specify the owner to use. This will be used for injections, and overall cleanup. + * @param {Object} [options.env] Optional renderer configuration + * @param {Object} [options.args] Optionally pass args in to the component. These may be reactive as long as it is an object or object-like + * @public + */ +export function renderComponent( + /** + * The component definition to render. + * + * Any component that has had its manager registered is valid. + * For the component-types that ship with ember, manager registration + * does not need to be worried about. + */ + component: object, + { + owner = {}, + env, + into, + args, + }: { + /** + * The element to render the component in to. + */ + into: IntoTarget; + + /** + * Optional owner. Defaults to `{}`, can be any object, but will need to implement the [Owner](https://api.emberjs.com/ember/release/classes/Owner) API for components within this render tree to access services. + */ + owner?: object; + /** + * Optionally configure the rendering environment + */ + env?: { + /** + * When false, modifiers will not run. + */ + isInteractive?: boolean; + /** + * All other options are forwarded to the underlying renderer. + * (its API is currently private and out of scope for this RFC, + * so passing additional things here is also considered private API) + */ + [rendererOption: string]: unknown; + }; + + /** + * These args get passed to the rendered component + * + * If your args are reactive, re-rendering will happen automatically. + * + */ + args?: Record; + } +): RenderResult { + /** + * SAFETY: we should figure out what we need out of a `document` and narrow the API. + * this exercise should also end up beginning to define what we need for CLI rendering (or to other outputs) + */ + let document = + env && 'document' in env + ? (env?.['document'] as SimpleDocument | Document) + : globalThis.document; + + // Reuse renderer per owner to avoid creating multiple EvaluationContexts + // which can cause tracking frame conflicts + let renderer = RENDERER_CACHE.get(owner); + if (!renderer) { + renderer = BaseRenderer.strict(owner, document, { + ...env, + isInteractive: env?.isInteractive ?? true, + hasDOM: env && 'hasDOM' in env ? Boolean(env?.['hasDOM']) : true, + }); + RENDERER_CACHE.set(owner, renderer); + } + + /** + * Replace all contents, if we've rendered multiple times. + * + * https://github.com/emberjs/rfcs/pull/1099/files#diff-2b962105b9083ca84579cdc957f27f49407440f3c5078083fa369ec18cc46da8R365 + * + * We could later add an option to not do this behavior + * + * NOTE: destruction is async + */ + let existing = RENDER_CACHE.get(into); + existing?.result.destroy(); + /** + * We can only replace the inner HTML the first time. + * Because destruction is async, it won't be safe to + * do this again, and we'll have to rely on the above destroy. + */ + if (!existing && into instanceof Element) { + into.innerHTML = ''; + } + + /** + * If there's an existing render result with valid bounds, use its + * firstNode as the nextSibling so that new content is inserted at + * the same DOM position. This ensures stable ordering when multiple + * renderComponent calls target the same element and one is re-invoked + * (e.g., due to tracked dependency changes). + * + * The old content's DOM nodes are still present (destruction is async), + * so firstNode() is a valid position reference. The new content is placed + * BEFORE the old content. When the old content is eventually destroyed + * (async clear of bounds), the new content remains in the correct position. + */ + let renderTarget: IntoTarget = into; + if (existing?.glimmerResult) { + let parentElement = + into instanceof Element ? (into as unknown as SimpleElement) : (into as Cursor).element; + let firstNode = existing.glimmerResult.firstNode(); + renderTarget = { element: parentElement, nextSibling: firstNode }; + } + + let innerResult = renderer.render(component, { into: renderTarget, args }).result; + + if (innerResult) { + associateDestroyableChild(owner, innerResult); + } + + let result: RenderResult = { + destroy() { + if (innerResult) { + destroy(innerResult); + } + }, + }; + + RENDER_CACHE.set(into, { result, glimmerResult: innerResult }); + + return result; +} + +const RENDER_CACHE = new WeakMap(); +const RENDERER_CACHE = new WeakMap(); + +export class BaseRenderer { + static strict( + owner: object, + document: SimpleDocument | Document, + options: { isInteractive: boolean; hasDOM?: boolean } + ) { + return new BaseRenderer( + owner, + { hasDOM: hasDOM, ...options }, + document as SimpleDocument, + {} as ClassicResolver, + clientBuilder + ); + } + + readonly state: RendererState; + + constructor( + owner: object, + envOptions: { isInteractive: boolean; hasDOM: boolean }, + document: SimpleDocument, + resolver: ClassicResolver, + builder: IBuilder + ) { + let sharedArtifacts = artifacts(); + + let env: EnvironmentDelegate = { + isInteractive: envOptions.isInteractive, + enableDebugTooling: ENV._DEBUG_RENDER_TREE, + onTransactionCommit() {}, + }; + let options = runtimeOptions({ document }, env, sharedArtifacts, resolver); + let context = new EvaluationContextImpl( + sharedArtifacts, + (heap) => new RuntimeOpImpl(heap), + options + ); + + this.state = RendererState.create( + { + owner, + context, + builder, + }, + this + ); + } + + get debugRenderTree(): DebugRenderTree { + let { debugRenderTree } = this.state.env; + + assert( + 'Attempted to access the DebugRenderTree, but it did not exist. Is the Ember Inspector open?', + debugRenderTree + ); + + return debugRenderTree; + } + + isValid(): boolean { + return this.state.isValid(); + } + + destroy() { + destroy(this); + } + + render( + component: object, + options: { into: IntoTarget; args?: Record } + ): RootState { + const root = new ComponentRootState(this.state, component, { + args: options.args, + into: intoTarget(options.into), + }); + return this.state.renderRoot(root, this); + } + + rerender(): void { + this.state.scheduleRevalidate(this); + } +} diff --git a/packages/@ember/-internals/glimmer/lib/renderer.ts b/packages/@ember/-internals/glimmer/lib/renderer.ts index cfc74e0e423..0e232653c31 100644 --- a/packages/@ember/-internals/glimmer/lib/renderer.ts +++ b/packages/@ember/-internals/glimmer/lib/renderer.ts @@ -1,23 +1,17 @@ import { privatize as P } from '@ember/-internals/container'; -import { ENV } from '@ember/-internals/environment'; import type { InternalOwner } from '@ember/-internals/owner'; import { getOwner } from '@ember/-internals/owner'; import { guidFor } from '@ember/-internals/utils'; import { getViewElement, getViewId } from '@ember/-internals/views'; import { assert } from '@ember/debug'; -import { _backburner, _getCurrentRunLoop } from '@ember/runloop'; import { associateDestroyableChild, destroy, isDestroyed, isDestroying, - registerDestructor, } from '@glimmer/destroyable'; -import { DEBUG } from '@glimmer/env'; import type { Bounds, - Cursor, - DebugRenderTree, Environment, DynamicScope as GlimmerDynamicScope, RenderResult as GlimmerRenderResult, @@ -25,12 +19,10 @@ import type { TemplateFactory, EvaluationContext, CurriedComponent, - TreeBuilder, ClassicResolver, } from '@glimmer/interfaces'; import type { Nullable } from '@ember/-internals/utility-types'; -import { artifacts, RuntimeOpImpl } from '@glimmer/program'; import type { Reference } from '@glimmer/reference'; import { createConstRef, UNDEFINED_REFERENCE, valueForRef } from '@glimmer/reference'; import type { CurriedValue } from '@glimmer/runtime'; @@ -40,29 +32,41 @@ import { curry, EMPTY_POSITIONAL, inTransaction, - renderComponent as glimmerRenderComponent, renderMain, - runtimeOptions, } from '@glimmer/runtime'; import { dict } from '@glimmer/util'; import { unwrapTemplate } from './component-managers/unwrap-template'; -import { CURRENT_TAG, validateTag, valueForTag } from '@glimmer/validator'; import type { SimpleDocument, SimpleElement, SimpleNode } from '@simple-dom/interface'; -import RSVP from 'rsvp'; import type Component from './component'; import { hasDOM } from '../../browser-environment'; import type ClassicComponent from './component'; import { BOUNDS } from './component-managers/curly'; import { createRootOutlet } from './component-managers/outlet'; import { RootComponentDefinition } from './component-managers/root'; -import { EmberEnvironmentDelegate } from './environment'; import ResolverImpl from './resolver'; import type { OutletState } from './utils/outlet'; import OutletView from './views/outlet'; import { makeRouteTemplate } from './component-managers/route-template'; -import { EvaluationContextImpl } from '@glimmer/opcode-compiler'; -export type IBuilder = (env: Environment, cursor: Cursor) => TreeBuilder; +// Import shared pieces from render-component +import { + errorLoopTransaction, + BaseRenderer, + type IBuilder, + type RootState, + type ViewRegistry, +} from './render-component'; + +// Re-export for backward compatibility +export { + renderComponent, + renderSettled, + _resetRenderers, + BaseRenderer, + type RenderResult, + type IBuilder, + type ViewRegistry, +} from './render-component'; export interface View { parentView: Nullable; @@ -102,95 +106,7 @@ export class DynamicScope implements GlimmerDynamicScope { } } -const NO_OP = () => {}; - -// This wrapper logic prevents us from rerendering in case of a hard failure -// during render. This prevents infinite revalidation type loops from occuring, -// and ensures that errors are not swallowed by subsequent follow on failures. -function errorLoopTransaction(fn: () => void) { - if (DEBUG) { - return () => { - let didError = true; - - try { - fn(); - didError = false; - } finally { - if (didError) { - // Noop the function so that we won't keep calling it and causing - // infinite looping failures; - fn = () => { - // eslint-disable-next-line no-console - console.warn( - 'Attempted to rerender, but the Ember application has had an unrecoverable error occur during render. You should reload the application after fixing the cause of the error.' - ); - }; - } - } - }; - } else { - return fn; - } -} - -type RootState = ClassicRootState | ComponentRootState; - -class ComponentRootState { - readonly type = 'component'; - - #result: GlimmerRenderResult | undefined; - #render: () => void; - - constructor( - state: RendererState, - definition: object, - options: { into: Cursor; args?: Record } - ) { - this.#render = errorLoopTransaction(() => { - let iterator = glimmerRenderComponent( - state.context, - state.builder(state.env, options.into), - state.owner, - definition, - options?.args - ); - - let result = (this.#result = iterator.sync()); - - associateDestroyableChild(this, this.#result); - - this.#render = errorLoopTransaction(() => { - if (isDestroying(result) || isDestroyed(result)) return; - - return result.rerender({ - alwaysRevalidate: false, - }); - }); - }); - } - - isFor(_component: ClassicComponent): boolean { - return false; - } - - render(): void { - this.#render(); - } - - destroy(): void { - destroy(this); - } - - get destroyed(): boolean { - return isDestroyed(this); - } - - get result(): GlimmerRenderResult | undefined { - return this.#result; - } -} - -class ClassicRootState { +class ClassicRootState implements RootState { readonly type = 'classic'; public id: string; public result: GlimmerRenderResult | undefined; @@ -274,541 +190,8 @@ class ClassicRootState { } } -const renderers: BaseRenderer[] = []; - -export function _resetRenderers() { - renderers.length = 0; -} - -function register(renderer: BaseRenderer): void { - assert('Cannot register the same renderer twice', renderers.indexOf(renderer) === -1); - renderers.push(renderer); -} - -function deregister(renderer: BaseRenderer): void { - let index = renderers.indexOf(renderer); - assert('Cannot deregister unknown unregistered renderer', index !== -1); - renderers.splice(index, 1); -} - -function loopBegin(): void { - for (let renderer of renderers) { - renderer.rerender(); - } -} - -let renderSettledDeferred: RSVP.Deferred | null = null; -/* - Returns a promise which will resolve when rendering has settled. Settled in - this context is defined as when all of the tags in use are "current" (e.g. - `renderers.every(r => r._isValid())`). When this is checked at the _end_ of - the run loop, this essentially guarantees that all rendering is completed. - - @method renderSettled - @returns {Promise} a promise which fulfills when rendering has settled -*/ -export function renderSettled() { - if (renderSettledDeferred === null) { - renderSettledDeferred = RSVP.defer(); - // if there is no current runloop, the promise created above will not have - // a chance to resolve (because its resolved in backburner's "end" event) - if (!_getCurrentRunLoop()) { - // ensure a runloop has been kicked off - _backburner.schedule('actions', null, NO_OP); - } - } - - return renderSettledDeferred.promise; -} - -function resolveRenderPromise() { - if (renderSettledDeferred !== null) { - let resolve = renderSettledDeferred.resolve; - renderSettledDeferred = null; - - _backburner.join(null, resolve); - } -} - -let loops = 0; -function loopEnd() { - for (let renderer of renderers) { - if (!renderer.isValid()) { - if (loops > ENV._RERENDER_LOOP_LIMIT) { - loops = 0; - // TODO: do something better - renderer.destroy(); - throw new Error('infinite rendering invalidation detected'); - } - loops++; - return _backburner.join(null, NO_OP); - } - } - loops = 0; - resolveRenderPromise(); -} - -_backburner.on('begin', loopBegin); -_backburner.on('end', loopEnd); - -interface ViewRegistry { - [viewId: string]: unknown; -} - -type Resolver = ClassicResolver; - -interface RendererData { - owner: object; - context: EvaluationContext; - builder: IBuilder; -} - -class RendererState { - static create(data: RendererData, renderer: BaseRenderer): RendererState { - const state = new RendererState(data, renderer); - associateDestroyableChild(renderer, state); - return state; - } - - readonly #data: RendererData; - #lastRevision = -1; - #inRenderTransaction = false; - #destroyed = false; - #roots: RootState[] = []; - #removedRoots: RootState[] = []; - - private constructor(data: RendererData, renderer: BaseRenderer) { - this.#data = data; - - registerDestructor(this, () => { - this.clearAllRoots(renderer); - }); - } - - get debug() { - return { - roots: this.#roots, - inRenderTransaction: this.#inRenderTransaction, - isInteractive: this.isInteractive, - }; - } - - get roots() { - return this.#roots; - } - - get owner(): object { - return this.#data.owner; - } - - get builder(): IBuilder { - return this.#data.builder; - } - - get context(): EvaluationContext { - return this.#data.context; - } - - get env(): Environment { - return this.context.env; - } - - get isInteractive(): boolean { - return this.#data.context.env.isInteractive; - } - - renderRoot(root: RootState, renderer: BaseRenderer): RootState { - let roots = this.#roots; - - roots.push(root); - associateDestroyableChild(this, root); - - if (roots.length === 1) { - register(renderer); - } - - this.#renderRootsTransaction(renderer); - - return root; - } - - #renderRootsTransaction(renderer: BaseRenderer): void { - if (this.#inRenderTransaction) { - // currently rendering roots, a new root was added and will - // be processed by the existing _renderRoots invocation - return; - } - - // used to prevent calling _renderRoots again (see above) - // while we are actively rendering roots - this.#inRenderTransaction = true; - - let completedWithoutError = false; - try { - this.renderRoots(renderer); - completedWithoutError = true; - } finally { - if (!completedWithoutError) { - this.#lastRevision = valueForTag(CURRENT_TAG); - } - this.#inRenderTransaction = false; - } - } - - renderRoots(renderer: BaseRenderer): void { - let roots = this.#roots; - let removedRoots = this.#removedRoots; - let initialRootsLength: number; - - do { - initialRootsLength = roots.length; - - inTransaction(this.context.env, () => { - // ensure that for the first iteration of the loop - // each root is processed - for (let i = 0; i < roots.length; i++) { - let root = roots[i]; - assert('has root', root); - - if (root.destroyed) { - // add to the list of roots to be removed - // they will be removed from `this._roots` later - removedRoots.push(root); - - // skip over roots that have been marked as destroyed - continue; - } - - // when processing non-initial reflush loops, - // do not process more roots than needed - if (i >= initialRootsLength) { - continue; - } - - root.render(); - } - - this.#lastRevision = valueForTag(CURRENT_TAG); - }); - } while (roots.length > initialRootsLength); - - // remove any roots that were destroyed during this transaction - while (removedRoots.length) { - let root = removedRoots.pop(); - - let rootIndex = roots.indexOf(root!); - roots.splice(rootIndex, 1); - } - - if (this.#roots.length === 0) { - deregister(renderer); - } - } - - scheduleRevalidate(renderer: BaseRenderer): void { - _backburner.scheduleOnce('render', this, this.revalidate, renderer); - } - - isValid(): boolean { - return ( - this.#destroyed || this.#roots.length === 0 || validateTag(CURRENT_TAG, this.#lastRevision) - ); - } - - revalidate(renderer: BaseRenderer): void { - if (this.isValid()) { - return; - } - this.#renderRootsTransaction(renderer); - } - - clearAllRoots(renderer: BaseRenderer): void { - let roots = this.#roots; - for (let root of roots) { - destroy(root); - } - - this.#removedRoots.length = 0; - this.#roots = []; - - // if roots were present before destroying - // deregister this renderer instance - if (roots.length) { - deregister(renderer); - } - } -} - -type IntoTarget = Cursor | Element | SimpleElement; - -/** - * The returned object from `renderComponent` - * @public - * @module @ember/renderer - */ -export interface RenderResult { - /** - * Destroys the render tree and removes all rendered content from the element rendered into - */ - destroy(): void; -} - -interface RenderCacheEntry { - result: RenderResult; - /** - * The GlimmerRenderResult from the last render. Used to get positional - * information (firstNode) when a re-render replaces the content, so - * that the new content is placed at the same DOM position. - */ - glimmerResult: GlimmerRenderResult | undefined; -} - -function intoTarget(into: IntoTarget): Cursor { - if ('element' in into) { - return into; - } else { - return { element: into as SimpleElement, nextSibling: null }; - } -} - -/** - * Render a component into a DOM element. - * - * @method renderComponent - * @static - * @for @ember/renderer - * @param {Object} component The component to render. - * @param {Object} options - * @param {Element} options.into Where to render the component in to. - * @param {Object} [options.owner] Optionally specify the owner to use. This will be used for injections, and overall cleanup. - * @param {Object} [options.env] Optional renderer configuration - * @param {Object} [options.args] Optionally pass args in to the component. These may be reactive as long as it is an object or object-like - * @public - */ -export function renderComponent( - /** - * The component definition to render. - * - * Any component that has had its manager registered is valid. - * For the component-types that ship with ember, manager registration - * does not need to be worried about. - */ - component: object, - { - owner = {}, - env, - into, - args, - }: { - /** - * The element to render the component in to. - */ - into: IntoTarget; - - /** - * Optional owner. Defaults to `{}`, can be any object, but will need to implement the [Owner](https://api.emberjs.com/ember/release/classes/Owner) API for components within this render tree to access services. - */ - owner?: object; - /** - * Optionally configure the rendering environment - */ - env?: { - /** - * When false, modifiers will not run. - */ - isInteractive?: boolean; - /** - * All other options are forwarded to the underlying renderer. - * (its API is currently private and out of scope for this RFC, - * so passing additional things here is also considered private API) - */ - [rendererOption: string]: unknown; - }; - - /** - * These args get passed to the rendered component - * - * If your args are reactive, re-rendering will happen automatically. - * - */ - args?: Record; - } -): RenderResult { - /** - * SAFETY: we should figure out what we need out of a `document` and narrow the API. - * this exercise should also end up beginning to define what we need for CLI rendering (or to other outputs) - */ - let document = - env && 'document' in env - ? (env?.['document'] as SimpleDocument | Document) - : globalThis.document; - - // Reuse renderer per owner to avoid creating multiple EvaluationContexts - // which can cause tracking frame conflicts - let renderer = RENDERER_CACHE.get(owner); - if (!renderer) { - renderer = BaseRenderer.strict(owner, document, { - ...env, - isInteractive: env?.isInteractive ?? true, - hasDOM: env && 'hasDOM' in env ? Boolean(env?.['hasDOM']) : true, - }); - RENDERER_CACHE.set(owner, renderer); - } - - /** - * Replace all contents, if we've rendered multiple times. - * - * https://github.com/emberjs/rfcs/pull/1099/files#diff-2b962105b9083ca84579cdc957f27f49407440f3c5078083fa369ec18cc46da8R365 - * - * We could later add an option to not do this behavior - * - * NOTE: destruction is async - */ - let existing = RENDER_CACHE.get(into); - existing?.result.destroy(); - /** - * We can only replace the inner HTML the first time. - * Because destruction is async, it won't be safe to - * do this again, and we'll have to rely on the above destroy. - */ - if (!existing && into instanceof Element) { - into.innerHTML = ''; - } - - /** - * If there's an existing render result with valid bounds, use its - * firstNode as the nextSibling so that new content is inserted at - * the same DOM position. This ensures stable ordering when multiple - * renderComponent calls target the same element and one is re-invoked - * (e.g., due to tracked dependency changes). - * - * The old content's DOM nodes are still present (destruction is async), - * so firstNode() is a valid position reference. The new content is placed - * BEFORE the old content. When the old content is eventually destroyed - * (async clear of bounds), the new content remains in the correct position. - */ - let renderTarget: IntoTarget = into; - if (existing?.glimmerResult) { - let parentElement = - into instanceof Element ? (into as unknown as SimpleElement) : (into as Cursor).element; - let firstNode = existing.glimmerResult.firstNode(); - renderTarget = { element: parentElement, nextSibling: firstNode }; - } - - let innerResult = renderer.render(component, { into: renderTarget, args }).result; - - if (innerResult) { - associateDestroyableChild(owner, innerResult); - } - - let result: RenderResult = { - destroy() { - if (innerResult) { - destroy(innerResult); - } - }, - }; - - RENDER_CACHE.set(into, { result, glimmerResult: innerResult }); - - return result; -} - -const RENDER_CACHE = new WeakMap(); -const RENDERER_CACHE = new WeakMap(); - -class BaseRenderer { - static strict( - owner: object, - document: SimpleDocument | Document, - options: { isInteractive: boolean; hasDOM?: boolean } - ) { - return new BaseRenderer( - owner, - { hasDOM: hasDOM, ...options }, - document as SimpleDocument, - new ResolverImpl(), - clientBuilder - ); - } - - readonly state: RendererState; - - constructor( - owner: object, - envOptions: { isInteractive: boolean; hasDOM: boolean }, - document: SimpleDocument, - resolver: Resolver, - builder: IBuilder - ) { - let sharedArtifacts = artifacts(); - - /** - * SAFETY: are there consequences for being looser with *this* owner? - * the public API for `owner` is kinda `Partial` - * aka: implement only what you need. - * But for actual ember apps, you *need* to implement everything - * an app needs (which will actually change and become less over time) - */ - let env = new EmberEnvironmentDelegate(owner as InternalOwner, envOptions.isInteractive); - let options = runtimeOptions({ document }, env, sharedArtifacts, resolver); - let context = new EvaluationContextImpl( - sharedArtifacts, - (heap) => new RuntimeOpImpl(heap), - options - ); - - this.state = RendererState.create( - { - owner, - context, - builder, - }, - this - ); - } - - get debugRenderTree(): DebugRenderTree { - let { debugRenderTree } = this.state.env; - - assert( - 'Attempted to access the DebugRenderTree, but it did not exist. Is the Ember Inspector open?', - debugRenderTree - ); - - return debugRenderTree; - } - - isValid(): boolean { - return this.state.isValid(); - } - - destroy() { - destroy(this); - } - - render( - component: object, - options: { into: IntoTarget; args?: Record } - ): RootState { - const root = new ComponentRootState(this.state, component, { - args: options.args, - into: intoTarget(options.into), - }); - return this.state.renderRoot(root, this); - } - - rerender(): void { - this.state.scheduleRevalidate(this); - } - - // render(component: Component, options: { into: Cursor; args?: Record }): void { - // this.state.renderRoot(component); - // } -} - export class Renderer extends BaseRenderer { - static strict( + static override strict( owner: object, document: SimpleDocument | Document, options: { isInteractive: boolean; hasDOM?: boolean } @@ -817,7 +200,7 @@ export class Renderer extends BaseRenderer { owner, { hasDOM: hasDOM, ...options }, document as SimpleDocument, - new ResolverImpl(), + new ResolverImpl() as ClassicResolver, clientBuilder ); } @@ -848,7 +231,7 @@ export class Renderer extends BaseRenderer { builder = clientBuilder, resolver = new ResolverImpl() ) { - super(owner, env, document, resolver, builder); + super(owner, env, document, resolver as ClassicResolver, builder); this._rootTemplate = rootTemplate(owner); this._viewRegistry = viewRegistry || owner.lookup('-view-registry:main'); } diff --git a/packages/@ember/-internals/metal/lib/namespace_search.ts b/packages/@ember/-internals/metal/lib/namespace_search.ts index a9ab713bf9e..8b167c0efca 100644 --- a/packages/@ember/-internals/metal/lib/namespace_search.ts +++ b/packages/@ember/-internals/metal/lib/namespace_search.ts @@ -1,4 +1,4 @@ -import { context } from '@ember/-internals/environment'; +import { context } from '@ember/-internals/environment/lib/context'; import { getName, setName } from '@ember/-internals/utils'; const hasOwnProperty = Object.prototype.hasOwnProperty; diff --git a/packages/@ember/-internals/metal/lib/observer.ts b/packages/@ember/-internals/metal/lib/observer.ts index 4154e3441a8..920fade1b30 100644 --- a/packages/@ember/-internals/metal/lib/observer.ts +++ b/packages/@ember/-internals/metal/lib/observer.ts @@ -1,4 +1,4 @@ -import { ENV } from '@ember/-internals/environment'; +import { ENV } from '@ember/-internals/environment/lib/env'; import { peekMeta } from '@ember/-internals/meta'; import type { schedule } from '@ember/runloop'; import { registerDestructor } from '@glimmer/destroyable'; diff --git a/packages/@ember/-internals/package.json b/packages/@ember/-internals/package.json index cc947bbb6b5..2b14326c0c8 100644 --- a/packages/@ember/-internals/package.json +++ b/packages/@ember/-internals/package.json @@ -11,7 +11,26 @@ "./environment": "./environment/index.ts", "./error-handling": "./error-handling/index.ts", "./glimmer": "./glimmer/index.ts", + "./glimmer/lib/component": "./glimmer/lib/component.ts", + "./glimmer/lib/components/input": "./glimmer/lib/components/input.ts", "./glimmer/lib/components/internal": "./glimmer/lib/components/internal.ts", + "./glimmer/lib/components/link-to": "./glimmer/lib/components/link-to.ts", + "./glimmer/lib/components/textarea": "./glimmer/lib/components/textarea.ts", + "./glimmer/lib/dom": "./glimmer/lib/dom.ts", + "./glimmer/lib/helper": "./glimmer/lib/helper.ts", + "./glimmer/lib/helpers/unique-id": "./glimmer/lib/helpers/unique-id.ts", + "./glimmer/lib/render-component": "./glimmer/lib/render-component.ts", + "./glimmer/lib/renderer": "./glimmer/lib/renderer.ts", + "./glimmer/lib/setup-registry": "./glimmer/lib/setup-registry.ts", + "./glimmer/lib/template": "./glimmer/lib/template.ts", + "./glimmer/lib/template_registry": "./glimmer/lib/template_registry.ts", + "./glimmer/lib/utils/managers": "./glimmer/lib/utils/managers.ts", + "./glimmer/lib/utils/outlet": "./glimmer/lib/utils/outlet.ts", + "./glimmer/lib/utils/serialization-first-node-helpers": "./glimmer/lib/utils/serialization-first-node-helpers.ts", + "./glimmer/lib/utils/string": "./glimmer/lib/utils/string.ts", + "./glimmer/lib/views/outlet": "./glimmer/lib/views/outlet.ts", + "./environment/lib/context": "./environment/lib/context.ts", + "./environment/lib/env": "./environment/lib/env.ts", "./meta": "./meta/index.ts", "./metal": "./metal/index.ts", "./owner": "./owner/index.ts", diff --git a/packages/@ember/-internals/runtime/lib/mixins/target_action_support.ts b/packages/@ember/-internals/runtime/lib/mixins/target_action_support.ts index f01223f2b42..ee7112b4da3 100644 --- a/packages/@ember/-internals/runtime/lib/mixins/target_action_support.ts +++ b/packages/@ember/-internals/runtime/lib/mixins/target_action_support.ts @@ -2,7 +2,7 @@ @module ember */ -import { context } from '@ember/-internals/environment'; +import { context } from '@ember/-internals/environment/lib/context'; import { get, computed } from '@ember/-internals/metal'; import Mixin from '@ember/object/mixin'; import { assert } from '@ember/debug'; diff --git a/packages/@ember/-internals/views/lib/system/event_dispatcher.ts b/packages/@ember/-internals/views/lib/system/event_dispatcher.ts index 1a023ff6cbd..5e3e95a2c7a 100644 --- a/packages/@ember/-internals/views/lib/system/event_dispatcher.ts +++ b/packages/@ember/-internals/views/lib/system/event_dispatcher.ts @@ -3,7 +3,7 @@ import { assert } from '@ember/debug'; import { get, set } from '@ember/-internals/metal'; import EmberObject from '@ember/object'; import { getElementView } from './utils'; -import type { BootEnvironment } from '@ember/-internals/glimmer'; +import type { BootEnvironment } from '@ember/-internals/glimmer/lib/views/outlet'; import type Component from '@ember/component'; /** diff --git a/packages/@ember/-internals/views/lib/system/utils.ts b/packages/@ember/-internals/views/lib/system/utils.ts index 05d81d7611c..1188af78811 100644 --- a/packages/@ember/-internals/views/lib/system/utils.ts +++ b/packages/@ember/-internals/views/lib/system/utils.ts @@ -1,4 +1,4 @@ -import type { View } from '@ember/-internals/glimmer'; +import type { View } from '@ember/-internals/glimmer/lib/renderer'; import type { InternalOwner } from '@ember/-internals/owner'; import { getOwner } from '@ember/-internals/owner'; import { guidFor } from '@ember/-internals/utils'; diff --git a/packages/@ember/-internals/views/lib/views/core_view.ts b/packages/@ember/-internals/views/lib/views/core_view.ts index 241b233da65..a2e7aff5423 100644 --- a/packages/@ember/-internals/views/lib/views/core_view.ts +++ b/packages/@ember/-internals/views/lib/views/core_view.ts @@ -1,4 +1,4 @@ -import type { Renderer, View } from '@ember/-internals/glimmer'; +import type { Renderer, View } from '@ember/-internals/glimmer/lib/renderer'; import { inject } from '@ember/-internals/metal'; import { ActionHandler } from '@ember/-internals/runtime'; import Evented from '@ember/object/evented'; diff --git a/packages/@ember/application/index.ts b/packages/@ember/application/index.ts index dc6d923835a..409153d0534 100644 --- a/packages/@ember/application/index.ts +++ b/packages/@ember/application/index.ts @@ -4,7 +4,7 @@ import { getOwner as actualGetOwner, setOwner as actualSetOwner } from '@ember/owner'; import { dictionary } from '@ember/-internals/utils'; -import { ENV } from '@ember/-internals/environment'; +import { ENV } from '@ember/-internals/environment/lib/env'; import { hasDOM } from '@ember/-internals/browser-environment'; import { assert } from '@ember/debug'; import { DEBUG } from '@glimmer/env'; @@ -23,7 +23,7 @@ import Engine, { buildInitializerMethod } from '@ember/engine'; import type { BootOptions } from '@ember/engine/instance'; import type { Container, Registry } from '@ember/-internals/container'; import { privatize as P } from '@ember/-internals/container'; -import { setupApplicationRegistry } from '@ember/-internals/glimmer'; +import { setupApplicationRegistry } from '@ember/-internals/glimmer/lib/setup-registry'; import RouterService from '@ember/routing/router-service'; import type { EngineInstanceOptions } from '@ember/engine/instance'; import type { SimpleDocument, SimpleElement } from '@simple-dom/interface'; diff --git a/packages/@ember/application/instance.ts b/packages/@ember/application/instance.ts index 47ecb342bd4..da85ae8f10a 100644 --- a/packages/@ember/application/instance.ts +++ b/packages/@ember/application/instance.ts @@ -3,12 +3,13 @@ */ import { get, set } from '@ember/object'; -import * as environment from '@ember/-internals/browser-environment'; +import { hasDOM } from '@ember/-internals/browser-environment'; import EngineInstance from '@ember/engine/instance'; import type { BootOptions } from '@ember/engine/instance'; import type Application from '@ember/application'; -import { renderSettled } from '@ember/-internals/glimmer'; -import type { BootEnvironment, Component } from '@ember/-internals/glimmer'; +import { renderSettled } from '@ember/-internals/glimmer/lib/renderer'; +import type { BootEnvironment } from '@ember/-internals/glimmer/lib/views/outlet'; +import type Component from '@ember/-internals/glimmer/lib/component'; import { assert } from '@ember/debug'; import Router from '@ember/routing/router'; import { EventDispatcher } from '@ember/-internals/views'; @@ -445,13 +446,13 @@ class _BootOptions { readonly rootElement?: string | SimpleElement; constructor(options: BootOptions = {}) { - this.isInteractive = Boolean(environment.hasDOM); // This default is overridable below + this.isInteractive = Boolean(hasDOM); // This default is overridable below this._renderMode = options._renderMode; if (options.isBrowser !== undefined) { this.isBrowser = Boolean(options.isBrowser); } else { - this.isBrowser = Boolean(environment.hasDOM); + this.isBrowser = Boolean(hasDOM); } if (!this.isBrowser) { @@ -493,10 +494,7 @@ class _BootOptions { } toEnvironment(): BootEnvironment { - // Do we really want to assign all of this!? return { - ...environment, - // For compatibility with existing code hasDOM: this.isBrowser, isInteractive: this.isInteractive, _renderMode: this._renderMode, diff --git a/packages/@ember/canary-features/index.ts b/packages/@ember/canary-features/index.ts index 0d2c89324f2..01548b71daa 100644 --- a/packages/@ember/canary-features/index.ts +++ b/packages/@ember/canary-features/index.ts @@ -1,4 +1,4 @@ -import { ENV } from '@ember/-internals/environment'; +import { ENV } from '@ember/-internals/environment/lib/env'; /** Set `EmberENV.FEATURES` in your application's `config/environment.js` file diff --git a/packages/@ember/component/helper.ts b/packages/@ember/component/helper.ts index c1aa1427027..85b8a435196 100644 --- a/packages/@ember/component/helper.ts +++ b/packages/@ember/component/helper.ts @@ -1,6 +1,6 @@ export { - Helper as default, + default, helper, type FunctionBasedHelper, type FunctionBasedHelperInstance, -} from '@ember/-internals/glimmer'; +} from '@ember/-internals/glimmer/lib/helper'; diff --git a/packages/@ember/component/index.ts b/packages/@ember/component/index.ts index 0834c935f80..cf91c3b3726 100644 --- a/packages/@ember/component/index.ts +++ b/packages/@ember/component/index.ts @@ -5,11 +5,13 @@ export { setComponentTemplate, getComponentTemplate } from '@glimmer/manager'; -export { Component as default, Input, Textarea } from '@ember/-internals/glimmer'; +export { default } from '@ember/-internals/glimmer/lib/component'; +export { default as Input } from '@ember/-internals/glimmer/lib/components/input'; +export { default as Textarea } from '@ember/-internals/glimmer/lib/components/textarea'; export { componentCapabilities as capabilities, setComponentManager, -} from '@ember/-internals/glimmer'; +} from '@ember/-internals/glimmer/lib/utils/managers'; /** * Assigns a TemplateFactory to a component class. diff --git a/packages/@ember/debug/lib/capture-render-tree.ts b/packages/@ember/debug/lib/capture-render-tree.ts index 6f7d28bb532..03c5cd4b3fa 100644 --- a/packages/@ember/debug/lib/capture-render-tree.ts +++ b/packages/@ember/debug/lib/capture-render-tree.ts @@ -1,4 +1,4 @@ -import type { Renderer } from '@ember/-internals/glimmer'; +import type { Renderer } from '@ember/-internals/glimmer/lib/renderer'; import type Owner from '@ember/owner'; import type { CapturedRenderNode } from '@glimmer/interfaces'; diff --git a/packages/@ember/debug/lib/deprecate.ts b/packages/@ember/debug/lib/deprecate.ts index 20a30302868..ce32eacfbd4 100644 --- a/packages/@ember/debug/lib/deprecate.ts +++ b/packages/@ember/debug/lib/deprecate.ts @@ -1,4 +1,4 @@ -import { ENV } from '@ember/-internals/environment'; +import { ENV } from '@ember/-internals/environment/lib/env'; import { DEBUG } from '@glimmer/env'; import { assert } from './assert'; diff --git a/packages/@ember/engine/index.ts b/packages/@ember/engine/index.ts index e16240fa87d..38d6fb9e4e2 100644 --- a/packages/@ember/engine/index.ts +++ b/packages/@ember/engine/index.ts @@ -13,7 +13,7 @@ import type { EngineInstanceOptions } from '@ember/engine/instance'; import EngineInstance from '@ember/engine/instance'; import { RoutingService } from '@ember/routing/-internals'; import { ComponentLookup } from '@ember/-internals/views'; -import { setupEngineRegistry } from '@ember/-internals/glimmer'; +import { setupEngineRegistry } from '@ember/-internals/glimmer/lib/setup-registry'; import { RegistryProxyMixin } from '@ember/-internals/runtime'; function props(obj: object) { diff --git a/packages/@ember/engine/instance.ts b/packages/@ember/engine/instance.ts index 097b5c6ca6e..25b2a114f52 100644 --- a/packages/@ember/engine/instance.ts +++ b/packages/@ember/engine/instance.ts @@ -14,7 +14,7 @@ import type Owner from '@ember/-internals/owner'; import { type FullName, isFactory } from '@ember/-internals/owner'; import type Engine from '@ember/engine'; import type Application from '@ember/application'; -import type { BootEnvironment } from '@ember/-internals/glimmer'; +import type { BootEnvironment } from '@ember/-internals/glimmer/lib/views/outlet'; import type { SimpleElement } from '@simple-dom/interface'; export interface BootOptions { diff --git a/packages/@ember/engine/parent.ts b/packages/@ember/engine/parent.ts index 61bd5a7eeb4..4a04bc06e53 100644 --- a/packages/@ember/engine/parent.ts +++ b/packages/@ember/engine/parent.ts @@ -1 +1 @@ -export * from './lib/engine-parent'; +export { ENGINE_PARENT, getEngineParent, setEngineParent } from './lib/engine-parent'; diff --git a/packages/@ember/helper/index.ts b/packages/@ember/helper/index.ts index 400979a4103..0662143002b 100644 --- a/packages/@ember/helper/index.ts +++ b/packages/@ember/helper/index.ts @@ -11,7 +11,7 @@ import { get as glimmerGet, fn as glimmerFn, } from '@glimmer/runtime'; -import { uniqueId as glimmerUniqueId } from '@ember/-internals/glimmer'; +import { uniqueId as glimmerUniqueId } from '@ember/-internals/glimmer/lib/helpers/unique-id'; import { type Opaque } from '@ember/-internals/utility-types'; /** diff --git a/packages/@ember/instrumentation/index.ts b/packages/@ember/instrumentation/index.ts index 00938f3a9c7..f3fb35d4775 100644 --- a/packages/@ember/instrumentation/index.ts +++ b/packages/@ember/instrumentation/index.ts @@ -1,7 +1,7 @@ /* eslint no-console:off */ /* global console */ -import { ENV } from '@ember/-internals/environment'; +import { ENV } from '@ember/-internals/environment/lib/env'; import { assert } from '@ember/debug'; export interface Listener { diff --git a/packages/@ember/modifier/index.ts b/packages/@ember/modifier/index.ts index dfbb7ffa3b5..c7a3cb5bb32 100644 --- a/packages/@ember/modifier/index.ts +++ b/packages/@ember/modifier/index.ts @@ -15,4 +15,4 @@ export const setModifierManager: ( export type { ModifierManager }; export type { ModifierCapabilities } from '@glimmer/interfaces'; -export { modifierCapabilities as capabilities } from '@ember/-internals/glimmer'; +export { modifierCapabilities as capabilities } from '@ember/-internals/glimmer/lib/utils/managers'; diff --git a/packages/@ember/object/index.ts b/packages/@ember/object/index.ts index 58325bcf278..ef3f0dc0b72 100644 --- a/packages/@ember/object/index.ts +++ b/packages/@ember/object/index.ts @@ -1,5 +1,5 @@ import { assert } from '@ember/debug'; -import { ENV } from '@ember/-internals/environment'; +import { ENV } from '@ember/-internals/environment/lib/env'; import type { ElementDescriptor, ExtendedMethodDecorator } from '@ember/-internals/metal'; import { isElementDescriptor, diff --git a/packages/@ember/renderer/index.ts b/packages/@ember/renderer/index.ts index 870e0508e02..91f5cf3114c 100644 --- a/packages/@ember/renderer/index.ts +++ b/packages/@ember/renderer/index.ts @@ -61,8 +61,6 @@ @public */ -export { renderSettled } from '@ember/-internals/glimmer'; - /** * Render a component into a DOM element. * @@ -79,4 +77,8 @@ export { renderSettled } from '@ember/-internals/glimmer'; * @param {Object} [options.args] Optionally pass args in to the component. These may be reactive as long as it is an object or object-like * @public */ -export { renderComponent } from '@ember/-internals/glimmer'; + +// Import directly from the source module (not the barrel) so that +// bundlers produce a separate chunk without the classic Renderer's +// heavy dependencies (routing, @ember/object, etc.) +export { renderComponent, renderSettled } from '@ember/-internals/glimmer/lib/render-component'; diff --git a/packages/@ember/routing/index.ts b/packages/@ember/routing/index.ts index 474d8b90c17..be0dd6e698f 100644 --- a/packages/@ember/routing/index.ts +++ b/packages/@ember/routing/index.ts @@ -1 +1 @@ -export { LinkTo } from '@ember/-internals/glimmer'; +export { default as LinkTo } from '@ember/-internals/glimmer/lib/components/link-to'; diff --git a/packages/@ember/routing/route.ts b/packages/@ember/routing/route.ts index 7b4a8f24eac..69c646b5413 100644 --- a/packages/@ember/routing/route.ts +++ b/packages/@ember/routing/route.ts @@ -23,7 +23,7 @@ import { dependentKeyCompat } from '@ember/object/compat'; import { once } from '@ember/runloop'; import { DEBUG } from '@glimmer/env'; import { hasInternalComponentManager } from '@glimmer/manager'; -import type { RenderState } from '@ember/-internals/glimmer'; +import type { RenderState } from '@ember/-internals/glimmer/lib/utils/outlet'; import type { TemplateFactory } from '@glimmer/interfaces'; import type { InternalRouteInfo, Route as IRoute, Transition, TransitionState } from 'router_js'; import { PARAMS_SYMBOL, STATE_SYMBOL } from 'router_js'; diff --git a/packages/@ember/routing/router.ts b/packages/@ember/routing/router.ts index 114c398476e..992e0482004 100644 --- a/packages/@ember/routing/router.ts +++ b/packages/@ember/routing/router.ts @@ -1,5 +1,9 @@ import { privatize as P } from '@ember/-internals/container'; -import type { BootEnvironment, OutletState, OutletView } from '@ember/-internals/glimmer'; +import type { + BootEnvironment, + default as OutletView, +} from '@ember/-internals/glimmer/lib/views/outlet'; +import type { OutletState } from '@ember/-internals/glimmer/lib/utils/outlet'; import { computed, get, set } from '@ember/object'; import type { default as Owner, FactoryManager } from '@ember/owner'; import { getOwner } from '@ember/owner'; diff --git a/packages/@ember/runloop/index.ts b/packages/@ember/runloop/index.ts index bac83b17287..95f1b0181fe 100644 --- a/packages/@ember/runloop/index.ts +++ b/packages/@ember/runloop/index.ts @@ -1,6 +1,5 @@ import { assert } from '@ember/debug'; import { onErrorTarget } from '@ember/-internals/error-handling'; -import { flushAsyncObservers } from '@ember/-internals/metal'; import Backburner, { type Timer, type DeferredActionQueues } from 'backburner.js'; import type { AnyFn } from '@ember/-internals/utility-types'; @@ -33,6 +32,15 @@ type RemainingParams = PartialPa ? All : never; +// Lazy observer flushing — no-op until metal registers its implementation. +// This avoids eagerly importing @ember/-internals/metal (which pulls in the +// entire observer/meta/object system) for apps that don't use classic observers. +let _flushAsyncObservers: (scheduleFn: typeof schedule) => void = () => {}; + +export function registerFlushAsyncObservers(fn: (scheduleFn: typeof schedule) => void): void { + _flushAsyncObservers = fn; +} + let currentRunLoop: DeferredActionQueues | null = null; export function _getCurrentRunLoop() { return currentRunLoop; @@ -45,12 +53,12 @@ function onBegin(current: DeferredActionQueues) { function onEnd(_current: DeferredActionQueues, next: DeferredActionQueues) { currentRunLoop = next; - flushAsyncObservers(schedule); + _flushAsyncObservers(schedule); } function flush(queueName: string, next: () => void) { if (queueName === 'render' || queueName === _rsvpErrorQueue) { - flushAsyncObservers(schedule); + _flushAsyncObservers(schedule); } next(); diff --git a/packages/@ember/template-compiler/-internal-primitives.ts b/packages/@ember/template-compiler/-internal-primitives.ts index 7808146a4cf..613276756a4 100644 --- a/packages/@ember/template-compiler/-internal-primitives.ts +++ b/packages/@ember/template-compiler/-internal-primitives.ts @@ -1 +1,6 @@ -export * from './lib/-internal/primitives'; +export { + RESOLUTION_MODE_TRANSFORMS, + STRICT_MODE_TRANSFORMS, + STRICT_MODE_KEYWORDS, + INTERNAL_PLUGINS, +} from './lib/-internal/primitives'; diff --git a/packages/@ember/template-compiler/-internal-utils.ts b/packages/@ember/template-compiler/-internal-utils.ts index 8e0d9aae4e0..517ac25c350 100644 --- a/packages/@ember/template-compiler/-internal-utils.ts +++ b/packages/@ember/template-compiler/-internal-utils.ts @@ -1 +1,7 @@ -export * from './lib/plugins/utils'; +export { + isPath, + isSubExpression, + isStringLiteral, + inScope, + trackLocals, +} from './lib/plugins/utils'; diff --git a/packages/@ember/template-compiler/index.ts b/packages/@ember/template-compiler/index.ts index 7d9e14cbe4d..f3707c7016f 100644 --- a/packages/@ember/template-compiler/index.ts +++ b/packages/@ember/template-compiler/index.ts @@ -1,3 +1,4 @@ -export * from './lib/public-api'; +export { template } from './lib/public-api'; +export type { EmberPrecompileOptions } from './lib/public-api'; export { ALLOWED_GLOBALS } from './lib/plugins/allowed-globals'; diff --git a/packages/@ember/template-compiler/lib/runtime.ts b/packages/@ember/template-compiler/lib/runtime.ts index 7e1a213e3ea..dd2fd3671da 100644 --- a/packages/@ember/template-compiler/lib/runtime.ts +++ b/packages/@ember/template-compiler/lib/runtime.ts @@ -1 +1,2 @@ -export * from './public-api'; +export { template } from './public-api'; +export type { EmberPrecompileOptions } from './public-api'; diff --git a/packages/@ember/template-compiler/runtime.ts b/packages/@ember/template-compiler/runtime.ts index 594745c2db1..be0cd015746 100644 --- a/packages/@ember/template-compiler/runtime.ts +++ b/packages/@ember/template-compiler/runtime.ts @@ -1 +1 @@ -export * from './lib/runtime'; +export { template, type EmberPrecompileOptions } from './lib/runtime'; diff --git a/packages/@ember/template/index.ts b/packages/@ember/template/index.ts index a177015c07b..e1095169d01 100644 --- a/packages/@ember/template/index.ts +++ b/packages/@ember/template/index.ts @@ -7,4 +7,4 @@ export { isHTMLSafe, type SafeString, type TrustedHTML, -} from '@ember/-internals/glimmer'; +} from '@ember/-internals/glimmer/lib/utils/string'; diff --git a/packages/ember-template-compiler/index.ts b/packages/ember-template-compiler/index.ts index 7fadb57f7be..6b8f6950881 100644 --- a/packages/ember-template-compiler/index.ts +++ b/packages/ember-template-compiler/index.ts @@ -1,4 +1,19 @@ -export * from './lib/public-api'; +export { + precompile, + compile, + compileOptions, + _buildCompileOptions, + _transformsFor, + RESOLUTION_MODE_TRANSFORMS, + STRICT_MODE_TRANSFORMS, + _preprocess, + _print, + _precompile, + _GlimmerSyntax, + VERSION, +} from './lib/public-api'; +export type { EmberPrecompileOptions } from './lib/public-api'; +// NOTE: import * is intentional here -- the namespace object is passed to __registerTemplateCompiler import * as ETC from './lib/public-api'; import { __registerTemplateCompiler } from '@ember/template-compilation'; diff --git a/packages/ember-template-compiler/lib/plugins/utils.ts b/packages/ember-template-compiler/lib/plugins/utils.ts index 64889cb560a..586eada16ae 100644 --- a/packages/ember-template-compiler/lib/plugins/utils.ts +++ b/packages/ember-template-compiler/lib/plugins/utils.ts @@ -1 +1,7 @@ -export * from '@ember/template-compiler/-internal-utils'; +export { + isPath, + isSubExpression, + isStringLiteral, + inScope, + trackLocals, +} from '@ember/template-compiler/-internal-utils'; diff --git a/packages/ember-template-compiler/lib/system/compile.ts b/packages/ember-template-compiler/lib/system/compile.ts index 6eb0e2a170e..ab49249e5fe 100644 --- a/packages/ember-template-compiler/lib/system/compile.ts +++ b/packages/ember-template-compiler/lib/system/compile.ts @@ -4,7 +4,7 @@ import type { EmberPrecompileOptions } from '../types'; import precompile from './precompile'; import type { SerializedTemplateWithLazyBlock, TemplateFactory } from '@glimmer/interfaces'; -import { template } from '@ember/-internals/glimmer'; +import { templateFactory as template } from '@glimmer/opcode-compiler'; /** Uses HTMLBars `compile` function to process a string into a compiled template. diff --git a/packages/ember-template-compiler/package.json b/packages/ember-template-compiler/package.json index 6bb122fbb56..081dcf1bc8f 100644 --- a/packages/ember-template-compiler/package.json +++ b/packages/ember-template-compiler/package.json @@ -27,6 +27,7 @@ "@ember/template-compiler": "workspace:*", "@ember/utils": "workspace:*", "@glimmer/compiler": "workspace:*", + "@glimmer/opcode-compiler": "workspace:*", "@glimmer/env": "workspace:*", "@glimmer/interfaces": "workspace:*", "@glimmer/manager": "workspace:*", diff --git a/packages/ember-testing/index.ts b/packages/ember-testing/index.ts index 4a99b5d26e0..a06bf5d774b 100644 --- a/packages/ember-testing/index.ts +++ b/packages/ember-testing/index.ts @@ -1,4 +1,5 @@ -export * from './lib/public-api'; +export { Test, Adapter } from './lib/public-api'; +// NOTE: import * is intentional here -- the namespace object is passed to registerTestImplementation import * as EmberTesting from './lib/public-api'; import { registerTestImplementation } from '@ember/test'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 76c7702f212..ed4ef0fbc68 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -155,7 +155,7 @@ importers: version: 2.1.0 ember-cli-dependency-checker: specifier: ^3.3.1 - version: 3.3.3(ember-cli@6.11.2(@babel/core@7.29.0)(@types/node@22.19.15)) + version: 3.3.3(ember-cli@6.11.2(@babel/core@7.29.0)(@types/node@22.19.15)(ejs@3.1.10)(handlebars@4.7.9)(underscore@1.13.8)) ember-cli-yuidoc: specifier: ^0.9.1 version: 0.9.1 @@ -2521,6 +2521,9 @@ importers: '@glimmer/manager': specifier: workspace:* version: link:../@glimmer/manager + '@glimmer/opcode-compiler': + specifier: workspace:* + version: link:../@glimmer/opcode-compiler '@glimmer/runtime': specifier: workspace:* version: link:../@glimmer/runtime @@ -2797,7 +2800,7 @@ importers: version: 3.0.0 ember-cli-dependency-checker: specifier: ^3.3.3 - version: 3.3.3(ember-cli@6.11.2(@babel/core@7.29.0)(@types/node@22.19.15)) + version: 3.3.3(ember-cli@6.11.2(@babel/core@7.29.0)(@types/node@22.19.15)(ejs@3.1.10)(handlebars@4.7.9)(underscore@1.13.8)) ember-cli-deprecation-workflow: specifier: ^3.4.0 version: 3.4.0(ember-source@) @@ -17512,7 +17515,7 @@ snapshots: transitivePeerDependencies: - supports-color - ember-cli-dependency-checker@3.3.3(ember-cli@6.11.2(@babel/core@7.29.0)(@types/node@22.19.15)): + ember-cli-dependency-checker@3.3.3(ember-cli@6.11.2(@babel/core@7.29.0)(@types/node@22.19.15)(ejs@3.1.10)(handlebars@4.7.9)(underscore@1.13.8)): dependencies: chalk: 2.4.2 ember-cli: 6.11.2(@babel/core@7.29.0)(@types/node@22.19.15)(ejs@3.1.10)(handlebars@4.7.9)(underscore@1.13.8)