Ember Classic Build vs Vite Build: What's the Difference and When to Migrate?
I spent an entire afternoon debugging why @ember/reactive worked perfectly in my Vite-based Ember app but failed silently in my coworker’s Classic build. The error message was cryptic:
Error: Could not find module `@ember/reactive` imported from `my-app/components/my-component`That’s when I realized the fundamental difference between Ember’s Classic build system and Vite builds—and why the Ember community is migrating away from AMD bundles.
The Problem: Why Classic Builds Cause Issues
Ember’s Classic build system uses pre-compiled AMD (Asynchronous Module Definition) vendor bundles. When you build an Ember app the traditional way, Ember CLI wraps everything in AMD define statements.
I saw this firsthand when checking the build output:
define('my-app/components/my-component', ['exports', '@ember/component', '@glimmer/tracking'], function (exports, _component, _tracking) { 'use strict'; // AMD wrapper adds overhead here});The AMD wrapper creates several problems:
- Debugging is harder - Stack traces point to AMD wrapper code instead of your source
- Modern tools expect ESM - Tree-shaking, code-splitting, and HMR work better with native ES modules
- New features require extra fixes - As I found with
@ember/reactive, some packages work out of the box with Vite but need patches for Classic builds
Ember 6.11’s release notes confirmed this: “@ember/reactive works out of the box with Vite-based builds. Classic ember-cli builds required a bugfix to work with @ember/reactive.”
The Solution: How Vite Builds Differ
Vite-based builds consume Ember’s sub-packages directly as ESM (ECMAScript Modules). No AMD wrapper, no intermediate bundle step.
I migrated my app to Vite and compared the output:
// Direct ESM import - no AMD wrapperimport Component from '@glimmer/component';import { tracked } from '@glimmer/tracking';
export default class MyComponent extends Component { @tracked value = 0;}The difference is stark. Vite serves native ES modules during development, which means:
- Instant HMR - Changes reflect in milliseconds instead of seconds
- Better tree-shaking - Unused code gets stripped more effectively
- Standard JavaScript - Your code looks like standard JS, not framework-specific AMD
Why This Matters: Performance and DX Benefits
After migrating to Vite, my development workflow transformed:
Classic Build: - Initial build: 15-20 seconds - Rebuild on change: 5-8 seconds - HMR refresh: 2-3 seconds
Vite Build: - Initial build: 2-3 seconds - Rebuild on change: 100-500ms - HMR refresh: 50-200msThat’s roughly 10-100x faster rebuilds. The difference becomes more pronounced as your app grows.
RFC 1101 formalized this shift by deprecating Ember Vendor Bundles. The RFC explicitly states that “Vite-based builds and Embroider@3 with Webpack (staticEmberSource: true) consume sub-packages directly from ember-source instead of AMD bundles.”
This means the Ember community is standardizing on Vite. Classic builds will receive bugfixes but not new features.
Common Mistakes: Migration Pitfalls
I made several mistakes during my migration. Here’s what to avoid:
Mistake 1: Attempting Migration Without Embroider
Vite + Ember requires Embroider as a compatibility layer. I tried going straight to Vite:
npm install vite @ember/vite-addon# This won't work aloneThe correct approach:
npm install @embroider/core @embroider/vite @embroider/compatMistake 2: Ignoring Addon Compatibility
Not all Ember addons support Vite. I had to check each addon:
# Check addon compatibilityember install ember-embroider-migration-checkSome addons needed updates. A few required temporary workarounds until maintainers added Vite support.
Mistake 3: Not Updating Import Paths
Classic builds allow some import path flexibility that Vite doesn’t:
// This works in Classic buildsimport Component from '@ember/component';
// But Vite requires specific sub-packagesimport Component from '@glimmer/component';I ran a codemod to update all import paths:
npx ember-vite-codemod transform-imports ./appMistake 4: Migrating Without Reading RFC 1101
RFC 1101 documents the migration path and deprecation timeline. Skipping it meant I missed important context about:
- Which Ember APIs have different behavior in Vite builds
- The timeline for Classic build deprecation
- Recommended migration strategies for different app sizes
When Should You Migrate?
Based on my experience and the Ember roadmap:
Start new projects with Vite. It’s the default recommendation from the Ember team.
Migrate existing projects when:
- You need faster development feedback
- You want better tree-shaking for smaller bundles
- Your dependencies support Vite (check with
ember-embroider-migration-check) - You’re planning significant refactoring anyway
Wait on migration if:
- You have many unmaintained addons without Vite support
- Your team lacks bandwidth for migration testing
- Your app is in maintenance mode with minimal development
Migration Steps Overview
For a typical Ember app:
-
Add Embroider dependencies
Terminal window npm install @embroider/core @embroider/compat -
Update ember-cli-build.js
ember-cli-build.js const EmberApp = require('ember-cli/lib/broccoli/ember-app');const { maybeEmbroider } = require('@embroider/test-setup');module.exports = function(defaults) {let app = new EmberApp(defaults, {});return maybeEmbroider(app);}; -
Test thoroughly Run your full test suite. Fix any addon compatibility issues.
-
Add Vite
Terminal window npm install vite @embroider/vite -
Update configuration Follow the Embroider Vite documentation for final configuration.
Summary
In this post, I shared my journey discovering why @ember/reactive worked differently between Classic and Vite builds. Classic Ember builds use AMD bundles with wrapper overhead, while Vite builds consume Ember sub-packages as native ESM. The result is 10-100x faster rebuilds and better developer experience. If you’re starting a new Ember project or planning significant work on an existing one, migrating to Vite is worth the effort—just remember to check addon compatibility first and read RFC 1101 for the complete migration path.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments