📁 项目结构
your-electron-app/
├── main.js # 主进程
├── preload.js # 预加载脚本
├── package.json
├── config/
│ └── app-config.json # 配置文件
├── index.html
└── renderer.js # 渲染进程
1. 创建配置文件
config/app-config.json
{
"appName": "我的Electron应用",
"version": "1.0.0",
"theme": {
"primaryColor": "#4CAF50",
"darkMode": false
},
"server": {
"host": "localhost",
"port": 3000,
"apiUrl": "http://localhost:3000/api"
},
"features": {
"autoUpdate": true,
"notifications": true,
"analytics": false
},
"users": [
{ "id": 1, "name": "管理员", "role": "admin" },
{ "id": 2, "name": "普通用户", "role": "user" }
]
}
2. 主进程读取配置 (main.js)
方法一:使用 fs 模块同步读取
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')
const fs = require('fs')
// 配置文件路径
const configPath = path.join(__dirname, 'config', 'app-config.json')
/**
* 读取JSON配置文件(同步)
*/
function loadConfigSync() {
try {
const data = fs.readFileSync(configPath, 'utf-8')
return JSON.parse(data)
} catch (error) {
console.error('读取配置文件失败:', error.message)
return getDefaultConfig()
}
}
/**
* 读取JSON配置文件(异步)
*/
function loadConfigAsync() {
return new Promise((resolve, reject) => {
fs.readFile(configPath, 'utf-8', (error, data) => {
if (error) {
console.error('读取配置文件失败:', error.message)
resolve(getDefaultConfig()) // 返回默认配置
return
}
try {
resolve(JSON.parse(data))
} catch (parseError) {
console.error('解析JSON失败:', parseError.message)
resolve(getDefaultConfig())
}
})
})
}
/**
* 写入配置到文件
*/
function saveConfig(newConfig) {
try {
const data = JSON.stringify(newConfig, null, 2) // 格式化输出
fs.writeFileSync(configPath, data, 'utf-8')
return true
} catch (error) {
console.error('保存配置失败:', error.message)
return false
}
}
/**
* 默认配置
*/
function getDefaultConfig() {
return {
appName: 'My App',
version: '1.0.0',
theme: { primaryColor: '#2196F3', darkMode: false },
server: { host: 'localhost', port: 3000, apiUrl: '' },
features: { autoUpdate: true, notifications: true, analytics: false }
}
}
// 应用启动时加载配置
let appConfig = loadConfigSync()
console.log('应用配置已加载:', appConfig.appName)
let mainWindow
function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false
}
})
mainWindow.loadFile('index.html')
// 将配置传递给渲染进程
mainWindow.webContents.on('did-finish-load', () => {
mainWindow.webContents.send('config-loaded', appConfig)
})
}
// IPC 处理渲染进程的配置请求
ipcMain.handle('get-config', async () => {
return await loadConfigAsync()
})
ipcMain.handle('save-config', async (event, newConfig) => {
const success = saveConfig(newConfig)
if (success) {
appConfig = { ...appConfig, ...newConfig } // 更新内存中的配置
}
return success
})
ipcMain.handle('get-config-value', (event, key) => {
// 支持点号分隔的路径,如 "theme.darkMode"
return getNestedValue(appConfig, key)
})
// 获取嵌套对象的值
function getNestedValue(obj, path) {
return path.split('.').reduce((current, key) => {
return current && current[key] !== undefined ? current[key] : undefined
}, obj)
}
app.whenReady().then(createWindow)
3. 预加载脚本 (preload.js)
const { contextBridge, ipcRenderer } = require('electron')
// 暴露安全的 API 给渲染进程
contextBridge.exposeInMainWorld('configAPI', {
// 获取完整配置
getConfig: () => ipcRenderer.invoke('get-config'),
// 保存配置
saveConfig: (config) => ipcRenderer.invoke('save-config', config),
// 获取单个配置项
get: (key) => ipcRenderer.invoke('get-config-value', key),
// 监听配置变化
onConfigUpdate: (callback) => {
ipcRenderer.on('config-updated', (event, config) => callback(config))
}
})
4. 渲染进程使用 (renderer.js)
// 方式1:等待主进程主动发送
window.electronAPI.onConfigLoaded((config) => {
console.log('收到配置:', config)
initApp(config)
})
// 方式2:主动请求配置
async function loadConfig() {
try {
const config = await window.configAPI.getConfig()
console.log('配置已加载:', config)
// 使用配置
document.title = config.appName
applyTheme(config.theme)
return config
} catch (error) {
console.error('获取配置失败:', error)
}
}
// 方式3:获取单个配置项
async function getServerUrl() {
const url = await window.configAPI.get('server.apiUrl')
return url
}
// 方式4:保存配置
async function updateConfig() {
const newConfig = {
theme: {
darkMode: true,
primaryColor: '#ff5722'
},
features: {
analytics: true
}
}
const success = await window.configAPI.saveConfig(newConfig)
if (success) {
alert('配置已保存!')
} else {
alert('保存失败!')
}
}
// 应用配置
function applyTheme(theme) {
if (theme.darkMode) {
document.body.classList.add('dark-theme')
} else {
document.body.classList.remove('dark-theme')
}
document.documentElement.style.setProperty('--primary-color', theme.primaryColor)
}
// 初始化
loadConfig()
5. 完整 HTML 示例 (index.html)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>配置演示</title>
<style>
:root {
--primary-color: #4CAF50;
}
body {
font-family: Arial, sans-serif;
padding: 20px;
background-color: #f5f5f5;
}
.config-panel {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
max-width: 600px;
margin: 0 auto;
}
h2 {
color: var(--primary-color);
margin-top: 0;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input[type="text"],
input[type="number"] {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
}
.switch {
position: relative;
display: inline-block;
width: 50px;
height: 24px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: 0.4s;
border-radius: 24px;
}
.slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 3px;
bottom: 3px;
background-color: white;
transition: 0.4s;
border-radius: 50%;
}
input:checked + .slider {
background-color: var(--primary-color);
}
input:checked + .slider:before {
transform: translateX(26px);
}
button {
background-color: var(--primary-color);
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
margin-right: 10px;
}
button:hover {
opacity: 0.9;
}
.config-display {
background: #f9f9f9;
padding: 15px;
border-radius: 4px;
margin-top: 20px;
font-family: monospace;
white-space: pre-wrap;
max-height: 200px;
overflow-y: auto;
}
</style>
</head>
<body>
<div class="config-panel">
<h2>⚙️ 应用配置</h2>
<div class="form-group">
<label>应用名称</label>
<input type="text" id="appName" placeholder="输入应用名称">
</div>
<div class="form-group">
<label>服务器端口</label>
<input type="number" id="serverPort" placeholder="输入端口号">
</div>
<div class="form-group">
<label>暗黑模式</label>
<label class="switch">
<input type="checkbox" id="darkMode">
<span class="slider"></span>
</label>
</div>
<div class="form-group">
<label>自动更新</label>
<label class="switch">
<input type="checkbox" id="autoUpdate">
<span class="slider"></span>
</label>
</div>
<div>
<button onclick="loadConfig()">📂 加载配置</button>
<button onclick="saveConfig()">💾 保存配置</button>
<button onclick="resetConfig()">🔄 重置</button>
</div>
<h3>当前配置</h3>
<div class="config-display" id="configDisplay">点击"加载配置"查看</div>
</div>
<script src="renderer.js"></script>
<script>
// 页面内联脚本
let currentConfig = {}
async function loadConfig() {
currentConfig = await window.configAPI.getConfig()
displayConfig(currentConfig)
fillForm(currentConfig)
}
function displayConfig(config) {
document.getElementById('configDisplay').textContent =
JSON.stringify(config, null, 2)
}
function fillForm(config) {
document.getElementById('appName').value = config.appName || ''
document.getElementById('serverPort').value = config.server?.port || 3000
document.getElementById('darkMode').checked = config.theme?.darkMode || false
document.getElementById('autoUpdate').checked = config.features?.autoUpdate ?? true
}
async function saveConfig() {
const newConfig = {
appName: document.getElementById('appName').value,
server: {
...currentConfig.server,
port: parseInt(document.getElementById('serverPort').value) || 3000
},
theme: {
...currentConfig.theme,
darkMode: document.getElementById('darkMode').checked
},
features: {
...currentConfig.features,
autoUpdate: document.getElementById('autoUpdate').checked
}
}
const success = await window.configAPI.saveConfig(newConfig)
if (success) {
currentConfig = newConfig
displayConfig(currentConfig)
alert('配置保存成功!')
} else {
alert('保存失败!')
}
}
function resetConfig() {
fillForm(currentConfig)
}
</script>
</body>
</html>
6. 配置文件热重载
主进程添加文件监视
const chokidar = require('chokidar') // npm install chokidar
// 在 createWindow 后添加
function watchConfig() {
const watcher = chokidar.watch(configPath, {
persistent: true,
ignoreInitial: true
})
watcher
.on('change', async (path) => {
console.log('配置文件已更改,重新加载...')
appConfig = await loadConfigAsync()
// 通知所有窗口配置已更新
BrowserWindow.getAllWindows().forEach(window => {
window.webContents.send('config-updated', appConfig)
})
})
.on('error', error => console.error('文件监视错误:', error))
return watcher
}
// 在 createWindow 中调用
createWindow()
watchConfig()
渲染进程监听配置变化
// 在页面加载时
window.electronAPI.onConfigUpdate((newConfig) => {
console.log('配置已更新:', newConfig)
// 重新应用配置
applyTheme(newConfig.theme)
// 刷新页面数据
refreshData()
})
7. 不同环境配置
创建多环境配置
// config/config-loader.js
const path = require('path')
const fs = require('fs')
const environments = ['development', 'production', 'staging']
let currentEnv = process.env.NODE_ENV || 'development'
// 确保环境有效
if (!environments.includes(currentEnv)) {
currentEnv = 'development'
}
// 主配置文件
const mainConfigPath = path.join(__dirname, 'app-config.json')
// 环境特定配置
const envConfigPath = path.join(__dirname, `app-config.${currentEnv}.json`)
function deepMerge(target, source) {
const result = { ...target }
for (const key in source) {
if (source.hasOwnProperty(key)) {
if (typeof source[key] === 'object' && source[key] !== null && !Array.isArray(source[key])) {
result[key] = deepMerge(result[key] || {}, source[key])
} else {
result[key] = source[key]
}
}
}
return result
}
function loadConfig() {
let config = {}
// 加载主配置
if (fs.existsSync(mainConfigPath)) {
config = JSON.parse(fs.readFileSync(mainConfigPath, 'utf-8'))
}
// 加载环境特定配置(覆盖主配置)
if (fs.existsSync(envConfigPath)) {
const envConfig = JSON.parse(fs.readFileSync(envConfigPath, 'utf-8'))
config = deepMerge(config, envConfig)
}
return config
}
module.exports = { loadConfig, currentEnv }
环境配置文件
config/app-config.development.json
{
"server": {
"host": "localhost",
"port": 3000,
"apiUrl": "http://localhost:3000/api"
},
"features": {
"debug": true,
"logging": "verbose"
}
}
config/app-config.production.json
{
"server": {
"host": "0.0.0.0",
"port": 8080,
"apiUrl": "https://api.example.com"
},
"features": {
"debug": false,
"logging": "minimal"
}
}
8. 安全注意事项
// ❌ 危险:直接暴露整个配置
contextBridge.exposeInMainWorld('dangerousConfig', {
data: require('./config.json') // 不推荐!
})
// ✅ 安全:只暴露需要的字段
contextBridge.exposeInMainWorld('safeConfig', {
get: (key) => {
// 白名单检查
const allowedKeys = ['appName', 'theme', 'server']
if (allowedKeys.includes(key)) {
return readConfigValue(key)
}
return null
}
})
9. 运行项目
# 安装依赖
npm init -y
npm install electron --save-dev
npm install chokidar --save # 可选,用于热重载
# 在 package.json 中添加
# "scripts": { "start": "electron main.js" }
# 运行
npm start
方法对比
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 同步读取 | 代码简单 | 阻塞主进程 | 启动时读取 |
| 异步读取 | 不阻塞 | 代码稍复杂 | 运行时读取 |
| 预加载暴露 | 安全 | 需要 IPC | 渲染进程访问 |
| 文件监视 | 实时更新 | 需要额外依赖 | 开发调试 |
需要我详细解释哪个部分吗?