Skip to content

Simplify build system: replace Vite with esbuild, consolidate configs#2

Draft
Copilot wants to merge 5 commits intomainfrom
copilot/refactor-electron-app-structure
Draft

Simplify build system: replace Vite with esbuild, consolidate configs#2
Copilot wants to merge 5 commits intomainfrom
copilot/refactor-electron-app-structure

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Jan 23, 2026

Refactor Complete: Match Electron "Building your First App" Tutorial ✅

Changes Made

  • Convert from TypeScript to plain JavaScript (CommonJS)
  • Remove React and convert to vanilla JavaScript
  • Remove all build tools (esbuild, TypeScript, Vite)
  • Move main.js and preload.js to root directory
  • Consolidate all UI into single index.html with inline styles and scripts
  • Update package.json to match tutorial structure
  • Remove all unnecessary dependencies
  • Update README to reflect new structure
  • Fix XSS vulnerabilities with proper HTML escaping
  • Use event delegation instead of inline onclick handlers

Structure Now Matches Tutorial Exactly

Before: Complex build system with 8+ dependencies

thyme/
├── electron/main.ts
├── electron/preload.ts  
├── src/App.tsx, types.ts, etc.
├── build.js
├── tsconfig.json
└── package.json (React, TypeScript, esbuild)

After: Simple Electron app following official tutorial

thyme/
├── main.js       # Main process (Node.js, CommonJS)
├── preload.js    # Preload script  
├── index.html    # Renderer (HTML + vanilla JS)
└── package.json  # Single dep: electron

Key Improvements

1 dependency (was 8): Only electron
No build step: Run directly with npm startelectron .
No TypeScript: Plain JavaScript as per tutorial
No React: Vanilla JavaScript DOM manipulation
No bundlers: No esbuild, Vite, or webpack
Matches tutorial exactly: Structure from Electron "Building your First App"
Secure: HTML escaping to prevent XSS, event delegation instead of inline handlers

All Functionality Preserved

  • ✅ Time tracking with start/stop
  • ✅ Category selection
  • ✅ GitHub issue linking
  • ✅ Daily and total duration stats
  • ✅ Local data persistence
  • ✅ Entry management (view, delete)
Original prompt

there is far too much slop on this repo. have a look at the standard starter guide to a electron app and weed out everything that we don't need. all I care about is running the electron app and nothing else. let's do a large refactor I wanna be able to run the app in the suggested way from electron


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 2 commits January 23, 2026 19:59
… start command

Co-authored-by: ItsSimonDavis <159572143+ItsSimonDavis@users.noreply.github.com>
… broken favicon

Co-authored-by: ItsSimonDavis <159572143+ItsSimonDavis@users.noreply.github.com>
Copilot AI changed the title [WIP] Refactor repository for streamlined Electron app usage Simplify build system: replace Vite with esbuild, consolidate configs Jan 23, 2026
Copilot AI requested a review from ItsSimonDavis January 23, 2026 20:05
@ItsSimonDavis
Copy link
Copy Markdown
Owner

@copilot simplify this app using the following information

Building your First App

Follow along the tutorial

This is part 2 of the Electron tutorial.

Prerequisites

Building your First App

Using Preload Scripts

Adding Features

Packaging Your Application

Publishing and Updating

Learning goals

In this part of the tutorial, you will learn how to set up your Electron project and write a minimal starter application. By the end of this section, you should be able to run a working Electron app in development mode from your terminal.

Setting up your project

Avoid WSL

If you are on a Windows machine, please do not use Windows Subsystem for Linux (WSL) when following this tutorial as you will run into issues when trying to execute the application.

Initializing your npm project

Electron apps are scaffolded using npm, with the package.json file as an entry point. Start by creating a folder and initializing an npm package within it with npm init.

npm

Yarn

mkdir my-electron-app && cd my-electron-app
npm init

This command will prompt you to configure some fields in your package.json. There are a few rules to follow for the purposes of this tutorial:

entry point should be main.js (you will be creating that file soon).

author, license, and description can be any value, but will be necessary for packaging later on.

Install dependencies with a regular node_modules folder

Electron's packaging toolchain requires the node_modules folder to be physically on disk in the way that npm installs Node dependencies. By default, Yarn Berry and pnpm both use alternative installation strategies.

Therefore, you must set nodeLinker: node-modules in Yarn or nodeLinker: hoisted in pnpm if you are using those package managers.

Then, install Electron into your app's devDependencies, which is the list of external development-only package dependencies not required in production.

Why is Electron a dev dependency?

This may seem counter-intuitive since your production code is running Electron APIs. Under the hood, Electron's JavaScript API binds to a binary that contains its implementations. The packaging step for Electron handles the bundling of this binary, eliminating the need to specify it as a production dependency.

npm

Yarn

npm install electron --save-dev

warning

In order to correctly install Electron, you need to ensure that its postinstall lifecycle script is able to run. This means avoiding the --ignore-scripts flag on npm and allowlisting electron to run build scripts on other package managers.

This is likely to change in a future version of Electron. See electron/rfcs#22 for more details.

Your package.json file should look something like this after initializing your package and installing Electron. You should also now have a node_modules folder containing the Electron executable, as well as a package-lock.json lockfile that specifies the exact dependency versions to install.

package.json

{
"name": "my-electron-app",
"version": "1.0.0",
"description": "Hello World!",
"main": "main.js",
"scripts": {
"test": "echo "Error: no test specified" && exit 1"
},
"author": "Jane Doe",
"license": "MIT",
"devDependencies": {
"electron": "23.1.3"
}
}

Advanced Electron installation steps

If installing Electron directly fails, please refer to our Advanced Installation documentation for instructions on download mirrors, proxies, and troubleshooting steps.

Adding a .gitignore

The .gitignore file specifies which files and directories to avoid tracking with Git. You should place a copy of GitHub's Node.js gitignore template into your project's root folder to avoid committing your project's node_modules folder.

Running an Electron app

Further reading

Read Electron's process model documentation to better understand how Electron's multiple processes work together.

The main script you defined in package.json is the entry point of any Electron application. This script controls the main process, which runs in a Node.js environment and is responsible for controlling your app's lifecycle, displaying native interfaces, performing privileged operations, and managing renderer processes (more on that later).

Before creating your first Electron app, you will first use a trivial script to ensure your main process entry point is configured correctly. Create a main.js file in the root folder of your project with a single line of code:

main.js

console.log('Hello from Electron 👋')

Because Electron's main process is a Node.js runtime, you can execute arbitrary Node.js code with the electron command (you can even use it as a REPL). To execute this script, add electron . to the start command in the scripts field of your package.json. This command will tell the Electron executable to look for the main script in the current directory and run it in dev mode.

package.json

{
"name": "my-electron-app",
"version": "1.0.0",
"description": "Hello World!",
"main": "main.js",
"scripts": {
"start": "electron .",
"test": "echo "Error: no test specified" && exit 1"
},
"author": "Jane Doe",
"license": "MIT",
"devDependencies": {
"electron": "23.1.3"
}
}

npm

Yarn

npm run start

Your terminal should print out Hello from Electron 👋. Congratulations, you have executed your first line of code in Electron! Next, you will learn how to create user interfaces with HTML and load that into a native window.

Loading a web page into a BrowserWindow

In Electron, each window displays a web page that can be loaded either from a local HTML file or a remote web address. For this example, you will be loading in a local file. Start by creating a barebones web page in an index.html file in the root folder of your project:

index.html

<title>Hello from Electron renderer!</title>

Hello from Electron renderer!

👋

Now that you have a web page, you can load it into an Electron BrowserWindow. Replace the contents of your main.js file with the following code. We will explain each highlighted block separately.

main.js

const { app, BrowserWindow } = require('electron')

const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600
})

win.loadFile('index.html')
}

app.whenReady().then(() => {
createWindow()
})

Importing modules

main.js (Line 1)

const { app, BrowserWindow } = require('electron')

In the first line, we are importing two Electron modules with CommonJS module syntax:

app, which controls your application's event lifecycle.

BrowserWindow, which creates and manages app windows.

Module capitalization conventionsTyped import aliases

ES Modules in Electron

ECMAScript modules (i.e. using import to load a module) are supported in Electron as of Electron 28. You can find more information about the state of ESM in Electron and how to use them in our app in our ESM guide.

Writing a reusable function to instantiate windows

The createWindow() function loads your web page into a new BrowserWindow instance:

main.js (Lines 3-10)

const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600
})

win.loadFile('index.html')
}

Calling your function when the app is ready

main.js (Lines 12-14)

app.whenReady().then(() => {
createWindow()
})

Many of Electron's core modules are Node.js event emitters that adhere to Node's asynchronous event-driven architecture. The app module is one of these emitters.

In Electron, BrowserWindows can only be created after the app module's ready event is fired. You can wait for this event by using the app.whenReady() API and calling createWindow() once its promise is fulfilled.

info

You typically listen to Node.js events by using an emitter's .on function.

  • app.on('ready', () => {
  • app.whenReady().then(() => {
    createWindow()
    })

However, Electron exposes app.whenReady() as a helper specifically for the ready event to avoid subtle pitfalls with directly listening to that event in particular. See electron/electron#21972 for details.

At this point, running your Electron application's start command should successfully open a window that displays your web page!

Each web page your app displays in a window will run in a separate process called a renderer process (or simply renderer for short). Renderer processes have access to the same JavaScript APIs and tooling you use for typical front-end web development, such as using webpack to bundle and minify your code or React to build your user interfaces.

Managing your app's window lifecycle

Application windows behave differently on each operating system. Rather than enforce these conventions by default, Electron gives you the choice to implement them in your app code if you wish to follow them. You can implement basic window conventions by listening for events emitted by the app and BrowserWindow modules.

Process-specific control flow

Checking against Node's process.platform variable can help you to run code conditionally on certain platforms. Note that there are only three possible platforms that Electron can run in: win32 (Windows), linux (Linux), and darwin (macOS).

Quit the app when all windows are closed (Windows & Linux)

On Windows and Linux, closing all windows will generally quit an application entirely. To implement this pattern in your Electron app, listen for the app module's window-all-closed event, and call app.quit() to exit your app if the user is not on macOS.

app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})

Open a window if none are open (macOS)

In contrast, macOS apps generally continue running even without any windows open. Activating the app when no windows are available should open a new one.

To implement this feature, listen for the app module's activate event, and call your existing createWindow() method if no BrowserWindows are open.

Because windows cannot be created before the ready event, you should only listen for activate events after your app is initialized. Do this by only listening for activate events inside your existing whenReady() callback.

app.whenReady().then(() => {
createWindow()

app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})

Final starter code

docs/fiddles/tutorial-first-app (40.0.0)

main.js

index.html

const { app, BrowserWindow } = require('electron/main')

const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600
})

win.loadFile('index.html')
}

app.whenReady().then(() => {
createWindow()

app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
})

app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})

Optional: Debugging from VS Code

If you want to debug your application using VS Code, you need to attach VS Code to both the main and renderer processes. Here is a sample configuration for you to run. Create a launch.json configuration in a new .vscode folder in your project:

.vscode/launch.json

{
"version": "0.2.0",
"compounds": [
{
"name": "Main + renderer",
"configurations": ["Main", "Renderer"],
"stopAll": true
}
],
"configurations": [
{
"name": "Renderer",
"port": 9222,
"request": "attach",
"type": "chrome",
"webRoot": "${workspaceFolder}"
},
{
"name": "Main",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
"windows": {
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
},
"args": [".", "--remote-debugging-port=9222"],
"outputCapture": "std",
"console": "integratedTerminal"
}
]
}

The "Main + renderer" option will appear when you select "Run and Debug" from the sidebar, allowing you to set breakpoints and inspect all the variables among other things in both the main and renderer processes.

What we have done in the launch.json file is to create 3 configurations:

Main is used to start the main process and also expose port 9222 for remote debugging (--remote-debugging-port=9222). This is the port that we will use to attach the debugger for the Renderer. Because the main process is a Node.js process, the type is set to node.

Renderer is used to debug the renderer process. Because the main process is the one that creates the process, we have to "attach" to it ("request": "attach") instead of creating a new one. The renderer process is a web one, so the debugger we have to use is chrome.

Main + renderer is a compound task that executes the previous ones simultaneously.

caution

Because we are attaching to a process in Renderer, it is possible that the first lines of your code will be skipped as the debugger will not have had enough time to connect before they are being executed. You can work around this by refreshing the page or setting a timeout before executing the code in development mode.

Further reading

If you want to dig deeper in the debugging area, the following guides provide more information:

Application Debugging

DevTools Extensions

Summary

Electron applications are set up using npm packages. The Electron executable should be installed in your project's devDependencies and can be run in development mode using a script in your package.json file.

The executable runs the JavaScript entry point found in the main property of your package.json. This file controls Electron's main process, which runs an instance of Node.js and is responsible for your app's lifecycle, displaying native interfaces, performing privileged operations, and managing renderer processes.

Renderer processes (or renderers for short) are responsible for displaying graphical content. You can load a web page into a renderer by pointing it to either a web address or a local HTML file. Renderers behave very similarly to regular web pages and have access to the same web APIs.

In the next section of the tutorial, we will be learning how to augment the renderer process with privileged APIs and how to communicate between processes

Copilot AI and others added 2 commits January 23, 2026 23:20
…e electron dependency

Co-authored-by: ItsSimonDavis <159572143+ItsSimonDavis@users.noreply.github.com>
… of inline handlers

Co-authored-by: ItsSimonDavis <159572143+ItsSimonDavis@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Jan 23, 2026

@copilot simplify this app using the following information

Building your First App...

Done! The app now follows the Electron "Building your First App" tutorial structure exactly:

Changes:

  • ✅ Converted to vanilla JavaScript (no TypeScript, no React)
  • ✅ Removed all build tools (no esbuild, no build step)
  • ✅ Single dependency: electron
  • ✅ Files in root: main.js, preload.js, index.html
  • ✅ Run with: npm startelectron .
  • ✅ HTML escaping to prevent XSS
  • ✅ Event delegation (no inline handlers)

Structure now matches the tutorial:

main.js       - Main process (CommonJS)
preload.js    - Preload script
index.html    - Complete UI with inline CSS/JS
package.json  - "main": "main.js", "start": "electron ."

All functionality preserved (time tracking, categories, GitHub issues, persistence).

Commits: 7427a42, a296271

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants