Perjalanan Figma Beralih ke TypeScript
(figma.com)- Figma memindahkan seluruh kode Skew ke TypeScript setelah bahasa internalnya, Skew, yang digunakan untuk rendering mobile dan viewer prototipe, menemui batasan dalam hal onboarding, integrasi, dan ekosistem
- Prasyarat transisi ini terbentuk berkat meluasnya dukungan WebAssembly di browser mobile, penggantian jalur inti dengan engine C++, serta pertumbuhan tim prototyping dan mobile
- Migrasi berlangsung dalam 3 tahap:
Write Skew, build Skew→Write Skew, build TypeScript→Write TypeScript, build TypeScript, dan transpiler Skew-to-TypeScript menjaga alur pengembangan tetap berjalan - Dalam proses migrasi nyata, muncul masalah kompatibilitas seperti performa destructuring array, perbedaan penghilangan pemanggilan virtual (devirtualization), urutan inisialisasi, penyambungan source map, dan ketiadaan kompilasi kondisional
- Pada akhirnya, Figma memperoleh codebase TypeScript yang lulus unit test dan memiliki performa mirip Skew, serta melihat integrasi kode internal dan eksternal, manajemen paket, dan pemanfaatan ekosistem TypeScript sebagai tugas berikutnya
Alasan berpindah dari Skew ke TypeScript
- Skew bermula dari proyek sampingan pada masa awal Figma, dan memenuhi kebutuhan untuk cepat membuat viewer prototipe yang mendukung web sekaligus mobile
- Seiring berkembang menjadi bahasa yang dikompilasi ke JavaScript, Skew menyediakan optimisasi tingkat lanjut dan waktu kompilasi cepat, tetapi biaya pemeliharaan meningkat ketika kode makin menumpuk di viewer prototipe
- Sulit dipelajari oleh karyawan baru
- Tidak mudah diintegrasikan dengan bagian codebase lainnya
- Tidak memiliki ekosistem developer di luar Figma
- Dampak yang diharapkan dari transisi ke TypeScript adalah peningkatan kecepatan pengembangan dan perluasan cakupan kolaborasi
- Menyederhanakan integrasi kode internal dan eksternal melalui import statis dan manajemen paket native
- Memanfaatkan alat yang dibuat komunitas developer besar, seperti linter, bundler, dan static analyzer
- Menggunakan fitur JavaScript modern seperti async/await serta sistem tipe yang lebih fleksibel
- Mempermudah onboarding developer baru dan menurunkan hambatan partisipasi tim lain
Kondisi yang baru-baru ini membuat transisi memungkinkan
- Saat Figma pertama kali membuat codebase mobile, browser mobile belum mendukung WebAssembly, dan memuat bundle besar dengan performa baik juga sulit dilakukan
- TypeScript juga masih berada pada tahap awal, sehingga Skew yang memiliki tipe statis dan sistem tipe lebih ketat saat itu merupakan pilihan yang lebih realistis
- WebAssembly memperoleh dukungan luas di mobile hingga 2018, dan berdasarkan pengujian Figma, pada 2020 performa mobile telah mencapai tingkat yang dapat diandalkan
- Kekuatan awal Skew adalah optimisasi compiler tradisional seperti constant folding dan penghilangan pemanggilan virtual, serta optimisasi khusus web seperti pembuatan JavaScript yang menggunakan operasi integer sungguhan
- Benchmark pada 2020 menunjukkan bahwa memuat prototipe Figma dengan TypeScript di Safari dapat menjadi hampir 2 kali lebih lambat, dan fakta bahwa Safari merupakan satu-satunya engine browser yang diizinkan di iOS menjadi faktor penghambat transisi
- Pada iOS 17.4, Apple membuka engine browser lain untuk pengguna di Uni Eropa, tetapi bagi pengguna di wilayah lain, WebKit tetap menjadi satu-satunya engine browser
- Setelah itu, banyak komponen inti engine Skew digantikan oleh komponen padanan di engine C++, sehingga risiko performa dari migrasi ke TypeScript berkurang
- Termasuk jalur kode terpanas seperti pemuatan file
- Figma memperoleh keyakinan bahwa penurunan performa akibat migrasi ke TypeScript dapat ditanggung
- Seiring tim prototyping dan mobile tumbuh menjadi organisasi yang lebih besar, Figma dapat mengalokasikan sumber daya untuk migrasi otomatis dan peningkatan developer experience
Transisi codebase dalam 3 tahap
- Pada prototipe migrasi pertama tahun 2020, performa TypeScript hampir 2 kali lebih lambat
- Setelah dukungan WebAssembly dan transisi engine mobile ke C++ sudah memadai, Figma memperbaiki prototipe yang ada selama Maker Week dan mendemonstrasikan migrasi yang berfungsi serta lulus semua pengujian
- Tujuannya adalah mengubah seluruh codebase ke TypeScript, tetapi penulisan ulang manual dapat menimbulkan risiko perlambatan pengembangan, error runtime, dan penurunan performa
- Skew dan TypeScript memiliki perbedaan semantik yang lebih besar daripada sekadar transisi antarbahasa “JavaScript bertipe”
- Di TypeScript, namespace dan class baru diinisialisasi setelah file di-import
- Jika urutan import tidak sesuai dugaan, error runtime dapat terjadi
- Di Skew, semua simbol tersedia bagi runtime di seluruh codebase saat loading
- Figma mengembangkan transpiler Skew-to-TypeScript berdasarkan pekerjaan yang dulu dimulai Evan Wallace
-
Phase 1: Menulis Skew dan membangun Skew
- Mengembangkan transpiler sambil mempertahankan proses build yang ada
- Mengecek masuk kode TypeScript yang dihasilkan ke GitHub agar developer dapat melihat bentuk codebase baru
-
Phase 2: Menulis Skew dan membangun TypeScript
- Setelah bundle TypeScript yang lulus semua unit test dihasilkan, Figma secara bertahap merilis traffic produksi ke build codebase TypeScript
- Developer masih menulis Skew, dan transpiler mengubah kode Skew menjadi TypeScript serta memperbarui kode TypeScript di GitHub
- Error tipe pada kode yang dihasilkan juga terus diperbaiki
- TypeScript tetap dapat menghasilkan bundle yang valid meski ada error tipe
-
Phase 3: Menulis TypeScript dan membangun TypeScript
- Setelah semua pengguna melewati proses build TypeScript, Figma menjadikan kode TypeScript sebagai satu-satunya sumber kebenaran untuk pengembangan
- Figma mencari saat ketika tidak ada penggabungan kode, lalu menghentikan proses pembuatan otomatis dan menghapus kode Skew dari codebase
- Pada Jumat malam, Figma menggabungkan penghapusan pembuatan otomatis dan perubahan job CI agar langsung menjalankan file TypeScript
- Selama rollout bertahap, kerusakan pada fitur Smart Animate ditemukan secara internal, dan berkat rollout berbasis gate, Figma dapat mematikan rollout, memperbaikinya, lalu meninjau ulang rencana
Masalah yang muncul di transpiler
- Compiler biasanya terdiri dari frontend dan backend
- Frontend mem-parse kode input, melakukan type checking dan syntax checking, lalu mengubahnya menjadi intermediate representation (IR)
- Backend mengubah IR menjadi bahasa lain
- Backend compiler Skew menghasilkan JavaScript yang diobfuscate dan diminify
- Transpiler adalah compiler khusus yang backend-nya menghasilkan kode yang dapat dibaca manusia, dan Figma perlu menghasilkan TypeScript yang dapat dibaca manusia dari IR Skew
- Implementasi awal relatif mudah karena banyak terinspirasi dari backend JavaScript Skew, tetapi pada tahap akhir muncul beberapa masalah yang sulit dilacak dan ditangani
-
Performa destructuring array
- Saat menyelidiki perbedaan performa offline antara Skew dan TypeScript pada sampel prototipe, Figma menemukan frame rate TypeScript lebih rendah
- Penyebabnya adalah destructuring array JavaScript
- Dalam operasi seperti
const [a, b] = function_that_returns_an_array(), JavaScript tidak langsung mengindeks array, melainkan membangun iterator dan melakukan iterasi - Figma menggunakan cara ini untuk mengambil argumen dari keyword
argumentsJavaScript, dan pada test case tertentu performanya melambat - Dengan menghasilkan kode yang langsung mengindeks array arguments alih-alih menggunakan destructuring, latensi per frame meningkat hingga 25%
-
Optimisasi penghilangan pemanggilan virtual di Skew
- Dalam kondisi tertentu, compiler Skew melakukan optimisasi devirtualization dengan mengeluarkan fungsi di dalam class dan menaikkannya menjadi fungsi global
myObject.myFunc(a, b)dapat berubah menjadimyFunc(myObject, a, b)- TypeScript tidak melakukan optimisasi ini
- Kerusakan Smart Animate terjadi ketika
myObjectbernilai null - Pemanggilan yang sudah di-devirtualize berjalan normal, tetapi pemanggilan non-devirtualized menimbulkan exception akses null
- Figma menambahkan logging ke semua fungsi yang dapat ikut dalam devirtualization untuk menemukan lokasi pemanggilan dengan masalah serupa
- Setelah mengaktifkan logging dalam waktu singkat, Figma menganalisis log produksi dan memperbaiki lokasi pemanggilan yang bermasalah
-
Perbedaan urutan inisialisasi
- Di Skew, urutan deklarasi tidak perlu diperhatikan meski variabel, class, namespace, atau definisi fungsi dideklarasikan di mana pun dalam kode
- Di TypeScript, urutan inisialisasi variabel global atau definisi class penting
- Menginisialisasi variabel static class sebelum definisi class menjadi error compile time
- Transpiler awal tidak menggunakan namespace dan meratakan semua fungsi ke global scope untuk mempertahankan perilaku yang mirip Skew
- Kode yang dihasilkan sulit dibaca, lalu transpiler kemudian diperbaiki agar mengeluarkan kode TypeScript dalam urutan yang benar dan menambahkan kembali namespace demi keterbacaan
- Pada akhirnya, Figma membuat transpiler yang dapat mengompilasi kode TypeScript yang lulus unit test dan sesuai dengan performa Skew
- Beberapa masalah kecil diselesaikan dengan memperbaiki kode sumber Skew secara manual atau diperbaiki setelah transisi ke TypeScript, alih-alih menambahkan perbaikan baru ke transpiler
Menjaga pengalaman debugging dengan source map
- Figma sangat memperhatikan source map agar developer tetap dapat melakukan debugging tanpa gangguan selama transisi ke TypeScript
- Browser debugger hanya memahami JavaScript, tetapi developer memasang breakpoint di source Skew atau TypeScript
- Source map menghubungkan lokasi JavaScript yang dikompilasi dengan lokasi source asli
- Sebagai contoh, mapping seperti
helper → c,myInt → a,arrayOfInts → bdimungkinkan
- Sebagai contoh, mapping seperti
- Umumnya, satu file source map berekstensi
.mapterhubung dengan bundle JavaScript akhir - Infrastruktur yang ada menghasilkan source map Skew → JavaScript, tetapi pada Phase 2 pipeline berubah menjadi alur Skew → TypeScript → bundling esbuild
- Jika source map lama digunakan apa adanya, mapping antara JavaScript dan kode Skew akan meleset, sehingga developer tidak dapat melakukan debugging
- Proses build baru menyusun source map dalam 3 tahap
- Step 1: esbuild menghasilkan source map TypeScript → JavaScript
ts-to-js.map - Step 2: transpiler menghasilkan source map Skew → TypeScript untuk setiap file Skew
- Step 3: Kedua source map dikomposisikan untuk menghasilkan source map akhir Skew → JavaScript
- Step 1: esbuild menghasilkan source map TypeScript → JavaScript
- Dengan source map akhir, bundle JavaScript baru dapat dipetakan ke kode Skew, sehingga developer experience tetap terjaga pada Phase 2
Menangani kompilasi kondisional di TypeScript
- Skew mendukung kompilasi kondisional melalui pernyataan
ifdi level teratas dan opsidefinesyang diteruskan ke compiler - Dengan fitur ini, Figma dapat membuat bundle berbeda dari codebase yang sama
- Bundle aktual yang didistribusikan ke pengguna
- Bundle khusus unit test
- Fungsi atau class yang menggunakan implementasi berbeda pada build debug atau release
- Karena TypeScript tidak memiliki kompilasi kondisional, Figma menjalankan kompilasi kondisional pada tahap bundling menggunakan
definesdan fitur dead code elimination milik esbuild setelah type checking - Dalam cara ini,
definestidak dapat memengaruhi type checking- Kode seperti
testOnlyFunctionyang hanya ada saatBUILD == "TEST"tidak dapat tetap ada begitu saja di TypeScript
- Kode seperti
- Solusinya adalah selalu mendefinisikan class dan method, lalu mengubahnya ke bentuk yang bercabang di dalam method berdasarkan nilai
BUILD- Jika
testOnlyFunctiondipanggil pada build yang bukan khusus test, ia melempar errorUnexpected call to test-only function
- Jika
- JavaScript akhir dapat dikompilasi menjadi sama seperti JavaScript yang sebelumnya dihasilkan kode Skew
- Namun, sebagian simbol yang sebelumnya hanya ada pada mode kompilasi tertentu menjadi ada di semua mode setelah perubahan, sehingga bundle akhir sedikit membesar
- Dalam pengujian, peningkatan ukuran bundle masih berada pada tingkat yang dapat diterima, dan simbol top-level yang tidak di-export dapat dihapus melalui tree-shaking
Arah setelah transisi
- Dengan memindahkan semua kode Skew ke TypeScript, salah satu codebase inti Figma dimodernisasi
- Integrasi dengan kode internal dan eksternal menjadi lebih mudah, dan developer dapat bekerja lebih efisien
- Pada kebutuhan dan kapabilitas Figma saat itu, keputusan memulai codebase dengan Skew sudah tepat
- Seiring teknologi matang, TypeScript yang dulu belum menjadi pilihan tepat kini menjadi opsi yang sesuai
- Untuk pekerjaan berikutnya, Figma sedang mengeksplorasi kemungkinan berikut
- Integrasi dengan sisa codebase
- Manajemen paket yang jauh lebih mudah
- Penggunaan langsung fitur baru dari ekosistem TypeScript yang aktif
- Sepanjang proses transisi, Figma mempelajari berbagai aspek TypeScript seperti resolusi import, sistem modul, dan pembuatan kode JavaScript
1 komentar
Komentar Hacker News
Mengejutkan juga bahwa Figma punya bahasa kustom untuk JS, dan lebih mengejutkan lagi bahwa itu lebih cepat daripada TS
Namun menarik juga bahwa pada akhirnya mereka pindah ke TS yang lebih lambat
Hal seperti ini cukup sering terlihat: perusahaan membuat teknologi sendiri di tahap awal, lalu setelah membesar bermigrasi ke sesuatu yang lebih “standar”
Saat tim dan produk menjadi jauh lebih besar, mereka memindahkan platform lagi ke stack yang lebih familier dan banyak dipakai
Keberhasilan transisi seperti ini sangat bergantung pada seberapa kuat budaya engineering organisasi. Saya tidak punya bukti, tetapi sepertinya Evan dan para pendiri telah membangun budaya engineering yang hebat di Figma, dan mereka tampak punya ketahanan yang cukup untuk memperbaiki kesalahan saat terjadi
Pada titik itu yang tersisa hanya kode perekat, dan bahasa kustom itu tidak lagi punya alasan untuk tetap ada
Lalu ketika komunitas mengejar ketertinggalan dan memiliki momentum yang lebih besar daripada perusahaan, masuk akal untuk pindah ke implementasi standar
Saya juga melihat alur dari Borg milik Google ke k8s sebagai sesuatu yang agak mirip. Hanya saja Google lebih ke mengundang komunitas ke sekitar standar yang mereka buat sendiri
Perlu membandingkan biaya developer yang bisa melihat dan mempelajari framework yang benar-benar baru untuk pertama kalinya dengan nilai developer yang sudah punya pengalaman terdokumentasi pada platform atau teknologi yang sudah dipakai
Dengan pengembangan yang sepenuhnya in-house, performa luar biasa bisa ditarik dari bahasa atau framework apa pun, tetapi makin banyak situasi yang harus ditangani proyek, makin banyak developer yang dibutuhkan untuk pemeliharaan, dan pada akhirnya ada titik ketika software jadi menjadi lebih baik meskipun lambat dan tidak sepenuhnya pas dengan kebutuhan
Jika dalam jangka panjang bisa mempertahankan standar perekrutan setingkat unicorn, stack yang sepenuhnya kustom mungkin bisa dipertahankan. Namun kebanyakan organisasi harus pindah ke sesuatu yang bisa dipelihara oleh rekrutan baru rata-rata ke depannya, dan biasanya itu berarti produk vendor software terkenal yang membosankan
Bagi orang yang ambisius, itu bisa menjadi alasan untuk pergi, perekrutan jadi lebih sulit, dan onboarding menjadi lebih lama. Saat nantinya bahasa kustom itu ditinggalkan, proyek migrasi besar juga dibutuhkan
Kadang itu layak jika peningkatan performanya sangat besar, jika memungkinkan kode yang lebih aman, atau jika memang berniat hanya merekrut orang yang suka mempelajari bahasa baru
Saya pernah bekerja di proyek ini. Saya menuliskan sedikit lebih banyak di Twitter: https://twitter.com/andrew_k_chan/status/1786769203912925477
Judul tulisannya menyesatkan. Di Figma, Typescript sudah digunakan di bagian lain codebase selama hampir 10 tahun, dan selama hampir seluruh periode itu jumlah kode Typescript lebih banyak daripada Skew
Seperti dijelaskan di posting blog, Skew digunakan untuk engine mobile, kemudian prototyping player, fitur mirroring, serta satu atau dua area produk lain yang tidak saya ingat
Skew bukan sekadar sedikit lebih cepat daripada TypeScript
Menurut Evan Wallace, mantan CTO Figma, berkat optimasi yang dimungkinkan oleh sistem tipe yang lebih ketat, Skew 1,5–2 kali lebih cepat
Sepertinya ada tantangan yang cukup besar, tetapi mengurangi sebagian sifat dinamis demi optimasi yang lebih dapat diprediksi terdengar seperti kompromi yang baik untuk aplikasi kelas produksi
Jika jalur eksekusi yang panas dioptimalkan, kemungkinan besar keunggulan 2 kali lipat itu akan hilang
Tentu, bisa dibilang itu berarti harus menulis JS yang dioptimalkan secara paksa di banyak tempat dan berdampak pada keterbacaan. Benar, tetapi alternatifnya adalah memakai bahasa kompilasi JS yang benar-benar baru dengan dukungan tooling dan pengenalan yang rendah
Dilihat sekarang, sepertinya itu tidak sepadan, dan posting blog ini terasa seperti sedikit memoles pilihan teknologi sebelumnya yang kontroversial agar terlihat lebih baik
Saya penasaran apakah penggunaan TypeScript bisa dibatasi pada subset yang menghasilkan performa tersebut
Menarik bahwa saat menyelesaikan operasi seperti
const [a, b] = function_that_returns_an_array(), JavaScript tidak mengindeks langsung dari array, melainkan membuat iterator yang menelusuri array tersebutMengapa JS tidak sekadar mengindeks array secara langsung dalam destructuring assignment?
Symbol.iterator, dan destructuring harus bekerja juga pada objek seperti itu. BahkanSymbol.iteratormilik array itu sendiri pun bisa di-patch, jadi VM harus menanganinyaArray.prototype[Symbol.iterator] = function*() { yield 1; yield 2; yield 3; }[...[4, 5, 6]][1, 2, 3]Performa protokol iterator yang buruk sudah dibahas saat itu, tetapi diabaikan dengan alasan escape analysis akan menyelesaikannya [0]. Hampir 10 tahun kemudian, escape analysis masih belum menyelesaikannya, memakai GC sangat banyak, dan tetap tidak bagus. Ini spesifikasi buruk yang dirancang oleh orang-orang yang tidak sadar performa
Jika pengguna tidak mem-patch
Symbol.iterator, masuk akal juga bila engine mengkhususkan destructuring assignment dan splicing pada array untuk menghilangkan overhead protokol iterator, tetapi itu adalah masalah rumit lain[0] https://esdiscuss.org/topic/performance-of-iterator-next-as-...
Yang lebih aneh, pada beberapa engine JavaScript, saat melakukan destructuring array yang elemennya dua atau tiga, object destructuring seperti
{0: foo, 1: bar}bisa lebih cepatArtikel ini tidak banyak membahas dampak developer experience yang berkelanjutan akibat hilangnya fitur-fitur kenyamanan Skew, dan hanya membicarakan transformasi yang dilakukan sekali saat memigrasikan codebase
Misalnya, di TypeScript mudah terjadi kasus ketika file harus diimpor dalam urutan yang benar, dan jika tidak, bisa rusak. Selain itu, destructuring lambat, jadi tidak boleh dipakai jika performa sedikit saja penting
Setelah memakai TypeScript selama beberapa tahun, ada puluhan jebakan seperti ini; sebagian diwarisi dari JavaScript dan sebagian tidak. Jika ada banyak engineer, setidaknya diperlukan style guide yang sangat besar
Saya penasaran apakah ada engineer yang merasa sayang Skew menghilang
Dengan cara ini, developer bisa melihat seperti apa hasilnya dalam review PR dan melaporkan masalah
Dari sisi performa dan kebenaran runtime, memang ada beberapa jebakan di TS. Masalah seperti destructuring array ditangkap dengan instrumentasi dan pemantauan ketat
Hilangnya beberapa fitur Skew, misalnya operator overloading dan tipe integer, memang jelas disayangkan. Namun migrasi ini pada akhirnya adalah keputusan seluruh tim, dan saya juga menganggap itu keputusan yang tepat
Tertulis “fitur JavaScript modern seperti async/await dan sistem tipe yang lebih fleksibel”; jadi apakah Skew hanya punya callback?
Promise?Sebagai orang yang tidak terlalu mengenal Figma, saya penasaran mengapa mereka memakai WebAssembly
Figma melakukan itu dengan sangat baik. Bahkan pada kanvas yang sangat besar dengan banyak layar UI kompleks, rasanya luar biasa responsif
Di antara aplikasi desktop masa kini pun hampir tidak ada yang berjalan sebagus ini. Saya yakin optimisasi seperti ini adalah bagian penting dari kesuksesan Figma
Detail menarik tentang cara Figma membuat TypeScript DSL + compiler kustom untuk menyelesaikan masalah keamanan, yaitu izin, juga ada di posting blog lain
https://www.figma.com/blog/how-we-rolled-out-our-own-permiss...
Rasanya agak menyakitkan. Setiap perusahaan besar punya tool internal, bahasa, sesuatu seperti Kubernetes, milik mereka sendiri. Mengapa tidak dibagikan saja?
Jika Skew open source, mungkin ia bisa menjadi TypeScript yang lebih baik
Open source tidak berarti kontribusi gratis akan muncul begitu saja. Setiap PR yang tidak sepele harus diikuti review panjang, diskusi, dan mungkin bahkan penulisan ulang
Saya suka TypeScript dan juga punya sistem full-stack dengan TypeScript, tetapi itu tidak sempurna. Mengonfigurasi TypeScript di monorepo itu mimpi buruk
Agar bisa dipahami bersama paket-paket internal di bawah monorepo pnpm, perlu banyak pengaturan manual di
tsconfig.jsonsupaya semua path saling cocokDan sampai paket
tsxyang hebat muncul, toolchain produksi praktis tidak mungkin dipeliharaSelain itu, kecepatannya sangat lambat. Karena Zod ada masalah yang sangat memperlambat kinerja language server TypeScript, pada akhirnya kami harus mengadopsi project references dan mematikan pengalihan project references
Secara keseluruhan, masih banyak ruang untuk membuat TypeScript lebih baik. Terutama dari sisi monorepo dan performa
Masalah monorepo itu terdengar seperti tidak memakai konfigurasi yang sama untuk semua paket. Kalau begitu, saya akan memperbaiki dulu dengan memaksakan konfigurasi dan standar coding yang sama. Ini juga bukan masalah TypeScript
Apakah sebelum
tsxAnda memakaits-node? Kalau ya,tsxjauh lebih kokoh. Ia langsung bekerja dengan baikKami menjalankan monorepo skala menengah, beberapa aplikasi, dan banyak service sebagian besar dengan TypeScript, dan bahkan dengan setup berbasis npm workspaces yang membosankan pun semuanya berjalan cukup baik
Bukan berarti TypeScript sama sekali cepat, tetapi karena ditulis dengan JavaScript, memang sulit untuk menjadi cepat
Namun, membiarkan TypeScript menjalankan kode untuk inferensi tipe di aplikasi berskala besar tampak seperti masalah yang dibuat sendiri