Serve static files with express from within electron-forge app - node.js

I've written a quick Electron Forge app that simply runs an express webserver that serves static files locally. I prefer this to running a node process directly for usability.
main.js
import { app, BrowserWindow } from 'electron';
import express from 'express';
const exApp = express();
exApp.use(express.static('web-app'));
exApp.listen(3333);
let mainWindow;
const createWindow = () => {
// Create the browser window.
mainWindow = new BrowserWindow({
// ...
});
// ...
};
// ...
I use the CopyWebpackPlugin to copy the files I need to serve into the .webpack/main/web-app/ directory.
webpack.main.config.js
module.exports = {
/**
* This is the main entry point for your application, it's the first file
* that runs in the main process.
*/
entry: './src/main.js',
// Put your normal webpack config below here
module: {
rules: require('./webpack.rules'),
},
plugins: [
new CopyPlugin([
{ from: path.resolve(__dirname, 'web-app'), to: 'web-app' }
]),
]
};
This works perfectly in development (via yarn start).
When I try to run yarn make, it successfully builds the app and generates a runnable exe, but trying to access http://localhost:3333/ after running the app, results in a Cannot GET / 404 message.
Any idea what I'm doing wrong?

What I was serving in development was actually the web-app directory relative to the node process, and not the one copied into .webpack/....
To serve the proper files in production, I changed the exApp.use() line to:
exApp.use(express.static('resources/app/.webpack/main/web-app'));

Related

NodeJS + WebPack setting client static data

I have a NodeJS/React/WebPack application that I'm trying to take environment variables that are present at build time and export them as variables that are available to the client without having to request them with AJAX.
What is currently setup is a /browser/index.js file with a method that is exported however the variables are not getting expanded when webpack runs.
function applicationSetup()
{
const config = JSON.parse(process.env.CONFIG);
const APPLICATION_ID = process.env.APPLICATION_ID;
.........
}
During the build process we run node node_modules/webpack/bin/webpack.js --mode production with npm.
What do I need to do in order to expand the environment variable to be their actual values when webpack creates the .js file?
Edit 8/23
I've tried adding it in the webpack.DefinePlugin section of the webpack.config.js file however it's still doesn't seem to be available in the client side code. What am I missing?
Edit #2 (webpack.config.js)
const getClientConfig = (env, mode) => {
return {
plugins: [
new webpack.DefinePlugin({
__isBrowser__: 'false',
__Config__: process.env.CONFIG,
__ApplicationID__:process.env.APPLICATION_ID
})]
}
module.exports = (env, options) => {
const configs = [
getClientConfig(options.env, options.mode)
];
return configs;
};

Is there a way to use Vite with HMR and still generate the files in the /dist folder?

First of all, I wanna say that I've started using Vite awhile ago and I'm no Vite expert in any shape or form.
Now, about my problem: I'm working on a Chrome Extension which requires me to have the files generated in the /dist folder. That works excellent using vite build. But, if I try to use only vite (to get the benefits of HMR), no files get generated in the /dist folder. So I have no way to load the Chrome Extension.
If anyone has faced similar issues, or knows a config that I've overlooked, feel free to share it here.
Thanks!
With this small plugin you will get a build after each hot module reload event :
In a file hot-build.ts :
/**
* Custom Hot Reloading Plugin
* Start `vite build` on Hot Module Reload
*/
import { build } from 'vite'
export default function HotBuild() {
let bundling = false
const hmrBuild = async () => {
bundling = true
await build({'build': { outDir: './hot-dist'}}) // <--- you can give a custom config here or remove it to use default options
};
return {
name: 'hot-build',
enforce: "pre",
// HMR
handleHotUpdate({ file, server }) {
if (!bundling) {
console.log(`hot vite build starting...`)
hmrBuild()
.then(() => {
bundling = false
console.log(`hot vite build finished`)
})
}
return []
}
}
}
then in vite.config.js :
import HotBuild from './hot-build'
// vite config
{
plugins: [
HotBuild()
],
}

How to bundle an environment dependant package?

problem
Currently my package is developed in a way where to import it you need to decide on your target. So:
import * as myLib from 'mylib/node' // if i want to use the node implementation
import * as myLib from 'mylib/web' // if i want to use the web implementation
The functionality is identical, the implementation differs tho because they use different APIs. I want to move to a single import that will work for node and web. To do that i changed my code to detect whether its running in node or the web. This alows me to import it like this:
import * as myLib from 'mylib'
Which works. However when i go to bundle some code using mylib with webpack (as web target) it goes bonkers as it tries to bundle the nodejs implementation (fails on bundling packages like worker_threads)
webpack.config.ts:
import * as path from 'path'
import { Configuration } from 'webpack'
const config: Configuration = {
entry: './dist/index.js',
target: 'web',
output: {
filename: 'mylib.web.js',
path: path.resolve(__dirname, 'dist'),
library: 'mylib',
libraryTarget: 'umd'
},
mode: 'production'
}
export default config
question
How can i bundle such a package or write my package in a way to support both node and web and bundle correctly.
edits
To specify i merged web and node implementation in such manner:
Before:
// mylib/node
export const func = () => {
// using worker_threads here
}
// mylib/web
export const func = () => {
// using web worker here
}
After:
// mylib
export const func = () => {
if(/* am i in node test */) {
// execute the worker_threads implementation
} else {
// execute the web workers implementation
}
}

Electron - "Cannot read property 'on' of undefined"; tried reinstalling,

I'm testing an electron app but I'm getting this pernicious error:
"TypeError: Cannot read property 'on' of undefined"
The research I've done pointed to either a botched module install, a syntax issue, or passing in an undefined variable to the app.on, and I suspect the issue may be Electron being pointed to incorrectly (now it's being pointed to the folder ending in electron\dist\electron.exe, which I've heard might not the right location), but I'm unsure.
Despite checking the require command, syntax, rechecking, uninstalling, and reinstalling node, I can't seem to make this darn error go away. What could be causing this?
const electron = require('electron');
const os = require('os');
const fs = require('fs');
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
var Mousetrap = require('mousetrap');
const path = require('path');
const url = require('url');
const ipc = electron.ipcMain;
let mainWindow;
function createWindow () {
// Create the browser window.
mainWindow = new BrowserWindow({width: 800, height: 600})
// and load the index.html of the app.
mainWindow.loadURL(url.format({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file:',
slashes: true
/* More code in this and sub functions*/
}))
}
})
const preferencesManager = require('./preferencesManager');
/******
Send data to database given constructor created in preferencesManager
******/
// First instantiate the class because we want to turn the class into an object to be able to use.
const store = new Store({ //create a new getting and setting logic
//We'll call our data file 'user-preferences'
configName: 'user-preferences',
defaults: {
//800 x 600 is the default size of our window
windowBounds: { width: 800, height: 600}
}
});
// When our app is ready, we'll create our BrowserWindow
app.on('ready',function(){
//Set up a listener for what I've done in keycapture (in the renderer process)
//???
ipc.on('invokeAction', function(event, args){
/* Do some action */
});
});
You are probably trying to run your application like a node app with:
$ node index.js
The electron file is a binary file, not a JavaScript file, when you require it an run with node there will be no object to call electron.app, so it parses for null and cannot have an property. As in the getting started documento of Electron.JS you must run the application like this:
Change your package.json script session adding start:
{
"scripts": {
"start": "electron ."
}
}
Now run:
$ npm start
The code you posted has an error, witch may be an edition error while coping and pasting but it should loose some parenthesis and curly brackets:
function createWindow () {
// Create the browser window.
mainWindow = new BrowserWindow({width: 800, height: 600})
// and load the index.html of the app.
mainWindow.loadURL(url.format({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file:',
slashes: true
/* More code in this and sub functions*/
}))
}
The application should now run correctly. I tested you exact code, removing the libs I did not have and it loaded with no errors.

How to integrate and run existing ReactJS Application in Electron

For instance, I have an ReactJS application: https://iaya-664f3.firebaseapp.com/
You can see in the HTML source the bundle.js file.
I have tried to run this application as desktop application using Electron, which should launch this web application in chromium window but it is not working.
Following is my main React application file app.js sitting in root directory. However compiled files bundle.js and index.html are in ./public/directory.
./app.js
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, browserHistory } from 'react-router';
import routes from './routes';
import {Provider} from "react-redux";
import { createStore, applyMiddleware } from 'redux';
import ReduxPromise from 'redux-promise';
import rootReducer from './reducers/index';
const store = applyMiddleware(ReduxPromise)(createStore)(rootReducer);
ReactDOM.render( <Provider store={store}>
<Router history={browserHistory} routes={routes} />
</Provider> ,
document.getElementById('react-app'));
./index.js
In this file I'm embedding my application to Electron to run in chromium.
var app = require("./app");
var BrowserWindow = require("browser-window");
// on electron has started up , booted up, everything loaded (Chromium ,goen, live)
app.on("ready", function(){
var mainWindow = new BrowserWindow({
width:800,
height:600
});
mainWindow.loadUrl("file://" + __dirname+ "/public/index.html");
});
But this give some error on import React from 'React' line in my ./app.js.
So I assume that, I should only give ./public/index.html file to load which includes the compiled bundle.js. But I wonder, how that will work as the line app.on("ready", function(){ expect an app.
Moreover I have also tried following way in ./index.js but it gives some other error.
const electron = 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');
// 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;
function createWindow () {
// Create the browser window.
mainWindow = new BrowserWindow({width: 800, height: 600});
// and load the index.html of the app.
/*mainWindow.loadURL(url.format({
pathname: path.join(__dirname, 'public/index.html'),
protocol: 'file:',
slashes: true
}));*/
mainWindow.loadURL("file://" + __dirname+ "/public/index.html");
// 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;
});
}
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();
}
});
Basically the thing is very simple. Electron acts just like a desktop chromium wrapper, that displays your any (any) type of web page inside desktop chromium.
So for example, we want to display http://www.google.com then you simply pass this URL to your loadURL() function.
Here is the working copy of code (asked in the question):
const electron = require('electron');
// Module to control application life.
const app = electron.app;
// Module to create native browser window.
const BrowserWindow = electron.BrowserWindow;
app.on('ready', function(){
var mainWindow = new BrowserWindow({width: 800, height: 600});
mainWindow.loadURL("http://localhost:8080/"); // option1: (loading a local app running on a local server)
//mainWindow.loadURL("https://iaya-664f3.firebaseapp.com"); // option2: (loading external hosted app)
// loading developer tool for debugging
mainWindow.webContents.openDevTools();
});
I hope this will clarify the confusion for many people, who are new to Electron. So the final words are that Electron only loads existing and running web application. It does not compile, does not act as a server. It is just a box in which you can put anything and give it a desktop sort of look e.g. menues, desktop notification, local file system access, etc.

Resources