Million.js – Masa Depan DOM Virtual

Steker: saya bekerja pada Juta.js: <1kb virtual DOM - cepat!

TL;DR

Virtual DOM perlu memanfaatkan kompiler, sehingga perbedaan yang tidak perlu tidak terjadi.

pengantar

Baru-baru ini, saya menerbitkan beberapa artikel yang merinci paradigma DOM Virtual, implementasi, serta manfaat dan kekurangan menggunakannya. Artikel-artikel ini mendapat sambutan yang beragam, dengan beberapa pengembang setuju dengan poin-poin dalam artikel, sementara yang lain tidak setuju. Mereka berpendapat bahwa kerangka kerja berbasis kompilasi yang melakukan beberapa tingkat analisis statis (terutama Svelte) dikompilasi ke operasi DOM yang penting dan oleh karena itu melewati overhead mesin DOM Virtual.

Anda mungkin bertanya-tanya: Apa gunanya menggunakan Virtual DOM, jika Anda bisa menggunakan kerangka kerja berbasis kompiler seperti Svelte? Sementara analisis dan kompilasi statis adalah masa depan, DOM Virtual tidak boleh sepenuhnya dikesampingkan sebagai teknologi usang. Svelte hanya dimungkinkan jika kondisi API dibatasi, sehingga kode bersifat prediktif dan karenanya dapat dianalisis. Untuk library yang membutuhkan lebih banyak fleksibilitas, seperti React atau Vue, kondisi tidak dapat dibatasi dengan mudah dan oleh karena itu varian DOM Virtual diperlukan untuk library tersebut.

Inilah sebabnya mengapa Million.js ada—untuk menghadirkan DOM Virtual ke masa depan dengan memanfaatkan kompiler untuk analisis dan pengoptimalan statis yang membuat manipulasi DOM menjadi berkinerja dan fleksibel.

Pengoptimalan DOM Virtual

Secara tradisional, mesin Virtual DOM melakukan sejumlah besar komputasi selama proses diffing. Misalnya, saat melakukan diffing children, mesin Virtual DOM tidak hanya menghitung secara linear node mana yang perlu diperbarui, tetapi juga menentukan kemungkinan swap/pindah yang dapat dilakukan. Meskipun ini menimbulkan paling sedikit modifikasi DOM, biaya komputasinya bisa sangat besar. Bahkan dengan algoritme pembeda daftar yang sangat efisien (seperti list-diff2), kompleksitas waktu adalah O(n) dalam kasus terbaik (tidak termasuk O(n^3 dasar kompleksitas waktu untuk diffing). Ulangi ini untuk semua anak di pohon vnode dan Anda bisa bayangkan saja betapa tidak efisiennya ini.

Inilah sebabnya mengapa salah satu konsep utama untuk membuat DOM Virtual berorientasi masa depan adalah menyadari dan membangun arsitektur berdasarkan kompiler. Ini tidak hanya meningkatkan kinerja dengan memungkinkan untuk lurus O(1) operasi, tetapi juga dengan anggun kembali ke diff normal bila diperlukan. Selain itu, ukuran bundel berkurang secara signifikan, mengurangi jumlah kode yang perlu dijalankan saat runtime.

Million.js mencoba menerapkan ini dengan tiga “garis pertahanan” utama:

  1. Kunci: Tentukan identitas vnode

    Kunci berguna ketika Anda mengetahui bahwa posisi vnode, data, dan turunan tertentu tidak akan berubah di antara dua status. Kunci dapat diberikan oleh pengguna secara manual, atau dihasilkan oleh kompiler. Ini memungkinkan vnode dilewati seluruhnya, menghindari perbedaan yang tidak perlu (O(1))

  2. Bendera: Menentukan jenis konten turunan vnode.

    Bendera memungkinkan diffing untuk melewati cabang kondisi tertentu yang mahal secara komputasi. Misalnya, jika anak-anak vnode hanya berisi simpul teks, maka cukup atur textContent elemen akan jauh lebih cepat daripada membangun dan mengganti simpul teks. Million.js saat ini hanya mendukung 3 flag: NO_CHILDREN (O(1)), ONLY_TEXT_CHILDREN (O(n)), dan ANY_CHILDREN (O(n^3)).

  3. delta: Menentukan modifikasi prediktif dan konsisten dari turunan vnode.

    Delta dapat digunakan ketika tindakan mikro yang sederhana dan imperatif dapat diprediksi melalui analisis statis. Delta secara default adalah serangkaian operasi imperatif, tetapi manfaatkan algoritme diffing internal untuk mengurangi manipulasi DOM. Million.js saat ini mendukung 3 operasi dasar Delta: INSERT (O(1)), UPDATE (O(1) to O(n^3)), DELETE (O(1)).

Pengoptimalan Kompiler

Pertama, sebagian besar—jika tidak semua kompleksitas implementasi akan ada pada kompiler. Ini karena analisis statis sangat sulit untuk dilakukan agar dapat beroperasi sebagaimana dimaksud. Di bawah ini adalah daftar kemungkinan pengoptimalan, dan sama sekali bukan “analisis statis nyata”.

  • Memanfaatkan fitur Million.js:

    Cara utama untuk mengoptimalkan Million.js adalah dengan memanfaatkan fitur yang berfokus pada kompiler yang disediakannya. Ini adalah satu-satunya cara untuk mengurangi diffing dengan asumsi bahwa cakupan patch tetap konstan.

  • Prarendering + pengurangan konten dinamis

    Cara lain untuk membuat kinerja lebih baik adalah dengan tidak mempertimbangkan konten statis dengan mengurangi cakupan patching—terutama jika aplikasi Anda hanya interaktif di area tertentu. Ini bahkan lebih efisien daripada menghasilkan operasi DOM imperatif, karena manipulasi DOM bahkan tidak diperlukan! Selain itu, vnode awal harus diprarender pada halaman, sehingga halaman tidak perlu diinisialisasi sepenuhnya saat runtime.

    Bad:
    <div></div> inject <button>Click Me!</button>
    
    Good:
    <div><button>Click Me!</button></div>
    
  • Pengangkatan vnode + alat peraga statis:

    Sebuah optimasi standar untuk mengangkat vnode dan props yang statis, memungkinkan mereka untuk di-cache dan tidak menimbulkan biaya komputasi generasi. Ini paling baik diilustrasikan dengan contoh kode:

    // Without static VNode hoist
    const render = () => patch(el, m('div', undefined, [`My favorite number: ${1 + 2 + 3}`]))
    render();
    render(); // Static VNode needs to be constructed twice
    
    // With static VNode hoist
    const _s = <div>Hello World!</div>
    const render = () => patch(el, _s)
    render();
    render(); // Static VNode is used twice and cached
    
    // Without static props hoist
    const render = () => patch(el, m('div', { id: `app${1 + 2 + 3}` }))
    render();
    render(); // Static props need to be constructed twice
    
    // With static props hoist
    const _s = { id: `app${1 + 2 + 3}` };
    const render = () => patch(el, m('div', _s))
    render();
    render(); // Static props are used twice and cached
    

Catatan: Jika Anda merasa bahwa paradigma semacam ini memiliki masa depan dan bersedia memenuhi tujuan tersebut—saya sangat menyarankan Anda memeriksa Million.js dan mencoba mengerjakan sendiri implementasi kompiler.

Kesimpulan

Million.js masih jauh dari selesai, dan ada banyak pekerjaan yang harus diselesaikan. Saya harap artikel ini membawa perspektif baru untuk memikirkan DOM Virtual seiring perkembangannya di masa depan. Jangan ragu untuk mengomentari saran atau pertanyaan tersisa yang mungkin Anda miliki!