Electron: 在反应中导入电子时出错 | 从“电子”导入 { ipcRenderer }

创建于 2017-07-03  ·  65评论  ·  资料来源: electron/electron

我用 create-react-app 创建了一个简单的 React 应用程序,并成功地将它与电子集成。 一切都很好,直到我尝试在动作创建器文件中导入电子。 如果我删除下面的行,应用程序就可以正常工作。 问题是我无法使用 ipcRenderer 从反应端到电子主进程进行通信。

此行导致应用程序崩溃:
import { ipcRenderer } from 'electron';

我收到以下错误:

类型错误:fs.existsSync 不是函数
(匿名函数)
node_modules/electron/index.js:6

  3 | 
  4 | var pathFile = path.join(__dirname, 'path.txt')
  5 | 
> 6 | if (fs.existsSync(pathFile)) {
  7 |   module.exports = path.join(__dirname, fs.readFileSync(pathFile, 'utf-8'))
  8 | } else {
  9 |   throw new Error('Electron failed to install correctly, please delete node_modules/electron and try installing again')

我在谷歌上发现这是尝试导入电子时的常见问题。

谢谢您的帮助

最有用的评论

@MarshallOfSound我的错误。

如果它可以帮助任何人,我在问题 #7300 中找到了解决方案。

const { ipcRenderer } = window.require('electron');

请注意,当您运行 Electron 应用程序时,这将起作用,但如果您只想在浏览器中测试您的 React 代码,它仍然会崩溃(window.require 在浏览器中没有像在 Electron 中那样定义)。

所有65条评论

CRA 使用 webpack,它会干扰标准模块加载(包括 fs)。

我建议查看 webpack 的 Electron 模式并从 CRA 中弹出

GitHub 问题是针对功能请求和错误报告,有关使用 Electron 的问题应直接提交给社区Slack 频道

@MarshallOfSound我的错误。

如果它可以帮助任何人,我在问题 #7300 中找到了解决方案。

const { ipcRenderer } = window.require('electron');

请注意,当您运行 Electron 应用程序时,这将起作用,但如果您只想在浏览器中测试您的 React 代码,它仍然会崩溃(window.require 在浏览器中没有像在 Electron 中那样定义)。

如果你想访问 app.quit(),你可以使用这个:

const { app } = window.require('electron').remote;

也许它可以帮助某人...

@CiriousJoker这些是解决方案,谢谢!

我仍然收到window.require is not a function 。 我正在将 Electron 与 React Starter Kit (https://github.com/kriasoft/react-starter-kit) 一起使用。 一切都很好,除了这个。

我已将 Electron 应用程序设置为从 Web 加载我的应用程序,因此该应用程序未在本地运行:
https://gist.github.com/holgersindbaek/68f6db82f507967a51ca75c527faeff6

我想要做的是在我的一个 React 文件中调用ipcRenderer 。 我不确定当我的应用程序从网络加载时是否可能。 有什么建议?

@holgersindbaek

和你在同一条船上......你找到解决方案了吗?

不。我很确定不可能从浏览器加载 ipcRenderer。

如果您在浏览器中运行 React 应用程序,它将无法工作。 在 Electron 中运行它,你应该没问题。

@Amthieu感谢您的建议。 我仍然怀疑如何让我的 React 项目(基于 React Starter Kit)在 Electron 中运行。 任何建议将不胜感激:

https://discuss.atom.io/t/getting-electron-to-work-with-react-starter-kit/48594

对了,我有办法。

1) 使用以下代码创建一个preload.js file

window.ipcRenderer = require('electron').ipcRenderer;

2)通过webPreferences在你的 main.js 中预加载这个文件:

  mainWindow = new BrowserWindow({
    width: 800, 
    height: 600,
    webPreferences: {
      nodeIntegration: false,
      preload: __dirname + '/preload.js'
    }
  });

3) 现在,您将可以从您的 React 应用程序访问。 例如,这将起作用:

componentDidMount() {
        if (isElectron()) {
            console.log(window.ipcRenderer);
            window.ipcRenderer.on('pong', (event, arg) => {
                this.setState({ipc: true})
            })
            window.ipcRenderer.send('ping')
        }
    }

注意 - 使用这个: https : isElectron()函数

@HemalR第 3 步应该如下(现在):

componentDidMount() {
    if (window.isElectron) {
        console.log(window.ipcRenderer);
        window.ipcRenderer.on('pong', (event, arg) => {
            this.setState({ipc: true})
        })
        window.ipcRenderer.send('ping')
    }
}

注意window.isElectron不是函数。

@nparsons08

道歉 - 应该添加我从哪里获取 isElectron,编辑我的代码示例并链接到: https :

@holgersindbaek
现在有解决办法吗

对我来说,只有在nodeIntegrationtrue时才有效;

webPreferences: {
      nodeIntegration: true, 
      preload: __dirname + '/preload.js'
}

@HemalR解决方案工作得很好!

现在如何从电子发送到反应?

试过
在电子侧

 ipcMain.emit("pong", "Hello!"); 

但是 React 监听器没有收到任何消息

window.ipcRenderer.on("pong", (event, arg) => {
        console.log("PONG");
});

使用 ipcMain.emit() 是正确的还是应该使用其他东西?

好的,刚刚发现我必须使用(在电子主进程上)

mainWindow.webContents.send("pong", "Hello!");

谢谢你们!

我尝试了以上所有方法都无济于事。 对我有用的是一个巨大的黑客。 修改文件./node_modules/electron/index.js并将路径硬编码为electron.exe

例如

function getElectronPath() {
  return 'D:\\Repos\\MyProject\\node_modules\\electron\\dist\\electron.exe';
}

module.exports = getElectronPath();

哇,我无法让 IPCRenderer 在我的 React 组件上工作。 上面的方法我都试过了。 你们中有人是否有任何提示可以让我使用它来工作? 谢谢

嗯...我的电子应用程序使用上面的解决方案仍然可以正常工作 - 但我已经几个月没有更新它了(不需要)。

我想知道是否有一个突破性的变化会阻止它工作? 也许你们可以发布你的电子版本?

@cyclonstep你有什么具体的错误吗? 如果没有代码片段或一些日志,很难提供帮助......

我正在使用包裹进行捆绑。
Window.require 也为我解决了它(也显示,什么没有):

从 'vue/dist/vue.min' 导入 Vue
从'./App'导入应用程序

// 坏的? 从“电子”导入 { ipcRenderer }
// 坏的? const { ipcRenderer } = require('electron')
// 好的:
const { ipcRenderer } = window.require('electron')

(在同一个文件的进一步下方是电子“pong-Demo” ,这有点证明,它有效)

也许值得注意:即使做错了,包的大小也不会增加(与没有电子要求的情况相比。这是迄今为止我的第一个也是迄今为止唯一的渲染端电子导入)整个电子大小等,但只有大约 20kb ,它本身似乎是一些垫片/包装器代码,来自node_modules/electron-download/node_modules/debug/dist/debug.js :242:ff ...

2: [function (require, module, exports) {
  // shim for using process in browser
  var process = module.exports = {}; // cached from whatever global is present so that test runners that
 stub it

无论如何,事情如上所说。

节点版本 10.2.0
Chrome 版本 66.0.3359.181
电子版本 3.0.2

window.require在我的主脚本中对我不起作用,错误window is not defined ,所以我切换到const electron = eval('require')("electron") 。 希望这可以帮助某人。 使用 webpack,问题是 webpack 在编译时评估我的 require 语句。

@MarshallOfSound我的错误。

如果它可以帮助任何人,我在问题 #7300 中找到了解决方案。

const { ipcRenderer } = window.require('electron');

请注意,当您运行 Electron 应用程序时,这将起作用,但如果您只想在浏览器中测试您的 React 代码,它仍然会崩溃(window.require 在浏览器中没有像在 Electron 中那样定义)。

对于打字稿:

import {IpcRenderer} from 'electron';

declare global {
  interface Window {
    require: (module: 'electron') => {
      ipcRenderer: IpcRenderer
    };
  }
}

const { ipcRenderer } = window.require('electron');

@moshfeu您的解决方案效果很好。 我不需要 Webpack 或 Browserfy 在我的 React 项目中使用 IpcRenderer。 再次非常感谢:D

对于打字稿并使用上面的@HemalR示例,但没有nodeIntegration: truehttps :

对了,我有办法。

  1. 创建一个preload.js file代码:
window.ipcRenderer = require('electron').ipcRenderer;
  1. 通过webPreferences在 main.js 中预加载此文件:
  mainWindow = new BrowserWindow({
    width: 800, 
    height: 600,
    webPreferences: {
      nodeIntegration: false,
      preload: __dirname + '/preload.js'
    }
  });
  1. 现在,您将可以从您的 React 应用程序访问。 例如,这将起作用:
componentDidMount() {
      if (isElectron()) {
          console.log(window.ipcRenderer);
          window.ipcRenderer.on('pong', (event, arg) => {
              this.setState({ipc: true})
          })
          window.ipcRenderer.send('ping')
      }
  }

注意 - 使用这个: https : isElectron()函数

结合
https://github.com/electron/electron/issues/9920#issuecomment -447157348

我用过这个:

import { IpcRenderer } from 'electron';

declare global {
  interface Window {
    ipcRenderer: IpcRenderer
  }
}

export const { ipcRenderer } = window;

希望能帮助到那里的人! 与 stenciljs 一起工作,我想象 react 和 angular

只需在 webpack 配置中添加目标:“电子渲染器”。
导出默认{
...
目标:“电子渲染器”
...
}

嗨,我使用 CRA + Electron。 我通过目标“电子渲染器”构建我的项目。 在构建文件夹中加载文件时它运行良好,但是当我开发和加载 url localhost:3000时它会抛出错误。 我想原因是我在反应组件中使用了节点 api 和电子 api。 有人有解决方案吗? 谢谢。

TypeError: fs.existsSync is not a function

是的, fs api 在浏览器中不可用。 如果我正确理解这种情况,您将使用react-scripts start (这是 CRA 中npm start的默认脚本)运行您的应用程序。

正确的方法是运行electron . 。 您可以在文档中看到它: https :

LMK,如果它适合你。 您还可以在我的应用程序中查看它是如何工作的 - https://github.com/moshfeu/y2mp3 (注意它不是用 CRA 创建的)

嗨,我使用 CRA + Electron。 我通过目标“电子渲染器”构建我的项目。 在构建文件夹中加载文件时它运行良好,但是当我开发和加载 url localhost:3000时它会抛出错误。 我想原因是我在反应组件中使用了节点 api 和电子 api。 有人有解决方案吗? 谢谢。

TypeError: fs.existsSync is not a function

只需在 webpack 配置中添加目标:“电子渲染器”。
导出默认{
...
目标:“电子渲染器”
...
}

它也适用于我。

这是我的工具版本:

  • 网络包:“3.8.1”
  • 电子:“4.2.1”

我认为electron-renderer只是为了解决这个电子问题。我不需要使用像window.required这样的有线语法,它甚至丢失了打字!

对我来说,设置目标:webpack 和 nodeIntegration 的“电子渲染”:BrowserWindow 选项中的 true
我正在使用今天最后一个版本的 webpack+electron+react

我正在使用 TypeScript 和 Electron 运行 create-react-app。 我遵循了这些非常好的设置说明。 但后来我也遇到了这个错误。 对我有用的解决方案是这个线程中所说的东西的总和:

  1. 让 react-script 使用"electron-renderer"作为target 。 为此,我将rescriptsrescript-env使用。

package.json

  "rescripts": [
    "env"
  ],

.rescriptsrc.js

module.exports = [require.resolve("./webpack.config.js")];

webpack.config.js

module.exports = config => {
  config.target = "electron-renderer";
  return config;
};
  1. 添加节点集成
new BrowserWindow({
  webPreferences: {
    nodeIntegration: true
  }
});
  1. 添加src/typings.d.ts
declare var window: Window;
interface Window {
  require: any;
}

最后在你的应用程序中

App.tsx

const { remote } = window.require("electron");
console.log(remote.getCurrentWindow());

@卢卡斯邦巴赫
这也应该有效:

declare var window: Window;
interface Window {
  require: NodeRequire
}

然后您将输入所需的常量

谢谢!

以下是我用于 Vue 的步骤,以防对其他人有帮助。

通过在创建浏览器窗口时将其添加到 Web 首选项,确保为渲染器进程启用节点集成:

new BrowserWindow({
    webPreferences: {
        nodeIntegration: true,
    },
})
````

Configure webpack to package your application for electron renderer by adding a target to your `vue.config.js` (or wherever you set your vue settings). 
```js
module.exports = {
    configureWebpack: {
        target: 'electron-renderer',
    },
}

在您的应用程序中导入您需要的内容。 我将 shell 模块导入到我的组件中,如下所示:

import { shell } from 'electron'

对于仍然有同样问题的任何人。 这是迄今为止我找到的最好的解决方案

```js
新浏览器窗口({
网络首选项:{
节点集成:真
}
});

我希望这条评论能引起注意,因为很多人都在问关于在你的应用程序中导入fsipcRenderer的问题。 这是电子应用程序的常见需求,但我发现没有多少人能做到这一点,并且正在使用过时的模式。 tl; dr - 如果您没有以正确的方式导入您的节点模块(即fs )或电子模块(即ipcRenderer ),则会存在安全漏洞。 如果您只为自己使用您的应用程序,您_可能_是安全的,但如果您想分享或出售您的应用程序,则应提前阅读。

我们的目标

在我进入解决方案之前,首先了解_为什么_我们这样做很重要。 Electron 应用程序允许我们在我们的应用程序中包含节点模块,这为它们提供了惊人的功能,但存在安全问题。 我们希望允许我们的应用程序使用 native-os(即节点)功能,但我们不希望它们被滥用

正如@raddevus在评论中提出的,这在加载远程内容时是必要的。 如果您的电子应用程序完全离线/本地,那么您可能只需打开nodeIntegration:true 。 但是,我仍然会选择保留nodeIntegration:false作为使用您的应用程序的意外/恶意用户的保护措施,并防止任何可能安装在您的计算机上的恶意软件与您的电子应用程序交互并使用nodeIntegration:true攻击向量(非常罕见,但可能发生)!

简单的方法

在 BrowserWindow 中设置nodeIntegration: true可以让渲染器进程访问节点模块。 做_这个_,是脆弱的。 您可以访问require("fs")require("electron") ,但这意味着如果有人要发现 XSS 漏洞,他们可以运行您在渲染器进程中公开的

考虑删除计算机上的所有文件,或者其他非常糟糕的文件。

(替代)简单的方法

除了将 nodeIntegration 设置为 true 之外,您的应用程序很可能正在使用 webpack 来捆绑应用程序文件。 Webpack 会混淆某些符号,因此诸如target: 'electron-renderer'或 webpack externals 之类的设置允许您将这些变量( ipcRenderer )传递到您的应用程序中。

尽管如此,除了您设置应用程序的方式之外,这没有任何改变。

(其他替代)简单方法

您可以使用远程模块来访问ipcRenderer 。 它基本上是另一种形式的“简单方法”。 它不建议用电子的安全建议,要做到这一点,因为从原型污染矢量这种类型的攻击遭受的。

IE。 使用 remote 可能允许某人修改 js-object 的原型并破坏您的机器/应用程序。

_几乎_正确的方式

@marksyzm有一个更好的解决方案,虽然并不完美,我们使用 IPC 将ipcRenderer发送到渲染器进程。 这种类型的设置也容易受到原型污染攻击。 如果你想让你的应用程序完成 80%,我会使用这种方法,因为它可能不需要你做太多的重构。

正确的方式

fs / ipcRenderer导入渲染器进程的正确方法是使用 IPC(进程间通信)。 这是 Electron 允许您在主进程和渲染器进程之间进行对话的方式。 分解,这就是您的应用程序需要的外观:

  1. BrowserWindow 有一个preload属性。 这个属性是一个js文件,加载时可以访问require (这意味着你可以要求ipcRenderer)
  2. 你的 BrowserWindow 也会有contextIsolation: true来防止原型污染攻击,但这意味着你需要使用contextBridge
  3. 使用预加载脚本和 contextBridge,您允许渲染器进程访问ipcRenderer
  4. 在您的主脚本中,您为 ipcRenderer(在ipcMain模块中)创建侦听器。 在这些侦听器中,您可以使用fs模块

_粗略地_这就是所有这些步骤的样子:

main.js

const {
  app,
  BrowserWindow,
  ipcMain
} = require("electron");
const path = require("path");
const fs = require("fs");

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win;

async function createWindow() {

  // Create the browser window.
  win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: false, // is default value after Electron v5
      contextIsolation: true, // protect against prototype pollution
      enableRemoteModule: false, // turn off remote
      preload: path.join(__dirname, "preload.js") // use a preload script
    }
  });

  // Load app
  win.loadFile(path.join(__dirname, "dist/index.html"));

  // rest of code..
}

app.on("ready", createWindow);

ipcMain.on("toMain", (event, args) => {
  fs.readFile("path/to/file", (error, data) => {
    // Do something with file contents

    // Send result back to renderer process
    win.webContents.send("fromMain", responseObj);
  });
});

预加载.js

const {
    contextBridge,
    ipcRenderer
} = require("electron");

// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld(
    "api", {
        send: (channel, data) => {
            // whitelist channels
            let validChannels = ["toMain"];
            if (validChannels.includes(channel)) {
                ipcRenderer.send(channel, data);
            }
        },
        receive: (channel, func) => {
            let validChannels = ["fromMain"];
            if (validChannels.includes(channel)) {
                // Deliberately strip event as it includes `sender` 
                ipcRenderer.on(channel, (event, ...args) => func(...args));
            }
        }
    }
);

索引.html

<!doctype html>
<html lang="en-US">
<head>
    <meta charset="utf-8"/>
    <title>Title</title>
</head>
<body>
    <script>
        window.api.receive("fromMain", (data) => {
            console.log(`Received ${data} from main process`);
        });
        window.api.send("toMain", "some data");
    </script>
</body>
</html>

至少,我_相信_你需要电子 v7 来实现这些功能。

我怎么知道这个?

我关心安全的电子应用程序,并构建了secure-electron-template以创建一个电子应用程序模板来增强安全性,而不是将安全性视为事后的想法。

基于@reZach上面的评论,我正在做类似以下的事情。 主要区别在于preload.js ,其中我的 API 更接近于 ipcRenderer。

main.js

let newWindow = null;
function createWindow() {
    newWindow = new BrowserWindow({
        webPreferences: {
            nodeIntegration: false,
            contextIsolation: true,
            enableRemoteModule: false,
            preload: path.join(__dirname, "preload.js")
        }
    });

    newWindow.webContents.on('did-finish-load', () => {
        newWindow.webContents.send('APP_MY_INIT', { data: 'hi' });
    });
}

ipcMain.on('APP_SOMETHING', (event, ...args) => {
    // ...
});

ipcMain.handle('APP_SOMETHING_ELSE', (event, ...args) => {
    // ...
    return myVar;
});

预加载.js

const { contextBridge, ipcRenderer } = require('electron');

function callIpcRenderer(method, channel, ...args) {
    if (typeof channel !== 'string' || !channel.startsWith('APP_')) {
        throw 'Error: IPC channel name not allowed';
    }
    if (['invoke', 'send'].includes(method)) {
        return ipcRenderer[method](channel, ...args);
    }
    if ('on' === method) {
        const listener = args[0];
        if (!listener) throw 'Listener must be provided';

        // Wrap the given listener in a new function to avoid exposing
        // the `event` arg to our renderer.
        const wrappedListener = (_event, ...a) => listener(...a);
        ipcRenderer.on(channel, wrappedListener);

        // The returned function must not return anything (and NOT
        // return the value from `removeListener()`) to avoid exposing ipcRenderer.
        return () => { ipcRenderer.removeListener(channel, wrappedListener); };
    }
}

contextBridge.exposeInMainWorld(
    'myIpcRenderer', {
        invoke: (...args) => callIpcRenderer('invoke', ...args),
        send: (...args) => callIpcRenderer('send', ...args),
        on: (...args) => callIpcRenderer('on', ...args),
    },
);

客户端.js

const { myIpcRenderer } = window;

const removeMyListener = myIpcRenderer.on('APP_MY_INIT', data => {
    console.log(data);
    myIpcRenderer.send('APP_SOMETHING', 'foo');
})

async function test() {
    const result = await myIpcRenderer.invoke('APP_SOMETHING_ELSE', 'foo', 'bar');
    console.log(result);
}
test();

if (/* should remove listener === true */) {
    removeMyListener();
}

对于那些使用 TypeScript 的人, types.d.ts

declare global {
    interface Window {
        myIpcRenderer: MyIpcRenderer,
    }
}

export interface MyIpcRenderer {
    invoke(channel: string, ...args: any[]): Promise<any>;
    send(channel: string, ...args: any[]): void;

    /** <strong i="22">@return</strong> A function that removes this listener. */
    on(channel: string, listener: (...args: any[]) => void): () => void;
}

有谁知道为什么当我尝试将它放入 preload.js 时会出现找不到模块的错误?

const errorLogging = require('../renderer/utils/errorLogging');

谢谢

@silentlight这与电子无关,您是否有自己的 github 存储库可以继续对话? (看起来你在require()路径不正确,这就是为什么它会抛出错误,而没有看到更多代码)。

虽然已经有window.require解决方案,我还是要分享我的,只是为了好玩:

function magic(module){
    require(module)
}

const { ipcRenderer } = magic('electron')

由于您没有使用 ES 模块或 CommonJS 语法,您的捆绑器不会识别您正在导入模块,因此它不会尝试捆绑它,因此不会抛出错误

我想知道为什么还没有人尝试过这个😆

@marc2332是的,但是随后您将自己直接暴露于这个魔法包装器的潜在攻击中。 它可能不适用于您的场景,但绝对是您应该考虑的问题。

@reZach 老实说,我看不出 window.require 更安全的原因。 顺便说一句,有更肮脏的方法来做到这一点。

const { ipcRenderer } = eval("require('electron')")

@marc2332两者都不安全。 我只是注意到,如果您使用远程资产,您就会以这种方式对注入攻击敞开大门。 😄

谢谢雷扎克! 我认为人们会更快地理解你的方法

或稍后

马克·埃尔芬斯通
www.oxfordsourceltd.com

嗨 Zach, contextBridge解决方案是否支持原型?

我带有contextIsolation: false原始代码只是在window上附加了一个函数。 这个函数返回一个Promise<MediaStream>但是contextBridge的文档说原型被删除了,当我使用contextBridge我收到一个空对象。

有没有办法支持原型同时确保contextIsolation: true

嗨 Zach, contextBridge解决方案是否支持原型?

我带有contextIsolation: false原始代码只是在window上附加了一个函数。 这个函数返回一个Promise<MediaStream>但是contextBridge的文档说原型被删除了,当我使用contextBridge我收到一个空对象。

有没有办法支持原型同时确保contextIsolation: true

不,它没有。 我试图找到一位团队成员的评论,我记得这是有意的行为,但我不能。 我能说的最好的是在 Electron v8( pr 20214 )中对 IPC 中对象的序列化进行了更改(当上下文隔离打开时有效),这将放弃对函数的支持(当前已弃用,将在未来版本中删除)或原型。

注意:不能使用 V8 的结构化克隆算法序列化的对象,例如函数、DOM 对象、特殊的 Node/Electron 对象(如 process.env 或 WebContents),或任何包含此类项目的对象都将使用旧的 base::Value- 进行序列化基于算法。 但是,此行为已被弃用,并且会从 Electron 9 开始抛出异常。

这可能不是你想听到的消息,但我希望我能帮到你。

嗨 Zach, contextBridge解决方案是否支持原型?
我带有contextIsolation: false原始代码只是在window上附加了一个函数。 这个函数返回一个Promise<MediaStream>但是contextBridge的文档说原型被删除了,当我使用contextBridge我收到一个空对象。
有没有办法支持原型同时确保contextIsolation: true

不,它没有。 我试图找到一位团队成员的评论,我记得这是有意的行为,但我不能。 我能说的最好的是在 Electron v8( pr 20214 )中对 IPC 中对象的序列化进行了更改(当上下文隔离打开时有效),这将放弃对函数的支持(当前已弃用,将在未来版本中删除)或原型。

注意:不能使用 V8 的结构化克隆算法序列化的对象,例如函数、DOM 对象、特殊的 Node/Electron 对象(如 process.env 或 WebContents),或任何包含此类项目的对象都将使用旧的 base::Value- 进行序列化基于算法。 但是,此行为已被弃用,并且会从 Electron 9 开始抛出异常。

这可能不是你想听到的消息,但我希望我能帮到你。

这很不幸,但感谢您的快速回复。

我遵循了上面概述的步骤(由@reZach和@aplum 提供),并将信息从client.js传递到preload.jsmain.js

在本地运行所有内容时,通信有效,但当我运行electron-builder ,通信中断。 例如,使用setBadge()在本地运行时效果很好。 我可以从client.js文件中获取计数,并看到它被传递到contextBridge ,然后传递到main.js并成功设置app.dock.setBadge(count) 。 在使用electron-builder构建 Electron 之后,我在它的通信中是否遗漏了什么?

"electron": "^8.2.3",
"electron-builder": "^22.4.0",

主程序

const { ipcMain } = electron;

app.on('ready', () => {
    mainWindow = new BrowserWindow({
        height: height,
        width: width,
        minHeight: 575,
        minWidth: 900,
        center: true,
        webPreferences: {
            nodeIntegration: false,
            contextIsolation: true,
            enableRemoteModule: false,
            spellcheck: true,
            preload: path.join(__dirname, "preload.js")
        },
    });
}

ipcMain.on('SEND_BADGE_COUNT', (e, count) => {
    const badgeCount = count > 0 ? count : '';
    app.dock.setBadge(`${badgeCount}`)
})

预加载.js

const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld(
    'setBadgeCountForElectron', {
        send: (channel, data) => {
            console.log(`Preload ${channel}, ${data}`)
            ipcRenderer.send(channel, data)
        }
    }
)

客户端.js

const { setBadgeCountForElectron } = window;

function sendBadgeCount(count) {
  !!setBadgeCountForElectron && setBadgeCountForElectron.send('SEND_BADGE_COUNT', count)
}

sendBadgeCount(count)

包.json

  "name": "desktop_app",
  "version": "0.1.8-beta",
  "private": true,
  "description": "",
  "homepage": "./",
  "main": "public/electron.js",
  "build": {
    "copyright": "Copyright © 2020 My Company",
    "appId": "com.my-app.app",
    "productName": "My Company",
    "buildVersion": "0.0.1-beta",
    "mac": {
      "category": "public.app-category.utilities",
      "icon": "./public/images/mac-icon.png"
    },
    "win": {
      "icon": "./public/images/windows-icon.png"
    },
    "files": [
      "build/**/*",
      "node_modules/**/*"
    ],
    "directories": {
      "buildResources": "assets"
    }
  },

@SterlingChin ,我不是电子团队的维护者,但这可能是一个更好的问题 - 适合电子构建器存储库。

当我花点时间思考它时,我更倾向于认为这是代码的问题,而不是电子构建器的问题。

当我花点时间思考它时,我更倾向于认为这是代码的问题,而不是电子构建器的问题。

我确定我遗漏了一些可以使这一切按预期工作的关键部分。 感谢您的答复。 我在electron-builder回购中打开了一个问题,但它没有受到任何关注。

当我花点时间思考它时,我更倾向于认为这是代码的问题,而不是电子构建器的问题。

我确定我遗漏了一些可以使这一切按预期工作的关键部分。 感谢您的答复。 我在electron-builder回购中打开了一个问题,但它没有受到任何关注。

您是否针对您的问题创建了 MVP 存储库? 这有时会有所帮助,在此过程中您会意识到发生了什么。

@Amthieu@CiriousJoker我爱你们,谢谢。

  1. yarn add -D custom-cra react-app-rewired
  2. 修改 package.json 以使用 react-app-rewired start app: "start": "BROWSER=none react-app-rewired start",
  3. 在 react 应用程序根目录中创建文件 'config-overrides':
    `const { override } = require('customize-cra');

函数 addRendererTarget(config) {
config.target = '电子渲染器';

返回配置;
}

module.exports = override(addRendererTarget);`

  1. 您现在可以从“电子”导入 { ipcRenderer} :)

我使用的是 vuejs,只需在 vue.config.js 中添加代码

模块.出口 = {
“transpileDependencies”:[
“验证”
],
插件选项:{
电子生成器:{
节点集成:真
}
}
}

@genilsonmm 太棒了! 在过去的 3 个小时里,我一直在发疯。 谢谢!!
对我有用的特定部分是
```javascript
插件选项:{
电子生成器:{
节点集成:真
}
},

“transpileDependencies”:[
“验证”
],

这对我不起作用......但 window.require('electron') 运行良好

对于所有遇到“window.require 不是函数”问题的人

您必须在电子上创建一个预加载脚本。

  1. 在你有de electron主脚本的目录下创建一个名为preload.js的文件,把这段代码放在上面:

    window.require = 要求;

  2. 转到您的电子主脚本并在您创建窗口的代码中输入:

    赢 = 新浏览器窗口({
    宽度:1280,
    高度:720,
    网络首选项:{
    节点集成:假,
    预加载:__dirname + '/preload.js'
    },

    })
    有了这个,您将首先预加载脚本,这为我解决了问题。 我也希望你:)

@reZach你的解决方案对我

        receive: (channel, func) => {
            let validChannels = ["fromMain"];
            if (validChannels.includes(channel)) {
                // Deliberately strip event as it includes `sender` 
                ipcRenderer.on(channel, (event, ...args) => fn(...args));
            }
        }

你将你的回调函数定义为 "func" ,然后调用 "fn",如果你改变它,它就像你描述的那样工作。
非常感谢您的详细帖子:+1:

@genilsonmm如果我把 vue.config.js :

插件选项:{
电子生成器:{
节点集成:真,

我收到错误“要求未定义”:

image

如果我评论“nodeIntegration: true”这一行,错误信息就会消失并且应用程序可以工作。

这是什么意思?

@marcoippolito我相信这意味着 webpack 捆绑代码试图使用require这是 node js 的模块解析方法。 由于如果禁用节点集成,则此功能不可用,因此代码无法执行。 您需要做的是修改您的 webpack 配置以定位浏览器(如果我没记错的话, var是目标)并确保您的代码都没有使用 node api,并且错误应该消失。

对于那些使用 TypeScript 的人,官方 Next.js 示例中有一个很好的示例:
https://github.com/vercel/next.js/search?q=ipcRenderer&unscoped_q=ipcRenderer

预载.ts

/* eslint-disable @typescript-eslint/no-namespace */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { ipcRenderer, IpcRenderer } from 'electron'

declare global {
  namespace NodeJS {
    interface Global {
      ipcRenderer: IpcRenderer
    }
  }
}

// Since we disabled nodeIntegration we can reintroduce
// needed node functionality here
process.once('loaded', () => {
  global.ipcRenderer = ipcRenderer
})

索引.tsx

import { useState, useEffect } from 'react'

const Home = () => {
  const [input, setInput] = useState('')
  const [message, setMessage] = useState(null)

  useEffect(() => {
    const handleMessage = (event, message) => setMessage(message)
    global.ipcRenderer.on('message', handleMessage)

    return () => {
      global.ipcRenderer.removeListener('message', handleMessage)
    }
  }, [])

  const handleSubmit = (event) => {
    event.preventDefault()
    global.ipcRenderer.send('message', input)
    setMessage(null)
  }

  return (
    <div>
      <h1>Hello Electron!</h1>
    </div>
  )
}

export default Home

@genilsonmm 太棒了! 在过去的 3 个小时里,我一直在发疯。 谢谢!!
对我有用的特定部分是

   pluginOptions: {
        electronBuilder: {
            nodeIntegration: true
        }
    },

正解,标记

我在启用 NodeIntegration 的情况下也有这个问题。 window.require 和 require 都不起作用。

+这只发生在反应中,而不是普通电子

此页面是否有帮助?
0 / 5 - 0 等级