Node.js 为Electron React JS应用程序创建安装程序-React JS组件不会';安装后运行时无法加载
我正在使用Electron和ReactJS进行一个新项目。该项目在开发模式下运行良好,但我正在尝试为Windows创建一个安装程序,但无论我尝试了什么,在Google上发现了什么,都不起作用。我只是得到一个空白的白色屏幕 下面是我的pacakge.jsonNode.js 为Electron React JS应用程序创建安装程序-React JS组件不会';安装后运行时无法加载,node.js,reactjs,installation,electron,Node.js,Reactjs,Installation,Electron,我正在使用Electron和ReactJS进行一个新项目。该项目在开发模式下运行良好,但我正在尝试为Windows创建一个安装程序,但无论我尝试了什么,在Google上发现了什么,都不起作用。我只是得到一个空白的白色屏幕 下面是我的pacakge.json { "name": "MyApp", "description": "My App Description", "version"
{
"name": "MyApp",
"description": "My App Description",
"version": "0.1.2",
"private": true,
"homepage": "./",
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"@types/jest": "^26.0.14",
"@types/node": "^14.11.2",
"@types/react": "^16.9.50",
"@types/react-dom": "^16.9.8",
"bootstrap": "^4.5.2",
"electron-is-dev": "^1.2.0",
"electron-settings": "^4.0.2",
"electron-squirrel-startup": "^1.0.0",
"react": "^16.13.1",
"react-bootstrap": "^1.3.0",
"react-dom": "^16.13.1",
"react-icons": "^3.11.0",
"react-json-pretty": "^2.2.0",
"react-scripts": "3.4.3",
"react-tooltip": "^4.2.10",
"typescript": "^4.0.3"
},
"main": "src/electron-starter.js",
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"electron-start": "set ELECTRON_START_URL=http://localhost:3000 && electron .",
"package-win": "electron-packager . --asar --out=release-builds --platform=win32 --arch=x64 --no-prune --ignore=/e2e --overwrite",
"create-installer-win": "node installers/windows/createInstaller.js"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"electron": "^10.1.3",
"electron-packager": "^12.0.1",
"electron-winstaller": "^4.0.1",
"react-router-dom": "^5.2.0",
"react-toastify": "^6.0.8"
}
}
下面是我的electron-start.js脚本
const {electron, Menu, app, BrowserWindow} = require('electron');
// Module to control application life.
//const app = electron.app;
// Module to create native browser window.
//const BrowserWindow = electron.BrowserWindow;
const path = require('path');
const url = require('url');
if (require('electron-squirrel-startup')) app.quit()
// if first time install on windows, do not run application, rather
// let squirrel installer do its work
const setupEvents = require('../installers/setupEvents')
if (setupEvents.handleSquirrelEvent()) {
console.log("Squirrel event returned true");
process.exit()
//return;
}
console.log("Starting main program");
// 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 mainWindow;
/*const env = process.env.NODE_ENV;
let windowUrlBase = "";
if (env === "production")
{
windowUrlBase = "/";
}
else
{
windowUrlBase = 'http://localhost:3000';
}*/
let windowUrlBase = 'http://localhost:3000';
function returnMainWindow()
{
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
//preload: __dirname + '/preload.tsx'
}
});
//const env = process.env.NODE_ENV;
//console.log("Environment: " + env);
const isDev = require('electron-is-dev');
windowUrlBase = "";
console.log("Not electron dev");
console.log("dir name: " + __dirname);
const startUrl = process.env.ELECTRON_START_URL || url.format({
//pathname: path.join(__dirname, '/../build/index.html'),
pathname: path.join(__dirname, '../index.html'),
protocol: 'file:',
slashes: true,
webSecurity: false
});
mainWindow.loadURL(startUrl);
return mainWindow;
}
function createWindow() {
// Create the browser window.
mainWindow = returnMainWindow();
mainWindow.maximize();
// and load the index.html of the app.
// Open the DevTools.
//mainWindow.webContents.openDevTools();
// Emitted when the window is closed.
mainWindow.on('closed', function () {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null
})
setMainMenu();
}
function setMainMenu()
{
const template = [
{
label: 'File',
submenu: [
{
label: 'Exit',
accelerator: "ctrl+f4",
click() {
app.quit();
}
}
]
},
{
label: 'Edit',
submenu: [
{
label: 'Settings',
click() {
mainWindow.loadURL(windowUrlBase + "/settings");
}
}
]
},
{
label: 'Help',
submenu: [
{
label: 'Show Dev Console',
accelerator: "f11",
click() {
mainWindow.webContents.openDevTools();
}
}
]
}
];
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow);
// Quit when all windows are closed.
app.on('window-all-closed', function () {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
});
app.on('activate', function () {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
createWindow()
}
});
const createWindowsInstaller = require('electron-winstaller').createWindowsInstaller
const path = require('path')
getInstallerConfig()
.then(createWindowsInstaller)
.catch((error) => {
console.error(error.message || error)
process.exit(1)
})
function getInstallerConfig () {
console.log('creating windows installer')
const rootPath = path.join('./')
const outPath = path.join(rootPath, 'release-builds')
return Promise.resolve({
appDirectory: path.join(outPath, 'crash-catch-control-panel-win32-x64'),
authors: 'Boardies IT Solutions',
noMsi: true,
outputDirectory: path.join(outPath, 'windows-installer'),
exe: 'crash-catch-control-panel.exe',
setupExe: 'crash-catch-control-panel-installer.exe'
//setupIcon: path.join(rootPath, 'assets', 'images', 'logo.ico')
})
}
下面是我的创建安装程序脚本
const {electron, Menu, app, BrowserWindow} = require('electron');
// Module to control application life.
//const app = electron.app;
// Module to create native browser window.
//const BrowserWindow = electron.BrowserWindow;
const path = require('path');
const url = require('url');
if (require('electron-squirrel-startup')) app.quit()
// if first time install on windows, do not run application, rather
// let squirrel installer do its work
const setupEvents = require('../installers/setupEvents')
if (setupEvents.handleSquirrelEvent()) {
console.log("Squirrel event returned true");
process.exit()
//return;
}
console.log("Starting main program");
// 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 mainWindow;
/*const env = process.env.NODE_ENV;
let windowUrlBase = "";
if (env === "production")
{
windowUrlBase = "/";
}
else
{
windowUrlBase = 'http://localhost:3000';
}*/
let windowUrlBase = 'http://localhost:3000';
function returnMainWindow()
{
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
//preload: __dirname + '/preload.tsx'
}
});
//const env = process.env.NODE_ENV;
//console.log("Environment: " + env);
const isDev = require('electron-is-dev');
windowUrlBase = "";
console.log("Not electron dev");
console.log("dir name: " + __dirname);
const startUrl = process.env.ELECTRON_START_URL || url.format({
//pathname: path.join(__dirname, '/../build/index.html'),
pathname: path.join(__dirname, '../index.html'),
protocol: 'file:',
slashes: true,
webSecurity: false
});
mainWindow.loadURL(startUrl);
return mainWindow;
}
function createWindow() {
// Create the browser window.
mainWindow = returnMainWindow();
mainWindow.maximize();
// and load the index.html of the app.
// Open the DevTools.
//mainWindow.webContents.openDevTools();
// Emitted when the window is closed.
mainWindow.on('closed', function () {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null
})
setMainMenu();
}
function setMainMenu()
{
const template = [
{
label: 'File',
submenu: [
{
label: 'Exit',
accelerator: "ctrl+f4",
click() {
app.quit();
}
}
]
},
{
label: 'Edit',
submenu: [
{
label: 'Settings',
click() {
mainWindow.loadURL(windowUrlBase + "/settings");
}
}
]
},
{
label: 'Help',
submenu: [
{
label: 'Show Dev Console',
accelerator: "f11",
click() {
mainWindow.webContents.openDevTools();
}
}
]
}
];
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow);
// Quit when all windows are closed.
app.on('window-all-closed', function () {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
});
app.on('activate', function () {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
createWindow()
}
});
const createWindowsInstaller = require('electron-winstaller').createWindowsInstaller
const path = require('path')
getInstallerConfig()
.then(createWindowsInstaller)
.catch((error) => {
console.error(error.message || error)
process.exit(1)
})
function getInstallerConfig () {
console.log('creating windows installer')
const rootPath = path.join('./')
const outPath = path.join(rootPath, 'release-builds')
return Promise.resolve({
appDirectory: path.join(outPath, 'crash-catch-control-panel-win32-x64'),
authors: 'Boardies IT Solutions',
noMsi: true,
outputDirectory: path.join(outPath, 'windows-installer'),
exe: 'crash-catch-control-panel.exe',
setupExe: 'crash-catch-control-panel-installer.exe'
//setupIcon: path.join(rootPath, 'assets', 'images', 'logo.ico')
})
}
下面是我的setupEvents.js
const electron = require('electron')
const app = electron.app
module.exports = {
handleSquirrelEvent: function() {
if (process.argv.length === 1) {
return false;
}
const ChildProcess = require('child_process');
const path = require('path');
const appFolder = path.resolve(process.execPath, '..');
const rootAtomFolder = path.resolve(appFolder, '..');
const updateDotExe = path.resolve(path.join(rootAtomFolder, 'Update.exe'));
const exeName = path.basename(process.execPath);
const spawn = function(command, args) {
let spawnedProcess, error;
try {
spawnedProcess = ChildProcess.spawn(command, args, {detached: true});
} catch (error) {}
return spawnedProcess;
};
const spawnUpdate = function(args) {
return spawn(updateDotExe, args);
};
const squirrelEvent = process.argv[1];
switch (squirrelEvent) {
case '--squirrel-install':
case '--squirrel-updated':
// Optionally do things such as:
// - Add your .exe to the PATH
// - Write to the registry for things like file associations and
// explorer context menus
// Install desktop and start menu shortcuts
spawnUpdate(['--createShortcut', exeName]);
setTimeout(app.quit, 1000);
return true;
case '--squirrel-uninstall':
// Undo anything you did in the --squirrel-install and
// --squirrel-updated handlers
// Remove desktop and start menu shortcuts
spawnUpdate(['--removeShortcut', exeName]);
setTimeout(app.quit, 1000);
return true;
case '--squirrel-obsolete':
// This is called on the outgoing version of your app before
// we update to the new version - it's the opposite of
// --squirrel-updated
app.quit();
return true;
}
}
}
import * as React from 'react';
import {BrowserRouter, Route, Switch} from 'react-router-dom'
import './Stylesheet.css'
import Dashboard from "./Components/Dashboard";
import Settings from "./Components/Settings";
import './ComponentStyles/BreadcrumbNav.css'
import 'react-toastify/dist/ReactToastify.min.css';
import { ToastContainer, toast } from 'react-toastify';
import CustomerDetails from "./Components/CustomerDetails";
toast.configure({
position: 'top-center',
hideProgressBar: true
});
function App() {
return (
<BrowserRouter>
<div>
<Switch>
<Route path="/" render={() => <Dashboard /> } exact />
<Route path="/customer-information/:customer_id" render={(props) => <CustomerDetails {...props} /> } exact />
<Route path="/settings" render={() => <Settings /> } exact />
</Switch>
</div>
</BrowserRouter>
);
}
export default App;
下面是我的App.js
const electron = require('electron')
const app = electron.app
module.exports = {
handleSquirrelEvent: function() {
if (process.argv.length === 1) {
return false;
}
const ChildProcess = require('child_process');
const path = require('path');
const appFolder = path.resolve(process.execPath, '..');
const rootAtomFolder = path.resolve(appFolder, '..');
const updateDotExe = path.resolve(path.join(rootAtomFolder, 'Update.exe'));
const exeName = path.basename(process.execPath);
const spawn = function(command, args) {
let spawnedProcess, error;
try {
spawnedProcess = ChildProcess.spawn(command, args, {detached: true});
} catch (error) {}
return spawnedProcess;
};
const spawnUpdate = function(args) {
return spawn(updateDotExe, args);
};
const squirrelEvent = process.argv[1];
switch (squirrelEvent) {
case '--squirrel-install':
case '--squirrel-updated':
// Optionally do things such as:
// - Add your .exe to the PATH
// - Write to the registry for things like file associations and
// explorer context menus
// Install desktop and start menu shortcuts
spawnUpdate(['--createShortcut', exeName]);
setTimeout(app.quit, 1000);
return true;
case '--squirrel-uninstall':
// Undo anything you did in the --squirrel-install and
// --squirrel-updated handlers
// Remove desktop and start menu shortcuts
spawnUpdate(['--removeShortcut', exeName]);
setTimeout(app.quit, 1000);
return true;
case '--squirrel-obsolete':
// This is called on the outgoing version of your app before
// we update to the new version - it's the opposite of
// --squirrel-updated
app.quit();
return true;
}
}
}
import * as React from 'react';
import {BrowserRouter, Route, Switch} from 'react-router-dom'
import './Stylesheet.css'
import Dashboard from "./Components/Dashboard";
import Settings from "./Components/Settings";
import './ComponentStyles/BreadcrumbNav.css'
import 'react-toastify/dist/ReactToastify.min.css';
import { ToastContainer, toast } from 'react-toastify';
import CustomerDetails from "./Components/CustomerDetails";
toast.configure({
position: 'top-center',
hideProgressBar: true
});
function App() {
return (
<BrowserRouter>
<div>
<Switch>
<Route path="/" render={() => <Dashboard /> } exact />
<Route path="/customer-information/:customer_id" render={(props) => <CustomerDetails {...props} /> } exact />
<Route path="/settings" render={() => <Settings /> } exact />
</Switch>
</div>
</BrowserRouter>
);
}
export default App;
import*as React from'React';
从“react router dom”导入{BrowserRouter,Route,Switch}
导入“./Stylesheet.css”
从“/Components/Dashboard”导入仪表板;
从“/Components/Settings”导入设置;
导入“./ComponentStyles/BreadcrumbNav.css”
导入“react toastify/dist/react toastify.min.css”;
从'react toastify'导入{ToastContainer,toast};
从“/Components/CustomerDetails”导入CustomerDetails;
toast.configure({
位置:'上止点',
hideProgressBar:对
});
函数App(){
返回(
}精确的/>
}精确的/>
}精确的/>
);
}
导出默认应用程序;
当我在应用程序加载时查看chrome控制台时,我看到以下错误:
不允许加载本地资源:
file:///C:/Users/Chris/AppData/Local/MyApp/app-0.1.2/resources/app.asar/index.html
如上所述,只有当我在安装electron应用程序时启动该应用程序时,问题才会发生。如果我将其作为Node dev服务器的一部分启动,那么它就可以正常工作
更新
目录结构如下所示
components目录包含实际的ReactJS组件,目录ComponentStyles是所有单独的组件样式表。组件是typescript,所以是tsx格式的。经过大量的尝试和错误,我终于让它工作了 第一件事是更改我的App.js,这样我就不用使用BrowserRouter,而是使用HashRouter,如下所示,并使用react router dom中的历史记录
import {HashRouter, useHistory} from "react-router-dom";
const history = createBrowserHistory();
<HashRouter basename="/" history={history} >
<Switch>
<Route path="/" exact component={Dashboard} />
<Route path="/customer-information/:customer_id" component={CustomerDetails} />
<Route path="/settings" component={Settings} />
<Route path="*" component={NotFound} />
</Switch>
</HashRouter>
当我加载浏览器窗口时,我现在有以下内容:
const history = useHistory();
history.push('/newlocation');
mainWindow.loadURL(isDev ? windowUrlBase : `file://${__dirname}/../build/index.html`);
const isDev = require('electron-is-dev');
并删除了下面的块
const startUrl = process.env.ELECTRON_START_URL || url.format({
//pathname: path.join(__dirname, '/../build/index.html'),
pathname: path.join(__dirname, '../index.html'),
protocol: 'file:',
slashes: true,
webSecurity: false
});
isDev的设置采用以下方法:
const history = useHistory();
history.push('/newlocation');
mainWindow.loadURL(isDev ? windowUrlBase : `file://${__dirname}/../build/index.html`);
const isDev = require('electron-is-dev');
对于使用IPC更改位置的设置菜单,请使用以下命令:
const history = useHistory();
history.push('/newlocation');
mainWindow.loadURL(isDev ? windowUrlBase : `file://${__dirname}/../build/index.html`);
const isDev = require('electron-is-dev');
在electron-start.js中
const { ipcMain } = require("electron");
submenu: [
{
label: 'Settings',
click() {
//mainWindow.loadURL(windowUrlBase + "/settings");
//history.push('/settings')
//location.pathname = "/settings";
//ipcMain.send("change-location", "/settings");
mainWindow.webContents.send('change-location', '/settings')
//mainWindow.location.pathname = "/settings";
}
}
]
然后在整个项目中共享的组件中,我有以下内容来接收IPC事件
const {ipcRenderer} = window.require('electron')
const history = useHistory();
useEffect(() => {
ipcRenderer.on('change-location', (event, arg) => {
history.push(arg);
});
})
你确定这行是对的吗
const startUrl=process.env.ELECTRON_START_URL|URL.format({
//路径名:path.join(uu dirname,'/../build/index.html'),
路径名:path.join(_dirname,'../index.html'),
协议:“文件:”,
斜杠:对,
网站安全性:false
})
我认为路径名应该是path.join(uu dirname,'./index.html')
double。
使您在上面两级(红色),而不是父文件夹(橙色)
您可以使用文件夹/main/sub/test.js中的一个简单文件来测试这一点:
const path=require('path'))
console.log(uu dirname)
console.log(path.join(uu dirname,“../”))
console.log(path.join(uu dirname,./))
产出将是:
~/main/sub
~/main/
~/main/sub/
因此,在您的情况下,./index.html
可以让您进入/crash catch控制面板/
,但是index.html
不在那里
我不熟悉使用asar,但我认为您的错误消息:file:///C:/Users/Chris/AppData/Local/MyApp/app-0.1.2/resources/app.asar/index.html
表示您不在src
文件夹中,您的index.html
文件位于该文件夹中,但您正在查找其上一级的index.html
特别是因为您在响应中的内容:
mainWindow.loadURL(isDev?windowUrlBase:`file://${\uu dirname}/./build/index.html`
上升一级,然后下降到build
。所以我关心的是,在这里,您要跳入build
文件夹,从build
文件夹中获取index.html。但是build
文件夹与您的src
文件夹处于同一层次结构,您没有被指向该层次结构。我需要您的项目文件结构。如果这是开源的,你应该链接repo URL。我已经按要求添加了我的目录结构-它不是开源项目当你说从“节点开发服务器”启动它时,你是说用“npm运行electron start”?你可以签出。我用它作为路由的一个例子。我从中构建没有任何问题。而且,通过您的更改,它看起来像“设置”除非您还更新了指向“设置”的菜单链接,否则“菜单项”可能无法在生产环境中使用。@Andrew我修复了我的答案,因为我删除了一块与正在加载的startURL相关的代码,正如您所提到的,“设置”菜单将无法使用我最初更新的设置。谢谢你指出我已经更新了我的答案,我确实移除了你提到的startURL块