preload script not loaded in packaged app
Asked Answered
P

2

13

I've made a small Electron app which needs to load a preload js file.

When I start the app with electron ., it finds the file, but when the app is packaged, it doesn't.

The call is made here:

mainWindow = new BrowserWindow({
   width: 800,
   height: 600,
   webPreferences: {
     nodeIntegration: false, 
     nativeWindowOpen: true,
     webSecurity: false,
     preload: path.join(__dirname, 'preload.js')  
  }
})

My simplified package.json:

"name": "app",
"version": "1.0.0",
"main": "main.js",
"scripts": {
  "start": "electron .",
  "build": "electron-packager . --platform=win32 --arch=x64 --overwrite"
 }
"devDependencies": {
  "electron": "^1.8.4",
  "electron-packager": "^12.0.1",
}

My project structure:

- node_modules

- main.js

- preload.js

- package.json

I've checked the result of the path.join and in both cases, the path is correct, and the file is there.

Poppied answered 17/4, 2018 at 10:2 Comment(3)
Could you try replacing path.join(__dirname, 'preload.js') with './preload.js'Parathion
Already tried this ! Tried many paths and seems not be the problem... ^^'Poppied
First, do you get a specific error when running the packaged app? Second, does your app have a webview? If so, rather than passing the preload script to the BrowserWindow constructor, try adding an attribute to the webview tag: preload="preload.js" (assuming the file is adjacent to the page you're loading in your window)Parathion
L
8

For peoples using Electron Forge webpack typescript boilerplate :

  1. Add the preload key in package.json file:
{
  "config": {
    "forge": {
      "plugins": [
        [
          "@electron-forge/plugin-webpack",
          {
            "mainConfig": "./webpack.main.config.js",
            "renderer": {
              "config": "./webpack.renderer.config.js",
              "entryPoints": [
                {
                  "html": "./src/index.html",
                  "js": "./src/renderer.tsx",
                  "name": "main_window",
                  "preload": {
                    "js": "./src/preload.ts"
                  }
                }
              ]
            }
          }
        ]
      ]
    }
  }
}

Preload script can be a typescript file.

  1. Add MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY constant as value for preload:
// Tell typescript about this magic constant
declare const MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY: any;

// [...]

  const mainWindow = new BrowserWindow({
    height: 1000,
    width: 1500,
    webPreferences: {
      preload:  MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,
    }
  });
  1. In preload.ts write :
import {
  contextBridge,
  ipcRenderer
} from 'electron';

contextBridge.exposeInMainWorld(
  'electron',
  {
    doThing: () => ipcRenderer.send('do-a-thing')
  }
)
  1. Add index.d.ts file, write :
declare global {
  interface Window {
    electron: {
      doThing(): void;
    }
  }
}
  1. Start your app, in your dev console you can type electron and view it is defined.

  2. BONUS: getting the typing right for the contextBridge exposed API:

Why a separated fie ? Not sure if needed, but I prefer not having to import an interface from a file that contain main process code (like preload.ts) in renderer process.

// exposed-main-api.model.ts

export interface ExposedMainAPI {
  doThat(data: string): Promise<number>;
}
// index.d.ts

declare global {
  interface Window {
    electron: ExposedMainAPI
  }
}
// preload.ts
import {
  contextBridge,
  ipcRenderer
} from 'electron';

const exposedAPI: ExposedAPI = {
  // You are free to omit parameters typing and return type if you feel so.
  // TS know the function type thanks to exposedAPI typing. 
  doThat: (data) => ipcRenderer.invoke('do-that-and-return-promise', data)
};
// note: this assume you have a `ipcMain.handle('do-thing-and-return-promise', ...)` 
// somewhere that return a number.

contextBridge.exposeInMainWorld('electron', exposedAPI);

Credits:

See also:

Levania answered 20/4, 2021 at 9:48 Comment(0)
D
6

Preload script needs to be specified as an absolute path. Thus it will differ from the time you're running it in development versus running it packaged as an asar file.

const getSourceDirectory = () => isDev()
    ? path.join(process.cwd(), 'build', 'src') // or wherever your local build is compiled
    : path.join(process.resourcesPath, 'app', 'src'); // asar location

const preload = path.resolve(getSourceDirectory(), 'preload.js');
Daina answered 10/2, 2020 at 12:21 Comment(1)
it worked for me in development because i knew the path but doesn’t work in production as there’s no preload.js there as i use electron-forge which uses webpack which bundles all files into a single file. how do i do it? i have written more about my use case here on a Github issueEccrine

© 2022 - 2024 — McMap. All rights reserved.