#node.js #reactjs #typescript #webpack #electron
#node.js #reactjs #машинопись #webpack #electron
Вопрос:
Я пишу приложение Electron с React и Typescript, используя Webpack, Babel и ESLint, но у меня возникли проблемы с настройкой:
mainWindow = new BrowserWindow({
title: "Biomech",
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
enableRemoteModule: false,
sandbox: true,
preload: path.join(__dirname, "./preload.js"),
nativeWindowOpen: true,
},
});
что я и хочу в качестве меры безопасности.
Причина в том, что, если я установлю webPreferences, как указано выше, мне нужно использовать contextBridge
и сценарий предварительной загрузки, который связывает функции, использующие IPC, с window
. И проблема в том, что contextBridge
он неправильно импортируется в мой файл preload.ts:
import { contextBridge, ipcRenderer } from "electron";
import { readFileSync } from 'fs';
import { History } from 'history';
import { LIST_DRIVE_FILES_CHANNEL, LOAD_PREV_TEST_RESULTS_CHANNEL, OAUTH2_ACCESS_TOKEN_REQUEST_CHANNEL } from "../src/constants/ipcChannels";
import { VISUALIZE_RESULTS_PATH } from "../src/constants/urls";
// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld(
"api", {
exchangeCodeForAccessToken: (code: string) => {
ipcRenderer.invoke(OAUTH2_ACCESS_TOKEN_REQUEST_CHANNEL, code);
},
listDriveFiles: (accessToken: string) => {
ipcRenderer.invoke(LIST_DRIVE_FILES_CHANNEL, accessToken);
},
openDirectoryDialog: (history: History) => {
ipcRenderer.invoke(LOAD_PREV_TEST_RESULTS_CHANNEL).then((dialog: any) => {
if (dialog.canceled) { // canceled with one l is correct
return;
} else {
const selectedDirectory = dialog.filePaths[0];
console.log(readFileSync(selectedDirectory "/README.md", 'utf-8'));
history.push(VISUALIZE_RESULTS_PATH);
}
})
}
}
);
На мой взгляд, он используется правильно, как примеры, которые я видел здесь, в SO, или в электронных документах. Но когда я запускаю свой основной сценарий процесса: npm run dev:electron
который указан в моем package.json:
{
"name": "electron-react-ts-app",
"version": "1.0.0",
"description": "Electron React Typescript",
"main": "./dist/main.js",
"preload": "./dist/preload.js",
"scripts": {
"dev": "concurrently --success first "npm run dev:electron" "npm run dev:react" -k",
"dev:electron": "NODE_ENV=development webpack --config webpack.electron.config.js --mode development amp;amp; electron .",
"dev:react": "NODE_ENV=development webpack serve --config webpack.react.config.js --mode development",
"build:electron": "NODE_ENV=production webpack --config webpack.electron.config.js --mode production",
"build:react": "NODE_ENV=production webpack --config webpack.react.config.js --mode production",
"build": "npm run build:electron amp;amp; npm run build:react",
"pack": "electron-builder --dir",
"dist": "electron-builder",
"lint": "eslint .",
"format": "prettier --write "**/*. (js|jsx|json|yml|yaml|css|md|vue)""
},
"keywords": [],
"license": "MIT",
"build": {
"files": [
"dist/",
"node_modules/",
"package.json"
],
"productName": "Example",
"appId": "com.example.app",
"directories": {
"output": "dist"
}
},
"devDependencies": {
"@babel/preset-env": "^7.9.5",
"@babel/preset-react": "^7.9.4",
"@babel/preset-typescript": "^7.9.0",
"@types/electron-devtools-installer": "^2.2.0",
"@types/react-router-dom": "^5.1.7",
"@types/regenerator-runtime": "^0.13.0",
"dpdm": "^3.6.0",
"electron": "^11.2.1",
"electron-builder": "^22.7.0",
"electron-devtools-installer": "^3.1.1",
"eslint": "^7.18.0",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^4.5.1",
"husky": "^4.3.8",
"lint-staged": "^10.5.3",
"prettier": "^2.2.1",
"react-router-dom": "^5.2.0",
"webpack": "^5.11.1",
"webpack-cli": "^4.3.1",
"webpack-dev-server": "^3.11.1"
},
"dependencies": {
"@babel/core": "^7.12.10",
"@popperjs/core": "^2.6.0",
"@types/node": "^14.14.22",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"axios": "^0.21.1",
"babel-loader": "^8.2.2",
"bootstrap": "^4.5.3",
"chokidar": "^3.5.1",
"core-js": "^3.8.3",
"css-loader": "^5.0.1",
"electron-fetch": "^1.7.3",
"fsevents": "^2.3.1",
"ini": "^2.0.0",
"jquery": "^3.5.1",
"react": "^17.0.1",
"react-bootstrap": "^1.4.0",
"react-dom": "^17.0.1",
"react-google-login": "^5.2.2",
"style-loader": "^2.0.0"
},
"husky": {
"hooks": {
"pre-commit": "npm run lint amp;amp; npm run format"
}
},
"lint-staged": {
"*. (js|jsx)": "eslint --fix",
"*. (json|css|md)": "prettier --write"
}
}
Я получаю следующую ошибку: не удается прочитать свойство ‘exposeInMainWorld’ неопределенного
webpack 5.19.0 compiled successfully in 1793 ms
App threw an error during load
TypeError: Cannot read property 'exposeInMainWorld' of undefined
at Object../electron/preload.ts (/Users/lucas_sg/Documents/ITBA/PF/pf-biomech/dist/preload.js:24:53)
at __webpack_require__ (/Users/lucas_sg/Documents/ITBA/PF/pf-biomech/dist/preload.js:128:41)
at /Users/lucas_sg/Documents/ITBA/PF/pf-biomech/dist/preload.js:252:11
at Object.<anonymous> (/Users/lucas_sg/Documents/ITBA/PF/pf-biomech/dist/preload.js:254:12)
at Module._compile (internal/modules/cjs/loader.js:1152:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1173:10)
at Module.load (internal/modules/cjs/loader.js:992:32)
at Module._load (internal/modules/cjs/loader.js:885:14)
at Function.f._load (electron/js2c/asar_bundle.js:5:12738)
at Module.require (internal/modules/cjs/loader.js:1032:19)
Я проверил это репозиторий, касающийся безопасности Electron, и не похоже, что он делает что-то слишком по-другому, по крайней мере, на первый взгляд, но я явно что-то напутал.
Вот моя конфигурация webpack (webpack.electron.config.js ) на случай, если это будет полезно:
const path = require("path");
module.exports = [
{
resolve: {
extensions: [".tsx", ".ts", ".js"],
},
devtool: "source-map",
entry: {
main: {
import: "./electron/main.ts",
dependOn: "preload"
},
preload: "./electron/preload.ts"
},
output: {
path: path.resolve(__dirname, "dist"),
filename: "[name].js",
},
target: "electron-main",
module: {
rules: [
{
test: /.(js|ts|tsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
},
},
{
test: /.css$/,
use: ["style-loader", "css-loader"],
},
],
},
node: {
__dirname: false,
},
},
];
Ответ №1:
Я думаю, что проблема заключается в вашем файле webpack.config:
entry: {
main: {
import: "./electron/main.ts",
dependOn: "preload"
},
preload: "./electron/preload.ts"
},
Возможно, webpack пытается разрешить всю зависимость «preload.ts» во время компиляции, а также пытается объединить ее с файлом main.ts. И в таком случае он никогда не получит доступ к contextBridge. preload.js файл должен запускаться только в отдельном «контексте» — «Изолированном мире», как они упоминаются в документах.
Что бы я попробовал, так это:
- удалите строки dependOn: и preload: из вашей конфигурации webpack.
- преобразовать файл preload.ts в JS (т.е. в формат CommonJS) — попробуйте сделать это с помощью TS CLI на данный момент
Ваш preload.js файл должен выглядеть примерно так:
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld(
'electron',
{
doThing: () => ipcRenderer.send('do-a-thing')
}
)
- копировать preload.js к __dirname (в моем случае это был /dist)
- запустите electron еще раз
Это был единственный способ получить доступ к объекту «contextBridge».