Ember.js: el framework para desarrolladores web ambiciosos

Ember.js es un framework JavaScript de código abierto orientado a aplicaciones web ambiciosas: las que tienen muchas rutas, estado complejo, equipos grandes y necesidad de mantener el código durante años. Desde su primera versión en 2011, ha apostado por convenciones fuertes sobre configuración, una CLI integrada y un ciclo de lanzamientos predecible. En 2026 está en la rama 5.x y atraviesa la transición a lo que el equipo llama la edición Polaris: componentes con sintaxis de clase nativa, reactividad granular y soporte de primera clase para TypeScript.

Filosofía: convenciones sobre configuración

La decisión de diseño que más define a Ember es preferir las convenciones a la configuración. En lugar de pedir al desarrollador que tome centenares de pequeñas decisiones —cómo organizar las carpetas, cómo nombrar los ficheros, cómo conectar el router con los modelos—, Ember propone una estructura estándar que cualquier desarrollador familiarizado con el framework reconocerá al instante en cualquier proyecto. Esto tiene un coste de entrada algo mayor al principio, pero reduce drásticamente el coste de mantenimiento a largo plazo.

Qué incluye Ember de serie

  • Router declarativo — uno de los más potentes entre los frameworks JavaScript. Soporta rutas anidadas, rutas dinámicas, transiciones y hooks de carga y error en cada nivel.
  • Sistema de componentes Glimmer — el motor de renderizado que usa Ember internamente, optimizado para actualizaciones mínimas del DOM.
  • Servicios — singletons inyectables para compartir estado o lógica entre componentes y rutas sin tirar de una store global.
  • Ember Data — capa de persistencia para comunicación con APIs REST o JSON:API, con caché de modelos integrada.
  • Ember CLI — la herramienta de línea de comandos que genera componentes, rutas, servicios y tests, y gestiona el pipeline de build.
  • Tests integrados — soporte nativo para tests unitarios, de integración y de aceptación usando QUnit y ember-test-helpers.

Instalación y primer proyecto

# Instalar Ember CLI de forma global
npm install -g ember-cli

# Crear una nueva aplicación
ember new mi-tienda --lang es

# Arrancar el servidor de desarrollo (http://localhost:4200)
cd mi-tienda
ember serve

La estructura que genera ember new incluye las carpetas app/routes/, app/components/, app/services/ y app/templates/, además de tests para cada capa. No hace falta configurar Babel, ESLint ni el bundler: todo viene preconfigurado.

Componentes Glimmer: reactividad con @tracked

Desde Ember 3.14, los componentes se escriben con clases ES6 y decoradores. El decorador @tracked marca las propiedades reactivas: cuando cambian, Ember actualiza solo las partes del template que las usan.

// app/components/contador.js
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';

export default class ContadorComponent extends Component {
  @tracked valor = 0;

  @action
  incrementar() {
    this.valor++;
  }

  @action
  decrementar() {
    if (this.valor > 0) this.valor--;
  }
}
{{! app/templates/components/contador.hbs }}
<div class="contador">
  <h2>Contador: {{this.valor}}</h2>
  <button {{on "click" this.decrementar}}>?</button>
  <button {{on "click" this.incrementar}}>+</button>
</div>

El template usa {{on "click" ...}} en lugar de atributos onclick de HTML, lo que mantiene la separación entre lógica y vista. Las propiedades marcadas con @tracked actúan de forma similar a useState en React o a ref() en Vue 3, pero sin necesidad de un wrapper funcional.

El router: la piedra angular de Ember

El router de Ember es declarativo y jerárquico. Las rutas anidadas comparten segmentos de URL y pueden compartir modelos de datos:

// app/router.js
import EmberRouter from '@ember/routing/router';
import config from 'mi-tienda/config/environment';

export default class Router extends EmberRouter {
  location = config.locationType;
  rootURL  = config.rootURL;
}

Router.map(function () {
  this.route('productos', function () {
    this.route('detalle', { path: '/:producto_id' });
  });
  this.route('carrito');
  this.route('checkout');
});

Cada ruta puede tener su propia clase con un método model() que devuelve los datos necesarios para esa vista. Ember resuelve el modelo antes de renderizar el template, por lo que nunca hay componentes a mitad de carga sin datos:

// app/routes/productos.js
import Route from '@ember/routing/route';
import { service } from '@ember/service';

export default class ProductosRoute extends Route {
  @service store;

  async model() {
    return this.store.findAll('producto');
  }
}

Servicios: estado y lógica compartida

Los servicios son la alternativa de Ember a un store global. Son singletons que viven durante toda la sesión y se pueden inyectar en cualquier componente, ruta u otro servicio:

// app/services/carrito.js
import Service from '@ember/service';
import { tracked } from '@glimmer/tracking';

export default class CarritoService extends Service {
  @tracked items = [];

  get total() {
    return this.items.reduce((acc, item) => acc + item.precio, 0);
  }

  agregar(producto) {
    this.items = [...this.items, producto];
  }

  quitar(producto) {
    this.items = this.items.filter((i) => i !== producto);
  }
}

Para usarlo en un componente basta con inyectarlo con el decorador @service:

// app/components/boton-carrito.js
import Component from '@glimmer/component';
import { service } from '@ember/service';
import { action } from '@ember/object';

export default class BotonCarritoComponent extends Component {
  @service carrito;

  get estaEnCarrito() {
    return this.carrito.items.includes(this.args.producto);
  }

  @action
  toggleCarrito() {
    if (this.estaEnCarrito) {
      this.carrito.quitar(this.args.producto);
    } else {
      this.carrito.agregar(this.args.producto);
    }
  }
}

Estado actual: Ember 5.x y la edición Polaris

Ember sigue un ciclo de lanzamientos de seis semanas con versiones LTS cada cuatro ciclos, lo que da estabilidad a los equipos que no pueden actualizar constantemente. La rama 5.x, activa en 2026, consolida los cambios de la edición Polaris:

  • Template tag components — los ficheros .gjs y .gts permiten colocar el template dentro del propio fichero JavaScript/TypeScript, eliminando el fichero .hbs separado y acercando la experiencia a los SFC de Vue o los JSX de React.
  • Soporte nativo de TypeScript — Ember 5 incluye tipos oficiales sin necesidad de paquetes externos.
  • Reactividad de señales — el modelo de @tracked se alinea con la propuesta de señales de TC39, lo que facilita la interoperabilidad con otras librerías.

Entre las empresas que usan Ember en producción destacan LinkedIn, HashiCorp (el panel de Vault), Discourse y Apple. El código del framework está en GitHub, donde se pueden seguir las propuestas de mejora a través del proceso RFC abierto a la comunidad.

Cuándo tiene sentido elegir Ember

Ember no es la elección habitual para una landing page o un proyecto pequeño. Su punto fuerte es la escala: equipos que crecen, aplicaciones que llevan años en producción y proyectos donde la coherencia del código entre desarrolladores vale más que la libertad de elegir cada librería individualmente. Si el contexto es ese, pocas alternativas ofrecen una base tan madura y un ecosistema tan integrado.

Para comparar con otros enfoques del ecosistema JavaScript, el artículo sobre los frameworks de frontend más relevantes en 2025 da una visión de conjunto de las alternativas actuales, y el de React detalla el enfoque de la librería más usada del ecosistema. Si el proyecto va a usar TypeScript —algo que Ember soporta de serie en la rama 5.x—, el artículo sobre TypeScript en 2025 cubre las últimas novedades del lenguaje.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP