Catatan bagus di mana saja

Gambar pemasaran Goodnotes yang menampilkan seorang perempuan sedang menggunakan produk di iPad.

Selama dua tahun terakhir, tim engineer Goodnotes telah mengerjakan project untuk menghadirkan aplikasi pencatatan iPad yang sukses ke platform lain. Studi kasus ini membahas bagaimana aplikasi iPad tahun 2022 mencapai web, ChromeOS, Android, dan Windows yang didukung oleh teknologi web dan WebAssembly menggunakan kembali kode Swift yang sama yang telah dikerjakan tim selama lebih dari sepuluh tahun.

Logo Goodnotes.

Mengapa Goodnotes hadir di web, Android, dan Windows

Pada tahun 2021, Goodnotes hanya tersedia sebagai aplikasi untuk iOS dan iPad. Tim teknik di Goodnotes menerima tantangan teknis yang besar: membuat versi baru Goodnotes, tetapi untuk sistem operasi dan platform tambahan. Produk harus sepenuhnya kompatibel dengan, dan merender catatan yang sama seperti aplikasi iOS. Setiap catatan yang diambil di atas PDF, atau gambar yang dilampirkan harus setara dan menunjukkan goresan yang sama seperti yang ditunjukkan aplikasi iOS. Goresan apa pun yang ditambahkan harus setara dengan yang dapat dibuat oleh pengguna iOS, terlepas dari alat yang digunakan pengguna—misalnya, pena, stabilo, pulpen, bentuk, atau penghapus.

Pratinjau aplikasi Goodnotes dengan catatan dan sketsa tulisan tangan.

Berdasarkan persyaratan dan pengalaman tim engineering, tim dengan cepat menyimpulkan bahwa menggunakan kembali codebase Swift akan menjadi tindakan terbaik, mengingat bahwa kode tersebut sudah ditulis dan teruji dengan baik selama bertahun-tahun. Namun, mengapa tidak hanya mem-port aplikasi iOS/iPad yang sudah ada ke platform atau teknologi lain seperti Flutter atau Compose Multiplatform? Untuk pindah ke platform baru, Anda harus menulis ulang Goodnotes. Jika hal tersebut dilakukan, mungkin akan memulai perlombaan pengembangan antara aplikasi iOS yang sudah diimplementasikan dan yang akan di-build dari nol aplikasi baru, atau melibatkan penghentian pengembangan baru pada aplikasi yang sudah ada saat codebase baru selesai. Jika Goodnotes dapat menggunakan kembali kode Swift, tim dapat memperoleh manfaat dari fitur baru yang diterapkan oleh tim iOS saat tim lintas platform sedang mengerjakan dasar-dasar aplikasi dan mencapai paritas fitur.

Produk ini telah menyelesaikan sejumlah tantangan menarik bagi iOS untuk menambahkan fitur seperti:

  • Rendering catatan.
  • Sinkronisasi dokumen dan catatan.
  • Resolusi konflik untuk catatan yang menggunakan Jenis Data Replika Bebas Konflik.
  • Analisis data untuk evaluasi model AI.
  • Penelusuran konten dan pengindeksan dokumen.
  • Pengalaman dan animasi scroll kustom.
  • Melihat implementasi model untuk semua lapisan UI.

Semuanya akan jauh lebih mudah diterapkan untuk platform lain jika tim engineer dapat membuat codebase iOS yang sudah berfungsi untuk aplikasi iOS dan iPad serta menjalankannya sebagai bagian dari project yang dapat dikirimkan Goodnotes sebagai aplikasi Windows, Android, atau web.

Tech stack Goodnotes

Untungnya, ada cara untuk menggunakan kembali kode Swift yang ada di web, yaitu WebAssembly (Wasm). Goodnotes membuat prototipe menggunakan Wasm dengan project SwiftWasm yang open source dan dikelola komunitas. Dengan SwiftWasm, tim Goodnotes dapat membuat biner Wasm menggunakan semua kode Swift yang telah diimplementasikan. Biner ini dapat disertakan dalam halaman web yang dikirimkan sebagai Progressive Web Application untuk Android, Windows, ChromeOS, dan setiap sistem operasi lainnya.

Urutan peluncuran Goodnotes yang dimulai dengan Chrome, lalu Windows, diikuti oleh Android, dan platform lain seperti Linux di akhir, semuanya berdasarkan PWA.

Tujuannya adalah untuk merilis Goodnotes sebagai PWA, dan dapat mencantumkannya di setiap penyimpanan platform. Selain Swift, bahasa pemrograman yang sudah digunakan untuk iOS, dan WebAssembly yang digunakan untuk mengeksekusi kode Swift di web, project ini menggunakan teknologi berikut:

  • TypeScript: Bahasa pemrograman yang paling sering digunakan untuk teknologi web.
  • React dan webpack: Framework dan pemaket paling populer untuk web.
  • PWA dan pekerja layanan: pengaktif besar untuk project ini karena tim dapat mengirimkan aplikasi kami sebagai aplikasi offline yang berfungsi seperti aplikasi iOS lainnya dan Anda dapat menginstalnya dari app store atau browser itu sendiri.
  • PWABuilder: Project utama yang digunakan Goodnotes untuk menggabungkan PWA ke dalam biner Windows native sehingga tim dapat mendistribusikan aplikasi kami dari Microsoft Store.
  • Aktivitas Web Tepercaya: Teknologi Android terpenting yang digunakan perusahaan untuk mendistribusikan PWA kami sebagai aplikasi native di balik layar.

Tech stack Goodnotes yang terdiri dari Swift, Wasm, React, dan PWA.

Gambar berikut menunjukkan apa yang diimplementasikan menggunakan TypeScript dan React klasik, serta apa yang diimplementasikan menggunakan SwiftWasm dan JavaScript vanilla, Swift, dan WebAssembly. Bagian project ini menggunakan JSKit, library interoperabilitas JavaScript untuk Swift dan WebAssembly yang digunakan tim untuk menangani DOM di layar editor dari kode Swift saat diperlukan atau bahkan menggunakan beberapa API khusus browser.

Screenshot aplikasi di perangkat seluler dan desktop yang menunjukkan area gambar tertentu yang dijalankan oleh Wasm, dan area UI yang dikelola oleh React.

Mengapa menggunakan Wasm dan web?

Meskipun Wasm tidak secara resmi didukung oleh Apple, ada alasan berikut mengapa tim engineer Goodnotes merasa pendekatan ini adalah keputusan terbaik:

  • Penggunaan kembali lebih dari 100 ribu baris kode.
  • Kemampuan untuk melanjutkan pengembangan pada produk inti sekaligus berkontribusi pada aplikasi lintas platform.
  • Kemampuan untuk mengakses setiap platform sesegera mungkin menggunakan proses pengembangan yang berulang.
  • Memiliki kontrol untuk merender dokumen yang sama tanpa menduplikasi semua logika bisnis, dan memperkenalkan perbedaan dalam implementasi kami.
  • Memanfaatkan semua peningkatan performa yang dilakukan di setiap platform secara bersamaan (dan semua perbaikan bug yang diterapkan di setiap platform).

Penggunaan lebih dari 100 ribu baris kode, dan logika bisnis yang menerapkan pipeline rendering adalah hal mendasar. Pada saat yang sama, agar kode Swift kompatibel dengan toolchain lain, mereka dapat menggunakan kembali kode ini di platform yang berbeda di masa mendatang jika diperlukan.

Pengembangan produk iteratif

Tim mengambil pendekatan berulang untuk memberikan sesuatu kepada pengguna secepat mungkin. Goodnotes dimulai dengan versi produk hanya baca yang memungkinkan pengguna mendapatkan dokumen bersama dan membacanya dari platform mana pun. Hanya dengan link, mereka akan dapat mengakses dan membaca catatan yang sama seperti yang mereka tulis dari iPad mereka. Fase berikutnya ditambahkan dalam fitur pengeditan, untuk membuat versi lintas platform setara dengan versi iOS.

Dua screenshot aplikasi yang melambangkan peralihan dari produk hanya baca ke produk berfitur lengkap.

Versi pertama produk hanya baca ini memerlukan waktu enam bulan untuk dikembangkan, sembilan bulan berikutnya dikhususkan untuk banyak fitur pengeditan pertama dan layar UI tempat Anda dapat memeriksa semua dokumen yang dibuat atau dibagikan oleh seseorang. Selain itu, fitur baru platform iOS mudah ditransfer ke project lintas platform berkat SwiftWasm Toolchain. Misalnya, jenis pena baru telah dibuat dan mudah diimplementasikan lintas platform dengan menggunakan kembali ribuan baris kode.

Membuat project ini adalah pengalaman yang luar biasa, dan Goodnotes telah belajar banyak darinya. Itulah sebabnya bagian berikut akan fokus pada poin teknis yang menarik tentang pengembangan web dan penggunaan WebAssembly serta bahasa seperti Swift.

Hambatan awal

Mengerjakan proyek ini sangat menantang dari berbagai sudut pandang yang berbeda. Hambatan pertama yang ditemukan tim adalah terkait toolchain SwiftWasm. Toolchain adalah pengaktif yang sangat besar bagi tim, tetapi tidak semua kode iOS kompatibel dengan Wasm. Misalnya, kode yang terkait dengan IO atau UI—seperti implementasi tampilan, klien API, atau akses ke database tidak dapat digunakan kembali, sehingga tim perlu mulai memfaktorkan ulang bagian tertentu aplikasi agar dapat menggunakannya kembali dari solusi lintas platform. Sebagian besar PR yang dibuat tim adalah pemfaktoran ulang untuk memisahkan dependensi sehingga tim nantinya dapat menggantinya menggunakan injeksi dependensi atau strategi serupa lainnya. Kode iOS awalnya menggabungkan logika bisnis mentah yang dapat diimplementasikan di Wasm dengan kode yang bertanggung jawab atas input/output dan antarmuka pengguna yang tidak dapat diimplementasikan di Wasm karena Wasm juga tidak mendukungnya. Jadi, kode IO dan UI harus diimplementasikan kembali di TypeScript setelah logika bisnis Swift siap digunakan kembali antar-platform.

Masalah performa terpecahkan

Setelah Goodnotes mulai mengerjakan editor, tim mengidentifikasi beberapa masalah terkait pengalaman pengeditan, dan batasan teknologi yang menantang masuk ke dalam roadmap kami. Masalah pertama terkait performa. JavaScript adalah bahasa thread tunggal. Ini berarti memiliki satu stack panggilan dan satu heap memori. Fungsi ini mengeksekusi kode secara berurutan dan harus selesai mengeksekusi potongan kode sebelum beralih ke kode berikutnya. Hal ini bersifat sinkron, tetapi terkadang dapat membahayakan. Misalnya, jika suatu fungsi memerlukan waktu beberapa saat untuk dijalankan atau harus menunggu sesuatu, fungsi tersebut akan membekukan semuanya pada saat yang sama. Dan inilah yang harus dipecahkan oleh para insinyur/perekayasa. Mengevaluasi beberapa jalur tertentu di codebase kami yang terkait dengan lapisan rendering atau algoritma kompleks lainnya menjadi masalah bagi tim, karena algoritma ini berjalan secara sinkron, dan menjalankannya akan memblokir thread utama. Tim Goodnotes menulis ulang ekstensi tersebut untuk menjadikannya lebih cepat, dan memfaktorkan ulang beberapa di antaranya agar menjadikannya asinkron. Mereka juga memperkenalkan strategi hasil sehingga aplikasi dapat menghentikan eksekusi algoritma dan melanjutkannya nanti, sehingga browser dapat mengupdate UI dan menghindari penurunan frame. Hal ini bukan masalah bagi aplikasi iOS karena aplikasi dapat menggunakan thread dan mengevaluasi algoritma ini di latar belakang saat thread iOS utama mengupdate antarmuka pengguna.

Solusi lain yang harus dipecahkan oleh tim teknik adalah memigrasikan UI berdasarkan elemen HTML yang dilampirkan ke DOM, ke UI dokumen berdasarkan kanvas layar penuh. Project ini mulai menampilkan semua catatan dan konten yang terkait dengan dokumen sebagai bagian dari struktur DOM menggunakan elemen HTML seperti halnya halaman web lainnya, tetapi pada titik tertentu bermigrasi ke kanvas layar penuh untuk meningkatkan performa pada perangkat kelas bawah dengan mengurangi waktu browser menangani update DOM.

Perubahan berikut diidentifikasi oleh tim engineering sebagai hal-hal yang dapat mengurangi beberapa masalah yang dialami, apakah mereka melakukannya di awal project.

  • Lebih banyak mengurangi beban thread utama dengan menggunakan pekerja web secara rutin untuk algoritma yang berat.
  • Gunakan fungsi yang diekspor dan diimpor, bukan library interop JS-Swift sejak awal agar dapat mengurangi dampak performa saat keluar dari konteks Wasm. Library interop JavaScript ini berguna untuk mendapatkan akses ke DOM atau browser, tetapi lebih lambat daripada fungsi Wasm native yang diekspor.
  • Pastikan kode mengizinkan penggunaan OffscreenCanvas di balik layar sehingga aplikasi dapat memindahkan thread utama dan memindahkan semua penggunaan Canvas API ke pekerja web yang memaksimalkan performa aplikasi saat menulis catatan.
  • Memindahkan semua eksekusi terkait Wasm ke pekerja web atau bahkan kumpulan pekerja web sehingga aplikasi dapat mengurangi beban kerja thread utama.

Editor teks

Masalah menarik lainnya terkait dengan satu alat tertentu, editor teks. Implementasi iOS untuk alat ini didasarkan pada NSAttributedString, set alat kecil yang menggunakan RTF di balik layar. Namun, implementasi ini tidak kompatibel dengan SwiftWasm sehingga tim lintas platform terpaksa membuat Parser kustom berdasarkan tata bahasa RTF terlebih dahulu dan kemudian menerapkan pengalaman pengeditan dengan mengubah RTF menjadi HTML, dan sebaliknya. Sementara itu, tim iOS mulai mengerjakan implementasi baru untuk alat ini, yang menggantikan penggunaan RTF dengan model kustom, sehingga aplikasi dapat menampilkan teks yang telah diberi gaya dengan cara yang mudah untuk semua platform yang menggunakan kode Swift yang sama.

Editor teks Goodnotes.

Tantangan ini adalah salah satu poin paling menarik dalam roadmap project karena diselesaikan secara berulang berdasarkan kebutuhan pengguna. Masalah teknis ini diselesaikan menggunakan pendekatan yang berfokus pada pengguna, yaitu saat tim perlu menulis ulang sebagian kode agar dapat merender teks, sehingga pengeditan teks dapat dilakukan dalam rilis kedua.

Rilis iteratif

Evolusi proyek selama dua tahun terakhir sungguh luar biasa. Tim mulai mengerjakan project versi hanya baca, dan beberapa bulan kemudian mengirimkan versi baru dengan banyak kemampuan pengeditan. Untuk sering merilis perubahan kode ke produksi, tim memutuskan untuk menggunakan tombol fitur secara ekstensif. Untuk setiap rilis, tim dapat mengaktifkan fitur baru dan juga merilis perubahan kode yang menerapkan fitur baru yang akan dilihat pengguna beberapa minggu kemudian. Namun, ada sesuatu yang menurut tim bisa diperbaiki! Mereka berpikir bahwa memperkenalkan sistem tombol fitur dinamis akan membantu mempercepat semuanya, karena akan meniadakan kebutuhan deployment ulang untuk mengubah nilai flag. Tindakan ini akan membuat Goodnotes lebih fleksibel dan juga mempercepat deployment fitur baru karena Goodnotes tidak perlu menautkan deployment project ke rilis produk.

Kerja offline

Salah satu fitur utama yang dikerjakan tim adalah dukungan offline. Dapat mengedit dokumen dan mengubahnya adalah salah satu fitur yang diharapkan dari aplikasi apa pun seperti ini. Namun, ini bukan fitur sederhana karena Goodnotes mendukung kolaborasi. Artinya, semua perubahan yang dilakukan oleh pengguna yang berbeda pada perangkat yang berbeda harus berakhir di setiap perangkat tanpa meminta pengguna untuk menyelesaikan konflik apa pun. Goodnotes sudah lama memecahkan masalah ini dengan menggunakan CRDT di balik layar. Berkat Jenis Data Replika Bebas Konflik ini, Goodnotes dapat menggabungkan semua perubahan yang dilakukan pada dokumen apa pun oleh pengguna mana pun dan menggabungkan perubahan tersebut tanpa konflik penggabungan. Penggunaan IndexedDB dan penyimpanan yang tersedia untuk browser web merupakan faktor pendukung yang sangat besar untuk pengalaman offline kolaboratif di web.

Aplikasi Goodnotes yang berfungsi secara offline.

Selain itu, membuka aplikasi web Goodnotes akan menyebabkan biaya download awal awal sekitar 40 MB karena ukuran biner Wasm. Awalnya, tim Goodnotes hanya mengandalkan cache browser reguler untuk app bundle itu sendiri dan sebagian besar endpoint API yang mereka gunakan, tetapi sebelumnya dapat memperoleh keuntungan dari Cache API dan pekerja layanan yang lebih andal. Tim awalnya menghindari tugas ini karena asumsinya kompleks, tetapi pada akhirnya, menyadari bahwa Workbox membuatnya tidak terlalu menakutkan.

Rekomendasi saat menggunakan Swift di web

Jika Anda memiliki aplikasi iOS dengan banyak kode yang ingin digunakan kembali, bersiaplah karena Anda akan memulai perjalanan yang luar biasa. Ada beberapa tips yang mungkin menarik sebelum Anda memulai.

  • Periksa kode yang ingin Anda gunakan kembali. Jika logika bisnis aplikasi Anda diterapkan di sisi server, kemungkinan Anda ingin menggunakan kembali kode UI, dan Wasm tidak akan membantu Anda di sini. Tim melihat secara singkat Tokamak, framework yang kompatibel dengan SwiftUI untuk membangun aplikasi browser dengan WebAssembly, tetapi belum cukup untuk memenuhi kebutuhan aplikasi. Namun, jika aplikasi Anda memiliki logika atau algoritma bisnis yang kuat yang diterapkan sebagai bagian dari kode klien, Wasm akan menjadi teman terbaik Anda.
  • Pastikan codebase Swift Anda sudah siap. Pola desain software untuk lapisan UI atau arsitektur tertentu yang membuat pemisahan kuat antara logika UI dan logika bisnis Anda akan sangat berguna karena Anda tidak akan dapat menggunakan kembali penerapan lapisan UI. Arsitektur bersih atau prinsip arsitektur heksagonal juga akan menjadi dasar, karena Anda harus memasukkan dan menyediakan dependensi untuk semua kode terkait IO dan akan jauh lebih mudah dilakukan jika Anda mengikuti arsitektur ini dengan detail implementasi yang ditentukan sebagai abstraksi dan prinsip inversi dependensi sangat sering digunakan.
  • Wasm tidak menyediakan kode UI. Oleh karena itu, tentukan framework UI yang ingin Anda gunakan untuk web.
  • JSKit akan membantu Anda mengintegrasikan kode Swift dengan JavaScript, tetapi perlu diingat jika Anda memiliki hotpath, menyeberangi jembatan JS–Swift mungkin mahal dan Anda perlu menggantinya dengan fungsi yang diekspor. Anda dapat mempelajari lebih lanjut cara kerja JSKit di dokumentasi resmi dan postingan Pencarian Anggota Dinamis di Swift, sebuah permata tersembunyi!.
  • Apakah Anda dapat menggunakan kembali arsitektur atau tidak akan bergantung pada arsitektur yang diikuti aplikasi Anda dan library mekanisme eksekusi kode asinkron yang Anda gunakan. Pola seperti MVVP atau arsitektur composable akan membantu Anda menggunakan kembali model tampilan dan bagian logika UI tanpa menggabungkan penerapan ke dependensi UIKit yang tidak dapat Anda gunakan dengan Wasm. RXSwift dan library lainnya mungkin tidak kompatibel dengan Wasm. Jadi, perhatikan hal ini karena Anda harus menggunakan OpenCombine, async/await, dan aliran dalam kode Swift Goodnotes.
  • Kompresi biner Wasm menggunakan gzip atau brotli. Perlu diingat bahwa ukuran biner akan cukup besar untuk aplikasi web klasik.
  • Meskipun dapat menggunakan Wasm tanpa PWA, pastikan Anda setidaknya menyertakan pekerja layanan, meskipun aplikasi web tidak memiliki manifes atau Anda tidak ingin pengguna menginstalnya. Pekerja layanan akan menyimpan dan menyajikan biner Wasm secara gratis dan semua resource aplikasi, sehingga pengguna tidak perlu mendownloadnya setiap kali membuka project Anda.
  • Perlu diingat bahwa perekrutan mungkin lebih sulit dari yang diperkirakan. Anda mungkin perlu mempekerjakan developer web yang andal dan berpengalaman di Swift, atau developer Swift yang berpengalaman dan memiliki pengalaman di web. Jika Anda dapat menemukan insinyur generalis dengan pengetahuan di kedua platform, itu akan luar biasa

Kesimpulan

Membangun project web menggunakan tech stack yang kompleks sambil mengerjakan produk yang penuh tantangan adalah pengalaman yang luar biasa. Ini akan sulit, tapi sangat berharga. Goodnotes tidak akan pernah merilis versi untuk Windows, Android, ChromeOS, dan web sambil mengerjakan fitur baru untuk aplikasi iOS tanpa menggunakan pendekatan ini. Berkat tech stack ini dan tim engineer Goodnotes, Goodnotes kini ada di mana-mana, dan tim kami siap untuk terus mengerjakan tantangan berikutnya. Jika ingin mengetahui project ini lebih lanjut, Anda dapat menonton pembicaraan yang diberikan tim Goodnotes di NSts 2023. Pastikan untuk mencoba Goodnotes untuk web.