๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
React

Electron + React ํ”„๋กœ์„ธ์Šค ๊ฐ„ IPC ํ†ต์‹ ๊ณผ preload ์‚ฌ์šฉ ๋ฐฉ๋ฒ•

by LasBe 2024. 5. 13.
๋ฐ˜์‘ํ˜•

๐Ÿ“’ Electron + React ํ”„๋กœ์„ธ์Šค ๊ฐ„ IPC ํ†ต์‹ ๊ณผ preload ์‚ฌ์šฉ ๋ฐฉ๋ฒ•


  • Electron ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ์ฃผ๋กœ ๋ฉ”์ธ ํ”„๋กœ์„ธ์Šค์™€ ๋ Œ๋”๋Ÿฌ ํ”„๋กœ์„ธ์Šค๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค.
  • Main Process๋Š” ๋‹จ ํ•˜๋‚˜๋งŒ ์‹คํ–‰๋˜๋ฉฐ ์•ฑ์˜ ๋ณธ์ฒด์ด๊ณ ,
    Renderer Process๋Š” ํ™”๋ฉด์— ๊ทธ๋ ค์ง€๋Š” n๊ฐœ์˜ ์›น๋ทฐ ํ”„๋กœ์„ธ์Šค์ž…๋‹ˆ๋‹ค.
  • ์ด ๋‘ ํ”„๋กœ์„ธ์Šค ๊ฐ„์—๋Š” Electron IPC (Inter-Process Communication)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ†ต์‹ ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿผ IPC๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋‘ ํ”„๋กœ์„ธ์Šค ๊ฐ„์— ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•˜๊ณ  ๋ช…๋ น์„ ๋ณด๋‚ด๋Š” ๋ฐฉ๋ฒ•์„ ์†Œ๊ฐœํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“Œ IPC๋กœ Main - Renderer ํ”„๋กœ์„ธ์Šค ๊ฐ„ ๋ฐ์ดํ„ฐ ์ฃผ๊ณ ๋ฐ›๊ธฐ

์‹ค์ œ๋กœ ๋‘ ํ”„๋กœ์„ธ์Šค ๊ฐ„ ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ ๋ฐ›๊ธฐ ์œ„ํ•ด์„œ๋Š” ์ •๋ง ๊ฐ„๋‹จํ•œ ์ ˆ์ฐจ๋งŒ ๊ฑฐ์น˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

  1. ํŠน์ • ์ฑ„๋„๋ช…์„ ์ •ํ•œ ๋’ค ipcMain or ipcRenderer์˜ on ํ•จ์ˆ˜๋กœ listen
  2. ipcMain or ipcRenderer ์˜ send ํ•จ์ˆ˜๋กœ ์ฑ„๋„๋ช…๊ณผ ๋ฐ์ดํ„ฐ๋ฅผ ๋‚ ๋ฆฌ๊ธฐ

๊ฐ„๋‹จํ•œ ์ฝ”๋“œ ํ™•์ธ ์ „ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด main process์˜ BrowserWindow์— ์˜ต์…˜์„ ๋„ฃ์–ด์ฃผ๊ฒ ์Šต๋‹ˆ๋‹ค.

๐Ÿ”Ž nodeIntegration, contextIsolation

ipc ๋ชจ๋“ˆ๋กœ ๋ฉ”์ธ - ๋ Œ๋”๋Ÿฌ ํ”„๋กœ์„ธ์Šค ๊ฐ„ ํ†ต์‹ ์„ ์œ„ํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด webPreferences์— ์˜ต์…˜์„ ์ถ”๊ฐ€ํ•ด ์ค๋‹ˆ๋‹ค.

function createWindow() {
  mainWindow = new BrowserWindow({
    width: 1200,
    height: 800,
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false,
    },
  });
  • nodeIntegration: true
    • ์ด ์˜ต์…˜์€ ๋ Œ๋”๋Ÿฌ ํ”„๋กœ์„ธ์Šค์—์„œ Node.js ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
    • ์ด๋ฅผ ํ†ตํ•ด Node.js์˜ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด์„œ ํŒŒ์ผ ์‹œ์Šคํ…œ ์•ก์„ธ์Šค๋‚˜ ๋„คํŠธ์›Œํฌ ์š”์ฒญ ๋“ฑ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • contextIsolation: false
    • ์ด ์˜ต์…˜์€ ๋ Œ๋”๋Ÿฌ ํ”„๋กœ์„ธ์Šค์˜ JavaScript ์ปจํ…์ŠคํŠธ๋ฅผ ๊ฒฉ๋ฆฌ์‹œํ‚ค๋Š” ๊ฒƒ์„ ํ•ด์ œํ•ฉ๋‹ˆ๋‹ค.
    • ๊ธฐ๋ณธ์ ์œผ๋กœ Electron์€ ๋ Œ๋”๋Ÿฌ ํ”„๋กœ์„ธ์Šค์˜ JavaScript ์ฝ”๋“œ๋ฅผ ๊ฒฉ๋ฆฌํ•˜์—ฌ ๋ณด์•ˆ์„ ๊ฐ•ํ™”ํ•ฉ๋‹ˆ๋‹ค.
    • ๊ทธ๋Ÿฌ๋‚˜ ์ด ์˜ต์…˜์„ ๋น„ํ™œ์„ฑํ™”ํ•˜๋ฉด ๋ Œ๋”๋Ÿฌ ํ”„๋กœ์„ธ์Šค์ธ ๋ฆฌ์•กํŠธ์—์„œ๋„ ๋ฉ”์ธ ํ”„๋กœ์„ธ์Šค์˜ require() ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
    • ์ด ์˜ต์…˜์„ ํ™œ์„ฑํ™”ํ•˜๊ณ  ๋ Œ๋”๋Ÿฌ ํ”„๋กœ์„ธ์Šค์—์„œ window.require๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด TypeError: window.require is not a function ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ”Ž Main Process, ipcMain

// electron.js or main.js
const { ipcMain } = await import('electron');

ipcMain.on("channel", (event, data) => {
  console.log(":: From Renderer Process ::", data);
  event.sender.send("channel", "From Main Process");
});

๋ฉ”์ธ ํ”„๋กœ์„ธ์Šค์—์„œ ipcMain ๋ชจ๋“ˆ์„ ์ด์šฉํ•ด “channel”์ด๋ž€ ์ฑ„๋„๋ช…์œผ๋กœ ํ†ต์‹ ํ•˜๋Š” ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.

๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ๊ทธ๋กœ ์ฐ์–ด์ค€ ๋’ค event ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ”Ž Renderer Process, ipcRenderer

import { useState, useEffect } from "react";

export const Ipc = () => {
  const [message, setMessage] = useState("");
  const { ipcRenderer } = window.require("electron");

  useEffect(() => {
    // IPC ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๋“ฑ๋ก
    ipcRenderer.on("channel", (event: any, data: string) => {
      setMessage(data); // ๋ฐ›์€ ๋ฉ”์‹œ์ง€๋ฅผ ์ƒํƒœ๋กœ ์„ค์ •
    });
  }, [ipcRenderer]);

  const handleClick = () => {
    ipcRenderer.send("channel", "HIHIHIHI");
  };

  return (
    <div className="App">
      <h1>Recive: {message}</h1>
      <button onClick={handleClick}>Click me</button>
    </div>
  );
};

๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ๋ฉ”์„ธ์ง€๋ฅผ ์ „์†กํ•˜๊ณ , ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋Š” useEffect๋ฅผ ํ†ตํ•ด ์ƒํƒœ์— ์ €์žฅํ•ด ์ค๋‹ˆ๋‹ค.

์œ ์˜ํ•ด์„œ ๋ด์•ผ ํ•  ์ ์€ ipcRenderer ๋ชจ๋“ˆ์„ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

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

์ด๋Š” ์ผ๋ ‰ํŠธ๋ก ์—์„œ ์ฃผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ๋ Œ๋”๋Ÿฌ ํ”„๋กœ์„ธ์Šค์—์„œ Node.js ๋ชจ๋“ˆ ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๋ฉ”์ปค๋‹ˆ์ฆ˜์ž…๋‹ˆ๋‹ค.

์ผ๋ฐ˜์ ์ธ ๋ธŒ๋ผ์šฐ์ €์—์„œ๋Š” require() ํ•จ์ˆ˜๊ฐ€ ์ •์˜๋˜์–ด ์žˆ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์—,
Electron์—์„œ๋Š” ์ด๋ฅผ window ๊ฐ์ฒด์˜ ์†์„ฑ์œผ๋กœ ์ œ๊ณตํ•˜์—ฌ ๋ Œ๋”๋Ÿฌ ํ”„๋กœ์„ธ์Šค์—์„œ๋„
Node.js ์Šคํƒ€์ผ์˜ ๋ชจ๋“ˆ ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

์ด๋ฅผ ์œ„ํ•ด์„  ์œ„์—์„œ ๋จผ์ € ์‚ดํŽด๋ดค๋˜ nodeIntegration, contextIsolation์— ๋Œ€ํ•œ ์„ธํŒ…์ด ์„ ํ–‰์ ์œผ๋กœ ์ด๋ค„์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ”Ž ๊ฒฐ๊ณผ

Click me ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๊ฒŒ ๋˜๋ฉด

์ •์ƒ์ ์œผ๋กœ ํ”„๋กœ์„ธ์Šค ๊ฐ„ ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ ๋ฐ›๋Š” ๋ชจ์Šต์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ“Œ preload๋กœ ์•ˆ์ „ํ•˜๊ฒŒ ํ†ต์‹ ํ•˜๊ธฐ

์œ„์—์„œ ์†Œ๊ฐœ๋“œ๋ฆฐ ๋ฐฉ๋ฒ•์€ contextIsolation: false๋ฅผ ํ†ตํ•ด

์›น์—์„œ ๋ฉ”์ธ ํ”„๋กœ์„ธ์Šค์— ์ง์ ‘ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ€๋Šฅ์„ฑ์„ ์—ด์–ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ๊ถŒ์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

 

๊ทธ๋ ‡๊ธฐ์— preload ํŒŒ์ผ๋กœ ํ”„๋กœ์„ธ์Šค ๊ฐ„ ์•ˆ์ „ํ•˜๊ฒŒ ํ†ต์‹ ํ•  ์ˆ˜ ์žˆ๋Š” ์ค‘๊ฐ„ ๋งค๊ฐœ์ฒด๋ฅผ ๋งŒ๋“ค์–ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๐Ÿ”Ž preload.js

import { contextBridge, ipcRenderer } from "electron";

const preloadInterface = "myPreload";

contextBridge.exposeInMainWorld(preloadInterface, {
  listenChannelMessage: (callback) =>
    ipcRenderer.on("channel", (_, data) => callback(data)),
  sendMessage: (data) => ipcRenderer.send("channel", data),
});
  • contextBridge.exposeInMainWorld ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ Œ๋”๋Ÿฌ ํ”„๋กœ์„ธ์Šค์˜ ์ „์—ญ ์ปจํ…์ŠคํŠธ์—์„œ๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ API๋ฅผ ๋…ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
  • preloadInterface ๋ณ€์ˆ˜์— ๋‹ด๊ธด ์ด๋ฆ„์€ ์›น๋‹จ์—์„œ window.myPreload์™€ ๊ฐ™์ด ์ „์—ญ์œผ๋กœ ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
  • ๊ณต์‹๋ฌธ์„œ์—๋„ ์ ํ˜€์žˆ๋“ฏ ๊ฐ•๋ ฅํ•œ API(ipcRenderer)๋ฅผ ์ง์ ‘ ๋…ธ์ถœํ•˜์ง€ ๋ง๊ณ  ํ•˜๋‚˜์˜ ๊ธฐ๋Šฅ์— ๋Œ€ํ•ด wrapping ํ•ด์„œ ์‚ฌ์šฉํ•˜๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ”Ž preload ํƒ€์ž… ์„ ์–ธ

declare global {
  interface Window {
    myPreload: {
      listenChannelMessage: (callback: (...arg: any[]) => void) => void;
      sendMessage: (message: string) => void;
    };
  }
}

preload.d.ts ํŒŒ์ผ์— ์ž‘์„ฑํ•œ ํ•จ์ˆ˜๋“ค์„ global window property ํƒ€์ž…์— ์ถ”๊ฐ€ํ•ด ์ค๋‹ˆ๋‹ค.

 

๊ทธ๋Ÿฐ๋ฐ CRA๋กœ ์ƒ์„ฑํ•œ react ํ”„๋กœ์ ํŠธ์˜ ๊ฒฝ์šฐ ์ด์™€ ๊ฐ™์€ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ

ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์ •์‹ ์„ ๋ชป ์ฐจ๋ ค์„œ ํƒ€์ž… ์ธ์‹์ด ์˜ค๋ฝ๊ฐ€๋ฝํ•ฉ๋‹ˆ๋‹ค.

 

๋งŒ์•ฝ ์ธ์‹์ด ์•ˆ ๋  ๊ฒฝ์šฐ, ๊ธฐ๋ณธ์ ์œผ๋กœ ์ƒ์„ฑ๋œ react-app-env.d.ts์— ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํƒ€์ž…์„ ์ถ”๊ฐ€ํ•ด ์ค๋‹ˆ๋‹ค.

/// <reference types="react-scripts" />
interface Window {
  myPreload: {
    listenChannelMessage: (callback: (...arg: any[]) => void) => void;
    sendMessage: (message: string) => void;
  };
}

๐Ÿ”Ž ๋ฉ”์ธ ํ”„๋กœ์„ธ์Šค ํŒŒ์ผ์— preload ์ถ”๊ฐ€

// ES Module์ผ ๊ฒฝ์šฐ์—๋งŒ ์„ ์–ธ
const __dirname = path.resolve();

mainWindow = new BrowserWindow({
  width: 1200,
  height: 800,
  webPreferences: {
    nodeIntegration: true,
    preload: path.join(__dirname, "public/preload.mjs"),
  },
});

์ƒ์„ฑํ•ด ๋‘” preload ํŒŒ์ผ์„ webPreferences์— ์ถ”๊ฐ€ํ•ด ์ค๋‹ˆ๋‹ค.

 

ํ”„๋กœ์ ํŠธ๋ฅผ ES Module ๋ฐฉ์‹์œผ๋กœ ์„ค์ •ํ•ด ๋‘์–ด์„œ __dirname ์ด์Šˆ ํ•ด๊ฒฐ๊ณผ preload.js -> preload.mjs ๋ฐฉ์‹์— ์ฐจ์ด๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  ๊ฐœ๋ฐœํ™˜๊ฒฝ์—๋งŒ ๋งž์ถฐ์„œ path๋ฅผ ์„ค์ •ํ•ด ๋‘์—ˆ๋Š”๋ฐ ์‹ค์ œ ํŒจํ‚ค์ง•๊นŒ์ง€ ๊ณ ๋ คํ•˜๋ฉด ๊ฒฝ๋กœ ๋ถ„๊ธฐ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ”Ž React์—์„œ preload ์‚ฌ์šฉ

import { useEffect, useState } from "react";

export const Preload = () => {
  const [message, setMessage] = useState("");
  useEffect(() => {
    window.myPreload.listenChannelMessage(setMessage);
  }, []);
  const handleClick = () => {
    window.myPreload.sendMessage("send message by preload");
  };
  return (
    <div className="App">
      <h1>Recive: {message}</h1>
      <button onClick={handleClick}>Click me</button>
    </div>
  );
};

๋ฏธ๋ฆฌ ๋“ฑ๋กํ•œ preload๋Š” window ์ „์—ญ ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

๋“ฑ๋กํ•œ ํƒ€์ž…์— ๋งž์ถฐ ์ž๋™์™„์„ฑ๋„ ๊น”๋”ํ•˜๊ฒŒ ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ”Ž ๊ฒฐ๊ณผ

์ž˜ ์ž‘๋™ํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


IPC ํ†ต์‹ ์„ ํ†ตํ•ด ์•ฑ๊ณผ ์›น์˜ ๊ธฐ๋Šฅ์„ ์ž˜ ๋ฒ„๋ฌด๋ ค์„œ ์‚ฌ์šฉํ•˜๋ฉด ์ข‹๊ฒ ์Šต๋‹ˆ๋‹ค.

 

์ „์ฒด ์ฝ”๋“œ๋Š” ์˜ˆ์ œ๋Š” ์ œ ๊นƒํ—ˆ๋ธŒ์— ์˜ฌ๋ ค๋‘์—ˆ์œผ๋‹ˆ ์ฐธ๊ณ ํ•ด ์ฃผ์„ธ์š”!

https://github.com/LasBe-code/electron-react-ex

 

GitHub - LasBe-code/electron-react-ex: Electron-React๋กœ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•œ ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค.

Electron-React๋กœ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•œ ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค. Contribute to LasBe-code/electron-react-ex development by creating an account on GitHub.

github.com

 

๋ฐ˜์‘ํ˜•

๋Œ“๊ธ€


์˜คํ”ˆ ์ฑ„ํŒ