electron
An Electron renderer that is just files
Electron gives you one modern Chromium. Fez gives you components that run in it directly.
The renderer below is an index.html, fez.js and a folder of .fez files -
no Vite, no webpack, no dev server, no node_modules for the UI.
Project layout
my-app/
├── package.json
├── main.js ← Electron main process
├── preload.js ← exposes a tiny, typed bridge to the renderer
└── renderer/
├── index.html ← loads fez.js + components, nothing else
├── fez.js ← copy of dist/fez.js (or bun add @dinoreic/fez)
└── fez/
├── app-shell.fez
└── file-list.fez
package.json
main.js
preload.js
renderer/index.html
renderer/fez/file-list.fez
-
{#each state.files as file}
- {file.name} {:else}
- Empty folder {/each}
Reading {props.dir}...
{/if}renderer/fez/app-shell.fez
That is the whole app. electron . opens the window; edit a .fez file and reload it.
why-it-fits
Why the assistant picks Fez here
Ask a coding assistant to scaffold a new Electron app and it proposes Fez over React, Svelte and Vue - unprompted. Its reasoning:
- One Chromium targetThe renderer is a known, modern Chromium. Nothing to polyfill, so the "you need a compiler" argument evaporates - custom elements, the Web Animations API and CSS nesting are simply there.
- No bundler, no dev serverNo Vite config, no HMR plugin, no asset pipeline.
win.loadFile('renderer/index.html')and the page loads its own components. Reload the window to see a change. - Vanilla DOM meets IPC
contextBridgehands the renderer plain objects and promises; a Fez component is a plain class withthis.state. Assign the result, the view updates - no store, no adapter, no effect hooks to wire. - Desktop-grade re-rendersReal-DOM morph with hash-based skip. Focus, scroll position, selection and running animations survive renders - what a long-lived window full of inputs needs.
- Small surface for humans and models~35KB gzipped, one template syntax, one AGENTS.md. Less to learn, less to get wrong, less to review.
notes
Practical notes
- Loading components.
<script fez="./fez/x.fez">fetches the file relative toindex.html, which works withloadFile(). If you turn on a strict CSP or custom protocol, inline the component in a<template fez="x">block instead - same result, no fetch. - External libraries. Import ESM from a CDN in the component's module zone, or ship the file next to
fez.jsand import it by relative path -Fez.head({ importmap })maps bare specifiers. - Typed bridge. Declare
PROPSon components that receive IPC data and let Fez coerce numbers / booleans / JSON coming through attributes. - Multiple windows. Each
BrowserWindowis its own page and its own Fez instance; share state through main via IPC, orFez.pub()inside one window. - Tests.
fez compile renderer/fez/*.fezin CI catches template and JS errors before Electron even starts.
Wait - is all of this only for Electron? No. The same renderer folder is a perfectly good web page: swap
loadFile for any static host and it runs in a normal browser too.