saltyshiomix / nextron

⚡ Next.js + Electron ⚡
https://npm.im/nextron
MIT License
3.97k stars 229 forks source link

unable to bundle an extra .exe into a production build #147

Closed yoroshikun closed 3 years ago

yoroshikun commented 3 years ago

This issue is similar to #120

Hi, I have been trying to bundle an .exe file within the build. I have been successful in dev mode with the following code in the electron-builder.yml

  - from: "lib/watcher/watcher.exe"
    to: "app/lib/watcher/watcher.exe"

Referenced in the main process with

const execFilePath = join(app.getAppPath(), "lib", "watcher", "watcher.exe");

However this does not seem to work when building for release as the file is nowhere to be found nor in the asar file.

I have been able to bundle this .exe for production utilizing the __static feature that is available in the electron-webpack package however I cannot find a similar option for this project.

I would really love to use this project in production over the electron-webpack as it is a much cleaner resolution comparatively.
As of now this is the only thing that is preventing me from utilizing this project fully.

Thanks for any guidance in advance

Psycokwet commented 3 years ago

Hello ! My solution may not be the best, but I did bundle an exe ! But I recognise it's a weird solution haha.

If you put the exe into a node module (I have a script that replace a node module folder by another one I made. I also created the orignal node module, so I have control over whatever is in it in the first place anyway), it will get into the bundle. :)

yoroshikun commented 3 years ago

@Psycokwet

Thanks for the prompt reply!

Could you explain how you were able to reference the .exe and exactly what the module looked like, I tried your solution however did not have much luck, it is quite likely I am missing 1 line to reference it correctly.

Psycokwet commented 3 years ago

Hello again ! Yes, I can develop my answer. First of all, I did package an exe, and dll files like that. However, I had issues accessing the dlls files from the archives, so, I had to play with path while in production, to get access to the unzipped asar folder. I did the same with the exe, without knowing if it was affecting the exe the same way. Did you check if the exe got into the asar archive ?

If it did, maybe try to access to the unzipped exe ?

I used this package : https://www.npmjs.com/package/bassaudio-updated

I have one local version with a few lines modified to change the path like needed while in production.

Basically, from the bass file :


function getEndPath(osLibFolder) {
  return path.join(
    "app.asar.unpacked",
    "node_modules",
    "bassaudio-updated",
    "lib"
  );
}

function getBasePath(isDev) {
  const osLibFolder = getPlatformDependencies().path;
  if (isDev) return path.join(__dirname, "lib", osLibFolder);
  if (osLibFolder === "macOs")
    return path.join(
      "/Applications/Vestalive.app/Contents/Resources",
      // path.resolve(path.resolve(__dirname, ".."), ".."),
      getEndPath(),
      osLibFolder
    );
  else return path.join(process.cwd(), "resources", getEndPath(), osLibFolder);
}

function Bass(isDev) {
  const options = {};
  const platformDependencies = getPlatformDependencies();
  this.libFiles = platformDependencies.libFiles;
  // const basePath = path.join(__dirname, "lib", platformDependencies.path);

  this.basePath = getBasePath(isDev);
Psycokwet commented 3 years ago

I did add the exe in my local project, so it's absent from the package, but, I managed it the same way as my dlls

yoroshikun commented 3 years ago

Hi, I am only somewhat following, I'm uncertain what this means getPlatformDependencies().path;

even though I have the node module it is not being bundled into the asar no. I think it might be shaking it.

And it looks like it is required for the exe to be copied into the app dir before bundling. is there a way to inject that build step in between the normal steps that nextron build does?

other than that it might be possible if I inject the exe into the renderer static however I dont want to do so that if possible.

Psycokwet commented 3 years ago

getPlatformSependencies is a function in the package https://www.npmjs.com/package/bassaudio-updated

If you do a new project, install bassaudio-update, copy paste it in your main folder, add this script to your package.json :

    "dev": "npm run switchBassAudio && nextron",
    "build": "npm run switchBassAudio && nextron build",
    "switchBassAudio": "node bassaudioInstall.js"

bassaudioInstall.js

var fs = require("fs");
var path = require("path");

// directory path
const dir = path.join("node_modules", "bassaudio-updated");

// delete directory recursively
try {
  fs.rmdirSync(dir, { recursive: true });

  console.log(`${dir} is deleted!`);
} catch (err) {
  console.error(`Error while deleting ${dir}.`);
}

copyFolderRecursiveSync("bassaudio-updated", "node_modules");
console.log("bassaudio-updated module successfully updated");

function copyFileSync(source, target) {
  var targetFile = target;

  // If target is a directory, a new file with the same name will be created
  if (fs.existsSync(target)) {
    if (fs.lstatSync(target).isDirectory()) {
      targetFile = path.join(target, path.basename(source));
    }
  }

  fs.writeFileSync(targetFile, fs.readFileSync(source));
}

function copyFolderRecursiveSync(source, target) {
  var files = [];

  // Check if folder needs to be created or integrated
  var targetFolder = path.join(target, path.basename(source));
  if (!fs.existsSync(targetFolder)) {
    fs.mkdirSync(targetFolder);
  }

  // Copy
  if (fs.lstatSync(source).isDirectory()) {
    files = fs.readdirSync(source);
    files.forEach(function (file) {
      var curSource = path.join(source, file);
      if (fs.lstatSync(curSource).isDirectory()) {
        copyFolderRecursiveSync(curSource, targetFolder);
      } else {
        copyFileSync(curSource, targetFolder);
      }
    });
  }
}

You should have the dlls in the bundle, and whatever exe you add too in the pasted folder.

But yeah, it's a weird workaround

yoroshikun commented 3 years ago

Hi, thanks for the detailed report, I think I finally was able to do a similar ugly workaround by doing the following

  1. Create a new package with just the exe and package.json
  2. yarn link the new package then yarn link 'exe-package' in the electron
  3. ensure that the new package was refrenced in the production dependencies in the electron package.json
  4. update the path resolver to the following
    const execFilePath = isDev ? join(process.cwd(), "node_modules", "watcher", "watcher.exe") :
    join(process.cwd(), "resources", join(
    "app.asar.unpacked",
    "node_modules",
    "watcher",
    "watcher.exe"
    ));

I think i can avoid the yarn linking however that was the process I did.

I think a build step that can copy a file into the app folder during a build might solve a lot of these work arounds. I may look into a patch if i get time

Psycokwet commented 3 years ago

Great !

Yes I agree, i was thinking of doing something similar when I get the time too haha. It's pretty ugly otherwise !

Good to know it worked for you in the end !

yoroshikun commented 3 years ago

@Psycokwet I had another look to making a cleaner workaround and I found an option in the electron-builder to help

Now there is no need to make a package to trick the builder. Clean 2 line config update & easier resource referencing.

  1. Create a folder at the root of your project (I called mine 'lib') and place your .dll or .exe inside
  2. Update your electron-builder.yml to include the files
    files:
    - from: .
    filter:
      - package.json
      - app
      - lib < I added this
  3. Update your electron-builder.yml OS specific config with the asarUnpack config option documented Here Example:
    win:
    asarUnpack: "**/lib/*.exe" < This can be any glob as explained in the documentation
  4. You can reference the lib in your main (or possible render) like expected, remembering to replace app.asar with app.asar.unpacked when exists. Example (the same joint path should work in both prod and dev):
    const execFilePath = join(
    __dirname,
    "..",
    "lib",
    "your-program.exe"
    ).replace("app.asar", "app.asar.unpacked"); // asar only exists to replace in production build

I really hope this helps people who have the same issue with needing spawn able processes bundled and unpacked in nextron projects.

WizardsOrb commented 3 years ago

@yoroshikun thank you so much, worked properly for me.

Psycokwet commented 3 years ago

It seems like a great solution yes ! Thank your for the updated answer :)

Psycokwet commented 3 years ago

It worked like a charm ! Thank you, it's really a neat solution. I think maybe we can close this one ?

Psycokwet commented 3 years ago

Closing since no new answers since then :)