Open erguotou520 opened 6 years ago
@erguotou520 this won't work as the second renderer file isn't being loaded up the the html file that electron-webpack
creates. #47 could provide you with some alternatives
If you want this because you want multiple windows you can achieve that with routing, basically each window will load the same html file, but you'll put a ?route=***
query parameter or something to instruct the renderer on which page/component it should load. Basically you need a router.
I've published this very simple router for solving the problem: https://www.npmjs.com/package/react-router-static, just add a ?route=***
parameter to the url and you're set.
The way webpack is configured makes it really difficult to modify the configuration, especially for multiple entry points. A callback would be nice where we could modify or replace options, webpack-merge's smart just isn't enough :(
My workaround for multiple entries: electron-webpack.json:
{
[...]
"renderer": {
"sourceDirectory": "src/renderer",
"webpackConfig": "webpack.renderer.additions.js"
}
}
webpack.renderer.additions.js:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const configuration = {
plugins: [],
entry: {}
};
const addChunk = (entry, renderer, addHtmlFile) => {
configuration.entry[entry] = [
"css-hot-loader/hotModuleReplacement",
`.\\src\\renderer\\${renderer}`
];
if (addHtmlFile) {
configuration.plugins.push(new HtmlWebpackPlugin({
"template": "!!html-loader?minimize=false&url=false!dist\\.renderer-index-template.html",
"filename": `${entry}.html`,
"hash": false,
"inject": true,
"compile": true,
"favicon": false,
"minify": false,
"cache": true,
"showErrors": true,
"chunks": ["theme", entry],
"excludeChunks": [],
"chunksSortMode": "auto",
"meta": {},
"title": `Chunk ${entry}`,
"xhtml": false,
"nodeModules": "node_modules"
}));
}
}
addChunk("theme", "theme.ts", false);
addChunk("app", "index.tsx", true);
addChunk("login", "login.tsx", true);
module.exports = configuration;
You have to watch out for styles though, since the MiniCssExtractPlugin
options are set to generate a styles.css
file. This will crash if you import any styles in more than one entry point. Since we cannot modify the options i added another chunk called theme
that contain all my styles. Not really happy with the setup but i guess it works for now.
@theuntitled Would you be willing to share a full example? I'm trying to have multiple renderers, to set as BrowserView in the main BrowserWindow. Isn't clear to me how to load them. Thx!
Please also revisit this after the next release - there will be a webpack config callback function, and you'll be able to mutate the config any way you like. Not sure how the rest of the system behaves when there are multiple entry points, but I guess we'll see :)
Hi there, I'm trying to use set up PDF.js in my Electron application. The recommended approach is to provide a second entry point for the worker process, which obviously isn't working here. It sounds like the upcoming release may help, but I figured I'd provide a concrete use case.
got same problem. does it has a good way to fix it
There’s a merged pull request which allows loading multiple entry points but there hasn’t been a release with it yet.
@walleXD we had a couple of releases these last days. 2.7.0 is out with several merged PRs, plus two fixes up to 2.7.2. I'm not sure which merged PR you are referring to - is it #254 - Support function as custom webpack config?
@loopmode Yup. That pretty much opens up the door for quite a bit of flexibility @erguotou520 Give the latest update a shot with additional entry points
Managed to inject a second renderer entry point by using the new feature @loopmode mentioned above. Specifically: the solution below uses a function to customize the default renderer webpack config supplied by electron-webpack. This should be expandable to any number of renderers you might want. It compiles successfully and bundles linked assets as expected.
(Sharing here in case someone ends up here via Google like I did.)
package.json
{
"name": "foo-bar",
"electronWebpack": {
"renderer": {
"webpackConfig": "webpack.renderer.transformer.js"
}
},
"dependencies": {
(…)
}
(…)
}
webpack.renderer.transformer.js
(or whatever you decide to call this)
module.exports = function(context) {
// Fix filename clash in MiniCssExtractPlugin
context.plugins.forEach((plugin) => {
if (plugin.constructor.name === "MiniCssExtractPlugin") {
plugin.options.filename = '[id].styles.css';
plugin.options.moduleFilename = (name) => {
return '[id].styles.css';
};
}
});
// Add entrypoint for second renderer
context.entry.secondRenderer = ['./src/renderer/second-renderer.js'];
return context;
};
Pretty straightforward, this basically boils down to monkeypatching the default webpack config supplied by electron-webpack. As @theuntitled mentioned, the MiniCssExtractPlugin
will throw a fit if you import styles in more than one entry point. We fix this by changing the filename format from styles.css
to [id].styles.css
– this will prefix the filename with a chunk ID.
Quick note: if you previously relied on extending the webpack config by passing an object to electronWebpack.renderer.webpackConfig
– for example to bundle images through file-loader – you can't combine that with this method. Instead, you'll have to manually apply the changes you need in the transformation function above.
Note that unlike with the "default" renderer entry point, you won't get a free HTML file out-of-the-box after doing this. I have yet to figure that part out myself, so I'm leaving that as an exercise for the reader. 😉
"We fix this by changing the filename format from styles.css
to [id].styles.css
– this will prefix the filename with a chunk ID."
We should do this by default! It won't bother any regular use case, and it will take the pain away from this one.
Ah, @DriesOeyen and interested reader, the second argument to the config function is the configurator itself. And as far I remember, it has a method for generating the index.html template - with some snooping around the source code, you might be able to reuse it for additional entry templates! But that's a different story and would be worth a separate issue.
Edit: the method is in RendererTarget
and that is currently not accessible via the configurator instance. Ideas and PRs welcome!
@loopmode Thanks for sharing! I ended up rolling my own with html-webpack-plugin
, similar to how it's done for the "main" renderer by electron-webpack
.
One remaining gotcha I ran into: the "main" renderer's auto-generated HTML page started importing my secondary renderer's scripts and stylesheets. I solved that by specifying the right chunk in the options electron-webpack
sets on the HtmlWebpackPlugin
it uses internally:
context.plugins.forEach((plugin) => {
// Ensure other renderers' scripts and styles aren't added to the main renderer
if (plugin.constructor.name === "HtmlWebpackPlugin") {
plugin.options.chunks = ['renderer'];
}
});
Likewise, if you're using another HtmlWebpackPlugin
to auto-generate a HTML file for your second renderer you want to use the chunks
option to prevent it from importing the scripts and stylesheets of your "main" renderer.
Yeah good job there! Seems we could do this "properly" out if the box, but we should put some thinking into that first. If we do officially support multiple entries, we need to make sure to delegate the right things to the right place. There might be even more dragons along the way. Maybe after making these experiences you'll be able to provide a PR, or we could work on something together. No hurries, see how it goes, but I'm looking forward to more findings you make.
And BTW, I was thinking about just cloning the entire configured plugin and just adjusting fields like the chunk. That would reuse the currently available logic, including custom template file etc.
Ah. And now that I think about it... If we did support multiple entries..I guess we'd need to rethink some mechanisms like custom template via electronWebpack config - obviously we'd need a way to specify a custom template per entry etc.. sounds complicated.
Maybe the best way is to not really change the current code, but to instead provide a helper function that does exactly what you are doing, on top of the current system..
@DriesOeyen What i wonder is.. what's your use case? Why do you actually need separate entries? I understand separate entries as useful in web development, but I don't quite understand the necessity in an electron app.
I always figured it would be enough to use the very same entry, then pass something to the (invisible) URL, like #entry-a
and #entry-b
, or ?entry=a
and ?entry=b
.
Then evaluate that in renderer/index.js
, and decide whether to include and bootstrap e.g. src/renderer/app-a
or src/renderer/app-b
.
On the other hand, I've only built a single app with electron, so I'm really curious.
@DriesOeyen What i wonder is.. what's your use case?
Yeah I also see no practical use for this. Do you just want multiple windows or actually multiple entry points?
The only reason I can think about for having multiple entry points would be to speed up incremental build times if you have a very big app with multiple windows each hosting a lot of logic.
But in most situations you'll be better off just defining a dll bundle to use during development.
I'm currently experimenting with an app-launcher setup where react-router sits in the root, and there are multiple "apps" for multiple routes, each with a lazy import. I hope I will find some time to stay on it as it looks promising. I wonder at what point performance becomes a factor and what can be done with e.g. DLLs, HardSourceWebpackPlugin, CacheLoader, ThreadLoader etc. (And whether that all is still possible or necessary with the latest webpack). I believe it's worth going that way, rather than the multi entry way.
But then again.. it all depends on the use case.
Fair points, @loopmode and @fabiospampinato. I'm migrating to electron-webpack from a simpler build toolchain and simply hadn't considered that I could achieve what I needed with a single entry point. Indeed, this turned out to be the superior option since it doesn't require messing with electron-webpack's default behavior.
My use-case is a fairly simple multi-window situation: my app supports 2 kinds of windows. I now load index.html
for both BrowserWindows, but pass webPreferences.additionalArguments: ['--second-window']
to the second kind and then load and bind the corresponding UI in the renderer's index.js
based on the presence of that flag.
Thanks for the pointers! 🙌
@DriesOeyen not sure whether you're working with react, but check out the project I started. I'm trying to implement everything I learned from the issues of the last year. https://github.com/loopmode/react-desktop
@loopmode Sorry, Vue here. 😄
@fabiospampinato Multiple entry points for renderer processes would be 'nice.'
I am working on project that is not 'view' focused and will have some windows where nodeIntegration is true and other windows where nodeIntegration is false. There are three primary views: a login screen, chat application, administrator panel. I did not implement a SPA framework because it would require additional learning from my team that is familiar with an Asp.Net stack. TypeScript, Electron, Node.js, etc are all new to them. To use electron webpack, I expect I will be putting the html and css files in the static folder to make this work.
The other tasks include 'listeners' and screen recorders that all sit in their own renderer process. There is a lot happening behind the scenes. It would 'nice' to keep it simple by having each html page have it's own js file specific to that html files purpose.
I guess what I am looking for is in the Core Concept section of the electron-webpack documentation would be highlighting that electron-webpack is designed to work with a SPA framework where different windows will route to parts of a single page. It's a fair assumption, but some may be approaching electron-webpack without a SPA.
If using a SPA is the assumption, single renderer entry point is a good choice and maybe add an explanation in the documentation for the reason of a single entry point ( Would that have helped @DriesOeyen?). If it's not the assumption, I would think about changing it.
Appreciating electron-webpack and this discussion!
I've published this very simple router for solving the problem: https://www.npmjs.com/package/react-router-static, just add a
?route=***
parameter to the url and you're set.
@fabiospampinato - just stumbled upon your suggestion, works perfectly for me. Thank you posting it.
If we are still looking for use-cases then I think the many git hub stars and instant comment activity elicited by this post is proof that people have many use-cases for multi render entries. In those comments, they reference point #3 in this post and I've found this more in-depth and recent post echoing the need for this strategy. If the rest of my post below doesn't have a simpler solution that I haven't found yet then a more in-house and better-documented strategy from electron-webpack then @DriesOeyen above would be of great help. I am not sure how to incorporate @DriesOeyen 's solution into my own project that already has a webpack.renderer.additions.js.
My work's use-case stems from not wanting to rewrite backend express server code for installs of our app that aren't networked to a server or that want the increased physical security of running on an air-gaped(no network connection) machine. We need to give our users a copy of the express server to be run in a hidden electron browser window which gives us a new thread and uses local storage(which is why web workers won't work) instead of a server's database. Many installs won't even have access to a network. We need the extra process/thread spawned by electron when it opens a browser window to offload computationally intensive tasks to a thread that won't hang the UI. We can literally just copy-paste our server code into the index.js file of this new hidden browser window and only have to write a small service to configure the networking/no-networking details. This will reduce our workload by a lot and let us focus on giving the user networking options and pretty UI and graphics things in the App.
We love electron-webpack and have been using it for a year now with a kludge that has webpack start and stops our express server for us. However, it's very kludgy and only set up for development ease. We need a more electron-webpack natural solution that is easier to make an install-package out of and hot-reloading of our server code would be AWESOME too. Otherwise we might need to either drop electron-webpack or make two separate electron-webpack apps. One for UI development and one for server development then glue them together into an install (which sounds like a testing and install nightmare).
I AM NOT AN EXPERT AT ANY OF THIS. I have been clumsily leading my team through all of this and I am only a mid-level dev without even much web-dev experience. I am pretty out-of-my-depth here so if anyone has a 'no-duh' solution I haven't seen please let me know. React routing is not out of the question but I am not sure what that entails or if it will get us our new process/thread to run on (and hot-reloading).
@DriesOeyen @loopmode I tried to just implement @DriesOeyen solution in my webpack.renderer.additions.js file by converting it to function notation and adding in his css function. I know so little about webpack besides how to get it running for my works project that I hope if I share my webpack.renderer.additions.js file it would be obvious what I am misunderstanding. As said in my post above this one. I just need another window to open and run my server express code:
webpack.renderer.additions.js
//webpack stuff
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
//var DashboardPlugin = require("webpack-dashboard/plugin");
//Cesium stuff
const path = require('path'); // The path to the CesiumJS source code
const cesiumSource = 'node_modules/cesium/Source';
const cesiumWorkers = '../Build/Cesium/Workers';
const cesiumMissingImagesFolder = 'static/Images';
const CopywebpackPlugin = require('copy-webpack-plugin');
const { emit } = require('process');
module.exports = function(context){
//context: __dirname,
context.entry.secondRenderer = ['./src/common/server.js']
context.module= {
unknownContextCritical: false,
rules: [
{
test: /\.(glb|gltf)$/,
use: 'url-loader'
}
]
}
context.output= {
// filename: 'index.js',
// path: path.resolve(__dirname, 'dist'),
// Needed to compile multiline strings in Cesium
sourcePrefix: ''
}
context.amd= {
// Enable webpack-friendly use of require in Cesium
toUrlUndefined: true
}
context.node= {
// Resolve node module use of fs
fs: 'empty'
}
context.resolve= {
alias: {
// CesiumJS module name
cesium: path.resolve(__dirname, cesiumSource)
}
}
context.plugins= [
//new DashboardPlugin(), // --uncomment after 'yarn add'ing webpack-dashboard for pretty webpack console output
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
// Copy Cesium Assets, Widgets, and Workers to a static directory,
new CopywebpackPlugin([ { from: path.join(cesiumSource, cesiumWorkers), to: 'Workers' } ]),
new CopywebpackPlugin([ { from: path.join(cesiumSource, 'Assets'), to: 'Assets' } ]),
new CopywebpackPlugin([ { from: path.join(cesiumSource, 'Widgets'), to: 'Widgets' } ]),
new CopywebpackPlugin([ { from: 'src/renderer/models', to: 'models', flatten: true }]),
new webpack.DefinePlugin({
// Define relative base path in cesium for loading assets
CESIUM_BASE_URL: JSON.stringify('')
})
]
context.plugins.forEach((plugin) => {
if (plugin.constructor.name === "MiniCssExtractPlugin") {
plugin.options.filename = 'main.main.css';
plugin.options.moduleFilename = (name) => {
return 'main.main.css';
};
}
});
context.devServer= {
contentBase: path.join(__dirname, "dist"),
stats: {
colors: true,
hash: false,
version: false,
timings: false,
//assets: false,
//chunks: false,
//modules: false,
//reasons: false,
//children: false,
//source: false,
//errors: false,
//errorDetails: false,
warnings: false,
//publicPath: false
}
}
return context;
}
Now nothing really loads in either electron window and new output error: ERROR in ./src/renderer/index.ts 56:8 Module parse failed: Unexpected token (56:8) You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders | var scene = viewer.scene; | class Mission {
data: any;
tracks: any; @ multi css-hot-loader/hotModuleReplacement ./src/renderer/index.ts renderer[1]
Hi @ThisNameNotUsed That's a lot of info, and I must admit that I didn't try to redroduce and I'm not trying to directly follow up (yet). Instead, what I did is I cloned a fresh electron-webpack-quick-start and added a very simple express app to it.
Everything is sketchy, I was just copy-pasting stuff from some tutorials or gists, there's duplication and hard-coded things, but it's a proof of concept.
https://github.com/loopmode/electron-webpack-quick-start/tree/express
Here's essentially the diff to the regular quick start example: https://github.com/loopmode/electron-webpack-quick-start/commit/13a8b4745b22ac3755e6e8f6bb3ae84fdc7ef98c
Some learnings:
Creating a renderer process for the express is a good way to go. It's basically a hidden window, and the express app runs in it.
So the main/index creates two windows now, the mainWindow
and expressWindow
. For debuggins, you might use show: true
on the express window and use the console and debugger.
In order to distinguish the two renderer entries - the usual app and the express app - I use the hash #express
in the URL.
Thus, the renderer/index.js
was moved to renderer/renderer.js
(the "old renderer"), and I replaced it by a module that acts as a switch:
// renderer/index.js
if (window.location.hash === '#express') {
require('./express')
}
else {
require('./renderer')
}
And in main/index.js
, when loading up the express window, I specify the hash:
if (isDevelopment) {
window.loadURL(`http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}/#express`)
}
else {
window.loadURL(formatUrl({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file',
slashes: true,
hash: 'express'
}))
}
Using this workflow, you still get nice benefits like hot reloading for the express app itself (not for templates/views, see next).
It seemed hard to make the templates work in both development and production build, unless using the __static
helper from electron-webpack. However, using that, it became easy.
app.set("views", path.join(__static, "./express/views"));
onceSince the renderer itself is running on ELECTRON_WEBPACK_WDS_PORT
and express on another, you need to enable cors
Here's the express app basically:
(function () {
"use strict";
let path = require("path");
let express = require("express");
let cors = require("cors");
let app = express();
app.use(cors());
app.set("view engine", "ejs");
app.set("views", path.join(__static, "./express/views"));
app.get("/", function (req, res) {
res.render("pages/index", {
title: "Hello from Express",
time: new Date().toLocaleString()
});
});
app.get("/about", function (req, res) {
res.render("pages/about", {
title: "Another page",
time: new Date().toLocaleString(),
});
});
app.get("/api/status", function (req, res) {
res.send({ status: "ok", time: Date.now() });
});
let server = app.listen(8080, function () {
console.log("Express server listening on port " + server.address().port);
});
module.exports = app;
})();
The "old" renderer app is basically the one from the quick start, however, the two buttons do different things: One makes a fetch
to the /api/status
endpoint of our express server, which I consider one use case.
The other use case and button are to display a page hosted by the express server, using some templating engine (I used ejs
here).
You'll need some more sophisticated setup for the express port - I simply hard-coded 8080
to get this working.
The production app will try to open up a server on that port - not sure how the host OS responds to that (firewalls, security etc). On windows, I was asked by the typical firewall prompt. After giving permission, it worked fine.
You could clone and run the example repo and see if it runs. (Only tested on Windows), You could then add/replicate things and see when exactly it breaks.
I have tried to add
electron-webpack.yml
and the
custom.webpack.renderer.js
content like this, but compile failed