electron-vite-react icon indicating copy to clipboard operation
electron-vite-react copied to clipboard

require is not defined

Open cuppachino opened this issue 3 years ago • 11 comments

electron-vite dev server error electron-vite dev server error2 )

  • Windows 11 Pro 10.0.22621 Build 22521
  • Node 16.13.2

cuppachino avatar Jul 22 '22 10:07 cuppachino

I faced no issues when running the application with your setup. Maybe you can provide some of your code if you have added/modified it?

I also have Windows 11 of the latest build and installed your Node version from its distros. Though I would recommend updating your Node.js to the LTS version at least.

Also electron-vite-react recently received some updates and it would be good if you could fresh-clone and start over.

PAXANDDOS avatar Jul 22 '22 13:07 PAXANDDOS

I updated node to 16.16.0 and created a new project from the template and its working. Thank you very much. Sorry for the silly issue.

cuppachino avatar Jul 22 '22 14:07 cuppachino

I'm reopening because this is still a problem. I've tested it with Node 16.16.0 and 18.6.0 on Windows. Usage of ipcRenderer in the renderer process causes this error in browsers: image

steps to reproduce:

  1. create a fresh project with npm create electron-vite
  2. in main.tsx, uncomment import "./samples/node-api"

Maybe I am missing something. What is the recommended approach for sending messages between the main & renderer processes without breaking browser support?

cuppachino avatar Jul 26 '22 12:07 cuppachino

I was able to get it working by enabling the contextBridge and extending the global Window interface.

// electron > main > index.ts
win = new BrowserWindow({
  title: "Main window",
  icon: join(ROOT_PATH.public, "favicon.svg"),
  webPreferences: {
    preload,
    nodeIntegration: true,
    contextIsolation: true,
  },
});
// electron > preload > index.ts
contextBridge.exposeInMainWorld("ipc", {
send: (channel: EventChannel, ...args: any[]) =>
  ipcRenderer.send(channel, ...args),
on: (channel: EventChannel, func: (...args: any[]) => void) =>
  ipcRenderer.on(channel, (event, ...args) => func(...args)),
});

example with typings:

// src > types > global.d.ts
declare global {
  type EventChannel = "main-process-message" | "window-event";

  type WindowEvent =
    | "focus"
    | "blur"
    | "close"
    | "minimize"
    | "maximize"
    | "unmaximize";

  type EventMessage<Channel extends EventChannel> =
    Channel extends "window-event"
      ? WindowEvent
      : Channel extends "main-process-message"
      ? string
      : any;

  type EventHandler<Channel extends EventChannel> = (
    type: EventMessage<Channel>
  ) => void;

  interface Window {
    ipc: {
      send: <Channel extends EventChannel>(
        channel: Channel,
        msg: EventMessage<Channel>
      ) => void;
      on: <Channel extends EventChannel>(
        channel: Channel,
        handler: EventHandler<Channel>
      ) => Electron.IpcRenderer;
    };
  }
}

export {};
// src > samples > node-api.ts
// IDE recognizes args as type 'WindowEvent'
window.ipc?.on("window-event", (args) => {
  console.log(args);
  // => "focus" | "blur" | "close" ...etc
});

if this is the intended approach, can it be mentioned in the README?

cuppachino avatar Jul 26 '22 13:07 cuppachino

@caoxiemeihao I guess your help will be needed here. I have always been working with ipcRenderer by passing its functionality in contextBridge. You implemented the feature to use it directly in renderer so you know better 😅

As about contextBridge, @Cuppachino. It is actually considered a more secure way to write the application (if it interacts with third-party services/websites). If you're going that way, don't forget to remove the renderer option in vite.config.ts, and also you can turn off nodeIntegration as in your example:

// electron > main > index.ts
win = new BrowserWindow({
  title: "Main window",
  icon: join(ROOT_PATH.public, "favicon.svg"),
  webPreferences: {
    preload,
-   nodeIntegration: true,
+   nodeIntegration: false,
    contextIsolation: true,
  },
});

None of them is an "intended approach", people can use contextBridge and can not, it all depends on what their application does and on their considerations. I'm not sure why direct import of ipcRenderer causes errors, will take a look when I have time, but maybe @caoxiemeihao knows better

PAXANDDOS avatar Jul 26 '22 14:07 PAXANDDOS

English

I think you are a developer new to Electron

First of all you need to determine whether you need to use the Node.js/Electron API in the Renderer-process, if you want to use them in the Renderer-process, then you need to do the following two things

  1. Enable Renderer-process support in vite-plugin-electron
# vite.config.ts

electron({
+ renderer: {}
})
  1. Enable Node.js integration in the Main-process
webPreferences: {
+  nodeIntegration: true
}
// src/main.tsx
import { ipcRenderer } from 'electron'
// ✅ work normally

For security reasons you don't want to use the Node.js/Electron API in the Renderer-process, then you need to pay attention to the following

  1. Don't use ipcRenderer directly in the Renderer-process because it means you want to use the Node.js/Electron API in the renderer - this is important
// src/main.tsx
import { ipcRenderer } from 'electron'
// ❌ Please don't do this, it means you are using the Node.js API
  1. Remove Node.js integration in Main-process
webPreferences: {
-  nodeIntegration: true
}
  1. Remove the Renderer configuration in vite-plugin-electron
# vite.config.ts

electron({
- renderer: {}
})
  1. Serving ipcRenderer via Preload-script

see preload/index.ts

// electron/preload.ts
import { contextBridge, ipcRenderer } from 'electron'
contextBridge.exposeInMainWorld('ipcRenderer', ipcRenderer)

see preload-module.ts

// src/main.tsx
window.ipcRenderer.on('main-process-message', (event) => { console.log(event) })

see global.d.ts

// src/electron-env.d.ts(need create)
export { }
declare global {
  interface Window {
    // Expose some Api through preload script
    ipcRenderer: import('electron').IpcRenderer
  }
}

  1. require is not defined Because you are using the Node.js API in the Renderer-process but not enabling the Node.js integration option in the Main-process
  2. samples/node-api is the example code to enable Node.js API integration, if you don't understand it, then don't use it.
  3. If you not need use Node.js API in Electron-Renderer, place remove vite-plugin-electron-renderer in vite.config.ts


简体中文

我想你是个刚刚接触 Electron 没多长时间的开发者

首先你需要明确你是否需要在渲染进程中使用 Node.js/Electron API,如果你希望在渲染进程中使用它们,那么你要做到下面两点

  1. 在 vite-plugin-electron 中开启渲染进程支持
# vite.config.ts

electron({
+ renderer: {}
})
  1. 在主进程中开启 Node.js 集成
webPreferences: {
+  nodeIntegration: true
}
// src/main.tsx
import { ipcRenderer } from 'electron'
// ✅ work normally

相反的,出于安全的考虑你不希望在渲染进程中使用 Node.js/Electron API,那么你需要注意如下几点

  1. 不要直接在渲染进程中使用 ipcRenderer,因为它代表你想要在渲染进程使用 Node.js/Electron API - 这个很重要
// src/main.tsx
import { ipcRenderer } from 'electron'
// ❌ 请不要这样做,这代表你使用了 Node.js API
  1. 移除主进程中关于 Node.js 集成的配置
webPreferences: {
-  nodeIntegration: true
}
  1. 将 vite-plugin-electron 中的渲染进程配置移除
# vite.config.ts

electron({
- renderer: {}
})
  1. ipcRenderer 功能将通过 preload 脚本提供

see preload/index.ts

// electron/preload.ts
import { contextBridge, ipcRenderer } from 'electron'
contextBridge.exposeInMainWorld('ipcRenderer', ipcRenderer)

see preload-module.ts

// src/main.tsx
window.ipcRenderer.on('main-process-message', (event) => { console.log(event) })

see global.d.ts

// src/electron-env.d.ts(need create)
export { }
declare global {
  interface Window {
    // Expose some Api through preload script
    ipcRenderer: import('electron').IpcRenderer
  }
}

  1. require is not defined 因为你在渲染进程中使用了 Node.js API 但是没有在主进程中开启 Node.js 集成选项
  2. samples/node-api 是开启 Node.js API 集成的实例代码,如果你弄不明白它,那么不要使用它
  3. 如果你需要在渲染进程中使用 Node.js API, 那么你应该把 vite.config.ts 中的 vite-plugin-electron-renderer 删掉

caoxiemeihao avatar Jul 27 '22 01:07 caoxiemeihao

  1. create a fresh project with npm create electron-vite
  2. in main.tsx, uncomment import "./samples/node-api"

I managed to reproduce your steps (Sorry that took really long 😅) and... everything works fine?

Screenshot

I mean you must have forgotten to turn something on. Again my environment is Windows 11 Home of latest build and Node.js v18.6.0 and everything works fine

What @caoxiemeihao said is how to configure application for use in both ways, but I believe you already found your way. What I wanted to add to his words is that you should also turn on contextIsolation when using contextBridge and turn off when not and that's it.

@Cuppachino What's your current status with the problem?

PAXANDDOS avatar Jul 30 '22 10:07 PAXANDDOS

@caoxiemeihao I've only been working with electron for the past couple of months, so your guess isn't too far off! I think what you have written here will be very helpful to new devs.

@PAXANDDOS the issue is when using the first method, the default method, and expecting the app to load in browser during dev.

steps to reproduce:
1. create a fresh project with `npm create electron-vite`
2. in main.tsx, uncomment `import "./samples/node-api"`
3. open chrome or firefox
4. navigate to localhost:7777

I don't think we should say "use contextIsolation & contextBridge for security reasons..." if NOT using them means losing access to dev tool extensions unsupported by electron.

cuppachino avatar Jul 30 '22 12:07 cuppachino

I will plan to write a very detailed document, which will be time consuming work.

caoxiemeihao avatar Jul 31 '22 08:07 caoxiemeihao

同时开启nodeIntegrationcontextIsolation的问题,若contextIsolation: false则正常:

代码片段:

electron\main\index.ts

    webPreferences: {
      webSecurity: false, // 允许跨域请求
      preload,
      nodeIntegration: true,
      contextIsolation: true,
    },

electron\preload\index.ts

import { contextBridge } from 'electron'
import Store from 'electron-store'

const store = new Store()

contextBridge.exposeInMainWorld('store', {
  get: (key) => store.get(key),
  set: (key, value) => {
    store.set(key, value)
  }
})

vite.config.ts

  plugins: [
    react(),
    electron({
      main: {
        entry: 'electron/main/index.ts',
        vite: withDebug({
          build: {
            outDir: 'dist/electron/main',
          },
        }),
      },
      preload: {
        input: {
          // You can configure multiple preload scripts here
          index: join(__dirname, 'electron/preload/index.ts'),
        },
        vite: {
          build: {
            // For debug
            sourcemap: 'inline',
            outDir: 'dist/electron/preload',
          }
        },
      },
      // Enables use of Node.js API in the Electron-Renderer
      renderer: {}
    }),
    createStyleImportPlugin({
      resolves: [AntdResolve()], libs: [
        // If you don’t have the resolve you need, you can write it directly in the lib, or you can provide us with PR
        {
          libraryName: 'antd',
          esModule: true,
          resolveStyle: (name) => {
            return `antd/es/${name}/style/index`
          },
        },
      ],
    })
  ],

结果:Uncaught ReferenceError: require is not defined at promises:1:13 image image

ashen114 avatar Aug 01 '22 04:08 ashen114

@ashen114 I might not understand the Chinese text, but here's what I can say depending on your code:

  1. Since you are using contextBridge you should turn off nodeIntegration:
// electron\main\index.ts

webPreferences: {
  webSecurity: false, // 允许跨域请求
  preload,
- nodeIntegration: true,
+ nodeIntegration: false,
  contextIsolation: true,
}
  1. And remove renderer option as well:
// vite.config.ts

plugins: [
    react(),
    electron({
      ...
-     renderer: {}
    })
]
  1. And finally, storing logic in preload is a bad thing and your code must not work because of it. Create a listener for a store in main/index.ts and call it in preload instead.

Simple example:

// electron/main/index.ts

import { ipcMain } from 'electron'
import Store from 'electron-store'

const store = new Store()

ipcMain.handle('electron-store-get', async (_event, key: string) => {
   return store.get(key)
})
ipcMain.on('electron-store-set', async (_event, key: string, value: string) => {
    store.set(key, value)
})
// electron/preload/index.ts

import { ipcMain } from 'electron'
import Store from 'electron-store'

contextBridge.exposeInMainWorld('api', {
    send: ipcRenderer.send,
    invoke: ipcRenderer.invoke,
})
// src/*

const value = window.api.invoke('electron-store-get', 'foo') // returns store value with key 'foo'
window.api.send('electron-store-set', 'foo', 'bar') // sets new value to store

PAXANDDOS avatar Aug 01 '22 09:08 PAXANDDOS

Issue resolved? 👀 @Cuppachino

PAXANDDOS avatar Sep 04 '22 14:09 PAXANDDOS

Hi everyone, i have similar issue here, i trying to send message from main process to renderer process, so i defined this :

// defined win BrowserWindow
win = new BrowserWindow({
        title: 'Main window',
        icon: join(ROOT_PATH.public, 'favicon.svg'),
        show: false,
        frame: false,
        resizable: true,
        webPreferences: {
            preload,
            nodeIntegration: false,
            contextIsolation: true,
        },
    });
    .....
// Send message to preload
win.webContents.send('token', `{ token: ${json.token} }`);

So, in preload i define my context Bridge like always:

import { contextBridge, ipcRenderer } from 'electron';

const api = {
    token: (data) => ipcRenderer.on('token', data),
};

contextBridge.exposeInMainWorld('api', api);

I'm reading above on this problem and from what I understand I also need to declare this for typescript:

export {};

declare global {
    interface Window {
        api: any;  // I don't know if i defined correctly maybe my error is here
    }
}

So, my final file is renderer process:

export default function App() {
    console.log('token', window.api.token); //this don't return error simply return the image below

    return (
        <Box>
            <Router />
        </Box>
    );
}

finally my console return this, not exactly an error but it doesn't return my main message either error

EliasLeguizamon123 avatar Sep 19 '22 14:09 EliasLeguizamon123

contextBridge.exposeInMainWorld(window, { api });

caoxiemeihao avatar Sep 19 '22 14:09 caoxiemeihao

Hi @caoxiemeihao thanks for your reply, i trying to change this in preload but unfortunately don't work for me, i reading the electron docs and this method only accepts apiKey and API (apiKey only can be a string) and i see window is of type Window & typeof globalThis.

So, if i persist your change from above. i have the issue of a image (also, window.api changes to undefined) WhatsApp Image 2022-09-19 at 2 07 25 PM

later, i change this exposeInMainWorld(window, { api }); to exposeInMainWorld('api', { api }); and the console.log changes, not with an error but is not the expected request.

errorElectron2

I trying log the token key inside api.api object but its same case console.log(window.api.api.token);

EliasLeguizamon123 avatar Sep 19 '22 17:09 EliasLeguizamon123

@caoxiemeihao Your explanation here was really helpful! Please consider making a demo with this configuration or linking to the document from the README. It took me awhile to find your comment but once I did I was able to quickly get the context bridge working.

spmonahan avatar Sep 12 '23 19:09 spmonahan

@spmonahan okay! I plan to do better in subsequent vite-plugin-electron-renderer versions and prompt users directly in the console how to resolve it.

caoxiemeihao avatar Sep 13 '23 01:09 caoxiemeihao