DevBlog 2026 week 27
Lowering the size and load time!
This week looks like a week of optimizations, but its not that simple, its never that simple.
OCC split
KiCad has OpenCascade as a dependency, which was about 30% of the pcbnew.wasm, but it only does STEP parsing/exporting for the 3D viewer. So we moved OCC into a stnadalone worker, with its own memory, and only fetch it on demand. The editor size dropped from ~146MB to ~109MB which is HUGE. If you don’t use the 3D viewer, you don’t need to load it, and the app loads way faster. As an added bonus, we found out that STEP export was not working, and this brought us a step closer to make that work too!
Native exceptions
We migrated the whole build from Emscripten’s (legacy) JS-based exception handling (which effectively did a wasm->js->wasm call on every try/catch) to native wasm exceptions. KiCad is very exception heavy, so we got a big payoff, eeschema.wasm gzip went from 37.2MB to 28.9MB, kinda the same ~24% reduction was in pcbnew too. The cold loads got 30-35% faster and documents opens up 20-35% faster. Not crossing the wasm <-> js boundary is a big win both in size and speed. It also unblocked the multithreading in some cases like in the 3D view raytracer.
Multithreading fixes
WASM pthreads are actually WebWorkers, so spawning one, requires the main thread to return to the browser event loop, and request one. This means that every block on the main thread that is waiting for a worker thread start effectively deadlocked themselves. The raytracer was hit with this, and we fixed it with a predefined threadpool, and also fixed problems with its resize interactions.
Libs loading (symbols and footprints)
We load libs in multiple passes. First while the editor loads we do an R2->IDB sync
(which is a bit slow, but we found an easy solution for that this morning so we will do that parallel), and we have a wasm <-> js sync.
This later was about 4.5 minutes for 222 symbol libs, which is far from being user friendly.
The previous solution used json between the boundary, but only the json deserialization in the wasm part was 61s (yes… 0.2s serialization in js land, 61s deser in wasm land, it seems removing \n-s is “hard”).
So we changed the protocol a bit, and we send a json in the first line, and a big concatenated text as a blob after, and the json tells the wasm where the next “file” starts in the blob. (This reduced the 61s to 0.2s.)
We changed the loading to be parallel, and the memory allocator to mimalloc (120s->45s raw cpu time in s-expr pars just with this change).
All of the above reduced the raw loadtime from 274s to 44s which is in parallel about 6-10s, which is much better.
We hiding the “freezed up” editor under the overlay while this happening, and communicate the fact that we need to wait, which makes the whole process less painful to the future users.
Size optimizations
I wanted to switch back the “default footprint” feature in eeschema, so we hit a problem. Previously we tried to minimize the wasm footprint from 500MB so we split the KiCad editor into multiple smaller editors. KiCad has these components called KiFaces. For the eeschema, we needed the symbol editor (and vica versa). For the pcbnew, we needed the footprint editor (and vica versa). But we effectively split KiCad to smaller pieces, achiving ~180-230MB wasms. But if you want to show the footprints in the symbol editor, we need the footprint editor KiFace, which is effectively dragging the whole pcbnew too. Also, at this point we have about 80-100MB wasms. So we made a single wasm from these four editors, only changing “main” to select which KiFace should open up.
Now instead of 4x80MB we have a single 100MB or so wasm. Also we switched on brotli-q11 which killed our release time (3m to 20m), but shaved the download size a LOT (currently 30MB).
3D viewer
We made two separate things in this part (4 actually if we count the multi threading and the OCC too).
We built a gl1-on-WebGL2 emulation layer, which lets KiCad’s real OpenGL 3D renderer to run unmodified. Basically we made a WebGL implementation of the functions that KiCad uses, and we made a translation layer to convert the OpenGL calls to WebGL calls. This is a huge win, because we can now run the 3D viewer on any browser, its fast, efficient, and worked like a charm!
On the other hand, we added 3D models to work the same as other libs, so they do the same R2->IndexedDb->WASM loading, but its done lazily, and per model instead of per lib, so you don’t need to download models that you never viewed. We also added some UI to the demo page to show you how much data we store in your IDB, and you can drop the 3D models.
Demo release
We made a demo release and wrote a post to HackerNews which was on the first page for a while, and got some buzz.