Image Converter Web App
Aplikasi konversi gambar lokal di browser, dibuat dengan React, TypeScript, Web Workers, dan engine npm Rust/WebAssembly.
Tautan Web App
Web App: https://image-converter-web-app-project.leonardwalujan.eu.org/
Tautan Repositori
GitHub: https://github.com/walujanle/image-converter-web-app
GitLab: https://gitlab.com/walujanle/image-converter-web-app
Tautan Unduh
Build File: https://links.leonardwalujan.eu.org/lw/image-converter-web-app-latest-build-file
Checksum Build File (SHA-256): https://links.leonardwalujan.eu.org/lw/image-converter-web-app-latest-build-file-checksum
Kode Sumber: https://links.leonardwalujan.eu.org/lw/image-converter-web-app-latest-source-code
Screenshot
-
Image Converter Web App Screenshot 1 -
Image Converter Web App Screenshot 2 -
Image Converter Web App Screenshot 3 -
Image Converter Web App Screenshot 4
Penjelasan Singkat
Image Converter Web App adalah konverter gambar batch yang berjalan di dalam tab browser. Anda menambahkan file, menentukan hasil yang diinginkan, menekan start, lalu menerima hasilnya lewat download browser biasa. Gambarnya tidak pernah dikirim ke mana pun, dan itu bukan sekadar janji: repositori ini memang tidak punya upload API sebagai tujuan pengiriman.
Proses konversinya sendiri tidak ditulis di sini. Semuanya berasal dari package npm buatan saya, @walujanle/image-converter-wasm, sebuah engine Rust yang dikompilasi ke WebAssembly. Repositori ini adalah host di sekelilingnya — antarmuka React, dua mode kerja, queue, worker pool, penyimpanan settings, preset, download, ZIP packaging, dan cache asset production.
Input yang diterima adalah JPEG, PNG, WebP, AVIF, HEIC, dan HEIF. Outputnya JPEG, PNG, WebP, dan AVIF. HEIC hanya bisa dibaca, karena engine-nya memang sengaja tidak melakukan encoding HEIC.
Alasan Saya Membuat Project Ini
Saya ingin seluruh proses konversi berjalan di perangkat pengunjung sendiri, dan saya ingin hal itu bisa dibuktikan, bukan sekadar diiklankan. Kebanyakan konverter online meminta Anda menyerahkan foto lebih dulu, lalu menjelaskan kebijakan privasinya belakangan. Aplikasi ini tidak punya tempat untuk menerima penyerahan itu.
Alasan yang lebih menarik: aplikasi ini adalah pengguna nyata pertama dari package WASM saya sendiri. Sebuah API publik selalu terlihat rapi di README. Ceritanya berbeda ketika ada lima puluh file di queue, pengguna membatalkan di gambar keempat puluh delapan, browser punya batas memori yang tidak bisa ditawar, dan sebuah konversi yang sudah berjalan baru selesai setelah batch pemiliknya berakhir. Membangun aplikasi ini yang mengubah package tersebut dari sesuatu yang saya tulis menjadi sesuatu yang benar-benar saya pakai.
Alasan ketiga adalah kendali atas hasilnya. Format output, kualitas, encode intent, mode lossless WebP, kompresi PNG, crop, resize, metadata, aturan nama file, auto suffix, batch size, concurrency, preset, dan dataset log — semuanya milik orang yang mengonversi gambarnya. Tujuannya memang bukan membuat layanan converter berbasis server dengan antrean yang dikelola orang lain.
Tech Stack yang Digunakan
- React 19 untuk antarmuka.
- TypeScript 6 dengan strict mode. Jalur 7.x sengaja belum dipakai; 6.x adalah target stabil untuk repositori ini.
- Node 24 LTS sebagai runtime development.
- Vite 8 untuk dev server dan production build.
- Bun untuk script dan package management.
- Zustand untuk state, terpisah per mode.
- Tailwind CSS 4 untuk styling.
- Web Workers supaya konversi tidak pernah berjalan di UI thread.
@walujanle/image-converter-wasmuntuk decode, crop, resize, encode, penanganan metadata, dan dimensi output.- JSZip, dimuat secara lazy dan hanya ketika ZIP benar-benar diminta.
- Service worker yang dihasilkan saat build untuk cache asset production.
- Biome untuk linting dan formatting.
- Vitest untuk testing.
Semua dependency dikunci pada versi persis dengan bun.lock ikut di-commit, sehingga checkout beberapa bulan lagi tetap menghasilkan pohon dependency yang sama dengan yang diverifikasi saat rilis.
Repositori ini memakai lisensi AGPL-3.0-only, mengikuti package WASM yang ikut dikirim ke browser bersamanya.
Fitur
Konversi batch yang local-first
Gambar dimasukkan ke queue lalu dikonversi dalam batch. Tidak ada langkah upload dan tidak ada perjalanan ke server; file datang dari file API milik browser dan keluar sebagai download Blob.
Format yang didukung
Input mencakup JPEG, PNG, WebP, AVIF, HEIC, dan HEIF. Output mencakup JPEG, PNG, WebP, dan AVIF. GIF, BMP, TIFF, dan RAW bukan bagian dari kontrak engine dan tidak diterima.
Simple dan Advanced mode
Simple mode berhenti pada apa yang biasanya memang dibutuhkan: format output, kualitas, optimasi PNG, queue, start, cancel, dan download. PNG tidak menampilkan panel kualitas sama sekali, karena format itu melakukan encoding secara lossless dan slider di sana hanya akan menjadi hiasan.
Advanced mode membuka panel penuh — resize, crop, transformasi nama file, auto suffix, dataset log generation, batch size, dan preset manager. Batas concurrency berada di app settings, yang bisa dibuka dari kedua mode.
State terpisah per mode
Kedua mode tidak berbagi apa pun. Queue terpisah, settings terpisah, storage key terpisah. Berpindah mode di tengah sesi tidak akan menyeret konfigurasi satu mode ke mode lainnya.
Convert again
Gambar yang sudah selesai bisa dikembalikan ke queue dan dikonversi ulang dengan format atau kualitas berbeda, tanpa perlu dihapus lalu ditambahkan lagi. Ada tombol per gambar dan tombol batch di kedua mode, dan tombol batch itu menyebutkan berapa gambar yang akan direset. Gambar yang dibatalkan punya badge sendiri dan dipulihkan dengan cara yang sama.
Konversi berbasis worker
Konversi berjalan di pool Web Worker khusus, dan setiap worker memanggil engine WASM. Pool membuat worker sampai batas yang dikonfigurasi, mengantre tugas secara FIFO, dan mendaur ulang worker setelah 100 tugas. Concurrency mengikuti jumlah hardware thread mesin, dengan batas atas 32.
Warmup engine di latar belakang
Setelah halaman dimuat, aplikasi memanaskan satu worker supaya modul WebAssembly sudah dikompilasi sebelum konversi pertama, bukan saat konversi berlangsung. Sebuah toast kecil melaporkan statusnya. Jika warmup timeout, worker di-restart dan konversi tetap bisa melakukan inisialisasi saat dibutuhkan — warmup adalah optimasi, bukan syarat.
Cache asset production
Production build menghasilkan service worker yang melakukan precache pada asset JavaScript, CSS, worker, dan WASM yang sudah ber-hash. Nama cache-nya diturunkan dari hash daftar precache, sehingga build yang mengubah nama file juga mengubah nama cache dan cache lama dibuang saat aktivasi. Tidak ada versi yang perlu diurus manual. Di development, modul yang sama justru membersihkan cache basi alih-alih mendaftarkan apa pun.
Memory guard
Browser menjalankan WebAssembly di ruang alamat 32-bit, dan satu konversi menahan input terenkode, frame hasil decode, serta outputnya sekaligus. Aplikasi memodelkan puncak pemakaian itu secara eksplisit dan menolak pekerjaan yang tidak akan bisa diselesaikan. Pemeriksaannya berjalan saat file ditambahkan, sehingga file yang terlalu besar langsung ditolak di titik Anda menjatuhkannya dengan batas nyata format tersebut di pesannya, lalu berjalan lagi sebelum pemanggilan worker, dan pemeriksaan kedua itulah jaminannya.
Encode intent
Alih-alih meminta semua orang menebak arti “kualitas 85” di empat codec yang berbeda, panel konversi menyediakan pilihan intent. Balanced memakai nilai kualitas apa adanya. Archive merelakan waktu encoding demi fidelitas. Social berpihak pada ukuran unggahan dan meminta engine membuang metadata privat sebelum encoding. Social juga membatasi kualitas di 90, dan UI menyebutkan batas itu, bukan membiarkan engine menerapkannya diam-diam di balik slider yang menunjukkan angka 100.
Nama file dan output dataset
Nama output bisa dibersihkan, diberi prefix, dikenai find and replace, diganti extension-nya, dan dijaga agar tidak bertabrakan. Auto suffix opsional menambahkan dimensi output dan kualitas. Dataset log generation menulis dataset_log.txt bersama gambarnya; ketika aktif, download non-individual dipaketkan sebagai ZIP supaya log-nya selalu ikut bersama file yang dijelaskannya.
Preset
Settings konversi bisa disimpan, diterapkan, diekspor, dan diimpor. Proses impor diperiksa ukurannya dan divalidasi sebelum apa pun masuk ke settings, karena file preset adalah input yang tidak tepercaya seperti file lainnya.
Pelestarian metadata
Pelestarian metadata mati secara default dan harus dinyalakan sendiri. Ketika aktif, permintaannya diteruskan ke engine, dan seberapa banyak yang bertahan bergantung pada container output: JPEG, PNG, dan WebP membawa metadata secara luas, sedangkan AVIF lebih terbatas.
Cara Kerja
Saat halaman dimuat, aplikasi membuat worker pool dan mengirim permintaan warmup. Satu worker menginisialisasi engine WASM, dan pada production service worker didaftarkan untuk asset cache. Sebuah toast melaporkan apakah engine sudah siap, tersedia dari cache, atau akan dimuat saat dibutuhkan.
Ketika file dipilih, masing-masing divalidasi sebelum masuk queue: ukuran terhadap anggaran memori per format, declared MIME terhadap daftar yang diterima, magic bytes terhadap container aslinya, struktur RIFF untuk WebP, dan brand ISO BMFF untuk AVIF serta HEIC. Extension nama file dan File.type dari browser sama-sama mudah keliru, jadi keduanya tidak dipercaya sendirian. File yang lolos kemudian dibaca dimensinya, empat sekaligus, karena membaca dimensi berarti membaca seluruh isi filenya.
Memulai batch berarti mengambil snapshot: mode aktif, settings-nya, ID item yang terlibat, dan sebuah run token. Nama output dibangun dari aturan penamaan, memori diestimasi, lalu pekerjaan mengalir lewat concurrency queue menuju pool. Setiap worker memetakan settings UI ke bentuk opsi publik milik engine dan memanggil convertImageWithInfo(...), yang menangani decode, crop opsional, resize opsional, encoding, metadata, dan dimensi akhir. Hasilnya kembali sebagai Blob lengkap dengan dimensi dan durasinya.
Run token itulah yang membuat sisi async-nya aman. Sebuah konversi bisa selesai jauh setelah batch pemiliknya berakhir, dan menulis state saat itu berarti menghidupkan kembali item yang sudah ditinggalkan pengguna. Setiap callback yang menyentuh state harus lolos tiga syarat lebih dulu: ada batch yang aktif, callback-nya membawa token batch tersebut, dan item-nya memang bagian dari batch itu. Callback dari run sebelumnya membawa token lama dan dibuang tanpa suara.
Download mengikuti apa yang sudah selesai. Satu artifact diunduh langsung, beberapa artifact dibungkus jadi ZIP, dan mengaktifkan dataset log mengarahkan semuanya lewat ZIP supaya log-nya ikut. Pembatalan ditangani secara defensif: tugas yang masih mengantre dibatalkan sebelum dimulai, hasil yang sudah telanjur berjalan dibuang alih-alih dipercaya, dan worker di-restart daripada diminta baik-baik untuk menghentikan operasi gambar yang panjang.
Catatan Privasi dan Keamanan
Pemrosesan lokal menghilangkan masalah privasi yang paling besar, tetapi tidak semuanya. File yang malformed atau kelewat besar tetap bisa merepotkan secara lokal, jadi validasi berjalan pada MIME type, content signature, brand ISO BMFF, ukuran file, nilai crop dan resize, batch size, serta ukuran preset import sebelum pekerjaan diterima.
Metadata diperlakukan sebagai data sensitif, karena EXIF, XMP, IPTC, dan ICC rutin membawa koordinat GPS, nomor seri kamera dan lensa, nama author, timestamp, serta riwayat penyuntingan. Karena itu pelestariannya bersifat opt-in. Intent Social adalah jalan tengahnya: metadata tetap dipertahankan, tetapi engine membuang field lokasi dan identitas perangkat sebelum encoding.
Production build menghasilkan file _headers berisi Content-Security-Policy, HSTS, Referrer-Policy, Permissions-Policy, nosniff, penolakan frame, COOP, COEP, CORP, immutable asset caching, dan no-store untuk service worker. Builder yang sama juga merender header yang dipakai bun run preview, sehingga kebijakan yang akan merusak production lebih dulu merusak di lokal. Kebijakannya diverifikasi dengan menjalankan konversi penuh di bawahnya tanpa satu pun violation event.
Direktif yang layak disebut namanya adalah connect-src 'self'. Direktif itulah yang mengubah “file Anda tidak pernah diunggah” dari sesuatu yang harus Anda percayai menjadi sesuatu yang dipaksakan oleh browser. Aplikasi ini tidak pernah butuh yang lebih longgar, karena memang tidak punya upload API dan tidak menghubungi pihak ketiga.
Ada satu catatan yang cukup penting untuk dinyatakan terang-terangan: format _headers bersifat spesifik per host dan diabaikan diam-diam oleh host yang tidak mendukungnya. Tidak ada error saat build dan tidak ada peringatan saat runtime — aplikasinya hanya terkirim tanpa perlindungan. GitHub Pages bahkan sama sekali tidak bisa menyajikan header ini, termasuk isolasi cross-origin yang dibutuhkan engine. Sebagian platform juga menimpa header tertentu dari dashboard mereka sendiri, jadi respons yang benar-benar dikirim adalah sumber kebenarannya, bukan file di hasil build.
Catatan Build dan Development
Repositori ini adalah aplikasi, bukan package. Aplikasi ini memakai @walujanle/image-converter-wasm dari npm sebagai runtime dependency.
Command yang dipakai sehari-hari:
bun install
bun run dev
bun run lint
bun run check
bun run typecheck
bun run test
bun run test:coverage
bun run audit
bun run build
bun run lint menjalankan linter; bun run check juga memeriksa formatting dan urutan import. Semua import di dalam src/ melewati satu alias @/ yang menunjuk ke src/, sehingga sebuah path terbaca sama dari file mana pun di pohon proyek.
Rilis saat ini adalah 1.1.2 - Stable Version, dan aplikasinya sudah feature-complete. Baseline validasi yang tercatat adalah 21 test file dengan 321 test lolos, ditambah lint, formatting, typecheck, audit dependency, dan production build yang semuanya lolos. Coverage berada di kisaran 45% statement yang diukur pada seluruh isi src/ — lapisan murni yang memuat validasi dan logic worker berada di atas 95%, sedangkan komponen React sebagian besar belum diuji, dan itu batasan yang lebih baik saya sebutkan daripada saya sembunyikan di balik angka yang terlihat bagus karena diukur pada penyebut yang lebih kecil.
Batasan
- Konversi dibatasi memori browser. Batas input praktisnya sekitar 10,7 MB untuk JPEG dan WebP, serta sekitar 7,1 MB untuk PNG, AVIF, dan HEIC. Angka itu diturunkan dari model puncak pemakaian memori, bukan dipilih, dan itulah sebabnya angkanya lebih kecil dari dugaan kebanyakan orang.
- Gambar di atas 8192 × 8192 piksel ditolak. Frame hasil decode yang lebih besar tidak muat di heap WebAssembly 32-bit, jadi ini batas keras, bukan nilai yang bisa disetel.
- Tidak ada jalur chunked streaming terpisah. Satu pipeline konversi lebih mudah dibatalkan, diuji, dan dipahami daripada dua jalur yang berperilaku berbeda.
- HEIC dan HEIF hanya untuk input. Tidak ada output HEIC.
- GIF, BMP, TIFF, dan RAW tidak diterima sebagai input.
- Output AVIF mempertahankan lebih sedikit metadata dibanding JPEG, PNG, dan WebP.
- Resize hanya ada di Advanced mode, dengan aspect ratio lock opsional, dan batas maksimal input resize adalah 7680 px.
- Penomoran dataset berlaku pada file log yang dihasilkan, bukan pada nama file output, dan hanya tersedia ketika log generation aktif.
- Header keamanan bergantung pada host yang menyajikannya. Pada host yang mengabaikan
_headers, aplikasi terkirim tanpa header sama sekali. - Konformansi WCAG penuh masih membutuhkan pengujian manual dengan assistive technology dan pemeriksaan aksesibilitas otomatis di luar unit test. Perilaku keyboard, focus, dan screen reader sudah dibangun di dalamnya; mensertifikasinya adalah pekerjaan yang berbeda.
- Sertifikasi legal dan compliance bergantung pada deployment serta kontrol operasional, bukan pada kode sumber.
Lisensi
Image Converter Web App dirilis dengan lisensi AGPL-3.0-only.
Aplikasi ini mendistribusikan dan menjalankan @walujanle/image-converter-wasm di browser, dan engine tersebut juga berlisensi AGPL-3.0-only, sehingga lisensinya harus sejalan. Membuat repositori ini menjadi permisif sementara engine AGPL itu tetap menjadi bagian dari runtime browser bukanlah penyederhanaan, melainkan kekeliruan.
Proyek Terbaru
Image Converter WASM
Package konversi gambar berbasis Rust WebAssembly untuk browser dan Node.js, dipublikasikan sebagai package npm.
Image Converter Windows App
Aplikasi konversi gambar Windows yang berjalan lokal, dibuat dengan Rust untuk konversi batch yang lebih privat.
Tes
Tes