mirror of
https://github.com/LukeHagar/sveltekit-adapters.git
synced 2025-12-06 04:21:32 +00:00
testing out a swap to a handler for electron
This commit is contained in:
@@ -7,44 +7,47 @@
|
|||||||
"name": "Luke Hagar",
|
"name": "Luke Hagar",
|
||||||
"email": "lukeslakemail@gmail.com"
|
"email": "lukeslakemail@gmail.com"
|
||||||
},
|
},
|
||||||
|
"homepage": "https://github.com/lukehagar/sveltekit-adapters",
|
||||||
"main": "./out/main/index.js",
|
"main": "./out/main/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "electron-vite preview",
|
"start": "electron-vite preview",
|
||||||
"dev": "electron-vite dev",
|
"dev": "svelte-kit sync && electron-vite dev",
|
||||||
"build": "electron-vite build",
|
"build": "electron-vite build",
|
||||||
|
"build:all": "npm run build && electron-builder -mwl --config",
|
||||||
"build:win": "npm run build && electron-builder --win --config",
|
"build:win": "npm run build && electron-builder --win --config",
|
||||||
"build:mac": "npm run build && electron-builder --mac --config",
|
"build:mac": "npm run build && electron-builder --mac --config",
|
||||||
"build:linux": "npm run build && electron-builder --linux --config"
|
"build:linux": "npm run build && electron-builder --linux --config"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@fontsource/fira-mono": "^5.0.8",
|
"@fontsource/fira-mono": "^5.2.6",
|
||||||
"@neoconfetti/svelte": "^2.2.1",
|
"@neoconfetti/svelte": "^2.2.2",
|
||||||
"@sveltejs/kit": "^2.5.0",
|
"@sveltejs/kit": "^2.22.2",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.0.2",
|
"@sveltejs/vite-plugin-svelte": "^5.1.0",
|
||||||
"@types/eslint": "8.56.2",
|
"@types/eslint": "9.6.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.0.1",
|
"@types/node": "^24.0.10",
|
||||||
"@typescript-eslint/parser": "^7.0.1",
|
"@typescript-eslint/eslint-plugin": "^8.35.1",
|
||||||
|
"@typescript-eslint/parser": "^8.35.1",
|
||||||
"adapter-electron": "workspace:*",
|
"adapter-electron": "workspace:*",
|
||||||
"concurrently": "^8.2.2",
|
"concurrently": "^9.2.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"dotenv": "^16.4.4",
|
"dotenv": "^17.0.1",
|
||||||
"electron": "^28.2.3",
|
"electron": "^37.2.0",
|
||||||
"electron-builder": "^24.9.1",
|
"electron-builder": "^26.0.12",
|
||||||
"electron-is-dev": "^3.0.1",
|
"electron-is-dev": "^3.0.1",
|
||||||
"electron-log": "^5.1.1",
|
"electron-log": "^5.4.1",
|
||||||
"electron-util": "^0.18.0",
|
"electron-util": "^0.18.1",
|
||||||
"electron-vite": "^2.0.0",
|
"electron-vite": "^3.1.0",
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^9.30.1",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^10.1.5",
|
||||||
"eslint-plugin-svelte": "^2.35.1",
|
"eslint-plugin-svelte": "^3.10.1",
|
||||||
"polka": "^0.5.2",
|
"polka": "^0.5.2",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.6.2",
|
||||||
"prettier-plugin-svelte": "^3.2.1",
|
"prettier-plugin-svelte": "^3.4.0",
|
||||||
"svelte": "^4.2.11",
|
"svelte": "^5.35.1",
|
||||||
"svelte-check": "^3.6.4",
|
"svelte-check": "^4.2.2",
|
||||||
"tslib": "^2.6.2",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.8.3",
|
||||||
"vite": "^5.1.3"
|
"vite": "^6.0.0"
|
||||||
},
|
},
|
||||||
"type": "module"
|
"type": "module"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,37 +3,57 @@ import { start, load } from 'adapter-electron/functions';
|
|||||||
import isDev from 'electron-is-dev';
|
import isDev from 'electron-is-dev';
|
||||||
import log from 'electron-log/main';
|
import log from 'electron-log/main';
|
||||||
import nodePath from 'node:path';
|
import nodePath from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
log.info('Hello, log!');
|
// Handle __dirname in ES modules
|
||||||
|
const __dirname = nodePath.dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
|
log.info('Starting Electron app with SvelteKit protocol integration...');
|
||||||
|
|
||||||
|
// Initialize the protocol manager
|
||||||
const port = await start();
|
const port = await start();
|
||||||
|
|
||||||
async function createWindow() {
|
async function createWindow() {
|
||||||
// Create the browser window
|
// Create the browser window
|
||||||
|
|
||||||
const mainWindow = new BrowserWindow({
|
const mainWindow = new BrowserWindow({
|
||||||
width: 800,
|
width: 1200,
|
||||||
height: 600,
|
height: 800,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
preload: nodePath.join(__dirname, '../preload/index.mjs')
|
preload: nodePath.join(__dirname, '../preload/index.mjs'),
|
||||||
|
nodeIntegration: false,
|
||||||
|
contextIsolation: true,
|
||||||
|
webSecurity: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load the local URL for development or the local
|
// Load the app - all routing is handled by protocol interception
|
||||||
// html file for production
|
load(mainWindow);
|
||||||
load(mainWindow, port);
|
|
||||||
|
|
||||||
if (isDev) mainWindow.webContents.openDevTools();
|
if (isDev) {
|
||||||
|
mainWindow.webContents.openDevTools();
|
||||||
}
|
}
|
||||||
|
|
||||||
app.whenReady().then(() => {
|
// Handle window events
|
||||||
|
mainWindow.webContents.on('did-finish-load', () => {
|
||||||
|
log.info('Window loaded successfully');
|
||||||
|
});
|
||||||
|
|
||||||
|
mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription) => {
|
||||||
|
log.error('Window failed to load:', errorDescription);
|
||||||
|
});
|
||||||
|
|
||||||
|
return mainWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
app.whenReady().then(async () => {
|
||||||
log.info('App is ready');
|
log.info('App is ready');
|
||||||
|
|
||||||
log.info('Creating window...');
|
log.info('Creating window...');
|
||||||
createWindow();
|
await createWindow();
|
||||||
|
|
||||||
app.on('activate', () => {
|
app.on('activate', async () => {
|
||||||
if (BrowserWindow.getAllWindows().length === 0) {
|
if (BrowserWindow.getAllWindows().length === 0) {
|
||||||
createWindow();
|
await createWindow();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -43,3 +63,9 @@ app.on('window-all-closed', () => {
|
|||||||
app.quit();
|
app.quit();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle render process crashes
|
||||||
|
app.on('render-process-gone', (event, webContents, details) => {
|
||||||
|
log.error('Render process crashed:', details.reason);
|
||||||
|
// You could restart the window here if needed
|
||||||
|
});
|
||||||
|
|||||||
@@ -9,7 +9,10 @@
|
|||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"moduleResolution": "bundler"
|
"moduleResolution": "bundler",
|
||||||
|
"module": "ES2022",
|
||||||
|
"target": "ES2022",
|
||||||
|
"types": ["node", "electron"]
|
||||||
}
|
}
|
||||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ import { sveltekit } from '@sveltejs/kit/vite';
|
|||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
target: 'chrome107'
|
||||||
|
},
|
||||||
logLevel: 'info',
|
logLevel: 'info',
|
||||||
plugins: [sveltekit()]
|
plugins: [sveltekit()]
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,19 +1,52 @@
|
|||||||
# adapter-electron
|
# adapter-electron
|
||||||
|
|
||||||
## A sveltekit adapter for Electron Desktop Apps
|
## A SvelteKit adapter for Electron Desktop Apps using protocol interception
|
||||||
|
|
||||||
This is a simple wrapper for the existing `adapter-node` SvelteKit adapter, with the exception that this package exports custom functions to handle the integration and running of the polka server and handler that are built from the node adapter.
|
This adapter provides a complete solution for building Electron desktop applications with SvelteKit by **embedding the full SvelteKit app directly into the Electron main process**. It uses protocol interception to handle all routing, SSR, and API endpoints without requiring a separate HTTP server.
|
||||||
|
|
||||||
You register the adapter in your `svelte.config.js` file just like any other adapter like so:
|
## Features
|
||||||
|
|
||||||
|
### 🚀 **Embedded SvelteKit**
|
||||||
|
- **Full SvelteKit app** embedded in Electron main process
|
||||||
|
- **Server-side rendering** for all routes
|
||||||
|
- **API endpoints** handled natively
|
||||||
|
- **Static asset serving** via custom protocol
|
||||||
|
|
||||||
|
### 🔄 **Protocol Interception**
|
||||||
|
- **HTTP protocol interception** for SvelteKit routes
|
||||||
|
- **Custom file protocol** for static assets
|
||||||
|
- **No external HTTP server** required
|
||||||
|
- **Seamless development and production** experience
|
||||||
|
|
||||||
|
### ⚡ **Performance Optimizations**
|
||||||
|
- **Response caching** with configurable TTL
|
||||||
|
- **Automatic cache cleanup** to prevent memory leaks
|
||||||
|
- **Efficient static file serving** from built assets
|
||||||
|
- **Minimal overhead** compared to external servers
|
||||||
|
|
||||||
|
### 🛠️ **Developer Experience**
|
||||||
|
- **Hot reload** support in development
|
||||||
|
- **Comprehensive logging** for debugging
|
||||||
|
- **Easy configuration** with sensible defaults
|
||||||
|
- **TypeScript support** throughout
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install adapter-electron
|
||||||
|
```
|
||||||
|
|
||||||
|
## Basic Setup
|
||||||
|
|
||||||
|
### 1. Configure SvelteKit
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
// svelte.config.js
|
||||||
import adapter from 'adapter-electron';
|
import adapter from 'adapter-electron';
|
||||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||||
|
|
||||||
/** @type {import('@sveltejs/kit').Config} */
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
const config = {
|
const config = {
|
||||||
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
|
|
||||||
// for more information about preprocessors
|
|
||||||
preprocess: vitePreprocess(),
|
preprocess: vitePreprocess(),
|
||||||
|
|
||||||
kit: {
|
kit: {
|
||||||
@@ -27,45 +60,240 @@ const config = {
|
|||||||
export default config;
|
export default config;
|
||||||
```
|
```
|
||||||
|
|
||||||
This adapter requires additional files and configuration to work properly.
|
### 2. Set up Electron Main Process
|
||||||
An example of a working electron app can be found in the `examples` directory [here](https://github.com/LukeHagar/sveltekit-adapters/tree/main/examples/electron).
|
|
||||||
|
|
||||||
This package uses `electron-builder` to build the electron app, and `electron-is-dev` to determine if the app is running in development mode.
|
|
||||||
|
|
||||||
This package includes some function exports that are used to start the server and load the local URL for the electron app.
|
|
||||||
in your projects main electron file, you will need to import these functions and use them to start the server and load the local URL.
|
|
||||||
|
|
||||||
Below is an example of how to use this adapters functions in your main electron file.
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
// src/main/index.js
|
||||||
import { app, BrowserWindow } from 'electron';
|
import { app, BrowserWindow } from 'electron';
|
||||||
import { start, load } from 'adapter-electron/functions';
|
import { start, load, protocolUtils } from 'adapter-electron/functions';
|
||||||
import isDev from 'electron-is-dev';
|
import isDev from 'electron-is-dev';
|
||||||
import log from 'electron-log/main';
|
import log from 'electron-log/main';
|
||||||
import nodePath from 'node:path';
|
import nodePath from 'node:path';
|
||||||
|
|
||||||
|
|
||||||
const port = await start();
|
const port = await start();
|
||||||
|
|
||||||
async function createWindow() {
|
async function createWindow() {
|
||||||
// Create the browser window
|
|
||||||
|
|
||||||
const mainWindow = new BrowserWindow({
|
const mainWindow = new BrowserWindow({
|
||||||
width: 800,
|
width: 1200,
|
||||||
height: 600,
|
height: 800,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
preload: nodePath.join(__dirname, '../preload/index.mjs')
|
preload: nodePath.join(__dirname, '../preload/index.mjs'),
|
||||||
|
nodeIntegration: false,
|
||||||
|
contextIsolation: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load the local URL for development or the local
|
// Load the app - all routing is handled by protocol interception
|
||||||
// html file for production
|
|
||||||
load(mainWindow, port);
|
load(mainWindow, port);
|
||||||
|
|
||||||
if (isDev) mainWindow.webContents.openDevTools();
|
if (isDev) {
|
||||||
|
mainWindow.webContents.openDevTools();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.whenReady().then(createWindow);
|
||||||
|
|
||||||
|
app.on('window-all-closed', () => {
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Optional: Configure protocol settings
|
||||||
|
protocolUtils.configure({
|
||||||
|
baseUrl: 'http://localhost:3000',
|
||||||
|
staticProtocol: 'app',
|
||||||
|
enableCaching: true,
|
||||||
|
cacheTimeout: 300000 // 5 minutes
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
### **Protocol Interception**
|
||||||
|
The adapter uses Electron's protocol API to intercept all HTTP requests to your SvelteKit app:
|
||||||
|
|
||||||
|
1. **HTTP Protocol Interception**: All requests to `http://localhost:3000/*` are intercepted
|
||||||
|
2. **SvelteKit Handler**: Requests are routed through SvelteKit's built handler
|
||||||
|
3. **Static Asset Protocol**: Static files are served via a custom `app://` protocol
|
||||||
|
4. **Response Caching**: Successful responses are cached for performance
|
||||||
|
|
||||||
|
### **Request Flow**
|
||||||
|
```
|
||||||
|
Browser Request → Protocol Interception → SvelteKit Handler → Response
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Static Assets**
|
||||||
|
```
|
||||||
|
Static Asset Request → Custom Protocol → File System → Response
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Options
|
||||||
|
|
||||||
|
### **Protocol Configuration**
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { protocolUtils } from 'adapter-electron/functions';
|
||||||
|
|
||||||
|
protocolUtils.configure({
|
||||||
|
baseUrl: 'http://localhost:3000', // Base URL for SvelteKit app
|
||||||
|
staticProtocol: 'app', // Protocol for static assets
|
||||||
|
enableCaching: true, // Enable response caching
|
||||||
|
cacheTimeout: 300000 // Cache TTL in milliseconds
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Cache Management**
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Clear all cached responses
|
||||||
|
protocolUtils.clearCache();
|
||||||
|
|
||||||
|
// Get cache statistics
|
||||||
|
const stats = protocolUtils.getCacheStats();
|
||||||
|
console.log(`Cache enabled: ${stats.enabled}, Size: ${stats.size}`);
|
||||||
|
|
||||||
|
// Manual cache cleanup
|
||||||
|
protocolUtils.cleanupCache();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advanced Usage
|
||||||
|
|
||||||
|
### **Custom Protocol Configuration**
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Use a custom base URL
|
||||||
|
protocolUtils.configure({
|
||||||
|
baseUrl: 'http://myapp.local',
|
||||||
|
staticProtocol: 'myapp-assets'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Disable caching for development
|
||||||
|
if (isDev) {
|
||||||
|
protocolUtils.configure({
|
||||||
|
enableCaching: false
|
||||||
|
});
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
If you are having issues with this adapter running or building properly, it's most likely related to the `ORIGIN` configured.
|
### **Performance Monitoring**
|
||||||
I implented a sort of SHIM that will set the value at runtime to the correct value for the local electron desktop environment.
|
|
||||||
|
```js
|
||||||
|
// Monitor cache performance
|
||||||
|
setInterval(() => {
|
||||||
|
const stats = protocolUtils.getCacheStats();
|
||||||
|
if (stats.enabled) {
|
||||||
|
console.log(`Cache size: ${stats.size} entries`);
|
||||||
|
}
|
||||||
|
}, 30000); // Every 30 seconds
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Error Handling**
|
||||||
|
|
||||||
|
```js
|
||||||
|
// The adapter automatically handles errors and provides logging
|
||||||
|
// You can also add custom error handling in your main process
|
||||||
|
|
||||||
|
app.on('render-process-gone', (event, webContents, details) => {
|
||||||
|
console.error('Render process crashed:', details.reason);
|
||||||
|
// Restart the window or handle the crash
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development vs Production
|
||||||
|
|
||||||
|
### **Development Mode**
|
||||||
|
- Uses external dev server when available
|
||||||
|
- Hot reload support
|
||||||
|
- Detailed logging
|
||||||
|
- Cache disabled by default
|
||||||
|
|
||||||
|
### **Production Mode**
|
||||||
|
- Full protocol interception
|
||||||
|
- Response caching enabled
|
||||||
|
- Optimized static asset serving
|
||||||
|
- Minimal logging
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### **Core Functions**
|
||||||
|
|
||||||
|
- `start()` - Initialize the protocol manager and set up interception
|
||||||
|
- `load(mainWindow, port, path)` - Load the app in an Electron window
|
||||||
|
- `protocolUtils.configure(options)` - Configure protocol settings
|
||||||
|
- `protocolUtils.clearCache()` - Clear all cached responses
|
||||||
|
- `protocolUtils.getCacheStats()` - Get cache statistics
|
||||||
|
- `protocolUtils.cleanupCache()` - Clean up expired cache entries
|
||||||
|
|
||||||
|
### **Configuration Options**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface ProtocolOptions {
|
||||||
|
baseUrl?: string; // Base URL for SvelteKit app
|
||||||
|
staticProtocol?: string; // Protocol for static assets
|
||||||
|
enableCaching?: boolean; // Enable response caching
|
||||||
|
cacheTimeout?: number; // Cache TTL in milliseconds
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### **Common Issues**
|
||||||
|
|
||||||
|
1. **App not loading**: Check that the SvelteKit build exists in the expected location
|
||||||
|
2. **Static assets not loading**: Verify the static protocol is correctly configured
|
||||||
|
3. **Performance issues**: Enable caching and adjust cache timeout
|
||||||
|
4. **Memory leaks**: Ensure cache cleanup is running periodically
|
||||||
|
|
||||||
|
### **Debug Mode**
|
||||||
|
|
||||||
|
Enable detailed logging by setting the environment variable:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
DEBUG=electron-protocol npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Cache Issues**
|
||||||
|
|
||||||
|
If you're experiencing stale content:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Clear cache manually
|
||||||
|
protocolUtils.clearCache();
|
||||||
|
|
||||||
|
// Or disable caching temporarily
|
||||||
|
protocolUtils.configure({ enableCaching: false });
|
||||||
|
```
|
||||||
|
|
||||||
|
## Migration from Standard Adapter
|
||||||
|
|
||||||
|
If you're migrating from the standard adapter-electron:
|
||||||
|
|
||||||
|
1. **Update imports** to include protocolUtils
|
||||||
|
2. **Remove any external server setup** - it's no longer needed
|
||||||
|
3. **Configure protocol settings** as needed
|
||||||
|
4. **Test static asset loading** with the new protocol
|
||||||
|
|
||||||
|
The adapter maintains backward compatibility while providing the new protocol-based functionality.
|
||||||
|
|
||||||
|
## Performance Benefits
|
||||||
|
|
||||||
|
### **Compared to External Server**
|
||||||
|
- **Faster startup** - no server initialization
|
||||||
|
- **Lower memory usage** - no separate Node.js process
|
||||||
|
- **Better integration** - direct access to Electron APIs
|
||||||
|
- **Simplified deployment** - single executable
|
||||||
|
|
||||||
|
### **Caching Benefits**
|
||||||
|
- **Reduced computation** - cached SSR responses
|
||||||
|
- **Faster navigation** - cached API responses
|
||||||
|
- **Better user experience** - instant page loads
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
This adapter is part of the SvelteKit adapters collection. Contributions are welcome!
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License - see the main repository for details.
|
||||||
|
|
||||||
|
|||||||
14
packages/adapter-electron/functions/index.d.ts
vendored
14
packages/adapter-electron/functions/index.d.ts
vendored
@@ -1,2 +1,14 @@
|
|||||||
export function load(mainWindow: any, port: string | undefined, path: string | undefined): void;
|
import type { BrowserWindow } from 'electron';
|
||||||
|
|
||||||
|
export interface ProtocolOptions {
|
||||||
|
baseUrl?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProtocolUtils {
|
||||||
|
configure: (options: ProtocolOptions) => void;
|
||||||
|
isConfigured: () => boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export function start(): Promise<string | undefined>;
|
export function start(): Promise<string | undefined>;
|
||||||
|
export function load(mainWindow: BrowserWindow, path?: string): void;
|
||||||
|
export const protocolUtils: ProtocolUtils;
|
||||||
|
|||||||
@@ -1,42 +1,152 @@
|
|||||||
import isDev from 'electron-is-dev';
|
import isDev from 'electron-is-dev';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import log from 'electron-log/main';
|
import log from 'electron-log/main';
|
||||||
import polka from 'polka';
|
import { protocol } from 'electron';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { pathToFileURL } from 'node:url';
|
||||||
|
|
||||||
|
const __dirname = fileURLToPath(new URL('../renderer', import.meta.url));
|
||||||
|
|
||||||
|
// Module-level state
|
||||||
|
let handler = null;
|
||||||
|
let isConfigured = false;
|
||||||
|
let baseUrl = 'http://localhost:3000';
|
||||||
|
|
||||||
|
// Initialize the protocol manager
|
||||||
|
async function initialize() {
|
||||||
|
if (isConfigured) {
|
||||||
|
log.warn('SvelteKit protocol already configured');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Import the built SvelteKit handler
|
||||||
|
const handlerModule = await import(`file://${path.join(__dirname, 'handler.js')}`);
|
||||||
|
|
||||||
|
handler = handlerModule.handler;
|
||||||
|
|
||||||
|
log.info('SvelteKit handler loaded successfully');
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
log.error('Failed to load SvelteKit handler:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure protocol settings
|
||||||
|
function configure(options = {}) {
|
||||||
|
const {
|
||||||
|
baseUrl: newBaseUrl = 'http://localhost:3000'
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
baseUrl = newBaseUrl;
|
||||||
|
|
||||||
|
log.info(`SvelteKit protocol configured with baseUrl: ${baseUrl}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up protocols
|
||||||
|
async function setupProtocols() {
|
||||||
|
if (!handler) {
|
||||||
|
throw new Error('SvelteKit handler not initialized. Call initialize() first.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register HTTP protocol handler
|
||||||
|
protocol.handle('http', async (req) => {
|
||||||
|
const { host, pathname } = new URL(req.url);
|
||||||
|
|
||||||
|
// Only handle requests to our configured base URL
|
||||||
|
if (host !== 'localhost:3000') {
|
||||||
|
return new Response('Not Found', {
|
||||||
|
status: 404,
|
||||||
|
headers: { 'content-type': 'text/plain' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle SvelteKit routes and API endpoints
|
||||||
|
try {
|
||||||
|
// Create a Request object for the SvelteKit handler
|
||||||
|
const sveltekitReq = new Request(req.url, {
|
||||||
|
method: req.method,
|
||||||
|
headers: req.headers,
|
||||||
|
body: req.body
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle request through SvelteKit
|
||||||
|
const res = await handler(sveltekitReq);
|
||||||
|
|
||||||
|
log.debug(`Handled SvelteKit request: ${req.method} ${pathname} -> ${res.status}`);
|
||||||
|
return res;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
log.error(`Error handling SvelteKit request ${pathname}:`, error);
|
||||||
|
return new Response('Internal Server Error', {
|
||||||
|
status: 500,
|
||||||
|
headers: { 'content-type': 'text/plain' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
isConfigured = true;
|
||||||
|
log.info('SvelteKit protocols configured successfully');
|
||||||
|
}
|
||||||
|
|
||||||
/** @type {import('./index.js').start} */
|
/** @type {import('./index.js').start} */
|
||||||
export const start = async () => {
|
export const start = async () => {
|
||||||
if (isDev) return undefined;
|
if (isDev) return undefined;
|
||||||
const { env } = await await import(`file://${path.join(__dirname, '../renderer/env.js')}`);
|
|
||||||
const port = env('PORT', '3000');
|
|
||||||
|
|
||||||
log.info(`Configured Port is: ${port}`);
|
try {
|
||||||
|
log.info('Initializing SvelteKit protocol manager...');
|
||||||
|
|
||||||
log.info(`Setting origin to http://localhost:${port}`);
|
// Initialize the protocol manager
|
||||||
process.env['ORIGIN'] = `http://localhost:${port}`;
|
const initialized = await initialize();
|
||||||
|
if (!initialized) {
|
||||||
|
throw new Error('Failed to initialize SvelteKit protocol manager');
|
||||||
|
}
|
||||||
|
|
||||||
log.info('Importing Polka handler');
|
// Configure with default settings
|
||||||
const { handler } = await import(`file://${path.join(__dirname, '../renderer/handler.js')}`);
|
configure({
|
||||||
|
baseUrl: 'http://localhost:3000'
|
||||||
// createHandler(port),
|
|
||||||
const server = polka().use(handler);
|
|
||||||
|
|
||||||
Object.assign(console, log.functions);
|
|
||||||
|
|
||||||
log.info('Starting server...');
|
|
||||||
server.listen({ path: false, host: 'localhost', port }, () => {
|
|
||||||
log.info(`Server Listening on http://localhost:${port}`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return port;
|
// Set up protocols
|
||||||
|
await setupProtocols();
|
||||||
|
|
||||||
|
log.info('SvelteKit protocol manager started successfully');
|
||||||
|
return '3000'; // Return port for compatibility
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
log.error('Failed to start SvelteKit protocol manager:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @type {import('./index.js').load} */
|
/** @type {import('./index.js').load} */
|
||||||
export const load = (mainWindow, port, path = '') => {
|
export const load = (mainWindow, path = '') => {
|
||||||
if (isDev && process.env['ELECTRON_RENDERER_URL']) {
|
if (isDev && process.env['ELECTRON_RENDERER_URL']) {
|
||||||
log.info(`Loading url: ${process.env['ELECTRON_RENDERER_URL']}${path}`);
|
const url = `${process.env['ELECTRON_RENDERER_URL']}${path}`;
|
||||||
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL']+path);
|
log.info(`Loading development URL: ${url}`);
|
||||||
|
mainWindow.loadURL(url);
|
||||||
} else {
|
} else {
|
||||||
log.info(`Loading url: http://localhost:${port}${path}`);
|
const url = `http://localhost:3000${path}`;
|
||||||
mainWindow.loadURL(`http://localhost:${port}${path}`);
|
log.info(`Loading production URL: ${url}`);
|
||||||
|
mainWindow.loadURL(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set up window event handlers for better integration
|
||||||
|
mainWindow.webContents.on('did-finish-load', () => {
|
||||||
|
log.info('Window loaded successfully');
|
||||||
|
});
|
||||||
|
|
||||||
|
mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription) => {
|
||||||
|
log.error('Window failed to load:', errorDescription);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Export protocol utilities for advanced usage
|
||||||
|
export const protocolUtils = {
|
||||||
|
// Configure the protocol manager
|
||||||
|
configure,
|
||||||
|
|
||||||
|
// Check if configured
|
||||||
|
isConfigured: () => isConfigured
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
// adapter-electron.js
|
// adapter-electron.js
|
||||||
import { fileURLToPath } from 'url';
|
|
||||||
import adapter from '@sveltejs/adapter-node';
|
import adapter from '@sveltejs/adapter-node';
|
||||||
|
|
||||||
const files = fileURLToPath(new URL('./files', import.meta.url).href);
|
|
||||||
|
|
||||||
/** @type {import('./index.js').default} */
|
/** @type {import('./index.js').default} */
|
||||||
export default function (opts = {}) {
|
export default function (opts = {}) {
|
||||||
const { out = 'out/renderer', options } = opts;
|
const { out = 'out/renderer', options } = opts;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "adapter-electron",
|
"name": "adapter-electron",
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
|
"description": "A SvelteKit adapter for Electron Desktop Apps using protocol interception",
|
||||||
"files": [
|
"files": [
|
||||||
"functions",
|
"functions",
|
||||||
"index.js",
|
"index.js",
|
||||||
@@ -46,7 +47,6 @@
|
|||||||
"vite": "^5.0.11"
|
"vite": "^5.0.11"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"polka": "^0.5.2",
|
|
||||||
"electron-is-dev": "^3.0.1",
|
"electron-is-dev": "^3.0.1",
|
||||||
"electron-log": "^5.1.1"
|
"electron-log": "^5.1.1"
|
||||||
},
|
},
|
||||||
|
|||||||
3247
pnpm-lock.yaml
generated
3247
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user