Skip to content

Spike: deprecate EmberObject (RFC 1234) - #21674

Draft
NullVoxPopuli-ai-agent wants to merge 3 commits into
emberjs:mainfrom
NullVoxPopuli-ai-agent:nvp/spike-deprecate-ember-object
Draft

NullVoxPopuli-ai-agent wants to merge 3 commits into
emberjs:mainfrom
NullVoxPopuli-ai-agent:nvp/spike-deprecate-ember-object

Conversation

@NullVoxPopuli-ai-agent

@NullVoxPopuli-ai-agent NullVoxPopuli-ai-agent commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Spike of RFC 1234. CI fails in the modes with all deprecations on. The todo list below says which test cleanups remove that failure.

The deprecation of the @ember/object/computed macros moved to #21688, which can land alone. This spike still has its own copy of that part, and a rebase removes it after #21688 merges.

What the spike does

  • Deprecation: deprecate-ember-object is new in the registry. It is available in 7.5.0, until 9.0.0, and not enabled.
  • Preview flag: the build-time flag import.meta.env?.EMBER_DROP_CLASSIC_FEATURES makes each deprecated API throw. This is the design of RFC 1247. RFC 1247 names the flag EMBER_DROP_EMBER_OBJECT. This PR uses one flag for the classic features as a group, so that later PRs can put more of them behind it. The published build keeps the expression, so the bundler of the app replaces it.
  • EmberObject: a class that extends EmberObject or CoreObject directly reports on each construction. Route, Controller, Service and the other Ember classes have a private mark, so their subclasses do not report.
  • Methods: get, set, getProperties, setProperties, incrementProperty, decrementProperty, toggleProperty and notifyPropertyChange report on all classes. An init on a subclass of a framework class also reports.
  • computed: the public computed and all macros from @ember/object/computed report. Ember code uses the internal versions, and calls the get and set functions in place of the methods.

Differences from the RFC

  • There is no internal copy of EmberObject. A copy makes service instanceof EmberObject false, so the spike uses a symbol mark, as Mixin does.
  • reopen and reopenClass are not in the spike. Deprecate classic classes (RFC 1117) #21673 already changes them, with a different id.
  • destroy, willDestroy, isDestroying and isDestroyed have no deprecation of their own.
  • The flag only throws. It does not remove the implementation from the build. The read is in one function, deprecateEmberObject. Removal needs the literal read at each site that holds EmberObject code.
  • The flag is not an optional feature, and EmberENV has no key for it.
  • The single deprecation for "the flag is not on" is not there. Each API reports separately.

Test results

Measured on main at e436b92.

Mode Result
Default 9296 tests, 0 failures, with 6 new tests in packages/@ember/object/tests/deprecate_ember_object_test.js
ALL_DEPRECATIONS_ENABLED=true The test page does not load
Build with EMBER_DROP_CLASSIC_FEATURES=true The test page does not load. On a page with only the new test file, the 5 tests for the flag pass.

Test cleanups that make this easier

A temporary build recorded each call of a deprecated API, with the test and the file of the call. The result: 1377 tests in 151 files use a deprecated API.

API Tests
EmberObject construction 683
set method 362
get method 324
init on a framework subclass 228
@ember/object/computed macros 199
getProperties method 165
computed 157
The other five methods 33

A test can be in more than one row.

1. Page load blockers: 3 files

Only these files use a deprecated API at module scope. They are the reason that the two modes above do not load.

  • @ember/-internals/glimmer/tests/integration/syntax/each-in-test: EmberObject instances at module scope
  • @ember/-internals/glimmer/tests/utils/shared-conditional-tests: EmberObject instances at module scope
  • @ember/-internals/runtime/tests/helpers/array: computed at module scope

2. Shared test harness: 6 files, 283 tests

One change here clears many tests.

  • @ember/-internals/runtime/tests/helpers/array (154 tests): the fixture classes extend EmberObject
  • internal-test-helpers/lib/test-cases/query-param (95 tests): this.set, appRouter.get('location.path'), and setProperties in setAndFlush
  • @ember/-internals/glimmer/tests/utils/shared-conditional-tests (13 tests): EmberObject fixtures
  • internal-test-helpers/lib/test-cases/application (9 tests): appRouter.get('currentURL')

3. Rendering tests of features that stay: 31 files, 282 tests

These need a rewrite to native classes, tracked state and the set function. Mostly this.set on the test context (127 tests), init (75), EmberObject fixtures (70) and computed (47).

  • syntax/each-test (46)
  • components/contextual-components-test (25)
  • custom-component-manager-test (23) and custom-modifier-manager-test (10)
  • application/engine-test (20) and mount-test (8): init on Engine subclasses
  • components/link-to/* (51 in four files)
  • content-test (15), components/curly-components-test (12)
  • 20 more files with 8 tests or less each

4. Routing and application tests: 33 files, 179 tests

Mostly router.get('currentURL') and controller.get(...) (90 tests), init on routes (55) and controller.set(...) (38).

  • ember/tests/routing/router_service_test/* (61 in eight files)
  • ember/tests/routing/query_params_test and its folder (38 in four files)
  • ember/tests/routing/decoupled_basic_test (16)
  • @ember/routing/tests/location/* (22 in two files)
  • 17 more files with 8 tests or less each

5. Tests of APIs that go away: 80 files, 814 tests

These need no rewrite. Each file needs one if (!isRemoved) block and an expectation helper, as the classic component files have.

  • @ember/object/tests/* and @ember/-internals/metal/tests/*: the object model itself, 320 tests or more
  • @ember/-internals/runtime/tests/array/* and mutable-array/*: EmberArray suites that use getProperties and EmberObject fixtures
  • @ember/-internals/runtime/tests/system/* and mixins/*: proxies and mixins
  • components/classic/* (15 files, 100 tests)

Decision that the cleanups need

228 tests define init on a Route, Engine, Service or classic component. Engine and Application subclasses in tests use init to register factories. The RFC shows class fields as the replacement for a Service, but it does not say what replaces init on an Engine.

Later work

  • Set the flag in the app blueprint, and add a CI variant that builds with the flag.
  • Write the deprecation guide and the @deprecated tags in the API docs.

🤖 Generated with Claude Code

Add the `deprecate-ember-object` deprecation and the
`EmberENV._NO_EMBER_OBJECT` flag. The flag is a preview of the removal:
each deprecated API throws.

A class that extends `EmberObject` or `CoreObject` directly reports the
deprecation on each construction. The classes that Ember builds on
`EmberObject` have a private symbol mark, so `Route`, `Controller`,
`Service` and their subclasses stay usable. The mark replaces the internal
copy that the RFC describes, because a copy makes
`service instanceof EmberObject` false.

The object model methods (`get`, `set`, `getProperties`, `setProperties`,
`incrementProperty`, `decrementProperty`, `toggleProperty`,
`notifyPropertyChange`) report on all classes. An `init` on a subclass of
a framework class reports too.

The public `computed` and the macros from `@ember/object/computed` are
wrappers that report. Ember code imports the implementations, and calls
the get and set functions in place of the methods.

The test page does not load with all deprecations on, or with the flag
on, because test files use `EmberObject` and `computed` at module scope.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@NullVoxPopuli-ai-agent
NullVoxPopuli-ai-agent force-pushed the nvp/spike-deprecate-ember-object branch from ca7ab41 to a92fe4f Compare October 10, 2026 14:34
NullVoxPopuli and others added 2 commits October 10, 2026 10:44
Replace `EmberENV._NO_EMBER_OBJECT` with the build-time flag
`import.meta.env?.EMBER_DROP_EMBER_OBJECT`, as RFC 1247 (build-time
feature flags) describes for sveltable deprecations.

The bundler of the app replaces the expression with a literal, so the
published build keeps the expression as it is. The test build passes
`EMBER_DROP_*` variables through the Vite `envPrefix`.

A build has one value for the flag, so the test file registers either the
deprecation module or the flag module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One flag covers the classic features as a group, so that later changes
can put more of them behind the same flag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants