It works-ish

This commit is contained in:
2026-01-24 17:02:42 -06:00
parent e219b5db4d
commit a9bf8317f4
29 changed files with 8431 additions and 130 deletions
+18 -130
View File
@@ -1,139 +1,27 @@
# Logs test-results
logs node_modules
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html) # Output
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json .output
.vercel
.netlify
.wrangler
/.svelte-kit
/build
# Runtime data # OS
pids .DS_Store
*.pid Thumbs.db
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover # Env
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env .env
.env.* .env.*
!.env.example !.env.example
!.env.test
# parcel-bundler cache (https://parceljs.org/) # Vite
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Sveltekit cache directory
.svelte-kit/
# vitepress build output
**/.vitepress/dist
# vitepress cache directory
**/.vitepress/cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# Firebase cache directory
.firebase/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v3
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
# Vite logs files
vite.config.js.timestamp-* vite.config.js.timestamp-*
vite.config.ts.timestamp-* vite.config.ts.timestamp-*
.vite
out
+2
View File
@@ -0,0 +1,2 @@
engine-strict=true
node-linker=hoisted
+116
View File
@@ -0,0 +1,116 @@
# SveltronKit
A minimal template for building Electron apps with SvelteKit.
Includes native support for Typscript and uses Electron's official recommended Electron Forge for packaging.
Everything you can do in SvelteKit, you can do in SveltronKit; meaning that you can use component
libraries like [Shadcn-Svelte](https://next.shadcn-svelte.com/).
> [!IMPORTANT]
> This template uses SvelteKit's [hash router](https://svelte.dev/docs/kit/configuration#router) to
> create a single-page app. The only difference you'll have to look out for is to start all your routed
> links with `#/` instead of `/`.
## Dependencies & Frameworks
- [SvelteKit](https://kit.svelte.dev/)
- [Electron](https://www.electronjs.org/)
- [Electron Forge](https://www.electronforge.io/)
- [TypeScript](https://www.typescriptlang.org/)
- [TailwindCSS](https://tailwindcss.com/)
> [!NOTE]
> I've included TailwindCSS in this template because I use it in my own projects, but you can remove
> it easily if you don't want it.
## Getting Started
> [!WARNING]
> This project uses [`pnpm`](https://pnpm.io/) and uses [patching](https://pnpm.io/cli/patch) to work
> around some issues with SvelteKit. When this [PR](https://github.com/sveltejs/kit/pull/13812) merges,
> you can remove the patching and use the latest version of SvelteKit.
Start by installing the dependencies:
```
pnpm install
```
**Development:**
```
pnpm run start
```
[Electron Forge](https://www.electronforge.io/) with the [Vite plugin](https://www.electronforge.io/plugins/vite)
will take care of running the development server and building the app for you. You don't need to run
`vite dev` or `vite build` yourself. This also means that it supports hot module replacement (HMR).
**Production:**
```
pnpm run package
```
This will build the app and you can find the output in the `out` directory. You can run the production
app by opening the `.app` file in the `out` directory. This will not create your app's installer
for distribution though.
To create a distributable installer, you can use:
```
pnpm run make
```
This will create a distributable installer for your app. You can configure this in the `makers` section
in `forge.config.ts`. Reference the [makers documentation](https://www.electronforge.io/makers) for more
information.
# Electron Crash Course
> [!NOTE]
> This is a super simplified version of the Electron documentation meant to give you a general idea
> of how Electron works and how each file corresponds to responsibilities in Electron. For a more
> accurate description of how Electron works, you can refer to the [official documentation](https://www.electronjs.org/docs).
I found that most of the problems I encountered when setting up Electron were because I didn't know
how Electron works and that the documentation was too dense to get up to speed with, so I'll include
a crash course here. _I will be making a lot of analogies to web development_ as it seems like a lot
of people who are new to Electron come from web development.
Because everything in Electron is client based, you'll need to host your own server if you want to
access any sensitive logic like a database or authentication, etc.
## main.ts
This file defines what the main process will do. The process runs your app. It's the one that
creates and manages windows and also has permissions to access the file system. You also define
"_signals_"/"_endpoints_", through IPC, that let the renderer process (browser that runs your app)
can "_call_" to interact with the file system.
By default, Electron will block off file system access to the renderer process as a security measure,
which is the reason why you need to use IPC to interact with the file system.
## preload.ts
Think about this as a "bridge" or a "network"/"proxy" between the main process and the renderer process.
You specify what functions that the renderer process can call and these functions will usually be
interacting with the file system through the main process.
## renderer
The renderer process is the browser that runs your app. Just treat this like another SvelteKit app.
## Overview
```mermaid
flowchart LR
subgraph main[Main Process]
electron
end
subgraph renderer[Renderer Process]
browser
end
electron <-- preload --> renderer
```
+1618
View File
File diff suppressed because it is too large Load Diff
+6
View File
@@ -0,0 +1,6 @@
import { expect, test } from '@playwright/test';
test('home page has expected h1', async ({ page }) => {
await page.goto('/');
await expect(page.locator('h1')).toBeVisible();
});
+59
View File
@@ -0,0 +1,59 @@
import { app, BrowserWindow } from "electron";
import path from "node:path";
import started from "electron-squirrel-startup";
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
if (started) {
app.quit();
}
const createWindow = () => {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(import.meta.dirname, "preload.js"),
},
});
// and load the index.html of the app.
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL);
mainWindow.webContents.on("did-frame-finish-load", () => {
mainWindow.webContents.openDevTools({ mode: "detach" });
});
} else {
mainWindow.loadFile(
path.join(
import.meta.dirname,
`../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`,
),
);
}
};
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on("ready", createWindow);
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});
app.on("activate", () => {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and import them here.
View File
+59
View File
@@ -0,0 +1,59 @@
import type { ForgeConfig } from "@electron-forge/shared-types";
import { MakerSquirrel } from "@electron-forge/maker-squirrel";
import { MakerZIP } from "@electron-forge/maker-zip";
import { MakerDeb } from "@electron-forge/maker-deb";
import { MakerRpm } from "@electron-forge/maker-rpm";
import { VitePlugin } from "@electron-forge/plugin-vite";
import { FusesPlugin } from "@electron-forge/plugin-fuses";
import { FuseV1Options, FuseVersion } from "@electron/fuses";
const config: ForgeConfig = {
packagerConfig: {
asar: true,
},
rebuildConfig: {},
makers: [
new MakerSquirrel({}),
new MakerZIP({}, ["darwin"]),
new MakerRpm({}),
new MakerDeb({}),
],
plugins: [
new VitePlugin({
// `build` can specify multiple entry builds, which can be Main process, Preload scripts, Worker process, etc.
// If you are familiar with Vite configuration, it will look really familiar.
build: [
{
// `entry` is just an alias for `build.lib.entry` in the corresponding file of `config`.
entry: "electron/main.ts",
config: "vite.main.config.ts",
target: "main",
},
{
entry: "electron/preload.ts",
config: "vite.preload.config.ts",
target: "preload",
},
],
renderer: [
{
name: "main_window",
config: "vite.config.ts",
},
],
}),
// Fuses are used to enable/disable various Electron functionality
// at package time, before code signing the application
new FusesPlugin({
version: FuseVersion.V1,
[FuseV1Options.RunAsNode]: false,
[FuseV1Options.EnableCookieEncryption]: true,
[FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,
[FuseV1Options.EnableNodeCliInspectArguments]: false,
[FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
[FuseV1Options.OnlyLoadAppFromAsar]: true,
}),
],
};
export default config;
+1
View File
@@ -0,0 +1 @@
/// <reference types="@electron-forge/plugin-vite/forge-vite-env" />
+69
View File
@@ -0,0 +1,69 @@
{
"name": "electron-svelte",
"productName": "electron-svelte",
"description": "Electron Svelte",
"private": true,
"version": "0.0.1",
"type": "module",
"main": ".vite/build/main.js",
"author": {
"name": "Pandoks_",
"email": "35944715+Pandoks@users.noreply.github.com"
},
"scripts": {
"prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"test:unit": "vitest",
"test": "npm run test:unit -- --run && npm run test:e2e",
"test:e2e": "playwright test",
"start": "electron-forge start",
"package": "vite build && electron-forge package",
"make": "vite build && electron-forge make",
"publish": "electron-forge publish"
},
"devDependencies": {
"@electron-forge/cli": "^7.8.1",
"@electron-forge/maker-deb": "^7.8.1",
"@electron-forge/maker-dmg": "^7.8.1",
"@electron-forge/maker-rpm": "^7.8.1",
"@electron-forge/maker-squirrel": "^7.8.1",
"@electron-forge/maker-zip": "^7.8.1",
"@electron-forge/plugin-auto-unpack-natives": "^7.8.1",
"@electron-forge/plugin-fuses": "^7.8.1",
"@electron-forge/plugin-vite": "^7.8.1",
"@electron/fuses": "^1.8.0",
"@playwright/test": "^1.49.1",
"@sveltejs/adapter-static": "^3.0.8",
"@sveltejs/kit": "^2.16.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"@tailwindcss/vite": "^4.0.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/svelte": "^5.2.4",
"@types/electron-squirrel-startup": "^1.0.2",
"@types/node": "^22",
"electron": "^36.2.1",
"jsdom": "^26.0.0",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"tailwindcss": "^4.0.0",
"typescript": "^5.0.0",
"vite": "^6.2.6",
"vitest": "^3.0.0"
},
"dependencies": {
"electron-squirrel-startup": "^1.0.1"
},
"pnpm": {
"onlyBuiltDependencies": [
"esbuild",
"electron",
"electron-winstaller"
]
},
"patchedDependencies": {
"@sveltejs/kit": "patches/@sveltejs__kit.patch"
}
}
+26
View File
@@ -0,0 +1,26 @@
diff --git a/src/exports/vite/index.js b/src/exports/vite/index.js
index e23cf2b833fc0a04287d34328b97bf64c7916f0d..81b6f30f3a5f2c8d3d0337777e141e549c4a5f98 100644
--- a/src/exports/vite/index.js
+++ b/src/exports/vite/index.js
@@ -473,7 +473,7 @@ Tips:
// for internal use only. it's published as $app/paths externally
// we use this alias so that we won't collide with user aliases
case sveltekit_paths: {
- const { assets, base } = svelte_config.kit.paths;
+ const { assets, base, relative } = svelte_config.kit.paths;
// use the values defined in `global`, but fall back to hard-coded values
// for the sake of things like Vitest which may import this module
@@ -488,10 +488,10 @@ Tips:
return dedent`
export let base = ${s(base)};
- export let assets = ${assets ? s(assets) : 'base'};
+ export let assets = ${relative ? "'.' + " : ''}${assets ? s(assets) : 'base'};
export const app_dir = ${s(kit.appDir)};
- export const relative = ${svelte_config.kit.paths.relative};
+ export const relative = ${relative};
const initial = { base, assets };
+9
View File
@@ -0,0 +1,9 @@
import { defineConfig } from '@playwright/test';
export default defineConfig({
webServer: {
command: 'npm run build && npm run preview',
port: 4173
},
testDir: 'e2e'
});
+6284
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -0,0 +1,3 @@
@import 'tailwindcss';
@plugin '@tailwindcss/forms';
@plugin '@tailwindcss/typography';
+13
View File
@@ -0,0 +1,13 @@
// See https://svelte.dev/docs/kit/types#app.d.ts
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};
+12
View File
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>
+7
View File
@@ -0,0 +1,7 @@
import { describe, it, expect } from 'vitest';
describe('sum test', () => {
it('adds 1 + 2 to equal 3', () => {
expect(1 + 2).toBe(3);
});
});
+1
View File
@@ -0,0 +1 @@
// place files you want to import through the `$lib` alias in this folder.
+1
View File
@@ -0,0 +1 @@
import "../app.css";
+2
View File
@@ -0,0 +1,2 @@
<h1>Welcome to SvelteKit</h1>
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>
+11
View File
@@ -0,0 +1,11 @@
import { describe, test, expect } from 'vitest';
import '@testing-library/jest-dom/vitest';
import { render, screen } from '@testing-library/svelte';
import Page from './+page.svelte';
describe('/+page.svelte', () => {
test('should render h1', () => {
render(Page);
expect(screen.getByRole('heading', { level: 1 })).toBeInTheDocument();
});
});
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

+16
View File
@@ -0,0 +1,16 @@
import adapter from "@sveltejs/adapter-static";
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
const config = {
preprocess: vitePreprocess(),
kit: {
adapter: adapter({
pages: ".vite/renderer/main_window",
}),
router: {
type: "hash",
},
},
};
export default config;
Submodule
+1
Submodule sveltronkit added at 6ccd3cf00b
+20
View File
@@ -0,0 +1,20 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"target": "ESNext",
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}
+33
View File
@@ -0,0 +1,33 @@
import tailwindcss from "@tailwindcss/vite";
import { svelteTesting } from "@testing-library/svelte/vite";
import { sveltekit } from "@sveltejs/kit/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [tailwindcss(), sveltekit()],
test: {
workspace: [
{
extends: "./vite.config.ts",
plugins: [svelteTesting()],
test: {
name: "client",
environment: "jsdom",
clearMocks: true,
include: ["src/**/*.svelte.{test,spec}.{js,ts}"],
exclude: ["src/lib/server/**"],
setupFiles: ["./vitest-setup-client.ts"],
},
},
{
extends: "./vite.config.ts",
test: {
name: "server",
environment: "node",
include: ["src/**/*.{test,spec}.{js,ts}"],
exclude: ["src/**/*.svelte.{test,spec}.{js,ts}"],
},
},
],
},
});
+13
View File
@@ -0,0 +1,13 @@
import { defineConfig } from "vite";
// https://vitejs.dev/config
export default defineConfig({
build: {
outDir: ".vite/build",
lib: {
formats: ["es"],
entry: "electron/main.ts",
fileName: "main",
},
},
});
+13
View File
@@ -0,0 +1,13 @@
import { defineConfig } from "vite";
// https://vitejs.dev/config
export default defineConfig({
build: {
outDir: ".vite/build",
lib: {
formats: ["es"],
entry: "electron/preload.ts",
fileName: "preload",
},
},
});
+18
View File
@@ -0,0 +1,18 @@
import "@testing-library/jest-dom/vitest";
import { vi } from "vitest";
// required for svelte5 + jsdom as jsdom does not support matchMedia
Object.defineProperty(window, "matchMedia", {
writable: true,
enumerable: true,
value: vi.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
})),
});
// add more mocks here if you need them