Electron 在预加载时获取 AppData
Electron get AppData on preload
如何在预加载中获取 AppData 目录?
background.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")
)
配置-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
我认为发生这种情况是因为“app”稍后初始化。
我的最终目标是从 AppData 目录读取 json 配置。
如果有更好的方法来做到这一点,请随时告诉我。
用户不必在 运行 时间内更改配置。但我必须能够将 defaultConfig
中的默认值写入配置文件。
app.getPath()
方法仅在应用程序 'ready' 后可用。使用 app.on('ready' () => { ... });
检测 'ready' 事件。有关详细信息,请参阅 Electron 的 Event: 'ready' 事件。
关于您的 preload.js
脚本,直接在其中包含函数有时会使阅读和理解变得困难(即使只有 require
)。目前,该文件没有关注点分离。 IE:您的 'config' 功能混合在您的 preload
脚本中。如果您希望分离关注点,那么您应该从 preload.js
文件中重构您的 'config' 代码并将其放在自己的文件中。这样,您的 preload.js
文件仅用于配置 IPC 通道和传输相关数据(如果有)。
好的,让我们看看如何解决 app.getPath('appData')
问题。
在您的 main.js
文件中,检测您的应用何时为 'ready',然后通过您的 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
文件中,我已将您的 'path' 变量移至 loadConfig()
函数范围,因为它们仅供该函数使用。如果您需要将它们暴露出来以便在文件的其他地方使用,则需要将它们移出 loadConfig()
函数范围。
我将对 electronApp.getPath('appData')
的引用移到了 loadConfig()
函数中,因为这是在应用 'ready'.
之后从 main.js
调用的
我添加了辅助函数 pathExists()
因为它的实现被使用了不止一次。
最后,我添加了 getConfig()
函数,以便在需要时从应用程序主线程中的任何位置轻松获取配置对象(只要将其包含在需要使用它的文件中即可。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);
}
}
}
);
Note that the above preload.js
file is only used for IPC channel configuration and implementation. IE: Communication between the main thread and render thread(s).
如果您在理解 IPC 通道的实现以及如何在主线程或渲染线程中发送/接收它们方面需要帮助,那么只需打开一个新问题。
如何在预加载中获取 AppData 目录?
background.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")
)
配置-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
我认为发生这种情况是因为“app”稍后初始化。
我的最终目标是从 AppData 目录读取 json 配置。
如果有更好的方法来做到这一点,请随时告诉我。
用户不必在 运行 时间内更改配置。但我必须能够将 defaultConfig
中的默认值写入配置文件。
app.getPath()
方法仅在应用程序 'ready' 后可用。使用 app.on('ready' () => { ... });
检测 'ready' 事件。有关详细信息,请参阅 Electron 的 Event: 'ready' 事件。
关于您的 preload.js
脚本,直接在其中包含函数有时会使阅读和理解变得困难(即使只有 require
)。目前,该文件没有关注点分离。 IE:您的 'config' 功能混合在您的 preload
脚本中。如果您希望分离关注点,那么您应该从 preload.js
文件中重构您的 'config' 代码并将其放在自己的文件中。这样,您的 preload.js
文件仅用于配置 IPC 通道和传输相关数据(如果有)。
好的,让我们看看如何解决 app.getPath('appData')
问题。
在您的 main.js
文件中,检测您的应用何时为 'ready',然后通过您的 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
文件中,我已将您的 'path' 变量移至 loadConfig()
函数范围,因为它们仅供该函数使用。如果您需要将它们暴露出来以便在文件的其他地方使用,则需要将它们移出 loadConfig()
函数范围。
我将对 electronApp.getPath('appData')
的引用移到了 loadConfig()
函数中,因为这是在应用 'ready'.
main.js
调用的
我添加了辅助函数 pathExists()
因为它的实现被使用了不止一次。
最后,我添加了 getConfig()
函数,以便在需要时从应用程序主线程中的任何位置轻松获取配置对象(只要将其包含在需要使用它的文件中即可。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);
}
}
}
);
Note that the above
preload.js
file is only used for IPC channel configuration and implementation. IE: Communication between the main thread and render thread(s).
如果您在理解 IPC 通道的实现以及如何在主线程或渲染线程中发送/接收它们方面需要帮助,那么只需打开一个新问题。