Skip to content

Deprecation staging config, deprecation shaking, and classic object model deprecations (wave 1)#21505

Draft
wagenet wants to merge 24 commits into
emberjs:mainfrom
wagenet:classic-object-model-deprecations
Draft

Deprecation staging config, deprecation shaking, and classic object model deprecations (wave 1)#21505
wagenet wants to merge 24 commits into
emberjs:mainfrom
wagenet:classic-object-model-deprecations

Conversation

@wagenet

@wagenet wagenet commented Jul 17, 2026

Copy link
Copy Markdown
Member

Draft for CI signal — not ready for review yet, and the design needs an RFC before any of it can merge. An RFC draft covering the full design lives in this PR at internal-docs/rfcs/deprecation-early-enablement-and-shaking.md.

This is two stacked pieces of work in one PR so CI covers the whole stack; it will be split for review:

1. Deprecation tooling (branch deprecation-stages-and-shaking)

  • EmberENV.DEPRECATION_STAGES: per-id early enablement of available-stage deprecations (enable), and compliance declarations that turn migrated-away deprecations into errors (compliance / assert / except). Builds on the staging metadata from RFC 0649.
  • Deprecation shaking: @ember/deprecated-features flags stay live in the published dist (externalized as a package self-reference), and apps can strip deprecated code paths with the ember-source/deprecation-shaking vite plugin or an EMBER_DEPRECATION_FLAGS source build. A false flag also makes the deprecation report itself as removed at runtime, so shaking is a size optimization on top of correct semantics.
  • New CI: deprecation-shaken-dist job with a dist scan, smoke-test scenario building an app both shaken and unshaken.

2. Classic object model deprecations, wave 1 (branch classic-object-model-deprecations)

Available-stage deprecations (since: 7.3.0, until: 8.0.0, silent unless opted into) for: .extend(), .reopen()/.reopenClass(), Mixin.create, computed() and the @ember/object/computed macros, observer()/addObserver/removeObserver, A(), ObjectProxy, ArrayProxy.

Ember's own framework definitions route through internal non-deprecating entry points (internalExtend, createMixin, …), including the runtime paths (autoboot Router re-extend, Router.map, engine initializers), so framework operation is never blamed on apps. The blanket and removal-simulation CI variants carry these ids in except, which now shields an id from enable: true and from _OVERRIDE_DEPRECATION_VERSION.

Verified locally: full suite under the standard, all-deprecations-enabled, and deprecations-as-errors variants; smoke scenarios standard and under removal simulation; published types across TS 5.2–7.0.

This PR was written by Claude (I reviewed and directed the work).

🤖 Generated with Claude Code

wagenet and others added 10 commits July 16, 2026 13:10
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Apps can enable individual available-stage deprecations early (enable),
declare compliance so migrated-away deprecations throw instead of warn
(compliance/assert/except), and test harnesses can swap config at runtime
via setDeprecationStagesConfig. DEPRECATIONS registry entries now compute
test/isEnabled/isRemoved lazily so config changes are reflected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Revives @ember/deprecated-features as the per-deprecation flag source: one
boolean const per shakable deprecation, named after its DEPRECATIONS
registry key. deprecation() takes the flag as a second argument; a false
flag makes the entry report isRemoved so unguarded reaches throw. The
Comparable mixin body and the deprecated service inject body are guarded
following the convention.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The standard dist externalizes @ember/deprecated-features to a package
self-reference so the flags stay live for app-side shaking, emits the
flags module and dist/deprecation-flags.json. EMBER_DEPRECATION_FLAGS
builds dist/deprecation-custom/{dev,prod} with the flags compile-time
folded and guarded code eliminated. bin/assert-deprecations-shaken.mjs
verifies both directions; a new CI job runs it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A vite/rollup plugin that replaces the externalized flags module in
ember-source's dist based on app config (compliantThrough, strip, keep):
shaken deprecations lose their implementation to DCE and throw the
removal error if reached. The smoke scenario builds a real Embroider
vite app both ways and verifies bundle contents and runtime behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
setDeprecationStagesConfig stays private with a follow-up-RFC path;
dynamic-id deprecation families take one flag, with the past-until
import-from-ember family deliberately unflagged. The compliance-scope
and shaken-undefined questions stay open with stated defaults.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The deprecation-shaking plugin matches module ids with POSIX separators
(vite normalizes ids on every platform; the node:path sep never matched
on Windows). The manifest metadata (id/since/until) is now pinned to the
DEPRECATIONS registry by a conformance test, and the scan script's
marker guidance describes what actually survives prod builds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
generateBundle runs before rollup writes output, so on a fresh checkout
the directory does not exist yet and every from-scratch build failed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@wagenet
wagenet force-pushed the classic-object-model-deprecations branch from 9f0e401 to 240cfdb Compare July 17, 2026 03:11
wagenet and others added 13 commits July 16, 2026 20:31
Under _OVERRIDE_DEPRECATION_VERSION simulation an unshaken deprecation
legitimately reports removed; the assertion tested version logic, not
shaking.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
except now means "treat this id as unconfigured": exempt from
compliance/assert throwing and excluded from enable, including
enable: true. EXCEPT_DEPRECATIONS threads through testem/index.html
so CI variants can blanket-enable available deprecations minus a
known-noisy list.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…iases

internalExtend/internalReopen/internalReopenClass (@ember/object/core) and
createMixin (@ember/object/mixin) are the entry points for ember-source's
own framework definitions; the public statics delegate to them. This
includes the runtime sites (autoboot Router extend, Router.map's
reopenClass, engine initializer buckets) and the PrototypeMixin/willReopen
machinery, so upcoming definition-time deprecations on the public statics
never fire from framework internals. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…age)

deprecate-ember-object-extend, deprecate-ember-object-reopen, and
deprecate-ember-mixins fire from the public statics only; framework
internals go through the internal aliases. Available-stage: silent
unless opted into via DEPRECATION_STAGES. The blanket CI variant rows
move from _ALL_DEPRECATIONS_ENABLED to ENABLED_DEPRECATIONS=true with
these ids excepted, since ember's own suite still exercises the classic
APIs pervasively.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
deprecate-computed-properties fires from the public computed() wrapper
and once at module eval of the @ember/object/computed macros barrel;
deprecate-observers fires from observer() and the public
addObserver/removeObserver. Framework internals use the metal modules
directly (the two remaining public-barrel imports in the routing
services moved to the deep path) and stay silent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
deprecate-ember-array fires from the public A(); internals use the new
internalA (data-adapter, reduce_computed_macros, route/router QP
handling). The EmberArray/MutableArray/NativeArray mixins ride the
extend/mixins deprecations when applied externally.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fires at init, deduped by a WeakSet on the constructor so high-volume
proxy creation warns once per class. Internals never instantiate these.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
RFC appendix covering the wave-1 ids, the internal-alias approach, why
these entries have no shaking flags, and the blanket-CI except-list
rationale; registry doc comment gains the internal-alias convention.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Consumer bundlers treat @ember/object/computed as side-effect-free (the
package-level sideEffects declaration) and drop top-level statements
when only re-exports are used, so the module-eval deprecation never
fired in bundled apps. Each macro export is now a call-time deprecating
wrapper; internals keep using the underlying lib modules directly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The proxy dedupe sets only record classes while their deprecation is
enabled, so enabling later still warns; expandProperties gets an
accurate message; createMixin notes its intentional divergence from
the this-based static.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
OVERRIDE_DEPRECATION_VERSION=15 made every wave-1 deprecation (until
8.0.0) throw the removal error across the suite and the smoke apps —
ember's tests and the app-testing ecosystem still use the classic APIs.
except now also excludes an id from the version-based removal
computation (never from a false shaking flag: that code is actually
gone), and the Deprecations-as-errors CI rows plus the
deprecations-removed smoke job carry the classic except list, threaded
into the app templates via EXCEPT_DEPRECATIONS.

setDeprecationStagesConfig(null) now restores the boot (EmberENV)
config instead of clearing it — test teardowns were wiping the
harness variant's config for the rest of the suite. {} is an
explicitly empty config. Warn-expecting deprecation tests skip under
removal simulation via testUnless, the standard pattern for removed
deprecations.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The stage configuration functions are no-op stubs in production builds,
so the tests that exercise them move to a development-only module. The
isRemoved compliance test starts from an explicitly empty config — the
compliance CI variant's boot config now survives teardown (null restores
it) and would otherwise make its enabled-stage deprecation throw.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@NullVoxPopuli

Copy link
Copy Markdown
Contributor

how would a consuming project configure their build tool to not include deprecations?

the changes I see are on the ember-publishing side, and I think the deprecation staging stuff we talked about all that time ago was user-configurable?

Module-scope calls to the imported internalReopen/internalReopenClass
helpers made six modules permanently side-effectful (caught by the
tree-shakability snapshot): rollup cannot scope an imported helper
call, but it can scope a static method call to its class. The
non-deprecating internal entry points for reopen/reopenClass are now
reopenInternal/reopenClassInternal statics on CoreObject; the module
functions remain as their implementation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@wagenet
wagenet force-pushed the classic-object-model-deprecations branch from 240cfdb to 7173c1c Compare July 17, 2026 19:25
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