adapter-electron
A SvelteKit adapter for Electron desktop apps that uses native protocol handling for production and seamless Vite dev server integration for development.
Features
- ✅ Native Protocol Handling: Uses Electron's
protocol.handle()API for production - ✅ Development Integration: Seamless Vite dev server integration with HMR
- ✅ No HTTP Server: Bypasses Node.js HTTP servers entirely in production
- ✅ Full SvelteKit Support: SSR, API routes, static assets, prerendered pages, form actions
- ✅ Clean Architecture: All Electron integration code is encapsulated
- ✅ Production Ready: Works with electron-builder and similar tools
- ✅ TypeScript Support: Full type definitions included
- ✅ Proper Error Handling: User-friendly error reporting with GitHub issue links
Installation
npm install adapter-electron
Quick Start
1. Configure SvelteKit
In your svelte.config.js:
import adapter from 'adapter-electron';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: vitePreprocess(),
kit: {
adapter: adapter()
}
};
export default config;
2. Set up Vite Configuration
In your vite.config.ts:
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
import { electronPlugin } from 'adapter-electron';
export default defineConfig({
plugins: [
sveltekit(),
electronPlugin({
// Auto-detects src/main.ts and src/preload.ts by default
// Override paths if needed:
// mainEntry: 'src/main.ts', // Main process entry (default: 'src/main.ts')
// preloadEntry: 'src/preload.ts', // Preload script entry (default: 'src/preload.ts')
// mainOut: 'out/main/index.js', // Main output (default: 'out/main/index.js')
// preloadOut: 'out/preload/index.js' // Preload output (default: 'out/preload/index.js')
})
]
});
3. Create Electron Main Process
Create src/main.ts:
import { app, BrowserWindow } from 'electron';
import { setupHandler, getPreloadPath, registerAppScheme } from 'adapter-electron/functions/setupHandler';
// Electron log is the best way I've found to get logs from compiled binaries
import log from 'electron-log/main';
console.log = log.log;
let mainWindow: BrowserWindow | null = null;
let stopIntercept: (() => void) | undefined;
process.on('SIGTERM', () => process.exit(0));
process.on('SIGINT', () => process.exit(0));
// First register the app scheme
registerAppScheme();
async function createWindow() {
// Create the browser window
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
// Second configure the preload script
preload: getPreloadPath(),
contextIsolation: true,
devTools: true
}
});
mainWindow.once('ready-to-show', () => mainWindow?.webContents.openDevTools());
mainWindow.on('closed', () => {
mainWindow = null;
stopIntercept?.();
});
// Third, Setup the handler
stopIntercept = await setupHandler(mainWindow);
return mainWindow;
}
app.on('ready', createWindow);
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', async () => {
if (BrowserWindow.getAllWindows().length === 0 && !mainWindow) {
try {
await createWindow();
} catch (error) {
console.error('Failed to create window:', error);
}
}
});
4. Create Preload Script
Create src/preload.ts:
console.log('Preload loaded');
5. Configure Package.json
Add these fields to your package.json:
{
"main": "./out/main/index.cjs",
"scripts": {
"sync": "svelte-kit sync",
"dev": "pnpm sync && concurrently \"vite dev\" \"wait-on http://localhost:5173 && nodemon --watch out --exec electron .\" --names \"sveltekit,electron\" --prefix-colors \"#ff3e00,blue\"",
"build": "pnpm sync && vite build",
"build:all": "pnpm build && electron-builder -mwl --config",
"build:win": "pnpm build && electron-builder --win --config",
"build:mac": "pnpm build && electron-builder --mac --config",
"build:linux": "pnpm build && electron-builder --linux --config"
}
}
6. Build and Run
# Development (uses Vite dev server with HMR)
npm run dev
# Production build
npm run build
# Package for distribution
npm run build:all # All platforms
npm run build:win # Windows only
npm run build:mac # macOS only
npm run build:linux # Linux only
How It Works
Development Mode
- Uses Vite dev server (
http://localhost:5173by default) - Full hot module replacement (HMR) support
- No protocol interception needed
- Set
VITE_DEV_SERVERenvironment variable to customize dev server URL
Production Mode
The adapter uses Electron's native protocol.handle() API to intercept http://127.0.0.1 requests:
- Static Assets: Serves files from the
client/directory - Prerendered Pages: Serves static HTML from the
prerendered/directory - SSR/API Routes: Calls SvelteKit's
Server.respond()directly - Form Actions: Full support for SvelteKit form actions
- Cookie Handling: Automatic cookie synchronization with Electron session
Build Output Structure
After running npm run build, you'll have:
out/
├── client/ # SvelteKit client assets (JS, CSS, images)
├── server/ # SvelteKit server files for SSR
│ ├── index.js # SvelteKit server
│ ├── manifest.js # App manifest
│ └── chunks/ # Server chunks
├── prerendered/ # Prerendered static HTML pages
├── functions/ # Protocol handler code
│ ├── setupHandler.js # Main protocol handler
│ └── setupHandler.d.ts # TypeScript definitions
├── main/ # Compiled main process
│ └── index.js
└── preload/ # Compiled preload script
└── index.js
Configuration Options
SvelteKit Adapter Options
adapter({
out: 'out', // Output directory (default: 'out')
precompress: false // Precompress assets with gzip/brotli (default: false)
})
| Option | Type | Default | Description |
|---|---|---|---|
out |
string |
'out' |
The directory to write the built application to |
precompress |
boolean |
false |
Whether to precompress assets with gzip/brotli |
Electron Plugin Options
electronPlugin({
mainEntry: 'src/main.ts', // Main process entry point (default: 'src/main.ts')
preloadEntry: 'src/preload.ts', // Preload script entry point (default: 'src/preload.ts')
mainOut: 'out/main/index.cjs', // Main process output (default: 'out/main/index.cjs')
preloadOut: 'out/preload/index.js', // Preload script output (default: 'out/preload/index.js')
externalMain: ['electron', 'electron-log', 'electron-is-dev', 'SERVER', 'MANIFEST'], // External dependencies for main process
externalPreload: ['electron'] // External dependencies for preload script
})
| Option | Type | Default | Description |
|---|---|---|---|
mainEntry |
string |
'src/main.ts' |
Path to the main process entry file |
preloadEntry |
string |
'src/preload.ts' |
Path to the preload script entry file |
mainOut |
string |
'out/main/index.cjs' |
Output path for the compiled main process |
preloadOut |
string |
'out/preload/index.js' |
Output path for the compiled preload script |
externalMain |
string[] |
['electron', 'electron-log', 'electron-is-dev', 'SERVER', 'MANIFEST'] |
External dependencies that won't be bundled for the main process |
externalPreload |
string[] |
['electron'] |
External dependencies that won't be bundled for the preload script |
Configuration Examples
Custom Output Directory
// svelte.config.js
import adapter from 'adapter-electron';
export default {
kit: {
adapter: adapter({
out: 'dist/electron'
})
}
};
Custom Entry Points
// vite.config.ts
import { electronPlugin } from 'adapter-electron';
export default defineConfig({
plugins: [
sveltekit(),
electronPlugin({
mainEntry: 'electron/main.ts',
preloadEntry: 'electron/preload.ts',
mainOut: 'dist/electron/main.js',
preloadOut: 'dist/electron/preload.js'
})
]
});
Custom External Dependencies
// vite.config.ts
import { electronPlugin } from 'adapter-electron';
export default defineConfig({
plugins: [
sveltekit(),
electronPlugin({
externalMain: ['electron', 'electron-log', 'my-custom-package'],
externalPreload: ['electron', 'another-package']
})
]
});
Environment Variables
| Variable | Description | Default |
|---|---|---|
VITE_DEV_SERVER |
Development server URL | http://localhost:5173 |
VITE_APP_URL |
Production app URL | http://127.0.0.1 |
API Reference
Protocol Handler Functions
setupHandler(mainWindow: BrowserWindow): Promise<() => void>
Sets up the protocol handler and loads the appropriate URL based on environment.
- Development: Loads Vite dev server
- Production: Sets up protocol interception and loads app
- Returns: Cleanup function to stop protocol interception
registerAppScheme(): void
Registers the HTTP scheme as privileged. Must be called before app.ready.
getPreloadPath(): string
Returns the correct preload script path for current environment.
- Development: Points to source preload script
- Production: Points to built preload script
Request Flow
Development:
Electron Window → Vite Dev Server (http://localhost:5173)
↓
Hot Module Replacement
Production:
Electron Request (http://127.0.0.1/page)
↓
Protocol Handler
↓
1. Check static files (client/)
2. Check prerendered pages (prerendered/)
3. Handle SSR/API (server.respond())
↓
Response with Cookie Sync
Advanced Usage
Custom Error Handling
The adapter includes built-in error reporting. Errors are:
- Logged to console in development
- Shown as dialog boxes in production
- Include GitHub issue reporting instructions
Cookie Management
Cookies are automatically synchronized between SvelteKit and Electron's session:
- Request cookies are extracted from Electron session
- Response
Set-Cookieheaders are applied to Electron session - Supports all cookie attributes (secure, httpOnly, etc.)
Security Features
- Path traversal protection for static files
- Automatic CORS handling
- Secure cookie handling
- Context isolation enforced
Production Packaging
With electron-builder
Create electron-builder.yaml:
appId: com.electron.app
productName: electron-sveltekit
directories:
buildResources: build
files:
- "!**/.vscode/*"
- "!src/*"
- "!electron.vite.config.{js,ts,mjs,cjs}"
- "!vite.electron.config.ts"
- "!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}"
- "!{.env,.env.*,.npmrc,pnpm-lock.yaml}"
- "!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}"
- "out/**/*"
asarUnpack:
- resources/**
win:
target: ["portable"]
executableName: electron-sveltekit
nsis:
artifactName: ${name}-${version}-setup.${ext}
shortcutName: ${productName}
uninstallDisplayName: ${productName}
createDesktopShortcut: always
mac:
entitlementsInherit: build/entitlements.mac.plist
extendInfo:
- NSCameraUsageDescription: Application requests access to the device's camera.
- NSMicrophoneUsageDescription: Application requests access to the device's microphone.
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
dmg:
artifactName: ${name}-${version}.${ext}
linux:
target:
- AppImage
- snap
- deb
maintainer: electronjs.org
category: Utility
appImage:
artifactName: ${name}-${version}.${ext}
npmRebuild: false
publish:
provider: generic
url: https://example.com/auto-updates
With electron-forge
// forge.config.js
module.exports = {
packagerConfig: {
dir: './out'
},
makers: [
{
name: '@electron-forge/maker-squirrel',
config: {}
}
]
};
Dependencies
The adapter requires these additional dependencies:
npm install electron electron-builder electron-log concurrently
npm install -D @types/node
Troubleshooting
Common Issues
Protocol not working in production:
- Ensure
registerAppScheme()is called beforeapp.ready - Check that
setupHandler()is called after window creation
Development server not loading:
- Verify Vite dev server is running on expected port
- Set
VITE_DEV_SERVERenvironment variable if using custom port
Form actions not working:
- Ensure you're using proper Web API Request objects (handled automatically)
- Check that cookies are being synchronized properly
Build errors:
- Verify all dependencies are installed
- Check that TypeScript configuration includes Electron types
- Ensure
concurrentlyis installed for development scripts
Debug Mode
Enable verbose logging:
// In main process
process.env.ELECTRON_ENABLE_LOGGING = 'true';
Getting Help
If you encounter issues:
- Check the console for error messages
- Verify your configuration matches the examples
- File an issue with error details and reproduction steps
License
MIT