如何獲取預(yù)加載的 AppData 目錄?
背景.js
[...] async function createWindow() { const win = new BrowserWindow({ width: 800, height: 600, webPreferences: { preload: path.join(__static, "preload.js"), nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION, contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION }, }) } [...]
preload.js
const { contextBridge } = require('electron') contextBridge.exposeInMainWorld( 'configManager', require("../src/utils/config-manager") )
config-manager.js
const app = require("electron").app const fs = require("fs") const resourcePath = app.getPath('appData').replaceAll("\", "/") + "my-custom-path" // <--- const configPath = resourcePath + "config.json" const defaultConfig = [ ... ] let config; function createFilesIfNotExists(){ if (!fs.existsSync(resourcePath)) fs.mkdirSync(resourcePath) if (!fs.existsSync(configPath)){ fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 4)) return true } return false } module.exports = { loadConfig() { createFilesIfNotExists() [...] return config } }
如果我運行這個,我會收到此錯誤。
TypeError: Cannot read property 'getPath' of undefined at Object.<anonymous> (VM77 config-manager.js:3) at Object.<anonymous> (VM77 config-manager.js:65) at Module._compile (VM43 loader.js:1078) at Object.Module._extensions..js (VM43 loader.js:1108) at Module.load (VM43 loader.js:935) at Module._load (VM43 loader.js:776) at Function.f._load (VM70 asar_bundle.js:5) at Function.o._load (VM75 renderer_init.js:33) at Module.require (VM43 loader.js:959) at require (VM50 helpers.js:88) (anonymous) @ VM75 renderer_init.js:93
我認為發(fā)生這種情況是因為“app”稍后初始化。
我的最終目標是從 AppData 目錄中讀取 json 配置。
如果有更好的方法來做到這一點,請隨時告訴我。
用戶不必能夠在運行時更改配置。但我必須能夠?qū)?defaultConfig
中的默認值寫入配置文件中。
app.getPath()
方法僅在應(yīng)用程序“就緒”后才可用。使用 app.on('ready' () => { ... });
檢測 'ready' 事件。有關(guān)更多信息,請參閱 Electron 的事件:'ready' 事件。
關(guān)于您的 preload.js
腳本,直接在其中包含函數(shù)有時會使內(nèi)容難以閱讀和理解(即使它僅由 require
實現(xiàn))。目前,該文件沒有關(guān)注點分離。 IE:您的“配置”功能混合在 preload
腳本中。如果您希望分離問題,那么您應(yīng)該從 preload.js
文件中重構(gòu)您的“配置”代碼,并將其放在自己的文件中。這樣,您的 preload.js
文件僅用于配置 IPC 通道和傳輸關(guān)聯(lián)數(shù)據(jù)(如果有)。
好吧,讓我們看看如何解決 app.getPath('appData')
問題。
在您的 main.js
文件中,檢測您的應(yīng)用程序何時“就緒”,然后通過您的 config-manager.js
文件獲取 appData
目錄。
main.js
(主線程)
const electronApp = require('electron').app; const electronBrowserWindow = require('electron').BrowserWindow; let appConfig = require('config-manager'); let appMainWindow = require('mainWindow'); let mainWindow; app.whenReady().then(() => { // Load the config. let configStatus = appConfig.loadConfig(); console.log(configStatus); let config = appConfig.getConfig(); console.log(config); // Create your main window. mainWindow = appMainWindow.create() ... }) })
在您的 config-manager.js
文件中,我已將您的“路徑”變量移至 loadConfig()
函數(shù)范圍,因為它們僅由該函數(shù)使用。如果您需要將它們公開以供文件中其他位置使用,則需要將它們移回 loadConfig()
函數(shù)作用域之外。
我將對 ElectronApp.getPath('appData')
的引用移至 loadConfig()
函數(shù)中,因為在應(yīng)用程序“就緒”后從 main.js
調(diào)用此函數(shù)。
我添加了輔助函數(shù) pathExists()
因為它的實現(xiàn)被多次使用。
最后,我添加了 getConfig()
函數(shù),以便在需要時從應(yīng)用程序主線程中的任何位置輕松獲取配置對象(只要將其包含在需要使用它的文件中即可)。IE: let appConfig = require('config-manager')
。
config-manager.js
(主線程)
const electronApp = require("electron").app const nodeFs = require("fs") const defaultConfig = [ ... ]; let config; function loadConfig() { let resourcePath = app.getPath('appData').replaceAll("\", "/") + "my-custom-path"; let configPath = resourcePath + "config.json"; if (! pathexists(resourcePath)) { nodeFs.mkdirSync(resourcePath); } if (! pathexists(configPath)) { nodeFs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 4)); config = defaultConfig; } else { config = JSON.parse(nodeFs.readFileSync(configPath , 'utf8')); }; } function getConfig() { return config; } function pathExists(path) { return (fs.existsSync(path)) ? true : false; } module.exports = {loadConfig, getConfig}
典型的 preload.js
腳本看起來像這樣。
const contextBridge = require('electron').contextBridge; const ipcRenderer = require('electron').ipcRenderer; // White-listed channels. const ipc = { 'render': { // From render to main. 'send': [ 'config:updateConfig' // Example only ], // From main to render. 'receive': [ 'config:showConfig' // Exmaple only ], // From render to main and back again. 'sendReceive': [] } }; contextBridge.exposeInMainWorld( // Allowed 'ipcRenderer' methods. 'ipcRender', { // From render to main. send: (channel, args) => { let validChannels = ipc.render.send; if (validChannels.includes(channel)) { ipcRenderer.send(channel, args); } }, // From main to render. receive: (channel, listener) => { let validChannels = ipc.render.receive; if (validChannels.includes(channel)) { // Deliberately strip event as it includes `sender` ipcRenderer.on(channel, (event, ...args) => listener(...args)); } }, // From render to main and back again. invoke: (channel, args) => { let validChannels = ipc.render.sendReceive; if (validChannels.includes(channel)) { return ipcRenderer.invoke(channel, args); } } } );
如果您需要幫助來了解 IPC 通道的實現(xiàn)以及如何在主線程或渲染線程中發(fā)送/接收它們,那么只需提出一個新問題即可。