Document how to use file-loader correctly for renderer webpack configs
Assets are not getting loaded after making the binary using "yarn make" on all platform (windows/Mac/Linux). We are getting error "net::ERR_FILE_NOT_FOUND". Looks like Js files are pointing to the proper location as per the package.json configuration. but assets and source files are located in different location.
And we are seeing the same issue for component source which uses Lazy import statements
const Login = React.lazy(() => import('./screens/login'));
E.g on Mac
Trying to load the png file from
/Contents/Resources/app/.webpack/renderer/main_window/33dbdd0177549353eeeb785d02c294af.png
But files are available in.
/Contents/Resources/app/.webpack/renderer/33dbdd0177549353eeeb785d02c294af.png
package.json
"plugins": [
[
"@electron-forge/plugin-webpack",
{
"mainConfig": "./webpack.main.config.js",
"renderer": {
"config": "./webpack.renderer.config.js",
"entryPoints": [
{
"html": "./src/index.html",
"js": "./src/app.tsx",
"name": "main_window"
}
]
}
}
]
]
Sample Project https://github.com/jega-ms/electron-forge-react-typescript-webpack
File Listing
#1196 Might solve the asset issue you are having... it did for me.
my file-loader ended up looking like
{
test: /\.(png|svg|jpe?g|gif|webm)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[hash]-[name].[ext]',
outputPath: 'static',
publicPath: '../static',
},
},
],
}
After landing on this issue and doing some of my own trial and error, I've come up with a solution that works both in dev and packaged, and combines various observations from this and linked issues.
Webpack setup
If you're using webpack5 (and you should be if you used the template), you no longer need file-loader or copy-webpack-plugin. Instead, you should rely on webpack's native asset management.
// webpack.rules.js
// Or in both your webpack.renderer.js and webpack.main.js if you keep them completely separate
module.exports = [
// ... your other rules
{
// Note: I dont have `svg` here because I run my .svg through the `@svgr/webpack` loader,
// but you can add it if you have no special requirements
test: /\.(gif|icns|ico|jpg|png|otf|eot|woff|woff2|ttf)$/,
type: 'asset/resource',
},
]
Main process
Because Electron does magical things to an image based on the filename of the image, you should keep the original source file name in the output file name by configuring how webpack outputs asset names. Note that you can also do this for the renderer config if you prefer, but it's not a requirement since Chromium doesn't do anything magical based on the file name.
// `webpack.main.js`
module.exports = {
entry: './src/main/index.ts',
// ...other configs
output: {
// [file] is the key thing here. [query] and [fragment] are optional
assetModuleFilename: '[file][query][fragment]',
},
}
When using bundled files in the main process, make sure to resolve the bundled path:
// main/index.ts
import path from 'path';
import dockImagePath from './path/to/dockImage.png';
import trayImagePath from './path/to/trayImage.png';
// For example setting the dock icon
app.dock.setIcon(path.resolve(__dirname, dockImagePath));
// Or setting the tray icon
const tray = new Tray(
path.resolve(
__dirname,
trayImagePath,
),
);
Renderer Processes
Because you can have many entrypoints, the webpack output of each entry point is nested under a renderer folder. By default, webpack outputs assets in the root output folder (which will be this same renderer folder). So your output file structure (inside of the .webpack folder) looks something like
> main
- index.js
> renderer
> entryPointOne
- index.js
> entryPointTwo
- index.js
- imageHash1.png
- imageHash2.png
This means that in the outputted renderer/entryPointOne/index.js, we need to rewrite the image paths to one level above the index.js: ../. Note that this is essentially the same solve as the publicPath: '..' setting in file-loader from this and other related issues
// webpack.renderer.config.js
module.exports = {
// ...other configs
output: {
publicPath: '../',
},
}
Note that, like the main webpack config, you may change where webpack outputs its assets, but you still have to set the public path since it's outside of folder containing the index.js of each renderer entry point.
// webpack.renderer.config.js
module.exports = {
// ...other configs
output: {
publicPath: '../myAssetFolder',
// Note that here you are also keeping the original file name, but for the renderer you don't have to
// and can simply rely on the default value of [hash][ext][query]
assetModuleFilename: 'myAssetFolder/[file][query]',
},
}
Here's a caveman solution, for my fellow Neanderthals.
Base64-encode the image. Here's a handy tool for it: https://github.com/veler/DevToys
Create a file like images.ts/images.js:
// TODO: Figure out how to use file-loader or Webpack Asset Modules later. (If you're reading this two years later, congrats! Your temporary solution became permanent.)
export const iconImage512x512Base64 = "data:image/png;base64,iVBORw0KGgoAAAA(etc. etc.)=";
Then in your TSX/JSX:
{/* Don't worry. This is fine. */}
<img src={iconImage512x512Base64} />