Se busca framework – Aurelia

Siguiendo con mi busqueda de framework he conocido a Aurelia, he quedado con ella un par de veces y parece que me gusta, de momento solo es un rollete pasajero pero quien sabe si se convierte en una relación más seria 😀

Bueno, como introducción decir que Aurelia es el framework que nos trae la gente de Durandal. Si bien AngularJS decidió llamar a su nuevo producto Angular 2, sin ser Angular -al estilo de ASP.NET 5- la gente de Durandal Inc. decidió algo raro hoy en día, llamar de forma distinta a algo que es distinto. El proyecto es liderado por Rob Eisenberg (@EisenbergEffect) el cual también fue el lider de Durandal. Lo curioso es que el tal Rob se unió al equipo de desarrollo de Angular 2, pero unos meses más tarde abandonó Angular 2, al parecer tenía desavenencias con la dirección que estaba tomando Angular 2 y decidió abandonar el proyecto y crear Aurelia.

A raíz de esto se creó una especie de carrera o competición entre ambos productos, Angular 2 vs Aurelia. En términos generales, las comparaciones vienen a decir que Aurelia está mejor pensado y es más afín a los estándares. Sin embargo la gente reconoce que Angular 2 tendrá más tirón debido al empuje de Google.

A primera vista, ojeando la documentación y los repos de GitHub, son bastante parecidos, ambos están orientados al uso de componentes. Debido por supuesto al empuje de Web Components. A simple vista:

  • Observamos facilmente que Aurelia es más modular, tiene muchos módulos que puedes usar o no. Angular 2 es monolítico.
  • Aurelia tiene two-way binding propiamente dicho y con Angular 2 lo obtienes sumando propiedad y evento.
  • Ambos tienen DI, siendo en de Aurelia más mágico que la de Angular 2, en Aurelia usando TypeScript y el decorador @autoinject no hace falta indicar cada inyección, en Angular 2 hay que especificar los proveedores.
  • Ambos tienen un buen router, anidable en ambos, lo que te permite diseñar los componentes sin preocuparte de las rutas de los padres, Angular 2 permite establecer las rutas con decoradores o en el constructor inyectando el router. Aurelia lo hace siempre inyectando.
  • Broadcast. AngularJS tuvo mucho problemas con rootScope.broadcast y en Angular 2 ha decidido cargarselo, personalmente no lo veo mal, pues el abuso de esto puede derivar en un bajo rendimiento pero todo depende del uso, al igual que el $rootScope. Aurelia ha decidido incluir una cosa llamada Event Aggregator, que viene a ser una via para enviar señales que reciben quien esten subscritos.

Una vez visto todo esto toca remangarse y ponerse a escribir código y probar coas en directo.

Aquí viene la primera sorpresa… y para mí el primer punto negativo en mis primeras horas con Aurelia, en el Getting Started te propone descargarte un kit, o bien el ES2016 Kit o bien el TypeScript Kit. En el Zip de descarga te encuentras un pequeño proyecto que usa JSPM, el paquete baja con todo, dependencias incluidas 7’5MB el Typescript Kit. Buceando en las carpetas y mirando el package.json vemos que se usan 16 dependencias más 5 dependencias de desarrollo. ¿Esto por qué? Sencillo. Si el hecho de ser modular es algo que te puede gustar porque así usas, dejas de usar o sustituyes cualquier paquete, al ser tan modular tienes que tener estas dependencias. Las dependencias del kit son paquetes de aurelia, systemjs, plugins de systemjs, bootstrap, font-awesome y alguna cosa más.

Por supuesto Aurelia no depende directamente de bootstrap, ni font-aweson, solo están ahí para el proyecto de ejemplo.

Así luce el [package.json]:

{
  "name": "aurelia-beginner-kit-typescript",
  "version": "1.0.0-beta.1.0.1",
  "description": "A beginner starter kit for learning Aurelia with TypeScript.",
  "keywords": [
    "aurelia",
    "kit",
    "beginner",
    "skeleton"
  ],
  "homepage": "http://aurelia.io",
  "license": "CC0-1.0",
  "author": "Rob Eisenberg <rob@bluespire.com> (http://robeisenberg.com/)",
  "main": "src/main.js",
  "jspm": {
    "dependencies": {
      "aurelia-animator-css": "npm:aurelia-animator-css@^1.0.0-beta.1.1.0",
      "aurelia-bootstrapper": "npm:aurelia-bootstrapper@^1.0.0-beta.1.1.1",
      "aurelia-fetch-client": "npm:aurelia-fetch-client@^1.0.0-beta.1.1.0",
      "aurelia-framework": "npm:aurelia-framework@^1.0.0-beta.1.1.0",
      "aurelia-history-browser": "npm:aurelia-history-browser@^1.0.0-beta.1.1.1",
      "aurelia-loader-default": "npm:aurelia-loader-default@^1.0.0-beta.1.1.1",
      "aurelia-logging-console": "npm:aurelia-logging-console@^1.0.0-beta.1.1.3",
      "aurelia-router": "npm:aurelia-router@^1.0.0-beta.1.1.0",
      "aurelia-templating-binding": "npm:aurelia-templating-binding@^1.0.0-beta.1.1.0",
      "aurelia-templating-resources": "npm:aurelia-templating-resources@^1.0.0-beta.1.1.0",
      "aurelia-templating-router": "npm:aurelia-templating-router@^1.0.0-beta.1.1.0",
      "bootstrap": "github:twbs/bootstrap@^3.3.5",
      "core-js": "npm:core-js@^2.0.3",
      "fetch": "github:github/fetch@^0.10.1",
      "font-awesome": "npm:font-awesome@^4.4.0",
      "text": "github:systemjs/plugin-text@^0.0.3",
      "ts": "github:frankwallis/plugin-typescript@^2.5.6"
    },
    "devDependencies": {
      "babel": "npm:babel-core@^5.8.24",
      "babel-runtime": "npm:babel-runtime@^5.8.24",
      "traceur": "github:jmcriffey/bower-traceur@0.0.91",
      "traceur-runtime": "github:jmcriffey/bower-traceur-runtime@0.0.91",
      "typescript": "npm:typescript@^1.6.2"
    },
    "overrides": {
      "npm:core-js@2.0.3": {
        "main": "client/shim.min"
      }
    }
  }
}

Usando Visual Studio Code, puedes ir haciéndote con el proyecto y ya lo vas viendo más sencillo. Realmente es solo un index.html que carga SystemJS y un config.js generado por JSPM donde se configura SystemJS para cargar las dependencias adquiridas por JSPM. Luego una carpeta src donde se encuentran app.ts y app.html.

Empezar a trastear siguiendo el Getting Started se hace cómodo y se progresa fácilmente, en poco tiempo aprendes a configurar el ruouter, a crearte un componente de nav-bar e incluso a consultar datos del api de GitHub y mostrarlos en pantalla.

Una vez seguida la guía de inicio, podemos ver un articulo llamado A Production Setup. Resulta que tienen preparados unos esqueletos para montar distintas aplicaciones, usando es2016, typescript, asp.net core, webpack,… buscando en internet ves que hay muchos más esqueletos montados por otras personas, por ejemplo para VSCode, atom, … de todo. En estos esqueletos ya vienen más archivos, viene bien montado el tema de las tareas con Gulp, tienen tareas de build, bundle, release,… He de confesar que verme tantos archivos en los esqueletos me ha tirado un poco para atrás, yo soy más de ir montándomelo todo poco a poco. Así pues he intentado tirar yo solo empezando con una carpeta vacía, me he puesto que si JSPM (que no lo conocía) que si el config.js para systemjs, que si npm para allá, que github para acá, que si … nada. Me lo he pensado mejor y debido a que lo que quería era probar Aurelia y no aprender a montarlo me he bajado un esqueleto para VSCode y typescript y me he centrado en lo mio.

Bien, el esqueleto baja con una pequeña aplicación de ejemplo con un nav-bar de bootstrap y el router configurado con 3 rutas, el app.ts luce así:

import {Router} from "aurelia-router";
 
export class App {
  router: Router;
  configureRouter(config, router) {
    config.title = "Aurelia";
    config.map([
      { route: ["", "welcome"], name: "welcome",      moduleId: "welcome",      nav: true, title: "Welcome" },
      { route: "users",         name: "users",        moduleId: "users",        nav: true, title: "Github Users" },
      { route: "child-router",  name: "child-router", moduleId: "child-router", nav: true, title: "Child Router" },
      { route: "crud-test",     name: "crud-test",    moduleId: "crud-test",    nav: true, title: "Crud" }
    ]);
 
    this.router = router;
  }
}

Voy a ir mostrando fragmentos de código y luego comento lo más significativo.

Lo primero que he hecho ha sido añadir una ruta para cargar un componente llamado crud-test, en el código crud-test.ts solo he escrito la clase export class CrudTest {} y en la plantilla:

<template>
    <require from="./components/crud/crud.component"></require>
    <crud entity-name="product"></crud>
</template>

Es decir, lo que quiero es pintar un crud para montar un mantenimiento de productos.

Entonces he creado mi crud.component, la plantilla html viene a ser algo así:

<template>
    <h1>Crud for ${entityName}</h1>
    <h2>Filter</h2>    
    <compose view="${filterTemplate}" view-model.bind="{model: filter}"></compose>    
    <h2>List</h2>
    <div repeat.for="item of entityList">        
        <compose view="${rowTemplate}" view-model.bind="{item : item}"></compose>        
    </div>    
</template>

De momento solo es un encabezado con un título, una zona de filtro y una lista de entidades.

El código queda así:

import {bindable, View} from "aurelia-framework";
 
export class Crud {
    @bindable entityName: string;
 
    filterTemplate: string;
    createTemplate: string;
    editTemplate: string;
    rowTemplate: string;
 
    entityTemplatesPath = "application";
    filter: any = {name: "cola"};
    entityList : any[] = [{id: 1, name: "cola"}, {id: 2, name: "nutella"}];
 
    created(owningView: View, myView: View) {
        this.filterTemplate = `${this.entityTemplatesPath}/${this.entityName}/${this.entityName}-filter.html`;
        this.createTemplate = `${this.entityTemplatesPath}/${this.entityName}/${this.entityName}-create.html`;
        this.editTemplate = `${this.entityTemplatesPath}/${this.entityName}/${this.entityName}-edit.html`;
        this.rowTemplate = `${this.entityTemplatesPath}/${this.entityName}/${this.entityName}-row.html`;
    }
}

Por último me he creado cada una de las plantillas. La plantilla de filtro:

<template bindable="model">
    <hr/>
    <label>Product name </label>
    <input value.bind="model.name"/>    
    <hr/>
</template>

Y la plantilla de cada fila de la lista:

<template bindable="item">
    ${item.id} - ${item.name}
</template>

Bien, ahora examinemos las partes calientes.

El router

Lo primero que vemos en app.ts es que en la clase App se define una función llamada configureRouter. Cuando un componente de Aurelia implementa dicha función, Aurelia la llama para establecer la configuración del router.

Cada ruta está definida por los valores: { route, name, moduleId, nav, title } los más importantes son route, que acepta un string con la cadena de la url o también un array de strings para poder resolverlas con distintas rutas, por ejemplo en el código de app.tsvemos que la primera ruta se resuelve con “wellcome” o “”, con esto establecemos que esta es la ruta por defecto. Y moduleId que viene siendo el path relativo al componente, al módulo del componente, con el que se resuelve la ruta. title se usa para establecer el document.title cuando la ruta se activa.

Una de las rutas se llama “child-router” que carga el modulo con el mismo nombre, el cual también implementa configureRouter y configura las mismas rutas y nos enseña la forma de anidar rutas unas dentro de otras.

El primer componente

Crear un componente ha sido tan fácil como crear un archivo .ts y otro .html con el mismo nombre en la misma carpeta, crud-test en mi caso. Este lo hemos referenciado con una de las rutas. En el código no he tenido que hacer nada, simplemente declarar una clase CrudTest vacía. En la plantilla podemos escribir html directamente. Aurelia nos obliga a que dicha plantilla esté escrita dentro de un tag template, dentro de este tag podemos escribir lo que queramos. En crud-test.html he escrito:

<template>
    <require from="./components/crud/crud.component"></require>
    <crud entity-name="product"></crud>
</template>

Lo que estoy haciendo es usar otro componente que me he creado, el componente crud, con require indico a aurelia que voy a usar un componente que está ubicado en ./components/crud/crud.component, después de esto ya puedo usarlo.

El componente CRUD, o más bien “como usar templates dinámicos on the fly”

En el código hemos definido la clase Crud y en ella hemos indicado con el decorador @bindable que la propiedad entityName se puede bindear desde la plantilla, por eso en crud-test.html hemos asignado “product” a entity-name, con esto conseguimos bindear atributos html a propiedades de nuestro componente.

El binding en aurelia es muy parecido a Angular 2, si usamos attribute="value" se pasa al atributo el valor literal asignado. Si usamos attribute.bind="expression" lo que hacemos es que bindeamos el valor de la expresión al atributo.

Luego hemos implementado el método created que es uno de los lifecycle hooks de los componentes, en este punto ya tenemos los bindings asignados, así que aprovechamos para crear los paths a los templates de la entidad, en este ejemplo hemos partido de que las plantillas estarán en application/[nombre-entidad]/[nombre-plantilla] y los nombres de las plantillas serán de la forma [nombre-entidad]-[nombre-template].html de forma que mis plantillas de la entidad “product” serán application/product/product-filter.html, application/product/product-filter.html, application/product/product-edit.html, y así sucesivamente.

También hemos inicializado un objeto filter con unos valores, en concreto fijamos la búsqueda de “name” a “cola”. Y también hemos creado una lista de dos productos en entityList, estos serán los productos a mostrar en la lista.

En cuanto a la vista crud.component.html podemos ver como se hace uso de la interpolación del mismo modo que en ECMAScript 6, usando ${expression}, esto lo ponen como punto a favor de Aurelia … por ser estándar … a mi me da igual poner “dobles llaves” que “dólar llaves”.

Lo siguiente que vemos es el uso de compose para incluir una plantilla en la vista. En compose usamos dos parámetros, uno es la vista a cargar y otro es el el view model que le pasamos a la vista. Podemos pasarle directamente el nombre de la vista como en el ejemplo o también bindear una propiedad del componente actual, lo mismo con el view model. Podemos usarlos de estas formas:

<compose view="application/product/product-filter.html" view-model="application/product/product-filter"></compose>
<compose view="${filterTemplate}" view-model="${filterComponent}"></compose>
<compose view.bind="filterTemplate" view-model.bind="filterComponent"></compose>
<compose view.bind="filterTemplate" view-model.bind="{model: filter}"></compose>

Con eso carga la plantilla y asigna un view model a la misma, así que como en “product-filter.html” tenemos:

<template bindable="model">
    <hr/>
    <label>Product name </label>
    <input value.bind="model.name"/>    
    <hr/>
</template>

Asignará filter a model y como al input le hemos bindeado la propiedad model.name de inicio el input contendrá el valor “cola” …

el atributo bindable en template te permite especificar que parámetros de entrada tienes en la vista sin tener que escribir un archivo de código con una clase casi vacía.

En crud.component también vemos el uso de repeat.for="item of entityList" con esto recorremos la lista de entidades y para cada una de ellas renderizamos la plantilla “row”, con compose otra vez, asignándole cada entidad para mostrar los valores.

Y bueno, no vamos a ver más código porque hay mucho por internet y tan solo llevo unas horas con Aurelia y no quiero meter la pata, jeje

Primera conclusión

Mi primera conclusión sobre el tema es grata, me gusta Aurelia y de momento no veo porque no poder usarlo en producción. Tampoco es que yo necesite grandes cosas, pero de momento lo visto me gusta.

Me gusta mucho el tema de que las dependencias con otros componentes se indiquen en la vista (require from ="...") y no en el código como Angular 2 (directives: [...]), me encanta poder incluir plantillas de html y poder asignarle un view model, de forma que se conserva el encapsulamiento de las vistas y no se renuncia a poder meter cualquier cosa en cualquier sitio. Me gusta que no haga falta código asociado a un componente, que un componente se pueda definir solo con html y así reducir considerablemente el número de archivos en el proyecto.

No me gusta mucho la cantidad de archivos que llevan los esqueletos… y es que la finalidad de esto es usarlo en Visual Studio y Visual Studio es un poco torpe con tantos archivos, con gulp y con tantas historias… además tengo que explicar al resto del equipo que es cada cosa y me parece que sería mucho ruido. Por otro lado me pondría también a limpiar y quitar muchas cosas que no usaríamos…

Y bueno, hasta aquí puedo leer, de momento.

2 comments

  • Muy interesante tu búsqueda de un nuevo hogar para tus CRUDs 😉

    La verdad es que tener que moverte dentro de Visual Studio parece un handicap importante a la hora de buscar cosas, porque te obliga a ir siempre a remolque del resto, pero ya hemos hablado otras veces de esto y entiendo tu contexto actual.

    Una duda, ¿cuánto te preocupa la (falta de) comunidad detrás de Aurelia?

    Sinceramente, a mi me da la impresión de que al final, igual que pasaba con Durandal, es sólo una persona (Rob Eisemberg), y en su momento ya se fue con Angular (aunque no acabe muy bien) dejando Durandal medio abandonado.

  • Muy buena pregunta, efectivamente entre ayer y hoy tengo esa idea rondándome la cabeza…

    A día de hoy (el post lo escribí el jueves noche) ya he evaluado con Aurelia casi todo lo que necesito, y cubre mis necesidades. He estado puteandolo un poco para probar la estabilidad y los resultados son buenos, lo considero bastante estable, ¿pero ahora que? ¿lo adopto o no?

    Efectivamente las pruebas que he realizado son sobre lo que hay, pero en cualquier proyecto surgen nuevas necesidades y en concreto yo suelo dar con los limites de las librerías, así que ahora, efectivamente amigo Juanma, mi pregunta es ¿si algo pasa quien me respalda?

    Ayer dí con un bug de Aurelia (lo llamo bug por que así lo han calificado ellos) resulta que el router es case sensitive, así que una ruta /user no se resuelve con /User… tienen ese bug desde el 2 de febrero y todavía está ahí. Eso me hizo pensar sobre lo que tú me preguntas y la verdad es que me preocupa. No es una preocupación muy grande, puesto que a mi no se me caen los anillos, me bajo el fuente, parcheo el fallo y si tengo que seguir en producción con mi propia versión del framework hasta que lo arreglen pues sigo, lo hice con Angular 1.3 en su momento y lo hice con Angular Material también. Pero es cierto que eso no mola.

    Para evaluar el tiempo de respuesta del equipo de Aurelia, ayer por la noche estuve investigando sobre el router de aurelia y me decidí a hacerme un fork de la parte que en mi opinión tenía el bug. Me pensaba que sería más sencillo la verdad, pensé que sería tan fácil como comparar con case insensitive o añadir 'i' a una expresión regular, pero me costó bastante más por que tienen una historia montada que no veas, me acordé de tu minimal-router mientras lo corregí. Al final hice un pull request con el tema solucionado. A ver lo que tardan en aceptarlo y sacar una versión con los cambios…

    En definitiva, ‘no me preocupa’ más que ‘me preocupa’, pero en definitiva sí me preocupa.

    Un abrazo Juanma.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax

Demuestra que no eres un bot *