Sebuah studi tentang Ivy, kompiler Angular baru

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, "div", _c0); i0.ɵE(1, "h1"); i0.ɵT(2); i0.ɵe(); i0.ɵE(3, "img", _c1); i0.ɵe(); i0.ɵe(); i0.ɵE(4, "h2"); i0.ɵT(5, "Here are some links to help you start: "); 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 Angular

Ada 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 VS

Meskipun 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 textBinding

Jadi, 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?

Source: https://habr.com/ru/post/id419995/


All Articles