Aprovechando que di una charla en los Angular Community Days para explicar el potencial de usar WebWorkers con Angular, voy a dejarte esta guía paso a paso de cómo modificar una aplicación de Angular (v2 o v4) para sacar provecho de esta killer feature.
Si quieres una introducción a los web workers, te recomiendo que le des un vistazo a la presentación. Pero si quieres saltarte la intro e ir al grano, te lo resumo:
Angular te permite ejecutar la lógica de tu aplicación en un segundo thread para liberar tu UI, mejorando la experiencia de usuario.
Angular te permite ejecutar toda la lógica de tu aplicación en un segundo thread (WebWorker), de modo que libera absolutamente a tu UI, evitando que vaya a trompicones cuando estas haciendo un uso exhaustivo de CPU en el código.
Puedes ver por ti mismo los resultados en la demo online del código que utilicé para este otro artículo sobre WebWorkers en Angular.
Spoiler de la demo
Precondiciones
Estoy asumiendo que tienes un proyecto en Angular (v2 o v4) generado con la CLI de Angular v1.0 o superior.
Esto último no es imprescindible, pero las modificaciones del archivo de webpack las haré basándome en la configuración que genera la CLI.
Instrucciones
Extraer el archivo de webpack
Desde Angular CLI v1.0, existe el comando eject que permite extraer el archivo de configuración de webpack y manipularlo a tu antojo.
- Ejecuta
ng eject
para que Angular CLI genere el archivo webpack.config.js. -
Ejecuta
npm install
para instalar las nuevas dependencias generadas por la CLI al extraer el archivo anterior.
Instala las dependencias de los webworkers
Para poder lanzar la app en un WebWorker, necesitas instalar algunas librerías de angular.
Ejecuta npm install --save @angular/platform-webworker @angular/platform-webworker-dynamic
Cambios en la inicialización del UI thread
####Cambios en app.module.ts
Reemplaza BrowserModule
por WorkerAppModule
en el archivo app.module.ts. También tienes que actualizar el import ya que usa la librería @angular/platform-webworker
.
//src/app/app.module.ts
import { WorkerAppModule } from '@angular/platform-webworker';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
//...other imports...
@NgModule({
declarations: [
AppComponent
],
imports: [
WorkerAppModule,
//...other modules...
],
providers: [/*...providers...*/],
bootstrap: [AppComponent]
})
export class AppModule { }
Cambios en src/main.ts
Reemplaza el proceso de bootstrap con bootstrapWorkerUI
(actualiza también el import).
Tendrás que pasarle una URL al archivo donde tienes definido el WebWorker. Utiliza el nombre webworker.bundle.js
, no te preocupes, lo creamos en un momento.
//main.ts
import { enableProdMode } from '@angular/core';
import { bootstrapWorkerUi } from '@angular/platform-webworker';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
bootstrapWorkerUi('webworker.bundle.js');
Crea el archivo workerLoader.ts
- Crea un nuevo archivo src/workerLoader.ts.
-
Como tu WebWorker será un único archivo que contiene todas sus dependencias (vive en un proceso aparte al thread principal y por tanto no tiene acceso al vendor.js que tengas allí), necesitas incluir los paquetes
polyfills.ts
,@angular/core
y@angular/common
. Más adelante verás como actualizar Webpack para transpilar y generar el bundle con el resultado. -
Importa
platformWorkerAppDynamic
-
Importa
AppModule
(elimina su import de main.ts) y lánzalo a través de la plataformaplatformWorkerAppDynamic
.
//workerLoader.ts
import 'polyfills.ts';
import '@angular/core';
import '@angular/common';
import { platformWorkerAppDynamic } from '@angular/platform-webworker-dynamic';
import { AppModule } from './app/app.module';
platformWorkerAppDynamic().bootstrapModule(AppModule);
Actualiza Webpack para generar tu WebWorker
El archivo auto-generado de Webpack es muy extenso, pero no te preocupes. Solo necesitas fijarte en estos puntos:
- Añade un entry point
webworker
para tu archivoworkerLoader.ts
. Si te fijas en la parte de output, verás que se añade el sufijo «bundle.js» a todos los chunks. Por eso, en la fase de bootstrap te he hecho usar el nombrewebworker.bundle.js
-
Ves a HtmlWebpackPlugin y excluye el entry point
webworker
, de modo que su archivo no se incluya en el index.html que se genera on the fly. -
Ves a CommonChunksPlugin y para el chunk
inline
, escribe explícitamente los chunks de entrada, para prevenir que se incluya el dewebworker
. -
Ves a AotPlugin y define el
entryModule
explícitamente.
A continuación tienes el resultado de los puntos modificados en el webpack config.
// webpack.config.js
//...some stuff...
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CommonsChunkPlugin } = require('webpack').optimize;
const { AotPlugin } = require('@ngtools/webpack');
//...some stuff...
module.exports = {
//...some stuff...
"entry": {
"main": [
"./src/main.ts"
],
"polyfills": [
"./src/polyfills.ts"
],
"styles": [
"./src/styles.css"
],
"webworker": [
"./src/workerLoader.ts"
]
},
"output": {
"path": path.join(process.cwd(), "dist"),
"filename": "[name].bundle.js",
"chunkFilename": "[id].chunk.js"
},
"module": { /*...a lot of stuff...*/ },
"plugins": [
//...some stuff...
new HtmlWebpackPlugin({
//...some stuff...
"excludeChunks": [
"webworker"
],
//...some more stuff...
}),
new BaseHrefWebpackPlugin({}),
new CommonsChunkPlugin({
"name": "inline",
"minChunks": null,
"chunks": [
"main",
"polyfills",
"styles"
]
}),
//...some stuff...
new AotPlugin({
"mainPath": "main.ts",
"entryModule": "app/app.module#AppModule",
//...some stuff...
})
],
//...some more stuff...
};
¡Listo!
Si has seguido estos pasos, lo único que te queda por hacer es compilar el código y ver los resultados.
Ejecuta npm start
Toda la lógica de tu app Angular debería estarse ejecutando dentro de un WebWorker, consiguiendo una UI más fluida..
Nota adicional
npm start
ejecuta el servidor de desarrollo webpack-dev, y por lo visto éste tiene algún problema con los WebWorkers y muestra un error por consola. Aún así, el WebWorker funciona correctamente (al menos en mi caso).
En todo caso, si compilas la app usando el comando webpack
y utilizas cualquier otro servidor de desarrollo como por ejemplo simplehttpserver, no te encontrarás con ningún error 😉
¡Eso es todo amigos!
Si has disfrutado de este artículo, no olvides compartirlo. ¡Gracias!
Hi. It is possible to use it in ionic?
Buenas. Muy buen post. Es posible utilizar los webworkers de esta manera en iónic?
Saludos
De momento no, pero hace un tiempo su tech lead me comentó que estaban en ello.
¡Saludos!
Gracias por la respuesta. Espero que sea parte de la versión 4, seria genial.
Saludos!
Hola Enrique,
Es posible ver el video de tu charla en linea? Saludos.
¡Hola!
Las charlas de comunidad no se grabaron, así que no 🙁
Hola. Excelente post.
Tengo una consulta que realizar, ¿alguno de ustedes a utilizado angular/material junto con webworkers?
¿Cual sería la mejor forma de usarlo?
Yo creé un proyecto desde cero usando el angular cli, agregué las dependencias de @angular/material, y coloqué un botón en el html de app.component.
Hasta ese momento todo funciona correctamente, es decir, el botón tiene las características gráficas correspondientes a un botón material.
Luego realicé la conversión planteada en este post, para poder correr la aplicación en el webworker.
Después de esto, la aplicación sigue funcionando, pero el botón deja de tener las características gráficas de un botón material.
Gracias por el post, y si tienen alguna sugerencia para usar angular/material con webworkers, lo sabré apreciar.
Hola enrique felicitaciones por tus excelentes post , una forma muy clara de expresar lo conceptos. Por otro lado hablas de los WEBWorkers que diferencia tiene de service workers ? .
Por que veo que ionic lo tiene en sus docs.
https://ionicframework.com/docs/developer-resources/service-worker/
Nada que ver:
– Los web workers te permiten trabajar en background. Son para hacer tareas pesadas que no quieres que bloqueen tu página.
– Los service workers son para las PWA, y permiten al navegador ejecutar tareas concretas relacionadas con tu página, incluso cuando tu página no está abierta. Entre otras cosas te permiten usar push notifications, hacer de proxy para cachear urls y navegar offline.
¡Saludos!
Hola Enrique, excelente trabajo.
Sabes si desde Angular 5 es posible configurar el web worker sin necesidad de hacer eject de la configuración de webpack?
Gracias!
¡Muy buena pregunta!
Hace tiempo que tengo esta misma inquietud, la verdad. Es probable, pero no he encontrado aún tiempo para mirarlo 🙁