electron-userland / electron-webpack

Scripts and configurations to compile Electron applications using webpack
https://webpack.electron.build/
903 stars 170 forks source link

Images not showing up in output folder #452

Open zachwvk opened 2 years ago

zachwvk commented 2 years ago

I've spent a more than a day debugging why my images and sound files were accessible in development but did not appear in the dist folder. Finally I have a "Fix" which is working for me, but I don't understand why is was necessary. Bellow is what I put into my webpack.renderer.js file.

Can anyone explain why the "url-loader" was being used for sound and image files? I'm not experienced in any of this, but because I had this issue is seems to me that this is a bug in electron webpack?

//webpack.renderer.js

var webpack = require('webpack')
fs = require('fs');

function replacer(key, value) {
  //RegExp would not print in a useful way without this
  if (value instanceof RegExp) {
    return value.toString();
  }
  return value;
}

module.exports = function(config) 
{
  fs.writeFileSync("webpack.renderer.before.js", JSON.stringify(config, replacer, 4));

  // replace image loader rules
  config.module.rules[6] = {
      test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
      use: {
          loader: 'file-loader',//"url-loader", why was this the default?
          options: {
              //limit: 10240,
              name: "imgs/[name]--[folder].[ext]"
          }
      },
  }
  // replace sound loader rules
  config.module.rules[7] = {
      test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
      use: {
          loader: 'file-loader',//"url-loader", why was this the default?
          options: {
              //"limit": 10240,
              name: "media/[name]--[folder].[ext]"
          }
      },
  }

  fs.writeFileSync("webpack.renderer.after.js", JSON.stringify(config, replacer, 4));

  return config
}
loopmode commented 2 years ago

Hi.

The url-loader works something like this: "if the file is smaller than the limit, behave just like file-loader and bundle the file into the javascript bundle. if the file is bigger, leave it as-is and load it at runtime". (see "url-loader works like file-loader, but can return a DataURL if the file is smaller than a byte limit." at https://v4.webpack.js.org/loaders/url-loader/) The reason for this is that base64 will always be at least 33% larger (more bytes) than the original, see https://developer.mozilla.org/en-US/docs/Glossary/Base64#encoded_size_increase, and especially in web apps, you won't see anything until the full bundle is loaded, parsed etc. You're slowing down the start and execution of your actual javascript etc.

What you have done now is definitely bundle it into the javascript by using file-loader, always making a base64. This works in your case, but you produce very large bundles that need to be parsed, which hits on the performance at some point.

The way you should handle this in electron-webpack is using the "static assets" feature: https://webpack.electron.build/using-static-assets However this comes with its own set of quirks. The idea is: Any files that are not code should not be part of code bundles. Instead they should be treated as static files ("as-is", no costly processing by webpack). So you would place your files into a folder "static" in your project (next to your src, not inside of it!). When building your app for production, this folder will be simply copied into the resulting "asar" file (the archive used by electron/chromium to contain your app). In your code, you would use the magic global __static provided by electron-webpack when referencing those files, The __static global is supposed to give you the correct path to your static asset. However, aas you can find out by searching for "static" in the issues here, it's .. somewhat buggy, or at least it doesn't provide out-of-the-box handling for both development and production. But you could use a helper like this one:

import path from "path";
import * as url from "url";

const isDevelopment = process.env.NODE_ENV !== "production";

// see https://github.com/electron-userland/electron-webpack/issues/241#issuecomment-582920906
export default function getStatic(relativePathToAsset = "") {
  if (isDevelopment) {
    return url.resolve(window.location.origin, relativePathToAsset);
  }
  return path.resolve(__static, relativePathToAsset);
}

// e.g. react <audio src={ getStatic('sounds/sound.mp3') } />
// e.g. js document.write(`<img class="logo" src="${getStatic("electron.png")}" />`);

What it does is: In development, it returns a URL (with http: etc) pointing to the file as hosted by webpack-dev-server, and at production, it returns a local file: path to file inside aforementioned asar file, at the installed location of your app.

Sounds complicated, but I totally recommend doing it that way, and reverting your changes up there. The files in your case are definitely static assets and should be treated as such. This is also the only scalable way. If you keep your config and bundle them into the source, the development process will become slower and slower, as webpack has to process all those files (maybe 100x, maybe 1000x more bytes than your actual code...)

You should check out this example project i set up once: https://github.com/loopmode/electron-webpack-static-examples It showcases a couple ways of using static files with electron-webpack practically, working around quirks.