Building imgplex: part 2

imgplex tooling game-dev

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 - The small, measured optimizations beneath the big ones

In part 1 I covered why I’m building imgplex - a node-based batch image tool on top of ImageMagick - and how I landed on a stack of Electron, Vite, Svelte 5, and Svelte Flow. This post is where the plan meets reality: getting all four of those to actually run together, and setting up an architecture I wouldn’t have to fight later.

Setting up a web dev environment

With the tech stack decided, the first practical challenge was getting Electron, Vite, Svelte 5, and Svelte Flow all working together in a single project. Each of these has opinions about how they want to be configured, and they don’t always agree with each other out of the box.

This is where Claude came in handy - figuring out all the dependencies and setting up a development environment. The starting point was electron-vite - a scaffolding tool that pre-wires the project structure and build pipeline. Vite here is essentially the build system and dev server, similar to how Unity handles compilation and hot-reloading in the editor. electron-vite extends it to handle Electron’s requirements: the main process, the preload script, and the renderer all need to be compiled separately, and electron-vite sets all of that up so you can focus on building the actual app.

The available templates didn’t include Svelte, so I went with the Vanilla template and added Svelte manually. This was the right call anyway - there was nothing to rip out, just things to add.

The first dependency conflict came up immediately: the scaffold shipped with Vite 5, but the latest Svelte plugin requires Vite 6+. The fix was pinning to an older version of the plugin that supports Vite 5. Minor, but a preview of the kind of version-negotiation that comes with assembling a stack from several fast-moving libraries. Not unlike trying to get a specific version of a Unity package to work with a specific editor version.

The three-process mental model

This is the thing most worth understanding early. Electron applications have two completely separate JavaScript environments that cannot share memory or imports directly - and getting this wrong wastes a lot of debugging time.

The main process is essentially a Node.js application running on your machine. It can access the filesystem, spawn processes, and use any native library. Think of it as the backend, or the editor scripts side of things.

The renderer process is basically a Chrome browser tab. It renders the UI. It has no access to the filesystem or native APIs by default. Think of it as the runtime game side - it only knows what you explicitly give it.

The preload script is a small bridge between the two. It runs in a special privileged context that has access to both environments, and its only job is to expose a controlled, safe API from the main process to the renderer. In Electron terms this is called the contextBridge.

In practice: whenever the UI needs to do something that touches the filesystem or spawns an ImageMagick process, it calls through IPC (inter-process communication) to the main process, which does the actual work and sends the result back. The renderer never touches files directly.

This is actually a clean architectural separation once you internalize it. The UI just asks for things, the backend does them. It maps reasonably well to how you’d separate gameplay logic from engine systems in a well-structured game codebase.

Folder structure

Rather than letting the scaffold dictate the structure, I set up the folder layout from the spec upfront:

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)

The src/shared/ folder is particularly important. Any type that crosses the IPC boundary - node definitions, graph state, pipeline progress events - is defined there so both sides of the app stay in sync. TypeScript catches mismatches at compile time, which saves a lot of runtime debugging.

First working state

The milestone for this phase was an Electron window showing a working Svelte Flow canvas with a node I could drag around. Nothing processed images yet - the goal was just confirming the full stack was correctly wired and hot-reload was working across all three bundles.

Once that was running, the foundation was solid enough to start building real features on top of.

Next post in this series: Building imgplex: part 3 - The node definition system