Building a WebP Converter in the Browser Without JavaScript Using Dart WASM
A fully client-side WebP image converter built in Dart - no JavaScript, no server. Learn how Dart + WebAssembly make it possible to build fast, privacy-friendly browser tools with a modern language you’ll actually enjoy writing
No JavaScript. No server. Just Dart and WebAssembly.
I recently built a fully client-side WebP image converter that runs entirely in the browser - without a single line of JavaScript. Instead, I used Dart compiled to WebAssembly (WASM). It’s fast, privacy-friendly (no files leave your device), and runs smoothly in any modern browser.
Dart is often seen as "just for Flutter," but it’s a surprisingly versatile language. I personally see it as a strong alternative to JavaScript and TypeScript - with better type safety, great tooling, and more usecases. Dart can be compiled to native code, JavaScript, or WebAssembly, making it very flexible. And of course, there’s Flutter, which supports building UI apps for a wide range of platforms. Just awesome.
Why Dart for WebP converter in browser?
Because why not? It was an amazing experience and I’m glad we finally have a friendly way to write WebAssembly without diving into advanced languages like C++ or Rust. Now we can use languages which use garbage collector for memory managment like Dart, Java and similar ones to compile as WebAssembly thanks to WASMGC - an extension to the WebAssembly specification.
Dart is clean, productive, and comes with null safety and static typing - which makes writing robust, maintainable code much easier than in plain JavaScript. I’ve used TypeScript a lot, and while it solves many of JavaScript’s problems, Dart feels more cohesive and predictable. The syntax is familiar, but the experience is closer to working in a strictly typed language.
With Dart now compiling to WebAssembly, we can build entire apps that run directly in the browser - without any JavaScript dependencies. That’s great for performance, but also for code simplicity - the browser only needs to load one small binary instead of dozens of JavaScript libraries. It also helps with code obfuscation, since the compiled WASM is harder to reverse-engineer than plain JavaScript. And you get more control over what actually runs in the browser, without unexpected behavior from external dependencies.
The goal was simple: build a drag-and-drop WebP converter running on client-side that doesn’t use JavaScript. Aside from a tiny bootstrap loader that fetches and initializes the WASM file, everything is handled by Dart.
Key Features of Web Converter
- No file upload: all processing happens locally in browser
- Support for drag-and-drop and file input
- Preview and download the converted WebP file
- Batch processing
- No Javascript dependency
All the logic is written in Dart - handling files, loading images as base64, converting them using the browser's Canvas
API. I used the package:web/web.dart
package to interact directly with DOM APIs: buttons, file inputs, drag-and-drop zones, and more.
Comparison with Blazor (C# WASM)
I’ve built a similar app in Blazor using C# in past. Blazor is mature, and honestly feels like a better React written in C#. But the downside is it’s tightly coupled to Microsoft’s tooling, and the final WASM bundle is large.
With Dart, my final .wasm
file is just 170KB. It’s lightweight, standalone, and can easily be plugged into any existing stack - like WordPress, Ghost, or even Electron.
That said, the Dart+WASM toolchain isn’t as mature as Blazor.
Browser API Access in Dart
This is where Dart shines: you can interact with browser APIs just like you would in JavaScript. In my converter I used:
FileReader
to read the image filesCanvas
to convert images to WebPAnchorElement
to download the resultDragEvent
andDataTransfer
to handle drag-and-drop- Standard DOM manipulation like toggling classes
Challenges using Dart WASM
Getting started wasn’t super smooth. Compared to Blazor and Microsoft's polished tooling, Dart still has room to grow. Documentation is lacking in places, and there’s no hot reload - so the workflow looks like: make a change, compile, reload the browser, hope it works.
I had to piece together info from GitHub issues, blog posts, and trial and error. Tools like ChatGPT helped a lot when figuring out how things work under the hood.
- Debugging Dart WASM apps isn’t as easy yet.
- The ecosystem is smaller than JavaScript’s, though Dart’s JavaScript interop is excellent and helps fill in the gaps.
- The WASM toolchain for Dart is still quite new. Even though the Flutter team says it’s stable, things will probably change as it matures.
Some useful code snippets
Here’s a basic demonstration of how to set up a Dart WASM project with an index.html
page, handle a button click event, and insert text into a <div>
element.
1. Create index.html with following code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple HTML Demo</title>
<style>
body {
margin: 20px;
}
#output {
border: 1px solid #ccc;
padding: 20px;
min-height: 100px;
margin-bottom: 20px;
background: #f9f9f9;
}
button {
padding: 10px 20px;
font-size: 16px;
}
</style>
<script type="module">
(async function () {
let dart2wasm_runtime;
let moduleInstance;
try {
const dartModulePromise = WebAssembly.compileStreaming(fetch('/app.wasm'));
const imports = {};
dart2wasm_runtime = await import('/app.mjs');
moduleInstance = await dart2wasm_runtime.instantiate(dartModulePromise, imports);
} catch (exception) {
console.error(`Failed to fetch and instantiate wasm module: ${exception}`);
console.error('See https://dart.dev/web/wasm for more information.');
}
if (moduleInstance) {
try {
await dart2wasm_runtime.invoke(moduleInstance);
} catch (exception) {
console.error(`Exception while invoking test: ${exception}`);
}
}
})();
</script>
</head>
<body>
<h1>Simple HTML Demo</h1>
<div id="output">
</div>
<button id="button">Add Text</button>
</body>
</html>
You can notice we define basic HTML elements to demonstrate Dart’s interaction with the DOM, as well as an initialization script that loads the WASM binary using fetch and runs the app.mjs
script which will be generated during Dart project compilation.
- Create simple pubspec.yaml:
name: dart_wasm_app
environment:
sdk: '>=3.2.0 <4.0.0'
dependencies:
web: any
- Create app.dart in your folder
We will define necessary import for DOM manipulation and write simple code in main function.
import 'package:web/web.dart' as web;
import 'dart:js_interop';
void main() {
print('[+] Dart Wasm init...'); //Should be visible in browser console
final output_element = web.document.getElementById('output');
output_element?.textContent = "Hello from Dart WASM"; //Should show text in output div
final button = web.document.querySelector('#button');
button?.addEventListener('click', (web.Event event){
print('button clicked');
output_element?.textContent = "Button clicked"; //Change text in output div
}.toJS);
}
- Download libraries and Compile to WASM
At first we need to fetch necessary libraries defined in pubspec.yaml
using command dart pub get
. Now we are ready to compile to wasm, run this command for compilation:dart compile wasm app.dart -o ./app.wasm
. Buch of files should be created in your project now.

- Run simple browser to test your Dart powered HTML
You can quickly start a simple local web server using Python from the command line. Just run:python3 -m http.server
Now open your browser and visit local url 127.0.0.1:8000, you should see something similar as on image below:

To wrap things up
I hope this short article helps you explore Dart as a serious option for running WebAssembly in the browser. There's already a lot working - you can even expose Dart functions to the window
object and call them from JavaScript. I might cover that in a future update.
I'm really excited that we can now use such a nice language for building browser-based tools with WASM. Dart’s web
package gives access to most browser APIs, so nearly everything you’d normally do in JavaScript is achievable in Dart too.
I’ll definitely be keeping an eye on how this evolves. And if I ever need a WASM-powered tool in the browser, Dart will likely be my first choice.
You can try our converter built in Dart + WASM https://noiseamplifier.com/convertor-webp.
Thanks for reading!