Electron 读取 JSON 配置文件教程

📁 项目结构

复制代码
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 渲染进程访问
文件监视 实时更新 需要额外依赖 开发调试

需要我详细解释哪个部分吗?

相关推荐
chxii15 小时前
Nginx 正则 location 指令匹配客户端请求的 URI
前端·nginx
qing2222222215 小时前
Linux:/var/log/journal 路径下文件不断增加导致根目录磁盘爆满
linux·运维·前端
Armouy16 小时前
Nuxt.js 学习复盘:核心概念与实战要点
前端·javascript·学习
safestar201216 小时前
React 19实战:Action、并发与性能,一次告别“意大利面状态”的升级
开发语言·javascript·vue.js
ZhaoJuFei16 小时前
React生态学习路线
前端·学习·react.js
早點睡39016 小时前
ReactNative项目OpenHarmony三方库集成实战:react-native-calendar-events(读取不到日历里新增的事件,待排查)
javascript·react native·react.js
apcipot_rain16 小时前
CSS知识概述
前端·css
837927397@QQ.COM16 小时前
个人理解无界原理
开发语言·前端·javascript
冰暮流星16 小时前
javascript之Dom查询操作1
java·前端·javascript
admin and root16 小时前
XSS之Flash弹窗钓鱼
前端·网络·安全·web安全·渗透测试·xss·src