“
Saya pikir kompiler sangat menarik ,” kata Uri Shaked, penulis materi, yang kami terbitkan hari ini. Tahun lalu, ia menulis
sebuah artikel yang membahas tentang rekayasa balik kompiler Angular dan mensimulasikan beberapa tahapan proses kompilasi, yang membantu untuk memahami fitur-fitur struktur internal mekanisme ini. Perlu dicatat bahwa biasanya apa yang dibicarakan oleh penulis materi ini sebagai "kompiler" disebut sebagai "mesin rendering".
Ketika Uri mendengar bahwa versi baru dari kompiler Angular, yang disebut Ivy, dirilis, ia segera ingin melihat lebih dekat dan mencari tahu apa yang telah berubah di dalamnya dibandingkan dengan versi yang lama. Di sini, seperti sebelumnya, kompiler menerima templat dan komponen yang dibuat oleh Angular, yang dikonversi ke kode HTML dan JavaScript biasa yang dapat dimengerti oleh Chrome dan browser lainnya.

Jika Anda membandingkan versi baru dari kompiler dengan yang sebelumnya, ternyata Ivy menggunakan algoritma pengocok pohon. Ini berarti bahwa kompiler secara otomatis menghapus fragmen kode yang tidak digunakan (ini juga berlaku untuk kode Angular), mengurangi ukuran bundel proyek. Peningkatan lainnya menyangkut fakta bahwa sekarang setiap file dikompilasi secara independen, yang mengurangi waktu kompilasi. Singkatnya, kemudian, berkat
kompiler baru
, kami mendapatkan majelis yang lebih kecil, percepatan kompilasi proyek, kode siap pakai yang lebih sederhana.
Memahami cara kerja kompiler menarik dalam dan dari dirinya sendiri (setidaknya penulis materi berharap demikian), tetapi juga membantu untuk lebih memahami mekanisme internal Angular. Ini mengarah pada peningkatan keterampilan "Pemikiran sudut", yang, pada gilirannya, memungkinkan Anda untuk lebih efektif menggunakan kerangka kerja ini untuk pengembangan web.
Omong-omong, apakah Anda tahu mengapa kompiler baru bernama Ivy? Faktanya adalah bahwa kata ini terdengar seperti kombinasi huruf "IV", dibaca keras-keras, yang mewakili angka 4, ditulis dalam angka Romawi. "4" adalah generasi keempat dari kompiler Angular.
Aplikasi Ivy
Ivy masih dalam proses pengembangan intensif, proses ini bisa diamati di
sini . Meskipun kompiler itu sendiri belum cocok untuk penggunaan tempur, abstraksi RendererV3, yang akan digunakannya, sudah cukup fungsional dan dilengkapi dengan Angular 6.x.
Meskipun Ivy masih belum siap, kita masih bisa melihat hasil karyanya. Bagaimana cara melakukannya? Dengan membuat proyek Angular baru:
ng new ivy-internals
Setelah itu, Anda harus mengaktifkan Ivy dengan menambahkan baris berikut ke file
tsconfig.json
terletak di folder proyek baru:
"angularCompilerOptions": { "enableIvy": true }
Dan akhirnya, kita memulai kompilator dengan menjalankan perintah
ngc
di folder proyek yang baru dibuat:
node_modules/.bin/ngc
Itu saja. Sekarang Anda dapat memeriksa kode yang dihasilkan yang terletak di
dist/out-tsc
. Misalnya, lihat fragmen
AppComponent
:
<div style="text-align:center"> <h1> Welcome to {{ title }}! </h1> <img width="300" alt="Angular Logo" src="…"> </div>
Berikut ini beberapa tautan untuk membantu Anda memulai:
Kode yang dihasilkan untuk templat ini dapat ditemukan dengan melihat file
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();
Dalam jenis kode JavaScript inilah Ivy mengubah templat komponen. Berikut ini cara melakukan hal yang sama pada versi kompiler sebelumnya:
Kode diproduksi oleh versi sebelumnya dari kompiler AngularAda perasaan bahwa kode yang dihasilkan Ivy jauh lebih sederhana. Anda dapat bereksperimen dengan templat komponen (terletak di
src/app/app.component.html
), kompilasi lagi dan lihat bagaimana perubahan yang dilakukan terhadapnya akan memengaruhi kode yang dihasilkan.
Parse kode yang dihasilkan
Mari kita coba mem-parsing kode yang dihasilkan dan melihat tindakan apa yang dilakukannya. Sebagai contoh, mari kita mencari jawaban untuk pertanyaan tentang arti panggilan seperti
i0.ɵE
dan
i0.ɵT
Jika Anda melihat di awal file yang dihasilkan, di sana kami akan menemukan ekspresi berikut:
var i0 = require("@angular/core");
Jadi
i0
hanyalah modul inti Angular, dan semua ini adalah fungsi yang diekspor oleh Angular. Huruf
ɵ
digunakan oleh tim pengembangan Angular untuk menunjukkan bahwa beberapa metode dimaksudkan hanya untuk menyediakan
mekanisme kerangka kerja
internal , yaitu, pengguna tidak boleh memanggil mereka secara langsung, karena invarian API API dari metode ini tidak dijamin ketika versi Angular baru dirilis (pada kenyataannya, Saya akan mengatakan bahwa API mereka hampir dijamin akan berubah).
Jadi, semua metode ini adalah API pribadi yang diekspor oleh Angular. Sangat mudah untuk mengetahui fungsionalitasnya dengan membuka proyek dalam VS Code dan menganalisis tooltips:
Analisis Kode dalam Kode VSMeskipun file JavaScript diuraikan di sini, VS Code menggunakan informasi tipe dari TypeScript untuk mengidentifikasi tanda tangan panggilan dan menemukan dokumentasi untuk metode tertentu. Jika, setelah memilih nama metode, gunakan kombinasi Ctrl + klik (Cmd + klik pada Mac), kami mengetahui bahwa nama sebenarnya dari metode ini adalah
elementStart
.
Teknik ini memungkinkan untuk mengetahui bahwa nama metode
ɵT
adalah
text
, nama metode
ɵe
adalah
ɵe
. Berbekal pengetahuan ini, kita bisa "menerjemahkan" kode yang dihasilkan, mengubahnya menjadi sesuatu yang akan lebih mudah dibaca. Berikut adalah sebagian kecil dari "terjemahan" tersebut:
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();
Dan, seperti yang telah disebutkan, kode ini sesuai dengan teks berikut dari templat HTML:
<div style="text-align:center"> <h1> Welcome to {{ title }}! </h1> <img width="300" alt="Angular Logo" src="…"> </div>
Berikut ini beberapa tautan untuk membantu Anda memulai:
Setelah menganalisis semua ini, mudah untuk memperhatikan hal-hal berikut:
- Setiap tag HTML pembuka memiliki panggilan ke
core.elementStart()
. - Tag penutup sesuai dengan panggilan ke
core.elementEnd()
. - Node teks berhubungan dengan panggilan ke
core.text()
.
Argumen pertama ke
elementStart
dan metode
text
adalah angka yang nilainya meningkat dengan setiap panggilan. Mungkin mewakili indeks dalam beberapa array di mana Angular menyimpan tautan ke elemen yang dibuat.
Argumen ketiga juga diteruskan ke metode
elementStart
. Setelah mempelajari materi di atas, kita dapat menyimpulkan bahwa argumennya opsional dan berisi daftar atribut untuk simpul DOM. Anda dapat memverifikasi ini dengan melihat nilai
_c0
dan menemukan bahwa itu berisi daftar atribut dan nilainya untuk elemen
div
:
var _c0 = ["style", "text-align:center"];
Catatan NgComponentDef
Sejauh ini, kami telah menganalisis bagian dari kode yang dihasilkan yang bertanggung jawab untuk membuat template untuk komponen tersebut. Kode ini sebenarnya terletak di bagian kode yang lebih besar yang ditugaskan ke
AppComponent.ngComponentDef
- properti statis yang berisi semua metadata tentang komponen, seperti pemilih CSS, strategi deteksi perubahannya (jika ada yang ditentukan), dan template. Jika Anda merasakan keinginan untuk berpetualang - sekarang Anda dapat secara mandiri mengetahui cara kerjanya, meskipun kami akan membicarakannya di bawah ini.
Ivy buatan sendiri
Sekarang kita, secara umum, memahami seperti apa kode yang dihasilkan, kita dapat mencoba membuat, dari awal, komponen kita sendiri menggunakan RendererV3 API yang sama seperti yang digunakan Ivy.
Kode yang akan kita buat akan mirip dengan kode yang dihasilkan kompiler, tetapi kita akan membuatnya sehingga lebih mudah dibaca.
Mari kita mulai dengan menulis komponen sederhana, dan kemudian secara manual menerjemahkannya ke dalam kode yang mirip dengan yang diperoleh oleh Ivy:
import { Component } from '@angular/core'; @Component({ selector: 'manual-component', template: '<h2><font color="#3AC1EF">Hello, Component</font></h2>', }) export class ManualComponent { }
Kompiler mengambil input dekorator
@component
sebagai
@component
, membuat instruksi, dan mengatur semuanya sebagai properti statis dari kelas komponen. Oleh karena itu, untuk mensimulasikan aktivitas Ivy, kami menghapus dekorator
@component
dan menggantinya dengan properti
ngComponent
statis:
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) => {
Kami mendefinisikan metadata untuk komponen yang dikompilasi dengan memanggil
ɵdefineComponent
. Metadata termasuk jenis komponen (digunakan sebelumnya untuk menerapkan ketergantungan), pemilih CSS (atau penyeleksi) yang akan memanggil komponen ini (dalam kasus kami,
manual-component
adalah nama komponen dalam templat HTML), pabrik yang mengembalikan instance baru komponen, dan kemudian fungsi yang menentukan templat untuk komponen. Template ini menampilkan representasi visual dari komponen dan memperbaruinya ketika sifat-sifat komponen berubah. Untuk membuat templat ini, kami akan menggunakan metode yang kami temukan di atas:
ɵE
,
ɵe
dan
ɵT
.
template: (rf: core.ɵRenderFlags, ctx: ManualComponent) => { core.ɵE(0, 'h2'); // h2 core.ɵT(1, 'Hello, Component'); // core.ɵe(); // h2 },
Pada tahap ini, kami tidak menggunakan parameter
rf
atau
ctf
disediakan oleh fungsi templat kami. Kami akan kembali kepada mereka. Tapi pertama-tama, mari kita lihat cara menampilkan komponen buatan kami yang pertama di layar.
Aplikasi pertama
Untuk menampilkan komponen di layar, Angular mengekspor metode yang disebut
ɵrenderComponent
. Yang perlu Anda lakukan adalah memeriksa apakah file
index.html
berisi tag HTML yang sesuai dengan pemilih elemen,
<manual-component>
, dan kemudian menambahkan berikut ini di akhir file:
core.ɵrenderComponent(ManualComponent);
Itu saja. Sekarang kami memiliki aplikasi Angular minimal buatan sendiri yang hanya terdiri dari 16 baris kode. Anda dapat bereksperimen dengan aplikasi yang sudah selesai di
StackBlitz .
Ubah mekanisme deteksi
Jadi, kami punya contoh yang bagus. Bisakah Anda menambahkan interaktivitas padanya? Katakan, bagaimana dengan sesuatu yang menarik, seperti menggunakan sistem deteksi perubahan Angular di sini?
Ubah komponen sehingga pengguna dapat menyesuaikan teks sambutan. Artinya, alih-alih komponen selalu menampilkan teks
Hello, Component
, kami akan membiarkan pengguna mengubah bagian teks yang datang setelah
Hello
.
Kami mulai dengan menambahkan properti
name
dan metode untuk memperbarui nilai properti ini ke kelas komponen:
export class ManualComponent { name = 'Component'; updateName(newName: string) { this.name = newName; }
Meskipun semua ini tidak terlihat sangat mengesankan, tetapi yang paling menarik adalah di depan.
Selanjutnya, kita akan mengedit fungsi templat sehingga, alih-alih teks yang tak berubah, ia menampilkan konten properti
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 - } },
Anda mungkin telah memperhatikan bahwa kami membungkus instruksi template
if
yang memeriksa nilai
rf
. Parameter ini digunakan oleh Angular untuk menunjukkan apakah komponen sedang dibuat untuk pertama kalinya (bit paling tidak signifikan akan
ditetapkan ), atau kita hanya perlu memperbarui konten dinamis dalam proses mendeteksi perubahan (inilah yang dimaksudkan oleh pernyataan kedua
if
ditujukan).
Jadi, ketika komponen ditampilkan untuk pertama kalinya, kami membuat semua elemen, dan kemudian, ketika perubahan terdeteksi, kami hanya memperbarui apa yang bisa berubah. Metode internal responsiblet bertanggung jawab untuk ini (perhatikan huruf kecil
t
), yang sesuai dengan fungsi
textBinding
diekspor oleh Angular:
Fungsi textBindingJadi, parameter pertama adalah indeks elemen yang akan diperbarui, yang kedua adalah nilainya. Dalam hal ini, kami membuat elemen teks kosong dengan indeks 2 dengan inti perintah.ɵT
core.ɵT(2);
. Ini bertindak sebagai pengganti untuk
name
. Kami memperbaruinya dengan command
core.ɵt(2, ctx.name);
setelah mendeteksi perubahan dalam variabel yang sesuai.
Saat ini, output dari komponen ini masih akan menampilkan teks
Hello, Component
, meskipun kita dapat mengubah nilai properti
name
, yang akan mengarah pada perubahan teks pada layar.
Agar aplikasi menjadi benar-benar interaktif, kami akan menambahkan di sini bidang entri data dengan pendengar acara yang memanggil metode komponen
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(); }
Pengikatan acara dilakukan dalam
core.ɵL('input', $event => ctx.updateName($event.target.value));
line.
core.ɵL('input', $event => ctx.updateName($event.target.value));
. Yaitu, metode
ɵL
bertanggung jawab untuk mengatur pendengar acara untuk elemen yang paling baru dari yang dinyatakan. Argumen pertama adalah nama acara (dalam hal ini,
input
adalah acara yang dimunculkan ketika konten elemen
<input>
berubah), argumen kedua adalah panggilan balik. Panggilan balik ini menerima data acara sebagai argumen. Lalu kami mengekstrak nilai saat ini dari elemen target acara, yaitu dari elemen
<input>
, dan meneruskannya ke fungsi di komponen.
Kode di atas setara dengan menulis HTML berikut di templat:
Your name: <input (input)="updateName($event.target.value)" />
Sekarang Anda dapat mengedit konten elemen
<input>
dan mengamati bagaimana teks dalam komponen berubah. Namun, bidang input tidak diisi ketika komponen dimuat. Agar semuanya berfungsi dengan cara ini, Anda perlu menambahkan satu instruksi lagi ke kode fungsi templat, dijalankan ketika perubahan terdeteksi:
template: (rf: core.ɵRenderFlags, ctx: ManualComponent) => { if (rf & 1) { ... } if (rf & 2) { core.ɵt(2, ctx.name); core.ɵp(4, 'value', ctx.name); } }
Di sini kita menggunakan metode
ɵp
lain dari sistem render,
ɵp
, yang memperbarui properti elemen dengan indeks yang diberikan. Dalam hal ini, indeks 4 diteruskan ke metode, yang merupakan indeks yang ditugaskan ke elemen
input
, dan kami
ctx.name
metode untuk meletakkan nilai
ctx.name
di properti
value
elemen ini.
Sekarang contoh kita akhirnya siap. Kami menerapkan, dari awal, pengikatan data dua arah menggunakan sistem rendering Ivy API. Ini bagus sekali.
Di sini Anda dapat bereksperimen dengan kode yang sudah jadi.
Kita sekarang akrab dengan sebagian besar blok bangunan dasar dari kompiler Ivy baru. Kami tahu cara membuat elemen dan simpul teks, cara mengikat properti dan mengonfigurasi pendengar acara, dan cara menggunakan sistem deteksi perubahan.
Tentang * ngIf dan * ngFor blok
Sebelum kita menyelesaikan studi Ivy, mari kita lihat topik menarik lainnya. Yaitu, mari kita bicara tentang cara kerja kompiler dengan subpatterns. Ini adalah pola yang digunakan untuk
*ngIf
atau
*ngFor
. Mereka diproses dengan cara khusus. Mari kita lihat cara menggunakan
*ngIf
dalam kode templat buatan kami.
Pertama, Anda perlu menginstal paket npm
@angular/common
- di sinilah
*ngIf
. Selanjutnya, Anda perlu mengimpor arahan dari paket ini:
import { NgIf } from '@angular/common';
Sekarang, untuk dapat menggunakan
NgIf
dalam templat, Anda perlu menyediakan beberapa metadata, karena modul
@angular/common
tidak dikompilasi dengan Ivy (setidaknya saat menulis materi, dan di masa depan ini mungkin akan berubah dari pengenalan
ngcc ).
Kita akan menggunakan metode
ɵdefineDirective
, yang terkait dengan metode
ɵdefineComponent
. Ini mendefinisikan metadata untuk arahan:
(NgIf as any).ngDirectiveDef = core.ɵdefineDirective({ type: NgIf, selectors: [['', 'ngIf', '']], factory: () => new NgIf(core.ɵinjectViewContainerRef(), core.ɵinjectTemplateRef()), inputs: {ngIf: 'ngIf', ngIfThen: 'ngIfThen', ngIfElse: 'ngIfElse'} });
Saya menemukan definisi ini dalam
kode sumber Angular , bersama dengan
ngFor
. Sekarang kita telah menyiapkan
NgIf
untuk digunakan di Ivy, kita dapat menambahkan berikut ini ke daftar arahan untuk komponen:
static ngComponentDef = core.ɵdefineComponent({ directives: [NgIf], // ... });
Selanjutnya, kita mendefinisikan subpattern hanya untuk partisi yang dibatasi oleh
*ngIf
.
Misalkan Anda perlu menampilkan gambar. Mari kita atur fungsi baru untuk template ini di dalam fungsi template:
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(); } }
Fungsi template ini tidak berbeda dari yang sudah kami tulis. Ia menggunakan konstruksi yang sama untuk membuat elemen
img
di dalam elemen
div
.
Dan akhirnya, kita bisa menggabungkan semuanya dengan menambahkan direktif
ngIf
ke templat komponen:
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) { // ... } },
Catat panggilan ke metode baru di awal kode (
core.ɵC(5, ifTemplate, null, ['ngIf']);
). Ini mendeklarasikan elemen kontainer baru, yaitu elemen yang memiliki template. Argumen pertama adalah indeks elemen, kita telah melihat indeks tersebut. Argumen kedua adalah fungsi subpattern yang baru saja kita definisikan. Ini akan digunakan sebagai templat untuk elemen wadah. Parameter ketiga adalah nama tag untuk elemen, yang tidak masuk akal di sini, dan akhirnya, ada daftar arahan dan atribut yang terkait dengan elemen ini. Di sinilah
ngIf
masuk.
Pada baris
core.ɵp(5, 'ngIf', (ctx.name === 'Igor'));
status elemen diperbarui dengan mengikat atribut
ngIf
ke nilai ekspresi logis
ctx.name === 'Igor'
. Ini memeriksa untuk melihat apakah properti
name
komponen sama dengan
Igor
.
Kode di atas setara dengan kode HTML berikut:
<div *ngIf="name === 'Igor'"> <img align="center" src="..."> </div>
Di sini dapat dicatat bahwa kompiler baru tidak menghasilkan kode yang paling ringkas, tetapi tidak terlalu buruk dibandingkan dengan apa yang sekarang.
Anda dapat bereksperimen dengan contoh baru di
sini . Untuk melihat bagian
NgIf
dalam aksi, masukkan nama
Igor
di bidang
Your name
.
Ringkasan
Kami cukup banyak bepergian di sekitar kemampuan kompiler Ivy. Semoga perjalanan ini memicu minat Anda untuk mengeksplorasi Angular lebih lanjut. Jika demikian, maka sekarang Anda memiliki semua yang Anda butuhkan untuk bereksperimen dengan Ivy. Sekarang Anda tahu cara "menerjemahkan" template ke JavaScript, cara mengakses mekanisme Sudut yang sama yang digunakan Ivy tanpa menggunakan kompiler ini. Saya kira semua ini akan memberi Anda kesempatan untuk menjelajahi mekanisme Angular baru sedalam yang Anda inginkan.
Di sini , di
sini dan di
sini - tiga bahan di mana Anda dapat menemukan informasi yang berguna tentang Ivy. Dan di
sini adalah kode sumber untuk Render3.
Pembaca yang budiman! Bagaimana perasaan Anda tentang fitur-fitur baru Ivy?
