我有一个使用 Electron Forge 应用程序以及 ReactJS 和 Socket.IO 的场景:
预加载.js:
import { contextBridge, ipcRenderer } from "electron";
window.addEventListener("DOMContentLoaded", () => {
const replaceText = (selector, text) => {
const element = document.getElementById(selector);
if (element) element.innerText = text;
};
for (const type of ["chrome", "node", "electron"]) {
replaceText(`${type}-version`, process.versions[type]);
}
});
let state = {
config: {},
store: {},
};
// Function to update the state
const updater = (key, value) => {
state[key] = value;
ipcRenderer.send("updater", {
key: key,
value: value,
});
};
const URL = `ws://localhost:3010`; // Test port
const io = require("socket.io-client");
const socket = io(URL);
socket.on("connect", () => {
console.log("Connect...");
});
socket.on("upload", (msg) => {
let json = JSON.parse(msg);
let key = json.key;
let value = json.value;
if (key == "config") updater("config", value);
if (key == "store") updater("store", value);
});
ipcRenderer.on('command', (payload) => {
socket.emit("command", JSON.stringify(payload));
});
contextBridge.exposeInMainWorld("stateAPI", {
state: state,
updater: updater, // Expose the update function
});
main.js
// Modules to control application life and create native browser window
const { app, BrowserWindow } = require("electron");
const path = require("path");
function createWindow() {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 640,
height: 480,
webPreferences: {
preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,
webSecurity: false, // Need this to use Socket.io? Is there other way to do it?
},
});
// and load the index.html of the app.
mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);
// if embedded Linux set fullscreen
if (process.arch.includes("arm")) {
mainWindow.setFullScreen(true);
}
// Open the DevTools.
mainWindow.webContents.openDevTools();
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
createWindow();
app.on("activate", function () {
console.log("-----> activate");
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on("window-all-closed", function () {
console.log("-----> window-all-closed");
if (process.platform !== "darwin") app.quit();
});
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
process.on("uncaughtException", (error) => {
console.error("Uncaught Exception:", error);
fs.writeFileSync(
"/home/torizon/app/error.log",
`Uncaught Exception: ${error.message}\n${error.stack}`
);
});
我的反应代码:index.js
import React, { useEffect, useState } from "react";
import { ipcRenderer } from "electron"; **<=== This is causing the error**
const command = (payload) => {
ipcRenderer.send("command", payload);
};
const Home = () => {
const [state, setState] = useState(window.stateAPI.state);
useEffect(() => {
const handleStateUpdate = (key, value) => {
let clone = Object.assign({}, state);
clone[key] = value;
setState(clone);
};
ipcRenderer.on("state_update", handleStateUpdate);
// Clean up the listener on component unmount
return () => {
ipcRenderer.removeListener("state_update", handleStateUpdate);
};
}, []);
return (
<div>
<h1>{state.config}</h1>
<h1>{state.store}</h1>
<button onClick={() => command("Test")}>Click me</button>
</div>
);
};
export default Home;
webpack 配置:webpack.main.config.js
module.exports = {
entry: './src/main.js',
module: {
rules: require('./webpack.rules'),
},
};
和webpack.renderer.config.js
const rules = require("./webpack.rules");
rules.push({
test: /\.css$/,
use: [
{
loader: "style-loader",
options: {
esModule: false,
},
},
{
loader: "css-loader",
options: {
esModule: false,
modules: {},
},
},
],
});
rules.push({
test: /\.(png|jpg|jpeg|gif)$/i, // Add support for image formats
type: 'asset/resource', // This will emit a separate file for the image
});
module.exports = {
// Put your normal webpack config below here
module: {
rules,
},
};
当我尝试在 ReactJs 应用程序中导入
ipcRenderer
时,出现以下错误:
index.js:1 Uncaught Error: Cannot find module 'fs'
at webpackMissingModule (index.js:1:1)
at eval (index.js:1:1)
at ./node_modules/electron/index.js (index.js:62:1)
at __webpack_require__ (index.js:8878:32)
at fn (index.js:9073:21)
at eval (index.js:7:66)
at ./src/components/home/index.js (index.js:945:1)
at __webpack_require__ (index.js:8878:32)
at fn (index.js:9073:21)
at eval (app.jsx:5:74)
如果我取消注释,则不会出现错误,但我无法更改 Electron 和我的应用程序之间的
state
数据。
什么可能导致此问题以及如何解决?感谢帮助。
您的代码至少存在两个问题。
首先,您会收到此错误,因为您尝试使用 webpack 编译 Node.js 代码(即此处通过导入
electron
来实现 ipcRenderer
),并且没有为其配置(请参阅 webpack 的 target
配置) )。 但这并不意味着您应该更改其配置。您的 React 代码位于 渲染器进程,出于安全原因,您不应该在此进程中使用 Node.js。
要解决第一个问题,您需要使用 preload 文件来公开您需要的内容(仅您需要的内容,而不是整个 IPC API)。您可以在官方 IPC 文档 以及我在此处链接的其他文档中找到大量示例。
解决该问题后,您的预加载文件可能会出现
module not found
错误。默认情况下,出于“安全原因”,预加载脚本是“沙盒”的,这意味着您只能导入 Electron 和 Node 内置模块的特定子集。如果您想导入 socket.io-client
并遵循安全建议,您需要在您的 主进程上执行此操作。