create-react-app์ผ๋ก ๊ฐ๋จํ ๋ฐ์ ์ฑ์ ๋ง๋ค๊ณ ์ ์์ ์ฑ๊ณต์ ์ผ๋ก ํตํฉํ์ต๋๋ค. ์ก์ ์์ฑ์ ํ์ผ์ ์ ์๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์ ๊น์ง ๋ชจ๋ ๊ฒ์ด ์ ์๋ํ์ต๋๋ค. ์๋ ์ค์ ์ ๊ฑฐํ๋ฉด ์ฑ์ด ์ ๋๋ก ์๋ํฉ๋๋ค. ๋ฌธ์ ๋ ipcRenderer๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ ์ธก์์ ์ ์ ๋ฉ์ธ ํ๋ก์ธ์ค๋ก ํต์ ํ ์ ์๋ค๋ ๊ฒ์ ๋๋ค.
์ด ์ค๋ก ์ธํด ์ฑ์ด ์ถฉ๋ํฉ๋๋ค.
import { ipcRenderer } from 'electron';
๋ค์ ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค.
TypeError: 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')
์ ์๋ฅผ ๊ฐ์ ธ์ค๋ ค๊ณ ํ ๋ ์ด๊ฒ์ด ์ผ๋ฐ์ ์ธ ๋ฌธ์ ๋ผ๋ ๊ฒ์ Google์์ ๋ฐ๊ฒฌํ์ต๋๋ค.
๋์ ์ฃผ์ ์ ๊ฐ์ฌํฉ๋๋ค
CRA๋ ํ์ค ๋ชจ๋ ๋ก๋ฉ(fs ํฌํจ)์ ์๋ง์ผ๋ก ๋ง๋๋ ์นํฉ์ ์ฌ์ฉํฉ๋๋ค.
webpack์ Electron ๋ชจ๋๋ฅผ ์ดํด๋ณด๊ณ CRA์์ ๊บผ๋ด๋ ๊ฒ์ด ์ข์ต๋๋ค.
GitHub ๋ฌธ์ ๋ ๊ธฐ๋ฅ ์์ฒญ ๋ฐ ๋ฒ๊ทธ ๋ณด๊ณ ์ฉ์ด๋ฉฐ Electron ์ฌ์ฉ์ ๋ํ ์ง๋ฌธ์ ์ปค๋ฎค๋ํฐ ๋๋ Slack ์ฑ๋๋ก ๋ณด๋ด์ผ ํฉ๋๋ค.
@MarshallOfSound ๋ด ์ค์.
๋๊ตฐ๊ฐ๋ฅผ ๋์ธ ์ ์๋ค๋ฉด ๋ฌธ์ #7300์์ ํด๊ฒฐ์ฑ ์ ์ฐพ์์ต๋๋ค.
const { ipcRenderer } = window.require('electron');
์ด๊ฒ์ Electron ์ฑ์ ์คํํ ๋ ์๋ํ์ง๋ง ๋ธ๋ผ์ฐ์ ๋ด์์ React ์ฝ๋๋ฅผ ํ ์คํธํ๋ ค๋ ๊ฒฝ์ฐ ์ฌ์ ํ ์ถฉ๋ํฉ๋๋ค(window.require๋ Electron์์์ ๊ฐ์ด ๋ธ๋ผ์ฐ์ ์์ ์ ์๋์ง ์์).
app.quit()์ ์ก์ธ์คํ๋ ค๋ฉด ๋ค์์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
const { ์ฑ } = window.require('์ ์').remote;
๋๊ตฐ๊ฐ์๊ฒ ๋์์ด ๋ ์ง๋...
@CiriousJoker ์ด๊ฒ์ ์๋ฃจ์ ์ ๋๋ค, ๊ฐ์ฌํฉ๋๋ค!
๋๋ ์ฌ์ ํ window.require is not a function
๋ฐ๊ณ ์์ต๋๋ค. ์ ๋ React Starter Kit(https://github.com/kriasoft/react-starter-kit)์ ํจ๊ป Electron์ ์ฌ์ฉํ๊ณ ์์ต๋๋ค. ์ด๊ฒ์ ์ ์ธํ๊ณ ๋ ๋ชจ๋ ๊ฒ์ด ์ ์๋ํฉ๋๋ค.
์น์์ ์ฑ์ ๋ก๋ํ๋๋ก Electron ์ฑ์ ์ค์ ํ์ผ๋ฏ๋ก ์ฑ์ด ๋ก์ปฌ์์ ์คํ๋์ง ์์ต๋๋ค.
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) ์ด์ ๋ฐ์ ์ฑ์์ ์ก์ธ์คํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด ๋ค์๊ณผ ๊ฐ์ด ์๋ํฉ๋๋ค.
componentDidMount() {
if (isElectron()) {
console.log(window.ipcRenderer);
window.ipcRenderer.on('pong', (event, arg) => {
this.setState({ipc: true})
})
window.ipcRenderer.send('ping')
}
}
์ฐธ๊ณ - isElectron()
ํจ์์ ๋ํด https://github.com/cheton/is-electron ์ฌ์ฉ
@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://github.com/cheton/is-electron ๋งํฌ๋ฅผ ์ฌ์ฉํ์ฌ ์ฝ๋ ์์ ๋ฅผ ํธ์งํ์ต๋๋ค.
@holgersindbaek
์ง๊ธ ํด๊ฒฐ์ฑ
์ด ์์ต๋๊น
์ ๋ฅผ ์ํด nodeIntegration
์ด true
๊ฒฝ์ฐ์๋ง ์๋ํฉ๋๋ค.
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();
์์ฐ, ๋ด React ๊ตฌ์ฑ ์์์์ ์๋ํ๋ IPCRenderer๋ฅผ ์ป์ ์ ์์ต๋๋ค. ์์ ๋ชจ๋ ๋ฐฉ๋ฒ์ ์๋ํ์ต๋๋ค. ํน์ ๋ด๊ฐ ์๋ํ๋ ๋ฐ ์ฌ์ฉํ ์ ์๋ ํํธ๊ฐ ์์ผ์ ๊ฐ์? ๊ฐ์ฌ
ํ ... ๋ด ์ ์ ์ฑ์ ์์ ์๋ฃจ์ ์ ์ฌ์ฉํ์ฌ ์ฌ์ ํ ์ ์๋ํ์ง๋ง ๋ช ๋ฌ ๋์ ์ ๋ฐ์ดํธํ์ง ์์์ต๋๋ค(ํ์ํ์ง ์์).
์ด๊ฒ์ด ์๋ํ์ง ์๋๋ก ํ๋ ์ฃผ์ ๋ณ๊ฒฝ ์ฌํญ์ด ์๋์ง ๊ถ๊ธํฉ๋๋ค. ์ ์ ๋ฒ์ ์ ๊ฒ์ํ ์ ์์ต๋๊น?
@cyclonstep ๋ฐ์ํ ํน์ ์ค๋ฅ๊ฐ ์์ต๋๊น? ์ฝ๋ ์กฐ๊ฐ์ด๋ ์ผ๋ถ ๋ก๊ทธ ์์ด๋ ๋๊ธฐ ์ด๋ ต์ต๋๋ค...
๋ด๊ฐ ์ฌ์ฉํ๊ณ ์ํฌ๋ฅผ ๋ฌถ์ด.
Window.require๋ ๋ํ ๋๋ฅผ ์ํด ๊ทธ๊ฒ์ ํด๊ฒฐํ์ต๋๋ค (๋ํ ๋ณด์ฌ์ฃผ์ง ์์ ๊ฒ์ ๋ณด์ฌ์ค).
'vue/dist/vue.min'์์ Vue ๊ฐ์ ธ์ค๊ธฐ
'./App'์์ ์ฑ ๊ฐ์ ธ์ค๊ธฐ// ๋์? '์ ์'์์ { ipcRenderer } ๊ฐ์ ธ์ค๊ธฐ
// ๋์? const { ipcRenderer } = ์๊ตฌ('์ ์')
// ์ข์:
const { ipcRenderer } = window.require('์ ์')
(๋์ผํ ํ์ผ์ ๋ ์๋์๋ ์ ์ "pong-Demo"๊ฐ ์์ต๋๋ค . ์ด๋ ์ผ์ข ์ ์ฆ๋ช , ์๋ํฉ๋๋ค)
์๋ง๋ ์ฃผ๋ชฉํ ๋งํฉ๋๋ค. ์๋ชปํ์ ๋์๋ ๋ฒ๋ค ํฌ๊ธฐ๋ ์ ์ฒด Electron ํฌ๊ธฐ๋งํผ ์ฆ๊ฐํ์ง ์์ง๋ง(electron-require๊ฐ ์๋ ๊ฒ๊ณผ ๋น๊ตํฉ๋๋ค. ์ด๊ฒ์ ์ง๊ธ๊น์ง ์ ์ ์ฒซ ๋ฒ์งธ์ด์ ์ ์ผํ ๋ ๋ ์ธก ์ ์ ๊ฐ์ ธ์ค๊ธฐ์
๋๋ค) ์ฝ 20kb ์ ๋๋ง ์ฆ๊ฐํฉ๋๋ค. , node_modules/electron-download/node_modules/debug/dist/debug.js
:242:ff์์ ์ค๋ ์์ฒด์ ์ผ๋ก ์ผ๋ถ shim/wrapper ์ฝ๋์ธ ๊ฒ ๊ฐ์ต๋๋ค.
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
ํฌ๋กฌ ๋ฒ์ 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 ๊ทํ์ ์๋ฃจ์ ์ ํ๋ฅญํ๊ฒ ์๋ํฉ๋๋ค. ๋ด React ํ๋ก์ ํธ์์ IpcRenderer๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด Webpack์ด๋ Browserfy๊ฐ ํ์ํ์ง ์์ต๋๋ค. ๋ค์ํ๋ฒ ๊ฐ์ฌ๋๋ฆฝ๋๋ค :D
typescript์ ๊ฒฝ์ฐ ์์ @HemalR ์์ ๋ฅผ ์ฌ์ฉํ์ง๋ง nodeIntegration: true
์์ด : https://github.com/electron/electron/issues/9920#issuecomment -336757899:
๋ค, ํด๊ฒฐ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค.
- ๋ค์ ์ฝ๋๋ฅผ ์ฌ์ฉํ์ฌ
preload.js file
์์ฑ:window.ipcRenderer = require('electron').ipcRenderer;
webPreferences
๋ฅผ ํตํด ์ด ํ์ผ์ main.js์ ๋ฏธ๋ฆฌ ๋ก๋ํ์ธ์.mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { nodeIntegration: false, preload: __dirname + '/preload.js' } });
- ์ด์ ๋ฐ์ ์ฑ์์ ์ก์ธ์คํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด ๋ค์๊ณผ ๊ฐ์ด ์๋ํฉ๋๋ค.
componentDidMount() { if (isElectron()) { console.log(window.ipcRenderer); window.ipcRenderer.on('pong', (event, arg) => { this.setState({ipc: true}) }) window.ipcRenderer.send('ping') } }
์ฐธ๊ณ -
isElectron()
ํจ์์ ๋ํด https://github.com/cheton/is-electron ์ฌ์ฉ
์ ๊ฒฐํฉ
https://github.com/electron/electron/issues/9920#issuecomment -447157348
๋๋ ์ด๊ฒ์ ์ฌ์ฉํ๋ค:
import { IpcRenderer } from 'electron';
declare global {
interface Window {
ipcRenderer: IpcRenderer
}
}
export const { ipcRenderer } = window;
๊ทธ๊ฒ์ด ๋๊ตฐ๊ฐ๋ฅผ ๋๊ธฐ๋ฅผ ๋ฐ๋๋๋ค! stenciljs์ ํจ๊ป ์๋ํ๋ฉฐ ๋ฐ์ ๋ฐ ๊ฐ๋๋ฅผ ์์ํฉ๋๋ค.
webpack ์ค์ ์์ target: "electron-renderer"๋ฅผ ์ถ๊ฐํ๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค.
๊ธฐ๋ณธ ๋ด๋ณด๋ด๊ธฐ {
...
๋์: "์ ์ ๋ ๋๋ฌ"
...
}
์๋
ํ์ธ์, ์ ๋ CRA + Electron์ ์ฌ์ฉํ๊ณ ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ '์ ์ ๋ ๋๋ฌ'๋ฅผ ๋์์ผ๋ก ํ๋ก์ ํธ๋ฅผ ๋น๋ํฉ๋๋ค. ๋น๋ ํด๋์์ ํ์ผ์ ๋ก๋ํ๋ ๋์ ์ ์๋ํ์ง๋ง url localhost:3000
๊ฐ๋ฐํ๊ณ ๋ก๋ํ ๋ ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค. ๊ทธ ์ด์ ๋ ๋ฐ์ ๊ตฌ์ฑ ์์์์ node api์ electron api๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ธ ๊ฒ ๊ฐ์ต๋๋ค. ๋๊ตฌ์๊ฒ๋ ํด๊ฒฐ์ฑ
์ด ์์ต๋๊น? ๊ฐ์ฌ ํด์.
TypeError: fs.existsSync is not a function
์, fs
API๋ ๋ธ๋ผ์ฐ์ ์์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ํฉ์ ์ฌ๋ฐ๋ฅด๊ฒ ์ดํดํ๋ค๋ฉด react-scripts start
(CRA์์ npm start
์ ๊ธฐ๋ณธ ์คํฌ๋ฆฝํธ)๋ฅผ ์ฌ์ฉํ์ฌ ์ฑ์ ์คํํฉ๋๋ค.
์ด๋ฅผ ์ํํ๋ ์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ์ electron .
๋ฅผ ์คํํ๋ ๊ฒ์
๋๋ค. ๋ฌธ์์์ ๋ณผ ์ ์์ต๋๋ค: https://electronjs.org/docs/tutorial/first-app.
๊ทธ๊ฒ์ด ๋น์ ์ ์ํด ์๋ํ๋ค๋ฉด LMK. ๋ด ์ฑ์์ ์ด๋ป๊ฒ ์๋ํ๋์ง ํ์ธํ ์๋ ์์ต๋๋ค. - https://github.com/moshfeu/y2mp3 (CRA๋ก ์์ฑ๋์ง ์์์์ ์ฐธ๊ณ
์๋ ํ์ธ์, ์ ๋ CRA + Electron์ ์ฌ์ฉํ๊ณ ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ '์ ์ ๋ ๋๋ฌ'๋ฅผ ๋์์ผ๋ก ํ๋ก์ ํธ๋ฅผ ๋น๋ํฉ๋๋ค. ๋น๋ ํด๋์ ํ์ผ์ ๋ก๋ํ๋ ๋์ ์ ์๋ํ์ง๋ง
localhost:3000
url์ ๊ฐ๋ฐํ๊ณ ๋ก๋ํ ๋ ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค. ๊ทธ ์ด์ ๋ ๋ฐ์ ๊ตฌ์ฑ ์์์์ node api์ electron api๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ธ ๊ฒ ๊ฐ์ต๋๋ค. ๋๊ตฌ์๊ฒ๋ ํด๊ฒฐ์ฑ ์ด ์์ต๋๊น? ๊ฐ์ฌ ํด์.
TypeError: fs.existsSync is not a function
webpack ์ค์ ์์ target: "electron-renderer"๋ฅผ ์ถ๊ฐํ๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค.
๊ธฐ๋ณธ ๋ด๋ณด๋ด๊ธฐ {
...
๋์: "์ ์ ๋ ๋๋ฌ"
...
}
์ ์๊ฒ๋ ํจ๊ณผ๊ฐ ์์ต๋๋ค.
๋ด ๋๊ตฌ ๋ฒ์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
๋ด ์๊ฐ electron-renderer
์ด ๋ฌธ์ ์ผ ๊ฐ์ด ์ ์ ๊ตฌ๋ฌธ์ ์ฌ์ฉํ ํ์๊ฐ ์์ต๋๋ค ์ ์ ํด๊ฒฐ ๋จ์ง์ ๋ํด window.required
, ๊ทธ๋ฆฌ๊ณ ์ฌ์ง์ด ์์ค ์
๋ ฅ์!
๋๋ฅผ ์ํด ์ค์ ๋์: webpack ๋ฐ nodeIntegration์ ๋ํ "์ ์ ๋ ๋๋ง": BrowserWindow ์ต์
์์ true
์ค๋ ์ต์ ๋ฒ์ ์ webpack+electron+react๋ฅผ ์ฌ์ฉํ๊ณ ์์ต๋๋ค.
TypeScript์ Electron์ ์ฌ์ฉํ์ฌ create-react-app์ ์คํํ๊ณ ์์ต๋๋ค. ๋๋ ์ค์ ์ ์ํด ์ด ์์ฃผ ์ข์ ์ง์นจ ์ ๋ฐ๋์ต๋๋ค. ๊ทธ๋ฌ๋ ๋๋ ๋ํ์ด ์ค๋ฅ์ ๋ถ๋ช์ณค๋ค. ๋๋ฅผ ์ํด ์๋ํ๋ ์๋ฃจ์ ์ ์ด ์ค๋ ๋์์ ๋งํ ๋ด์ฉ์ ํฉ๊ณ์ ๋๋ค.
"electron-renderer"
๋ฅผ target
ํฉ๋๋ค. ์ด๋ฅผ ์ํด rescripts
์ ํจ๊ป rescript-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;
};
new BrowserWindow({
webPreferences: {
nodeIntegration: true
}
});
src/typings.d.ts
:declare var window: Window;
interface Window {
require: any;
}
๊ทธ๋ฆฌ๊ณ ๋ง์นจ๋ด ๋น์ ์ ์ฑ์์
App.tsx
const { remote } = window.require("electron");
console.log(remote.getCurrentWindow());
@LucasBombach
์ด๊ฒ๋ ์๋ํด์ผ ํฉ๋๋ค.
declare var window: Window;
interface Window {
require: NodeRequire
}
๊ทธ๋ฐ ๋ค์ ํ์ํ consts๋ฅผ ์ ๋ ฅํด์ผ ํฉ๋๋ค.
๊ฐ์ฌ ํด์!
Vue๊ฐ ๋ค๋ฅธ ์ฌ๋์๊ฒ ๋์์ด ๋๋ ๊ฒฝ์ฐ๋ฅผ ๋๋นํ์ฌ Vue์ ์ฌ์ฉํ ๋จ๊ณ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
๋ธ๋ผ์ฐ์ ์ฐฝ์ ์์ฑํ ๋ ์น ํ๊ฒฝ ์ค์ ์ ์ถ๊ฐํ์ฌ ๋ ๋๋ฌ ํ๋ก์ธ์ค์ ๋ํด ๋ ธ๋ ํตํฉ์ด ํ์ฑํ๋์๋์ง ํ์ธํฉ๋๋ค.
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',
},
}
์ ํ๋ฆฌ์ผ์ด์ ๋ด์์ ํ์ํ ๊ฒ์ ๊ฐ์ ธ์ต๋๋ค. ๋ค์๊ณผ ๊ฐ์ด ์ ธ ๋ชจ๋์ ๊ตฌ์ฑ ์์๋ก ๊ฐ์ ธ์ต๋๋ค.
import { shell } from 'electron'
์ฌ์ ํ ๊ฐ์ ๋ฌธ์ ๊ฐ ์๋ ์ฌ๋์๊ฒ. ์ด๊ฒ์ ๋ด๊ฐ ์ง๊ธ๊น์ง ์ฐพ์ ์ต๊ณ ์ ์๋ฃจ์ ์ ๋๋ค
```js
์ ๋ธ๋ผ์ฐ์ ์ฐฝ({
์น ๊ธฐ๋ณธ ์ค์ : {
nodeIntegration: ์ฐธ
}
});
๋ง์ ์ฌ๋๋ค์ด ๊ทํ์ ์ฑ์์ fs
๋๋ ipcRenderer
๊ฐ์ ธ์ค๊ธฐ์ ๋ํด ๋ฌป๊ณ ์๊ธฐ ๋๋ฌธ์ ์ด ๋๊ธ์ด ์ฃผ๋ชฉ๋ฐ๊ธฐ๋ฅผ ๋ฐ๋๋๋ค. ์ ์ ์ฑ์ ๋ํ ์ผ๋ฐ์ ์ธ ์๊ตฌ ์ฌํญ์ด์ง๋ง ๋ง์ ์ฌ๋๋ค์ด ์ฌ๋ฐ๋ฅด๊ฒ ์ดํดํ๊ณ ๊ตฌ์ ํจํด์ ์ฌ์ฉํ๊ณ ์์ง ์๋ค๋ ๊ฒ์ ์์์ต๋๋ค. tl;dr - ๋
ธ๋ ๋ชจ๋(์: fs
) ๋๋ ์ ์ ๋ชจ๋(์: ipcRenderer
)์ ์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ์ผ๋ก ๊ฐ์ ธ์ค์ง ์์ผ๋ฉด ๋ณด์ ์ทจ์ฝ์ ์ด ์์ต๋๋ค. ์ฑ์ ์์ ๋ง์ ์ํด ์ฌ์ฉํ๋ ๊ฒฝ์ฐ _์๋ง๋_ ์์ ํ์ง๋ง ์ฑ์ ๊ณต์ ํ๊ฑฐ๋ ํ๋งคํ๋ ค๋ ๊ฒฝ์ฐ ๋ฏธ๋ฆฌ ์ฝ์ด์ผ ํฉ๋๋ค.
์๋ฃจ์ ์ ๋ค์ด๊ฐ๊ธฐ ์ ์ ๋จผ์ ์ฐ๋ฆฌ๊ฐ ์ด ์์ ์ ์ํํ๋ _์ด์ _๋ฅผ ์ดํดํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค. Electron ์ฑ์ ์ฌ์ฉํ๋ฉด ์ฑ์ ๋ ธ๋ ๋ชจ๋์ ํฌํจํ ์ ์์ผ๋ฏ๋ก ๋๋ผ์ด ์ฑ๋ฅ์ ์ ๊ณตํ์ง๋ง ๋ณด์ ๋ฌธ์ ๊ฐ ๋ฐ์ํฉ๋๋ค. ์ฐ๋ฆฌ๋ ์ฑ์ด ๊ธฐ๋ณธ OS(์ฆ, ๋ ธ๋) ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋๋ก ํ์ฉํ๊ณ ์ถ์ง๋ง ๋จ์ฉ ๋๋
@raddevus ๊ฐ ์ฃผ์์์ ์ธ๊ธ
nodeIntegration:true
์ผ๋ ๊ด์ฐฎ์ต๋๋ค. ๊ทธ๋ฌ๋ ๋๋ ์ฌ์ ํnodeIntegration:false
๋ฅผ ์ ์งํ์ฌ ์ฑ์ ์ฌ์ฉํ๋ ์ฐ๋ฐ์ /์ ์์ ์ธ ์ฌ์ฉ์๋ฅผ ๋ณดํธํ๊ณ ์ปดํจํฐ์ ์ค์น๋ ์ ์๋ ๊ฐ๋ฅํ ๋งฌ์จ์ด๊ฐ ์ ์ ์ฑ๊ณผ ์ํธ ์์ฉํ๊ณnodeIntegration:true
๊ณต๊ฒฉ ๋ฒกํฐ(๋งค์ฐ ๋๋ฌผ์ง๋ง ๋ฐ์ํ ์ ์์)!
BrowserWindow์์ nodeIntegration: true
๋ฅผ ์ค์ ํ๋ฉด ๋ ๋๋ฌ ํ๋ก์ธ์ค๊ฐ ๋
ธ๋ ๋ชจ๋์ ์ก์ธ์คํ ์ ์์ต๋๋ค. _์ด๊ฒ_์ ํ๋ ๊ฒ์ ์ทจ์ฝํฉ๋๋ค. require("fs")
๋ฐ require("electron")
์ก์ธ์คํ ์ ์์ง๋ง ์ด๋ ๋๊ตฐ๊ฐ XSS ์ทจ์ฝ์ ์ ๋ฐ๊ฒฌํ๋ฉด ๋ ๋๋ฌ ํ๋ก์ธ์ค์์ ๋
ธ์ถ๋ ๋ชจ๋ ๋ช
๋ น ์
์ปดํจํฐ์ ์๋ ๋ชจ๋ ํ์ผ์ ์ญ์ ํ๊ฑฐ๋ ์ ๋ง ๋์ ๊ฒ์ ์ญ์ ํ๋ ๊ฒ์ ์๊ฐํด ๋ณด์ญ์์ค.
nodeIntegration์ true๋ก ์ค์ ํ๋ ๊ฒ๊ณผ ํจ๊ป ์ฑ์ด ์นํฉ์ ์ฌ์ฉํ์ฌ ์ ํ๋ฆฌ์ผ์ด์
ํ์ผ์ ๋ฌถ์ ๊ฐ๋ฅ์ฑ์ด ์์ต๋๋ค. Webpack์ ํน์ ๊ธฐํธ๋ฅผ ์๋ง์ผ๋ก ๋ง๋ค๊ธฐ ๋๋ฌธ์ target: 'electron-renderer'
๋๋ webpack ์ธ๋ถ์ ๊ฐ์ ์ค์ ์ ์ฌ์ฉํ๋ฉด ์ด๋ฌํ ๋ณ์( ipcRenderer
)๋ฅผ ๋์ ์ฑ์ ์ ๋ฌํ ์ ์์ต๋๋ค.
๊ทธ๋๋ ์ฑ์ ์ค์ ํ๋ ๋ฐฉ๋ฒ ์ธ์๋ ์๋ฌด ๊ฒ๋ ๋ณ๊ฒฝ๋์ง ์์ต๋๋ค.
ipcRenderer
๋ํ ์ก์ธ์ค๋ฅผ ์ ๊ณตํ๋ ์๊ฒฉ ๋ชจ๋ ์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ๊ธฐ๋ณธ์ ์ผ๋ก ๋ค๋ฅธ ํํ์ '์ฌ์ด ๋ฐฉ๋ฒ'์
๋๋ค. Electron์ ๋ณด์ ๊ถ์ฅ ์ฌํญ์์๋ ์ด๋ฌํ ์ ํ์ ๊ณต๊ฒฉ์ด ํ๋กํ ํ์
์ค์ผ ๋ฒกํฐ์ ์ํฅ์ ๋ฐ๊ธฐ ๋๋ฌธ์ ๊ถ์ฅํ์ง ์์ต๋๋ค .
์ฆ. ์๊ฒฉ์ ์ฌ์ฉํ๋ฉด ๋๊ตฐ๊ฐ๊ฐ js-object์ ํ๋กํ ํ์ ์ ์์ ํ๊ณ ์ปดํจํฐ/์ฑ์์ ํฐ ํผ๋์ ์ผ์ผํฌ ์ ์์ต๋๋ค.
@marksyzm ์ ์๋ฒฝํ์ง๋ ์์ง๋ง ๋ ๋์ ์๋ฃจ์
์ ๊ฐ์ง๊ณ ์์ต๋๋ค. ์ฌ๊ธฐ์ IPC๋ฅผ ์ฌ์ฉํ์ฌ ipcRenderer
๋ฅผ ๋ ๋๋ฌ ํ๋ก์ธ์ค์ ๋ณด๋
๋๋ค. ์ด๋ฌํ ์ ํ์ ์ค์ ์ ํ๋กํ ํ์
์ค์ผ ๊ณต๊ฒฉ์๋ ์ทจ์ฝํฉ๋๋ค. ์ฑ์ 80% ์ ๋ ๊ฐ์ ธ์ค๋ ค๋ฉด ์ด ๋ฐฉ๋ฒ์ ์ฌ์ฉํฉ๋๋ค. ๋ฆฌํฉํ ๋ง์ ๋ง์ด ํ ํ์๊ฐ ์๊ธฐ ๋๋ฌธ์
๋๋ค.
fs
/ ipcRenderer
๋ฅผ ๋ ๋๋ฌ ํ๋ก์ธ์ค๋ก ๊ฐ์ ธ์ค๋ ์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ์ IPC(ํ๋ก์ธ์ค ๊ฐ ํต์ )๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์
๋๋ค. ์ด๊ฒ์ ๋ฉ์ธ ํ๋ก์ธ์ค์ ๋ ๋๋ฌ ํ๋ก์ธ์ค ์ฌ์ด์ ๋ํ๋ฅผ ํ์ฉํ๋ Electron์ ๋ฐฉ์์
๋๋ค. ๋ถํดํ๋ฉด ๋ค์๊ณผ ๊ฐ์ด ์ฑ์ด ํ์๋์ด์ผ ํฉ๋๋ค.
preload
์์ฑ์ด ์์ต๋๋ค. ์ด ์์ฑ์ require
๋ํ ์ก์ธ์ค ๊ถํ์ผ๋ก ๋ก๋๋๋ js ํ์ผ์
๋๋ค(์ฆ, ipcRenderer๊ฐ ํ์ํ ์ ์์).contextIsolation: true
์์ง๋ง ์ด๋ ipcRenderer๋ฅผ ๋ ๋๋ฌ ํ๋ก์ธ์ค์ ์ ๋ฌํ๊ธฐ ์ํด contextBridge ๋ฅผ ์ฌ์ฉํด์ผ ํจ์ ์๋ฏธํฉ๋๋ค.ipcRenderer
์ ์ก์ธ์คํ๋๋ก ํ์ฉํฉ๋๋ค.fs
๋ชจ๋์ ์ฌ์ฉํ ์ ์์ต๋๋ค._๋๋ต_ ์ด ๋ชจ๋ ๋จ๊ณ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
๋ฉ์ธ.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);
});
});
preload.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));
}
}
}
);
index.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 ์ ์์ต๋๋ค.
๋ฉ์ธ.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;
});
preload.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๊ฐ ๋ ์์ ํ ์ด์ ๊ฐ ์์ต๋๋ค. Btw, ๊ทธ๊ฒ์ํ๋ ๋ ๋๋ฌ์ด ๋ฐฉ๋ฒ์ด ์์ต๋๋ค.
const { ipcRenderer } = eval("require('electron')")
@marc2332 ๋ ๋ค ์์ ํ์ง ์์ต๋๋ค. ์๊ฒฉ ์์ฐ์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ์ด๋ฌํ ๋ฐฉ์์ผ๋ก ์ฃผ์ ๊ณต๊ฒฉ์ ๋ ธ์ถ๋ ์ ์์ต๋๋ค. ๐
๊ฐ์ฌํฉ๋๋ค reZach! ์ฌ๋๋ค์ด ๋น์ ์ ์ ๊ทผ ๋ฐฉ์์ ๋ ๋นจ๋ฆฌ ์ดํดํ๊ฒ ๋ ๊ฒ์ด๋ผ๊ณ ์๊ฐํฉ๋๋ค.
๋งํฌ ์ํ์คํค
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( 20214 ๋
์ฐธ๊ณ : ํจ์, DOM ๊ฐ์ฒด, process.env ๋๋ WebContents์ ๊ฐ์ ํน์ Node/Electron ๊ฐ์ฒด ๋๋ ์ด๋ฌํ ํญ๋ชฉ์ ํฌํจํ๋ ๋ชจ๋ ๊ฐ์ฒด์ ๊ฐ์ด V8์ ๊ตฌ์กฐ์ ๋ณต์ ์๊ณ ๋ฆฌ์ฆ์ผ๋ก ์ง๋ ฌํํ ์ ์๋ ๊ฐ์ฒด๋ ์ด์ base::Value-๋ก ์ง๋ ฌํ๋ฉ๋๋ค. ๊ธฐ๋ฐ ์๊ณ ๋ฆฌ์ฆ. ๊ทธ๋ฌ๋ ์ด ๋์์ ๋ ์ด์ ์ฌ์ฉ๋์ง ์์ผ๋ฉฐ Electron 9๋ถํฐ ์์ธ๊ฐ ๋ฐ์ํฉ๋๋ค.
์ด๊ฒ์ ๋น์ ์ด ๋ฃ๊ณ ์ถ์๋ ์์์ด ์๋ ์๋ ์์ง๋ง ๋์์ด ๋์๊ธฐ๋ฅผ ๋ฐ๋๋๋ค.
Zach๋,
contextBridge
์๋ฃจ์ ์ด ํ๋กํ ํ์ ์ ์ง์ํฉ๋๊น?
๋์ ์๋์ ์ฝ๋contextIsolation: false
๋จ์ํ์ ๊ธฐ๋ฅ ๋ถ์ฐฉwindow
. ์ด ํจ์๋Promise<MediaStream>
๋ฐํํ์ง๋งcontextBridge
๋ฌธ์์๋ ํ๋กํ ํ์ ์ด ์ญ์ ๋๊ณcontextBridge
์ฌ์ฉํ ๋ ๋น ๊ฐ์ฒด๊ฐ ์์ ๋๋ค๊ณ ๋์ ์์ต๋๋ค.
ํ๋กํ ํ์ ์ ์ง์ํ๋ฉด์๋contextIsolation: true
๋ณด์ฅํ๋ ๋ฐฉ๋ฒ์ด ์์ต๋๊น?์๋์ค, ๊ทธ๋ ์ง ์์ต๋๋ค. ํ์ ์ค ํ ๋ช ์ด ์ด๊ฒ์ด ์๋๋ ํ๋์ด๋ผ๋ ์๊ฒฌ์ ์ฐพ์ผ๋ ค๊ณ ๋ ธ๋ ฅํ์ง๋ง ์ฐพ์ ์ ์์์ต๋๋ค. ๋ด๊ฐ ๋งํ ์ ์๋ ์ต์ ์ Electron v8( 20214 ๋
์ฐธ๊ณ : ํจ์, DOM ๊ฐ์ฒด, process.env ๋๋ WebContents์ ๊ฐ์ ํน์ Node/Electron ๊ฐ์ฒด ๋๋ ์ด๋ฌํ ํญ๋ชฉ์ ํฌํจํ๋ ๋ชจ๋ ๊ฐ์ฒด์ ๊ฐ์ด V8์ ๊ตฌ์กฐ์ ๋ณต์ ์๊ณ ๋ฆฌ์ฆ์ผ๋ก ์ง๋ ฌํํ ์ ์๋ ๊ฐ์ฒด๋ ์ด์ base::Value-๋ก ์ง๋ ฌํ๋ฉ๋๋ค. ๊ธฐ๋ฐ ์๊ณ ๋ฆฌ์ฆ. ๊ทธ๋ฌ๋ ์ด ๋์์ ๋ ์ด์ ์ฌ์ฉ๋์ง ์์ผ๋ฉฐ Electron 9๋ถํฐ ์์ธ๊ฐ ๋ฐ์ํฉ๋๋ค.
์ด๊ฒ์ ๋น์ ์ด ๋ฃ๊ณ ์ถ์๋ ์์์ด ์๋ ์๋ ์์ง๋ง ๋์์ด ๋์๊ธฐ๋ฅผ ๋ฐ๋๋๋ค.
์ํ๊น์ง๋ง ๋น ๋ฅธ ๋ต๋ณ ๊ฐ์ฌํฉ๋๋ค.
์์์ ์ค๋ช
ํ ๋จ๊ณ( @reZach ๋ฐ @aplum ์ ๊ณต)๋ฅผ ์ํํ์ผ๋ฉฐ client.js
์์ preload.js
์์ main.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}`)
})
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld(
'setBadgeCountForElectron', {
send: (channel, data) => {
console.log(`Preload ${channel}, ${data}`)
ipcRenderer.send(channel, data)
}
}
)
const { setBadgeCountForElectron } = window;
function sendBadgeCount(count) {
!!setBadgeCountForElectron && setBadgeCountForElectron.send('SEND_BADGE_COUNT', count)
}
sendBadgeCount(count)
"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 ๋ ๋ค ์ฌ๋ํฉ๋๋ค ๊ฐ์ฌํฉ๋๋ค.
๊ธฐ๋ฅ addRendererTarget(๊ตฌ์ฑ) {
config.target = '์ ์ ๋ ๋๋ฌ';
๋ฐํ ๊ตฌ์ฑ;
}
module.exports = ์ฌ์ ์(addRendererTarget);`
vuejs๋ฅผ ์ฌ์ฉํ๊ณ vue.config.js์ ์ฝ๋๋ฅผ ์ถ๊ฐํ๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค.
module.exports = {
"transpileDependencies": [
"๋ทฐํฐํ์ด"
],
ํ๋ฌ๊ทธ์ธ ์ต์
: {
์ ์ ๋น๋: {
nodeIntegration: ์ฐธ
}
}
}
@genilsonmm ํจ๊ณผ๊ฐ
๋๋ฅผ ์ํด ์ผํ ํน์ ๋ถ๋ถ์
```์๋ฐ์คํฌ๋ฆฝํธ
ํ๋ฌ๊ทธ์ธ ์ต์
: {
์ ์ ๋น๋: {
nodeIntegration: ์ฐธ
}
},
"transpileDependencies": [
"๋ทฐํฐํ์ด"
],
๊ทธ๊ฒ์ ๋์๊ฒ ์๋ํ์ง ์์ต๋๋ค ... ๊ทธ๋ฌ๋ window.require('electron')๋ ์ ์๋ํฉ๋๋ค
"window.require๋ ํจ์๊ฐ ์๋๋๋ค"๋ผ๋ ๋ฌธ์ ๊ฐ ์๋ ๋ชจ๋ ์ฌ๋๋ค์ ์ํด
์ ์์ preoload ์คํฌ๋ฆฝํธ๋ฅผ ์์ฑํด์ผ ํฉ๋๋ค.
์ ์ ๋ฉ์ธ ์คํฌ๋ฆฝํธ๊ฐ ์๋ ๋๋ ํ ๋ฆฌ์ preload.js๋ผ๋ ์ด๋ฆ์ ํ์ผ์ ๋ง๋ค๊ณ ์ด ์ฝ๋๋ฅผ ๋ฃ์ต๋๋ค.
window.require = ํ์;
์ ์ ๋ฉ์ธ ์คํฌ๋ฆฝํธ๋ก ์ด๋ํ์ฌ ์ฐฝ์ ์์ฑํ๋ ์ฝ๋์ ๋ค์์ ์ ๋ ฅํฉ๋๋ค.
์น๋ฆฌ = ์๋ก์ด BrowserWindow({
๋๋น: 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๋ฅผ ๋ฃ์ผ๋ฉด :
ํ๋ฌ๊ทธ์ธ ์ต์
: {
์ ์ ๋น๋: {
nodeIntegration: ์ฐธ,
"require is not defined" ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค.
"nodeIntegration: true" ์ค์ ์ฃผ์ ์ฒ๋ฆฌํ๋ฉด ์ค๋ฅ ๋ฉ์์ง๊ฐ ์ฌ๋ผ์ง๊ณ ์ฑ์ด ์๋ํฉ๋๋ค.
๋ฌด์จ ๋ป์ธ๊ฐ์?
@marcoippolito webpack ๋ฒ๋ค ์ฝ๋๊ฐ node js์ ๋ชจ๋ ํ์ธ ๋ฐฉ๋ฒ์ธ require
๋ฅผ ์ฌ์ฉํ๋ ค๊ณ ํ๋ค๋ ๋ป์ด๋ผ๊ณ ์๊ฐํฉ๋๋ค. ๋
ธ๋ ํตํฉ์ ๋นํ์ฑํํ๋ฉด ์ฌ์ฉํ ์ ์์ผ๋ฏ๋ก ์ฝ๋๋ฅผ ์คํํ ์ ์์ต๋๋ค. ๋น์ ์ดํด์ผ ํ ๊ฒ์ ๋์ ๋ธ๋ผ์ฐ์ ๋ก ์นํฉ ์ค์ ์ (์์ ์
๋๋ค var
๋ด ๊ธฐ์ต์ด ๋ง๋ค ๊ฒฝ์ฐ ๋์) ๋ฐ ๋
ธ๋ 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
})
index.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 ํจ๊ณผ๊ฐ
๋๋ฅผ ์ํด ์ผํ ํน์ ๋ถ๋ถ์pluginOptions: { electronBuilder: { nodeIntegration: true } },
ๆญฃ่งฃ, ๆ ่ฎฐ
NodeIntegration์ด ํ์ฑํ๋ ์ํ์์๋ ์ด ๋ฌธ์ ๊ฐ ์์ต๋๋ค. window.require ๋ฐ require ๋ชจ๋ ์๋ํ์ง ์์ต๋๋ค.
+ ์ด๊ฒ์ ์ผ๋ฐ ์ ์๊ฐ ์๋ ๋ฐ์์์๋ง ๋ฐ์ํฉ๋๋ค.
๊ฐ์ฅ ์ ์ฉํ ๋๊ธ
@MarshallOfSound ๋ด ์ค์.
๋๊ตฐ๊ฐ๋ฅผ ๋์ธ ์ ์๋ค๋ฉด ๋ฌธ์ #7300์์ ํด๊ฒฐ์ฑ ์ ์ฐพ์์ต๋๋ค.
์ด๊ฒ์ Electron ์ฑ์ ์คํํ ๋ ์๋ํ์ง๋ง ๋ธ๋ผ์ฐ์ ๋ด์์ React ์ฝ๋๋ฅผ ํ ์คํธํ๋ ค๋ ๊ฒฝ์ฐ ์ฌ์ ํ ์ถฉ๋ํฉ๋๋ค(window.require๋ Electron์์์ ๊ฐ์ด ๋ธ๋ผ์ฐ์ ์์ ์ ์๋์ง ์์).