Skip to content

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:

classic-build-output.js
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:

  1. Debugging is harder - Stack traces point to AMD wrapper code instead of your source
  2. Modern tools expect ESM - Tree-shaking, code-splitting, and HMR work better with native ES modules
  3. 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:

vite-build-output.js
// Direct ESM import - no AMD wrapper
import 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-200ms

That’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:

Terminal window
npm install vite @ember/vite-addon
# This won't work alone

The correct approach:

Terminal window
npm install @embroider/core @embroider/vite @embroider/compat

Mistake 2: Ignoring Addon Compatibility

Not all Ember addons support Vite. I had to check each addon:

Terminal window
# Check addon compatibility
ember install ember-embroider-migration-check

Some 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:

classic-imports.js
// This works in Classic builds
import Component from '@ember/component';
// But Vite requires specific sub-packages
import Component from '@glimmer/component';

I ran a codemod to update all import paths:

Terminal window
npx ember-vite-codemod transform-imports ./app

Mistake 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:

  1. Add Embroider dependencies

    Terminal window
    npm install @embroider/core @embroider/compat
  2. 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);
    };
  3. Test thoroughly Run your full test suite. Fix any addon compatibility issues.

  4. Add Vite

    Terminal window
    npm install vite @embroider/vite
  5. 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