This is a series of posts on building imgplex, best read in order:
Part 1 - The why, what, and how of imgplex
Part 2 - Getting things up and running
Part 3 - The node definition system
Part 4 - Executing the node graph, making it fast
Part 5 - Two graphs in one
Part 6 - Multiple inputs and outputs, processing images as sets
Part 7 - Profiling and Optimization
Setting up the development environment
With the stack chosen, I needed to get Electron, Vite, Svelte 5, and Svelte Flow running in the same project. None of the available templates matched that combination exactly, so some manual setup and dependency pinning was unavoidable.
I used Claude to generate the first pass of the project configuration, then worked
through the dependency and build errors until the stack was stable. I started with
electron-vite, which sets up the separate build targets Electron needs for its main
process, preload script, and renderer. That saved me from having to assemble the
initial Vite configuration by hand and gave me a working development server with hot
reload.
The available templates didn’t include Svelte, so I went with the Vanilla template and added Svelte manually. This was probably the cleaner option anyway - there was nothing to rip out, only the Svelte pieces to add.
The first conflict appeared immediately: the scaffold used Vite 5, while the current Svelte plugin required Vite 6 or newer. For the initial setup, I pinned the plugin to an older version that supported Vite 5 rather than upgrading the rest of the scaffold.
Electron’s process structure
The part of Electron I needed to understand first was the separation between the main process and the renderer. They run in different JavaScript environments, so the renderer cannot directly import code or access state from the main process. Some of my early setup decisions became much clearer once I understood that boundary. I initially treated them too much like parts of one application, which led to several confusing import and API-access errors.
The main process is a Node.js application with access to the operating system. It handles work such as reading files, launching ImageMagick, managing application windows, and registering IPC handlers.
The renderer is the Svelte application displayed inside an Electron browser window. It is responsible for the interface, but it cannot directly access the filesystem or other Node.js APIs.
The preload script runs alongside the renderer in an isolated context. It exposes a small, controlled API that the renderer can call without giving the UI unrestricted access to Node.js.
In imgplex, that means the renderer sends an IPC request when the user selects a file, loads node definitions, or starts an ImageMagick operation. The main process performs the work and sends the result or progress update back. The renderer never accesses those system APIs directly.
It felt cumbersome at first because even simple file operations needed an IPC call. Once the boundary was in place, though, it made the rest of the application easier to divide: the renderer managed the graph and UI, while the main process handled system-level work.
Folder structure
The generated folder structure was not a good fit for the application, so I changed it to this:
electron/ - main process and preload script
src/shared/ - types and constants shared by both processes
src/main/ - Node.js business logic (pipeline, registry, IPC handlers)
src/renderer/ - Svelte application (all the UI)
node-definitions/ - JSON node descriptor files (loaded at runtime)
Types used on both sides of the IPC boundary live in src/shared/. This includes
node definitions, serialized graph data, and pipeline progress messages. Keeping
those types in one place meant that changing an IPC payload produced TypeScript
errors in both the sender and receiver instead of failing later at runtime.
First working state
The first milestone was an Electron window containing a Svelte Flow canvas and one node I could drag around. It did not process anything yet. I mainly wanted to test that the renderer worked, the Electron processes could communicate, and changes rebuilt without restarting the whole application. It was not much of an application yet, but it proved the stack worked.
Next post in this series: Building imgplex: part 3 - The node definition system