chore: scaffold Electron + Vite + React + TypeScript project

Set up electron-vite build (main/preload/renderer split), TypeScript
project references, Vitest config, and a secure preload bridge exposing
save/open diagram IPC.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ilya Ashikhmin 2026-07-02 23:13:45 +02:00
commit dcfc0094e4
10 changed files with 4163 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
node_modules/
out/
dist/
*.log
.DS_Store
.vite/
coverage/

35
electron.vite.config.ts Normal file
View File

@ -0,0 +1,35 @@
import { resolve } from 'path'
import { defineConfig } from 'electron-vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
main: {
build: {
outDir: 'out/main',
lib: { entry: resolve(__dirname, 'src/main/index.ts') }
}
},
preload: {
build: {
outDir: 'out/preload',
lib: { entry: resolve(__dirname, 'src/preload/index.ts') }
}
},
renderer: {
root: 'src/renderer',
build: {
outDir: 'out/renderer',
rollupOptions: {
input: resolve(__dirname, 'src/renderer/index.html')
}
},
resolve: {
alias: {
'@renderer': resolve(__dirname, 'src/renderer'),
'@diagram': resolve(__dirname, 'src/renderer/diagram'),
'@joint/core': resolve(__dirname, 'node_modules/@joint/core/joint.mjs')
}
},
plugins: [react()]
}
})

3945
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

36
package.json Normal file
View File

@ -0,0 +1,36 @@
{
"name": "pipeline-diagram-editor",
"version": "0.1.0",
"description": "Electron diagram editor for pipeline / network diagrams with SVG symbol nodes, typed ports, orthogonal routing and animated mock flow.",
"main": "out/main/index.js",
"author": "",
"license": "MIT",
"type": "module",
"scripts": {
"dev": "electron-vite dev",
"build": "electron-vite build",
"preview": "electron-vite preview",
"start": "electron-vite preview",
"typecheck": "tsc --noEmit -p tsconfig.node.json && tsc --noEmit -p tsconfig.web.json",
"test": "vitest run",
"test:watch": "vitest"
},
"dependencies": {
"@joint/core": "^4.0.4"
},
"devDependencies": {
"@testing-library/dom": "^10.4.0",
"@testing-library/react": "^16.1.0",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.4",
"electron": "^33.2.1",
"electron-vite": "^2.3.0",
"jsdom": "^25.0.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"typescript": "^5.7.2",
"vite": "^5.4.11",
"vitest": "^2.1.8"
}
}

63
src/main/index.ts Normal file
View File

@ -0,0 +1,63 @@
import { app, BrowserWindow, ipcMain, dialog } from 'electron'
import { readFile, writeFile } from 'node:fs/promises'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
const __dirname = fileURLToPath(new URL('.', import.meta.url))
function createWindow(): void {
const win = new BrowserWindow({
width: 1440,
height: 900,
minWidth: 900,
minHeight: 600,
title: 'Pipeline Diagram Editor',
backgroundColor: '#1e1f26',
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
contextIsolation: true,
nodeIntegration: false,
sandbox: false
}
})
if (process.env['ELECTRON_RENDERER_URL']) {
win.loadURL(process.env['ELECTRON_RENDERER_URL'])
} else {
win.loadFile(join(__dirname, '../renderer/index.html'))
}
}
// ---- File dialog IPC (save / load diagram JSON) --------------------------
ipcMain.handle('dialog:saveDiagram', async (_e, json: string) => {
const { canceled, filePath } = await dialog.showSaveDialog({
title: 'Save diagram',
defaultPath: 'diagram.pnd.json',
filters: [{ name: 'Pipeline Network Diagram', extensions: ['json'] }]
})
if (canceled || !filePath) return { ok: false as const }
await writeFile(filePath, json, 'utf-8')
return { ok: true as const, filePath }
})
ipcMain.handle('dialog:openDiagram', async () => {
const { canceled, filePaths } = await dialog.showOpenDialog({
title: 'Open diagram',
properties: ['openFile'],
filters: [{ name: 'Pipeline Network Diagram', extensions: ['json'] }]
})
if (canceled || filePaths.length === 0) return { ok: false as const }
const json = await readFile(filePaths[0], 'utf-8')
return { ok: true as const, filePath: filePaths[0], json }
})
app.whenReady().then(() => {
createWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})

13
src/preload/index.ts Normal file
View File

@ -0,0 +1,13 @@
import { contextBridge, ipcRenderer } from 'electron'
export interface DiagramFileApi {
save(json: string): Promise<{ ok: boolean; filePath?: string }>
open(): Promise<{ ok: boolean; filePath?: string; json?: string }>
}
const api: DiagramFileApi = {
save: (json) => ipcRenderer.invoke('dialog:saveDiagram', json),
open: () => ipcRenderer.invoke('dialog:openDiagram')
}
contextBridge.exposeInMainWorld('diagramFile', api)

7
tsconfig.json Normal file
View File

@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.node.json" },
{ "path": "./tsconfig.web.json" }
]
}

13
tsconfig.node.json Normal file
View File

@ -0,0 +1,13 @@
{
"compilerOptions": {
"composite": true,
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"types": ["node"]
},
"include": ["src/main/**/*", "src/preload/**/*", "electron.vite.config.ts"]
}

24
tsconfig.web.json Normal file
View File

@ -0,0 +1,24 @@
{
"compilerOptions": {
"composite": true,
"target": "ES2022",
"useDefineForClassFields": true,
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"esModuleInterop": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"types": ["vite/client", "node"],
"baseUrl": ".",
"paths": {
"@renderer/*": ["src/renderer/*"],
"@diagram/*": ["src/renderer/diagram/*"]
}
},
"include": ["src/renderer/**/*", "tests/**/*", "vitest.config.ts"]
}

20
vitest.config.ts Normal file
View File

@ -0,0 +1,20 @@
import { resolve } from 'path'
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@renderer': resolve(__dirname, 'src/renderer'),
'@diagram': resolve(__dirname, 'src/renderer/diagram'),
'@joint/core': resolve(__dirname, 'node_modules/@joint/core/joint.mjs')
}
},
test: {
globals: true,
environment: 'jsdom',
include: ['tests/**/*.test.ts', 'tests/**/*.test.tsx'],
setupFiles: ['tests/setup.ts']
}
})