Angular 2 AOT con Webpack 2

Andrés Gesteira

Twitter: Andres Gesteira (@GeorgeKaplan_G)

Angular 2 Developer

Antes Wordpres & Drupal Developer

Ahora trabajando para GFT

(@kaplan81)

E-mail: gesteira2046@gmail.com

Angular 2

Reescritura completa de Ng1 en Typescript.

Basado en componentes controlados por el framework.

Multiplataforma

Altísimo rendimiento.

Angular CLI

Prolegómenos

Lo peor de Javascript:

The worst of all of JavaScript's bad features is its dependence on global variables. A global variable is a variable that is visible in every scope. Global variables can be a convenience in very small programs, but they quickly become unwieldy as programs get larger.

las variables globales

Douglas Crockford

Javascript: The Good Parts

JS: problemática de los módulos

ECMAScript2015 incorpora el patrón modular...

...pero los navegadores no, porque no tienen un objeto System.

Otro mejor: escribir módulos y luego transpilar.

Un rodeo: Immediately-Invoked Function Expressions (IIFE).

Las variables globales son contrarias al patrón modular.

Tipos de módulos (I):

Asynchronous Module Definition (AMD)

Es el de RequireJS, y tiene este aspecto:

// file for exporting (jquery.js)
define(['jquery'] , function ($) {
    return function () {};
});

// file for importing (app.js)
var $ = require('jquery');

Tipos de módulos (II):

CommonJS

Es el de Node y tiene este aspecto:

// file for exporting (jquery.js)
var jquery = function ($) {
    return function () {};
};
module.exports = jquery;

// file for importing (app.js)
var $ = require('jquery.js');

Tipos de módulos (III):

ES Harmony

Es el de ECMAScript, y tiene este aspecto:

// file for exporting (jquery.js)
export function jquery($) {
    return function() {};
}

// file for importing (app.js)
import { jquery } from 'jquery';

Nuestra aplicación

Cómo va a estar estructurada

index.html con un componente padre <app-root></app-root>

main.ts con un método de bootstraping.

Carpeta 'app' con módulos que contienen componentes.

main.ts

Lanza la aplicación cargando el módulo principal.

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/';

platformBrowserDynamic().bootstrapModule(AppModule);

El método proviene de una librería multiplataforma.

app.module.ts

Importa los otros módulos de mi aplicación.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HomeModule } from './home/home.module';
import { NotFoundModule } from './not-found/not-found.module';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { UserService } from './user.service';

@NgModule({
    imports: [
        BrowserModule,
        HomeModule,
        NotFoundModule,
        AppRoutingModule
    ],
    declarations: [AppComponent],
    providers: [UserService],
    bootstrap: [AppComponent]
})
export class AppModule { }

Declara y lanza el componente padre.

Declara los servicios de mi aplicación.

app.component.ts

Es la declaración de nuestro componente padre.

import { Component } from '@angular/core';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent { }

Lleva asociado un template de HTML y, ocasionalmente, una hoja de estilos de CSS.

¿Qué necesita nuestro navegador?

No sabe Typescript. Tendremos compilar a Javascript.

No entiende Angular. Habrá que aplicar un Abstract Syntax Tree (AST).

No conoce los módulos. Habrá que utilizar un module loader que gestiones el Dependency Graph.

Le cuesta gestionar muchos archivos, así que estaría bien que optimizáramos el resultado final creando bundles.

Webpack te lo hace TODO

Webpack

¿Por qué Webpack y no SystemJS?

Con SystemJS tendríamos que acabar añadiendo un gestor de tareas como Grunt o Gulp.

Webpack crea bundles de archivos, pero también es capaz de seguir el gráfico de dependencias de los módulos.

Los loaders de Webpack tienen las mismas capacidades que Grunt o Gulp.

Funciona tanto en el lado del cliente como en el lado del servidor.

Ha sido incorporado a la Angular CLI.

Tiene algo único: el Hot Module Replacement.

Dispone de todo tipo de plugins de optimización.

¿Por qué Webpack 2 y no Webpack 1?

Sintaxis de configuración algo más clara.

Soporta import/export de ES Harmony Modules.

Soporta System.import de SystemJS.

Tree Shaking

¿Qué es?

La expresión proviene de la Navidad norteamericana.

Consiste en eliminar los todos aquellos elementos que han sido exportados pero no importados.

Se produce cuando uglificamos archivos compilados a ES2015.

¿Y qué es eso de uglificar?

Es un proceso de parseo de nuestro código (ugligy) que nos lo pone "más feo". Suele constar de dos pasos:

1) Minificación: quita los espacios en blanco y los comentarios.

2) Compresión: lo pone todo en una línea.

tsconfig.json

Debe especificar un módulo resultante tipo "es2015".

{
    "compilerOptions": {
        "target": "es5",
        "module": "es2015",
        "moduleResolution": "node",
        "inlineSourceMap": true,
        "sourceMap": false,
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true,
        "removeComments": false,
        "noImplicitAny": false,
        "suppressImplicitAnyIndexErrors": true,
        "typeRoots": [
            "./node_modules/@types/"
        ]
    }
}

my-es2015-output.js

Después de la compilación:

function(module, exports, __webpack_require__) {
    
    /* harmony export */ exports["foo"] = foo;
    /* unused harmony export bar */;

    function foo() {
        return 'foo';
    }
    function bar() {
        return 'bar';
    }
}

my-es2015-output.min.js

Después de la minificación:

function (t, n, r) {
    function e() {
        return "foo"
    }

    n.foo = e
}

Lazy Loading

¿Qué es?

Consiste en no cargar todos los módulos a la vez.

Al entrar en nuestra aplicación se cargarán sólo los módulos que se renderizan en la Home.

El resto se irá cargando de manera "perezosa" a medida que los vayamos necesitando.

En Angular 2 esta funcionalidad nos la ofrece el Router.

Puede causar conflicto con la compilación AOT.

app-routing.module.ts

import { NgModule, ModuleWithProviders }     from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { NotFound } from './not-found/not-found.component';

const routes: Routes = [
    { path: '', redirectTo: 'home', pathMatch: 'full' },
    {
        path: 'dashboard',
        loadChildren: './+dashboard/dashboard.module#DashboardModule'
    },
    {
        path: 'wikipedia', 
        loadChildren: './+wikipedia/wikipedia.module#WikipediaModule'
    },
    { path: '**', component: NotFound }
];
const routing: ModuleWithProviders = RouterModule.forRoot(
                                        routes,
                                        { useHash: true }
                                     );

@NgModule({
    imports: [routing],
    exports: [RouterModule]
})
export class AppRoutingModule {}

AOT vs JIT

¿Qué son?

El compilador de Angular 2 por defecto hace uso del Typescript Compiler (tsc) y produce una compilación Just-In-Time (JIT).

El compilador de la Angular CLI (ngc) realiza su propia compilación de Typescript y posee la virtud de elegir entre JIT y Ahead-Of-Time (AOT).

En el modo JIT la aplicación se compila en el navegador, en tiempo de ejecución.

En el modo AOT se le ofrece al navegador una aplicación precompilada.

Ventajas de la AOT

La aplicación está precompilada, así que la renderización de la pagina es mucho más rápida.

No existe la necesidad de incluir el compilardor por defecto dentro de nuestra aplicación, así que el peso del archivo a descargar en el navegador es mucho menor.

Se detectan errorres de manera más temprana.

Es más segura.

JIT compilation

Inputs

(TS)

AST

Parse & Generate

SERVER

BROWSER

JS

eval()

 JS

factories

new...

Running

App

tsc

AOT compilation

Inputs

(TS)

AST

Parse & Generate

SERVER

BROWSER

JS

<script>

 **TS

factories

new...

Running

App

*ngc

**Muy diferentes de las JS factories en JIT.

*ngc + tsc en nuestra aplicación.

tsconfig-aot.json

Gestiona nuestra precompliación AOT.

{
    "compilerOptions": {
        // SAME AS BEFORE
    },
    // PLUS OTHER OPTIONS
    "angularCompilerOptions": {
        "genDir": "aot",
        "skipMetadataEmit": true
    }
}

Se ejecuta con el comando:

node_modules/.bin/ngc -p tsconfig-aot.json

main-aot.ts

Lanza la aplicación cargando la factoría precompilada.

import { platformBrowser }    from '@angular/platform-browser';
import { enableProdMode } from '@angular/core';
import { AppModuleNgFactory } from '../aot/src/app/app.module.ngfactory';

enableProdMode();
platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);

DEMO

FIN

AOT Compilation with Angular 2 + Webpack 2

By Andres Gesteira

AOT Compilation with Angular 2 + Webpack 2

  • 1,651