"
Creo que los compiladores son muy interesantes ", dice Uri Shaked, el autor del material, que publicamos hoy. El año pasado, escribió
un artículo que hablaba sobre ingeniería inversa del compilador Angular y simulaba algunas etapas del proceso de compilación, lo que ayuda a comprender las características de la estructura interna de este mecanismo. Cabe señalar que generalmente lo que el autor de este material habla como un "compilador" se llama un "motor de renderizado".
Cuando Uri escuchó que se lanzó una nueva versión del compilador Angular, llamada Ivy, inmediatamente quiso echar un vistazo más de cerca y descubrir qué había cambiado en comparación con la versión anterior. Aquí, como antes, el compilador recibe las plantillas y componentes creados por Angular, que se convierten en código HTML y JavaScript normal que es comprensible para Chrome y otros navegadores.

Si compara la nueva versión del compilador con la anterior, resulta que Ivy usa el algoritmo de sacudida de árboles. Esto significa que el compilador elimina automáticamente los fragmentos de código no utilizados (esto también se aplica al código angular), reduciendo el tamaño de los paquetes de proyectos. Otra mejora se refiere al hecho de que ahora cada archivo se compila de forma independiente, lo que reduce el tiempo de recompilación. En pocas palabras, entonces, gracias al nuevo
compilador, obtenemos ensamblajes más pequeños, recompilación acelerada de proyectos, código listo más simple.
Comprender cómo funciona el compilador es interesante en sí mismo (al menos el autor del material lo espera), pero también ayuda a comprender mejor los mecanismos internos de Angular. Esto conduce a la mejora de las habilidades del "pensamiento angular", que, a su vez, le permite utilizar de manera más eficaz este marco para el desarrollo web.
Por cierto, ¿sabes por qué el nuevo compilador se llamaba Ivy? El hecho es que esta palabra suena como una combinación de letras "IV", leídas en voz alta, que representa el número 4, escrito en números romanos. "4" es la cuarta generación de compiladores angulares.
Aplicación de hiedra
Ivy todavía está en proceso de desarrollo intensivo, este proceso se puede observar
aquí . Aunque el compilador en sí aún no es adecuado para el uso en combate, la abstracción de RendererV3, que utilizará, ya es bastante funcional y viene con Angular 6.x.
Aunque Ivy aún no está listo, aún podemos echar un vistazo a los resultados de su trabajo. Como hacerlo Al crear un nuevo proyecto angular:
ng new ivy-internals
Después de eso, debe habilitar Ivy agregando las siguientes líneas al archivo
tsconfig.json
ubicado en la nueva carpeta del proyecto:
"angularCompilerOptions": { "enableIvy": true }
Y finalmente, comenzamos el compilador ejecutando el comando
ngc
en la carpeta del proyecto recién creado:
node_modules/.bin/ngc
Eso es todo Ahora puede examinar el código generado ubicado en la
dist/out-tsc
. Por ejemplo, eche un vistazo al siguiente fragmento de la plantilla
AppComponent
:
<div style="text-align:center"> <h1> Welcome to {{ title }}! </h1> <img width="300" alt="Angular Logo" src="…"> </div>
Aquí hay algunos enlaces para ayudarlo a comenzar:
El código generado para esta plantilla se puede encontrar mirando el
dist/out-tsc/src/app/app.component.js
:
i0.ɵE(0, , _c0); i0.ɵE(1, ); i0.ɵT(2); i0.ɵe(); i0.ɵE(3, , _c1); i0.ɵe(); i0.ɵe(); i0.ɵE(4, ); i0.ɵT(5, ); i0.ɵe();
Es en este tipo de JavaScript que Ivy transforma la plantilla del componente. Así es como se hizo lo mismo en la versión anterior del compilador:
Código producido por una versión anterior del compilador angularExiste la sensación de que el código que genera Ivy es mucho más simple. Puede experimentar con la plantilla del componente (se encuentra en
src/app/app.component.html
), compilarla nuevamente y ver cómo los cambios realizados afectarán el código generado.
Analizar código generado
Intentemos analizar el código generado y ver exactamente qué acciones realiza. Por ejemplo, busquemos una respuesta a una pregunta sobre el significado de llamadas como
i0.ɵE
e
i0.ɵT
Si observa el comienzo del archivo generado, allí encontraremos la siguiente expresión:
var i0 = require("@angular/core");
Entonces
i0
es solo el módulo central Angular, y todas estas son las funciones exportadas por Angular. La letra
ɵ
utilizada por el equipo de desarrollo de Angular para indicar que algunos métodos están destinados únicamente a proporcionar
mecanismos de marco
interno , es decir, los usuarios no deben llamarlos directamente, ya que la invariabilidad de la API de estos métodos no está garantizada cuando se lanzan nuevas versiones de Angular (de hecho, Diría que sus API están casi garantizadas a cambiar).
Por lo tanto, todos estos métodos son API privadas exportadas por Angular. Es fácil descubrir su funcionalidad abriendo el proyecto en VS Code y analizando la información sobre herramientas:
Análisis de código en código VSAunque aquí se analiza un archivo JavaScript, VS Code utiliza información de tipo de TypeScript para identificar la firma de la llamada y buscar documentación para un método en particular. Si, después de haber seleccionado el nombre del método, use la combinación Ctrl + clic (Cmd + clic en Mac), descubriremos que el nombre real de este método es
elementStart
.
Esta técnica permitió descubrir que el nombre del método
ɵT
es
text
, el nombre del método
ɵe
es
ɵe
. Armados con este conocimiento, podemos "traducir" el código generado, convirtiéndolo en algo que sea más conveniente para leer. Aquí hay un pequeño fragmento de tal "traducción":
var core = require("angular/core"); //... core.elementStart(0, "div", _c0); core.elementStart(1, "h1"); core.text(2); core. (); core.elementStart(3, "img", _c1); core.elementEnd(); core.elementEnd(); core.elementStart(4, "h2"); core.text(5, "Here are some links to help you start: "); core.elementEnd();
Y, como ya se mencionó, este código corresponde al siguiente texto de la plantilla HTML:
<div style="text-align:center"> <h1> Welcome to {{ title }}! </h1> <img width="300" alt="Angular Logo" src="…"> </div>
Aquí hay algunos enlaces para ayudarlo a comenzar:
Después de analizar todo esto, es fácil notar lo siguiente:
- Cada etiqueta HTML de apertura tiene una llamada a
core.elementStart()
. - Las etiquetas de cierre corresponden a llamadas a
core.elementEnd()
. - Los nodos de texto corresponden a llamadas a
core.text()
.
El primer argumento para los métodos
elementStart
y
text
es un número cuyo valor aumenta con cada llamada. Probablemente representa un índice en alguna matriz en la que Angular almacena enlaces a elementos creados.
El tercer argumento también se pasa al método
elementStart
. Habiendo estudiado los materiales anteriores, podemos concluir que el argumento es opcional y contiene una lista de atributos para el nodo DOM. Puede verificar esto mirando el valor de
_c0
y descubriendo que contiene una lista de atributos y sus valores para el elemento
div
:
var _c0 = ["style", "text-align:center"];
NgComponentDef Note
Hasta ahora, hemos analizado la parte del código generado que es responsable de representar la plantilla para el componente. Este código se encuentra realmente en un fragmento de código más grande que se asigna a
AppComponent.ngComponentDef
, una propiedad estática que contiene todos los metadatos sobre el componente, como los selectores CSS, su estrategia de detección de cambios (si se especifica uno) y la plantilla. Si sientes ansias de aventura, ahora puedes descubrir de forma independiente cómo funciona, aunque hablaremos de ello a continuación.
Hiedra casera
Ahora que, en términos generales, entendemos cómo se ve el código generado, podemos intentar crear, desde cero, nuestro propio componente utilizando la misma API RendererV3 que utiliza Ivy.
El código que vamos a crear será similar al código que produce el compilador, pero lo haremos para que sea más fácil de leer.
Comencemos escribiendo un componente simple, y luego traduzca manualmente en un código similar al obtenido por Ivy:
import { Component } from '@angular/core'; @Component({ selector: 'manual-component', template: '<h2><font color="#3AC1EF">Hello, Component</font></h2>', }) export class ManualComponent { }
El compilador toma la entrada del decorador
@component
como
@component
, crea instrucciones y luego lo organiza todo como una propiedad estática de la clase componente. Por lo tanto, para simular la actividad de Ivy, eliminamos el decorador
@component
y lo reemplazamos con la propiedad estática
ngComponent
:
import * as core from '@angular/core'; export class ManualComponent { static ngComponentDef = core.ɵdefineComponent({ type: ManualComponent, selectors: [['manual-component']], factory: () => new ManualComponent(), template: (rf: core.ɵRenderFlags, ctx: ManualComponent) => {
Definimos metadatos para el componente compilado llamando a
ɵdefineComponent
. Los metadatos incluyen el tipo de componente (utilizado anteriormente para implementar la dependencia), el selector (o selectores) CSS que llamará a este componente (en nuestro caso,
manual-component
es el nombre del componente en la plantilla HTML), la fábrica que devuelve la nueva instancia componente, y luego la función que define la plantilla para el componente. Esta plantilla muestra una representación visual del componente y la actualiza cuando cambian las propiedades del componente. Para crear esta plantilla, utilizaremos los métodos que encontramos arriba:
ɵE
,
ɵe
y
ɵT
.
template: (rf: core.ɵRenderFlags, ctx: ManualComponent) => { core.ɵE(0, 'h2'); // h2 core.ɵT(1, 'Hello, Component'); // core.ɵe(); // h2 },
En esta etapa, no usamos los parámetros
rf
o
ctf
proporcionados por nuestra función de plantilla. Volveremos a ellos. Pero primero, veamos cómo mostrar nuestro primer componente casero en la pantalla.
Primera aplicación
Para mostrar los componentes en la pantalla, Angular exporta un método llamado
ɵrenderComponent
. Todo lo que necesita hacer es verificar que el archivo
index.html
contenga una etiqueta HTML correspondiente al selector de elementos,
<manual-component>
, y luego agregue lo siguiente al final del archivo:
core.ɵrenderComponent(ManualComponent);
Eso es todo Ahora tenemos una mínima aplicación Angular hecha a sí misma que consta de solo 16 líneas de código. Puede experimentar con la aplicación terminada en
StackBlitz .
Cambiar mecanismo de detección
Entonces, tenemos un ejemplo de trabajo. ¿Puedes agregarle interactividad? Digamos, ¿qué tal algo interesante, como usar el sistema de detección de cambios de Angular aquí?
Cambie el componente para que el usuario pueda personalizar el texto de bienvenida. Es decir, en lugar de que el componente siempre muestre el texto
Hello, Component
, vamos a permitir que el usuario cambie la parte del texto que viene después de
Hello
.
Comenzamos agregando la propiedad de
name
y un método para actualizar el valor de esta propiedad a la clase de componente:
export class ManualComponent { name = 'Component'; updateName(newName: string) { this.name = newName; }
Si bien todo esto no parece particularmente impresionante, pero lo más interesante está por delante.
A continuación, editaremos la función de plantilla para que, en lugar de texto inmutable, muestre el contenido de la propiedad de
name
:
template: (rf: core.ɵRenderFlags, ctx: ManualComponent) => { if (rf & 1) { // : core.ɵE(0, 'h2'); core.ɵT(1, 'Hello, '); core.ɵT(2); // <-- name core.ɵe(); } if (rf & 2) { // : core.ɵt(2, ctx.name); // ctx - } },
Es posible que haya notado que incluimos las instrucciones de la plantilla en
if
que verifican los valores de
rf
. Angular utiliza este parámetro para indicar si el componente se está creando por primera vez (se establecerá el bit menos significativo), o simplemente necesitamos actualizar el contenido dinámico en el proceso de detección de cambios (esto es a lo
if
se dirige la segunda
if
).
Entonces, cuando el componente se muestra por primera vez, creamos todos los elementos, y luego, cuando se detectan cambios, solo actualizamos lo que podría cambiar. El método interno
ɵt
es responsable de esto (observe la letra minúscula
t
), que corresponde a la función
textBinding
exportada por Angular:
Función textBindingEntonces, el primer parámetro es el índice del elemento a actualizar, el segundo es el valor. En este caso, creamos un elemento de texto vacío con el índice 2 con el comando
core.ɵT(2);
. Actúa como un marcador de posición para el
name
. Lo actualizamos con el comando
core.ɵt(2, ctx.name);
al detectar un cambio en la variable correspondiente.
Por el momento, la salida de este componente seguirá mostrando el texto
Hello, Component
, aunque podemos cambiar el valor de la propiedad de
name
, lo que provocará un cambio en el texto en la pantalla.
Para que la aplicación se vuelva verdaderamente interactiva, agregaremos aquí un campo de entrada de datos con un detector de eventos que llama al método de componente
updateName()
:
template: (rf: core.ɵRenderFlags, ctx: ManualComponent) => { if (rf & 1) { core.ɵE(0, 'h2'); core.ɵT(1, 'Hello, '); core.ɵT(2); core.ɵe(); core.ɵT(3, 'Your name: '); core.ɵE(4, 'input'); core.ɵL('input', $event => ctx.updateName($event.target.value)); core.ɵe(); }
El enlace de eventos se realiza en la línea
core.ɵL('input', $event => ctx.updateName($event.target.value));
. A saber, el método
ɵL
responsable de configurar el detector de eventos para los elementos declarados más recientes. El primer argumento es el nombre del evento (en este caso, la
input
es el evento que se genera cuando el contenido del elemento
<input>
cambia), el segundo argumento es una devolución de llamada. Esta devolución de llamada acepta datos de eventos como argumento. Luego extraemos el valor actual del elemento objetivo del evento, es decir, del elemento
<input>
, y lo pasamos a la función en el componente.
El código anterior es equivalente a escribir el siguiente HTML en una plantilla:
Your name: <input (input)="updateName($event.target.value)" />
Ahora puede editar el contenido del elemento
<input>
y observar cómo cambia el texto en el componente. Sin embargo, el campo de entrada no se completa cuando se carga el componente. Para que todo funcione de esta manera, debe agregar una instrucción más al código de función de la plantilla, que se ejecuta cuando se detecta un cambio:
template: (rf: core.ɵRenderFlags, ctx: ManualComponent) => { if (rf & 1) { ... } if (rf & 2) { core.ɵt(2, ctx.name); core.ɵp(4, 'value', ctx.name); } }
Aquí usamos otro método incorporado del sistema de representación,
ɵp
, que actualiza la propiedad de un elemento con un índice dado. En este caso, el índice 4 se pasa al método, que es el índice que se asigna al elemento de
input
, y le
ctx.name
método que coloque el valor
ctx.name
en la propiedad
value
de este elemento.
Ahora nuestro ejemplo está finalmente listo. Implementamos, desde cero, el enlace de datos bidireccional utilizando la API del sistema de renderizado Ivy. Esto es simplemente genial.
Aquí puedes experimentar con el código terminado.
Ahora estamos familiarizados con la mayoría de los componentes básicos del nuevo compilador Ivy. Sabemos cómo crear elementos y nodos de texto, cómo vincular propiedades y configurar escuchas de eventos, y cómo usar el sistema de detección de cambios.
Acerca de los bloques * ngIf y * ngFor
Antes de terminar el estudio de Ivy, veamos otro tema interesante. A saber, hablemos sobre cómo funciona el compilador con subpatrones. Estos son los patrones que se usan para los
*ngIf
o
*ngFor
. Se procesan de manera especial. Veamos cómo usar
*ngIf
en nuestro código de plantilla casero.
Primero debe instalar el paquete npm
@angular/common
: aquí es donde
*ngIf
. A continuación, debe importar la directiva desde este paquete:
import { NgIf } from '@angular/common';
Ahora, para poder usar
NgIf
en la plantilla, debe proporcionarle algunos metadatos, ya que el módulo
@angular/common
no se compiló con Ivy (al menos al escribir el material, y en el futuro esto probablemente cambiará de introducción de
ngcc ).
Vamos a utilizar el método
ɵdefineDirective
, que está relacionado con el método familiar
ɵdefineComponent
. Define metadatos para directivas:
(NgIf as any).ngDirectiveDef = core.ɵdefineDirective({ type: NgIf, selectors: [['', 'ngIf', '']], factory: () => new NgIf(core.ɵinjectViewContainerRef(), core.ɵinjectTemplateRef()), inputs: {ngIf: 'ngIf', ngIfThen: 'ngIfThen', ngIfElse: 'ngIfElse'} });
Encontré esta definición en el
código fuente angular , junto con la
ngFor
. Ahora que hemos preparado
NgIf
para usar en Ivy, podemos agregar lo siguiente a la lista de directivas para el componente:
static ngComponentDef = core.ɵdefineComponent({ directives: [NgIf], // ... });
A continuación, definimos el subpatrón solo para la partición delimitada por
*ngIf
.
Suponga que necesita mostrar una imagen. Vamos a establecer una nueva función para esta plantilla dentro de la función de plantilla:
function ifTemplate(rf: core.ɵRenderFlags, ctx: ManualComponent) { if (rf & 1) { core.ɵE(0, 'div'); core.ɵE(1, 'img', ['src', 'https://pbs.twimg.com/tweet_video_thumb/C80o289UQAAKIqp.jpg']); core.ɵe(); } }
Esta función de plantilla no es diferente de la que ya escribimos. Utiliza las mismas construcciones para crear un elemento
img
dentro de un elemento
div
.
Y finalmente, podemos poner todo junto agregando la directiva
ngIf
a la plantilla del componente:
template: (rf: core.ɵRenderFlags, ctx: ManualComponent) => { if (rf & 1) { // ... core.ɵC(5, ifTemplate, null, ['ngIf']); } if (rf & 2) { // ... core.ɵp(5, 'ngIf', (ctx.name === 'Igor')); } function ifTemplate(rf: core.ɵRenderFlags, ctx: ManualComponent) { // ... } },
Tenga en cuenta la llamada al nuevo método al comienzo del código (
core.ɵC(5, ifTemplate, null, ['ngIf']);
). Declara un nuevo elemento contenedor, es decir, un elemento que tiene una plantilla. El primer argumento es el índice del elemento, ya hemos visto dichos índices. El segundo argumento es la función del subpatrón que acabamos de definir. Se utilizará como plantilla para el elemento contenedor. El tercer parámetro es el nombre de la etiqueta para el elemento, que no tiene sentido aquí, y finalmente, hay una lista de directivas y atributos asociados con este elemento. Aquí es donde entra
ngIf
.
En la línea
core.ɵp(5, 'ngIf', (ctx.name === 'Igor'));
el estado del elemento se actualiza vinculando el atributo
ngIf
al valor de la expresión lógica
ctx.name === 'Igor'
. Esto verifica si la propiedad de
name
del componente es igual a
Igor
.
El código anterior es equivalente al siguiente código HTML:
<div *ngIf="name === 'Igor'"> <img align="center" src="..."> </div>
Aquí se puede observar que el nuevo compilador no produce el código más compacto, pero no es tan malo en comparación con lo que es ahora.
Puedes experimentar con un nuevo ejemplo
aquí . Para ver la sección
NgIf
en acción, ingrese el nombre
Igor
en el campo
Your name
.
Resumen
Prácticamente viajamos por las capacidades del compilador Ivy. Esperemos que este viaje haya despertado su interés en una mayor exploración de Angular. Si es así, ahora tienes todo lo que necesitas para experimentar con Ivy. Ahora sabe cómo "traducir" plantillas a JavaScript, cómo acceder a los mismos mecanismos angulares que utiliza Ivy sin utilizar este compilador. Supongo que todo esto te dará la oportunidad de explorar los nuevos mecanismos angulares tan profundo como quieras.
Aquí ,
aquí y
aquí : tres materiales en los que puede encontrar información útil sobre Ivy. Y
aquí está el código fuente de Render3.
Estimados lectores! ¿Cómo te sientes acerca de las nuevas características de Ivy?
