Help us improve
Share bugs, ideas, or general feedback.
From aai-stack-electron
Configures esbuild bundling and electron-builder packaging for native Node.js modules like better-sqlite3, sharp, keytar in Electron apps. For SQLite, image processing, keychain access.
npx claudepluginhub bradtaylorsf/alphaagent-teamHow this skill is triggered — by the user, by Claude, or both
Slash command
/aai-stack-electron:electron-native-modulesThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Native modules require special handling in Electron because they must be compiled for Electron's Node.js version, not the system Node.js.
Guides Next.js Cache Components and Partial Prerendering (PPR): 'use cache' directives, cacheLife(), cacheTag(), revalidateTag() for caching, invalidation, static/dynamic optimization. Auto-activates on cacheComponents: true.
Migrates code, prompts, and API calls from Claude Sonnet 4.0/4.5 or Opus 4.1 to Opus 4.5, updating model strings on Anthropic, AWS, GCP, Azure platforms.
Breaks plans, specs, or PRDs into thin vertical-slice issues on the project issue tracker using tracer bullets. Useful for converting high-level work into grabbable implementation tickets.
Share bugs, ideas, or general feedback.
Native modules require special handling in Electron because they must be compiled for Electron's Node.js version, not the system Node.js.
| Module | Purpose | Special Notes |
|---|---|---|
better-sqlite3 | SQLite database | Most common, requires rebuild |
sharp | Image processing | Large, multi-platform binaries |
keytar | OS keychain access | Security-sensitive |
node-pty | Terminal emulation | Platform-specific |
serialport | Serial port access | Hardware access |
Native modules MUST be external - they cannot be bundled:
# Build command
esbuild src/server/index.ts \
--bundle \
--platform=node \
--format=cjs \
--outfile=dist/server/index.cjs \
--external:better-sqlite3 \
--external:sharp \
--external:keytar \
--external:electron
{
"scripts": {
"build:server": "esbuild src/server/index.ts --bundle --platform=node --format=cjs --outfile=dist/server/index.cjs --external:better-sqlite3 --external:sharp --external:keytar",
"electron:build": "esbuild electron/*.ts --bundle --platform=node --format=cjs --outdir=electron-dist --out-extension:.js=.cjs --external:electron --external:keytar --external:better-sqlite3 --external:sharp"
}
}
// electron-builder.config.cjs
module.exports = {
appId: 'com.yourcompany.yourapp',
productName: 'Your App',
// CRITICAL: Include native modules in the app
files: [
'dist/**/*',
'electron-dist/**/*',
'public/**/*',
'package.json',
// Native modules need their binary files
'node_modules/better-sqlite3/**/*',
'node_modules/keytar/**/*',
'node_modules/sharp/**/*',
// Also include their dependencies
'node_modules/bindings/**/*',
'node_modules/file-uri-to-path/**/*',
'node_modules/prebuild-install/**/*',
],
// CRITICAL: Rebuild native modules for Electron
npmRebuild: true,
// Don't use asar for apps with native modules (simpler, avoids issues)
asar: false,
// Or use asar with unpacking
// asar: true,
// asarUnpack: [
// 'node_modules/better-sqlite3/**/*',
// 'node_modules/keytar/**/*',
// 'node_modules/sharp/**/*',
// ],
mac: {
category: 'public.app-category.developer-tools',
target: [
{ target: 'dmg', arch: ['x64', 'arm64'] },
{ target: 'zip', arch: ['x64', 'arm64'] },
],
// Required for keytar
hardenedRuntime: true,
entitlements: 'build-resources/entitlements.mac.plist',
entitlementsInherit: 'build-resources/entitlements.mac.plist',
},
win: {
target: [
{ target: 'nsis', arch: ['x64'] },
],
},
linux: {
target: [
{ target: 'AppImage', arch: ['x64'] },
{ target: 'deb', arch: ['x64'] },
],
category: 'Development',
},
};
<!-- build-resources/entitlements.mac.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)com.yourcompany.yourapp</string>
</array>
</dict>
</plist>
import Database from 'better-sqlite3';
import path from 'path';
function getDbPath(): string {
if (process.env.ELECTRON_DB_PATH) {
return process.env.ELECTRON_DB_PATH;
}
return path.resolve(process.cwd(), 'data', 'app.db');
}
const db = new Database(getDbPath());
// Ensure tables exist on first launch
function initializeDatabase(db: Database.Database) {
db.exec(`
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT
);
`);
}
// Call at startup
initializeDatabase(db);
// electron/keychain.ts
import * as keytar from 'keytar';
const SERVICE_NAME = 'YourAppName';
export const KEY_NAMES = {
API_KEY: 'api-key',
AUTH_TOKEN: 'auth-token',
} as const;
export async function getCredential(key: string): Promise<string | null> {
try {
return await keytar.getPassword(SERVICE_NAME, key);
} catch (error) {
console.error('Failed to get credential:', error);
return null;
}
}
export async function setCredential(key: string, value: string): Promise<boolean> {
try {
await keytar.setPassword(SERVICE_NAME, key, value);
return true;
} catch (error) {
console.error('Failed to set credential:', error);
return false;
}
}
export async function deleteCredential(key: string): Promise<boolean> {
try {
return await keytar.deletePassword(SERVICE_NAME, key);
} catch (error) {
console.error('Failed to delete credential:', error);
return false;
}
}
import sharp from 'sharp';
import path from 'path';
function getOutputDir(): string {
if (process.env.ELECTRON_DB_PATH) {
return path.join(path.dirname(process.env.ELECTRON_DB_PATH), 'processed');
}
return path.resolve(process.cwd(), 'public/processed');
}
async function processImage(inputPath: string, outputName: string): Promise<string> {
const outputDir = getOutputDir();
const outputPath = path.join(outputDir, outputName);
await sharp(inputPath)
.resize(800, 600)
.jpeg({ quality: 80 })
.toFile(outputPath);
return outputPath;
}
Create a deployment script that installs only production dependencies:
// scripts/prepare-electron.cjs
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const deployDir = 'electron-deploy';
// Clean and create deploy directory
if (fs.existsSync(deployDir)) {
fs.rmSync(deployDir, { recursive: true });
}
fs.mkdirSync(deployDir);
// Copy package.json with only production deps
const pkg = require('../package.json');
const prodPkg = {
name: pkg.name,
version: pkg.version,
main: 'electron-dist/main.cjs',
dependencies: pkg.dependencies,
};
fs.writeFileSync(
path.join(deployDir, 'package.json'),
JSON.stringify(prodPkg, null, 2)
);
// Install production dependencies
execSync('npm install --omit=dev', { cwd: deployDir, stdio: 'inherit' });
// Copy built files
fs.cpSync('dist', path.join(deployDir, 'dist'), { recursive: true });
fs.cpSync('electron-dist', path.join(deployDir, 'electron-dist'), { recursive: true });
fs.cpSync('public', path.join(deployDir, 'public'), { recursive: true });
console.log('Deployment directory ready');
files array in electron-builder config--external flag in esbuild commanddependencies, not devDependenciesnode_modules and reinstall./node_modules/.bin/electron-rebuildUsed by:
electron-converter agentelectron-build-config skill