14

I'm struggling to understand how to correctly import ipcRenderer in a .vue file.

I put in /src/background.js file :

webPreferences: {
  nodeIntegration:false,
  contextIsolation: true, // protects against prototype pollution
  preload: path.join(__dirname, "../dist_electron/preload.js"),
}

And, based on https://nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration I put in preload.js :

window.ipcRenderer = ipcRenderer

webpack.config.js :

module.exports = {
  entry: './src/background.js',
  target: 'node',
  output: {
    path: path.join(__dirname, 'build'),
    filename: 'background.js'
  }
}

In order to facilitate the debugging, I created a github repo. You can git clone the repo from here: https://github.com/raphael10-collab/ElectronVueTypeScriptScaffolding.git

After executing yarn -> yarn electron:serve you will get the correct page.

But when activating in /src/views/Home.vue this line:

//import { ipcRenderer } from 'electron'

you will get this error:

__dirname is not defined

Environment Info:

  System:
    OS: Linux 5.4 Ubuntu 18.04.5 LTS (Bionic Beaver)
    CPU: (8) x64 Intel(R) Core(TM) i7-4790K CPU @ 4.00GHz
  Binaries:
    Node: 14.5.0 - ~/.nvm/versions/node/v14.5.0/bin/node
    Yarn: 1.22.4 - /usr/bin/yarn
    npm: 6.14.5 - ~/.nvm/versions/node/v14.5.0/bin/npm
  Browsers:
    Chrome: 85.0.4183.83
    Firefox: 79.0
  npmPackages:
    @vue/babel-helper-vue-jsx-merge-props:  1.0.0 
    @vue/babel-plugin-transform-vue-jsx:  1.1.2 
    @vue/babel-preset-app:  4.4.6 
    @vue/babel-preset-jsx:  1.1.2 
    @vue/babel-sugar-functional-vue:  1.1.2 
    @vue/babel-sugar-inject-h:  1.1.2 
    @vue/babel-sugar-v-model:  1.1.2 
    @vue/babel-sugar-v-on:  1.1.2 
    @vue/cli-overlay:  4.4.6 
    @vue/cli-plugin-babel: ~4.4.0 => 4.4.6 
    @vue/cli-plugin-e2e-cypress: ~4.4.0 => 4.4.6 
    @vue/cli-plugin-router: ~4.4.0 => 4.4.6 
    @vue/cli-plugin-typescript: ~4.4.0 => 4.4.6 
    @vue/cli-plugin-unit-mocha: ~4.4.0 => 4.4.6 
    @vue/cli-plugin-vuex: ~4.4.0 => 4.4.6 
    @vue/cli-service: ~4.4.0 => 4.4.6 
    @vue/cli-shared-utils:  4.4.6 
    @vue/component-compiler-utils:  3.2.0 
    @vue/preload-webpack-plugin:  1.1.2 
    @vue/test-utils: ^1.0.3 => 1.0.3 
    @vue/web-component-wrapper:  1.2.0 
    babel-helper-vue-jsx-merge-props:  2.0.3 
    typescript: ^3.9.7 => 3.9.7 
    vue: ^2.6.11 => 2.6.11 
    vue-class-component: ^7.2.5 => 7.2.5 
    vue-cli-plugin-electron-builder: ~2.0.0-rc.4 => 2.0.0-rc.4 
    vue-hot-reload-api:  2.3.4 
    vue-i18n: ^8.20.0 => 8.20.0 
    vue-loader:  15.9.3 
    vue-property-decorator: ^9.0.0 => 9.0.0 
    vue-router: ^3.2.0 => 3.3.4 
    vue-style-loader:  4.1.2 
    vue-template-compiler: ^2.6.11 => 2.6.11 
    vue-template-es2015-compiler:  1.9.1 
    vuex: ^3.5.1 => 3.5.1 
    vuex-class: ^0.3.2 => 0.3.2 
  npmGlobalPackages:
    @vue/cli: 4.4.6

node version: v14.5.0

Update 1)

I tried to set webPreferences as follows (with nodeIntegration: true) :

webPreferences: {
  nodeIntegration: true,
  //contextIsolation: true, // protects against prototype pollution
  //preload: path.join(__dirname, "../dist_electron/preload.js"),
},

and got this error:

fs.existsSync is not a function

Searching around for info about this kind of problem, I found this post: How to resolve fs.existsSync is not a function With this link: https://webpack.js.org/concepts/targets/

But I already specified in webpack.config.js the target ‘node’:

in webpack.config.js :

module.exports = {
  entry: './src/background.js',
  target: 'node',
  output: {
    path: path.join(__dirname, 'build'),
    filename: 'background.js'
  }
}

So... how to solve this new problem?

By the way, Why must I put

webPreferences: {
    nodeIntegration: true,
} 

if, for security reasons, it is more secure to have:

webPreferences: {
  nodeIntegration:false,
  contextIsolation: true, // protects against prototype pollution
  preload: path.join(__dirname, "../dist_electron/preload.js"),
}

dist_electron/preload.js :

const {
    contextBridge,
    ipcRenderer
} = require("electron");

// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld(
    "api", {
        send: (channel, data) => {
            // whitelist channels
            let validChannels = ["toMain"];
            if (validChannels.includes(channel)) {
                ipcRenderer.send(channel, data);
            }
        },
        receive: (channel, func) => {
            let validChannels = ["fromMain"];
            if (validChannels.includes(channel)) {
                // Deliberately strip event as it includes `sender` 
                ipcRenderer.on(channel, (event, ...args) =>   
func(...args));
            }
        }
    }
);

window.ipcRenderer = ipcRenderer

https://www.electronjs.org/docs/tutorial/security#electron-security-warnings

Update 2)

in vue.config.js I've put:

module.exports = {
  pluginOptions: {
    electronBuilder: {
      preload: 'dist_electron/preload.js',
      // Or, for multiple preload files:
      //preload: { preload: 'src/preload.js', otherPreload: 
      //'src/preload2.js' }
    }
  }
}

But I get the same error when I do

yarn electron:serve

UncaughtReferenceError: __dirname is not defined

When setting nodeIntegration: true (but I would prefer to set it to false, and use preload.js file), I get this other error (as above):

Uncaught TypeError: fs.existsSync is not a function

Uncaught TypeError: fs.existsSync is not a function

How to solve the problem? Looking forward to your kind help

Raphael10
  • 2,508
  • 7
  • 22
  • 50

3 Answers3

25

Updated Answer - Nodeintegration disabled and contextIsolation enabled

In order to use the ipcRenderer with Vue CLI plugin Electron Builder you need to first setup electron to utilize a preload.js file.

Inside your vue.config.js file you need to add the preload.js path like this:

// vue.config.js - project root

module.exports = {
  pluginOptions: {
    electronBuilder: {
      preload: 'src/preload.js',
      // Or, for multiple preload files:
      preload: { preload: 'src/preload.js', otherPreload: 'src/preload2.js' }
    }
  }
}

Next you need to updated your background.js file to use preload.js in the web preferences like this:

// src/background.js

const win = new BrowserWindow({
  width: 800,
  height: 600,
  webPreferences: {
    // Use pluginOptions.nodeIntegration, leave this alone
    // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
    nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
    contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
    enableRemoteModule: true,
    preload: path.join(__dirname, 'preload.js'),
  },
})

Note: nodeIntegration is disabled and contextIsolation is enabled by default

Once you have that complete you can create the preload.js file in your src directory.
With contextIsolation enabled you need to import the contextBridge along with ipcRenderer. Then you can can expose the ipcRenderer to your client.

Then add this to the file:

// src/preload.js

import { contextBridge, ipcRenderer } from 'electron'

// Expose ipcRenderer to the client
contextBridge.exposeInMainWorld('ipcRenderer', {
  send: (channel, data) => {
    let validChannels = ['nameOfClientChannel'] // <-- Array of all ipcRenderer Channels used in the client
    if (validChannels.includes(channel)) {
      ipcRenderer.send(channel, data)
    }
  },
  receive: (channel, func) => {
    let validChannels = ['nameOfElectronChannel'] // <-- Array of all ipcMain Channels used in the electron
    if (validChannels.includes(channel)) {
      // Deliberately strip event as it includes `sender`
      ipcRenderer.on(channel, (event, ...args) => func(...args))
    }
  }
})

Note: You need to make sure your preload.js file is in the src folder and not dist_electron

To test and make sure the preload file is working you can also create an alert in the preload.js file

// src/preload.js

import { contextBridge, ipcRenderer } from 'electron'

// Expose ipcRenderer to the client
contextBridge.exposeInMainWorld('ipcRenderer', {
  send: (channel, data) => {
    let validChannels = ['nameOfClientChannel'] // <-- Array of all ipcRenderer Channels used in the client
    if (validChannels.includes(channel)) {
      ipcRenderer.send(channel, data)
    }
  },
  receive: (channel, func) => {
    let validChannels = ['nameOfElectronChannel'] // <-- Array of all ipcMain Channels used in the electron
    if (validChannels.includes(channel)) {
      // Deliberately strip event as it includes `sender`
      ipcRenderer.on(channel, (event, ...args) => func(...args))
    }
  }
})

alert("It Worked!") // Remove this line once you confirm it worked

When you have verified that your preload script is working correctly, you can access the ipcRenderer from your vue app.

like this:

// src/App.vue

<template>
     \\ Some html
</template>

<script>
  export default {
    name: "App",
    methods: {
      test(){
        window.ipcRenderer.send(channel, args...) // or any other ipcRenderer method you want to invoke
    }
};
</script>

In electron you can listen for those events

// background.js
ipcMain.on(channel, (event, args) => {
  // Do stuff
});

Sources:

Original Answer

In order to use the ipcRenderer with Vue CLI plugin Electron Builder you need to first setup electron to utilize a preload.js file.

Inside your vue.config.js file you need to add the preload.js path like this:

// vue.config.js - project root

module.exports = {
  pluginOptions: {
    electronBuilder: {
      preload: 'src/preload.js',
      // Or, for multiple preload files:
      preload: { preload: 'src/preload.js', otherPreload: 'src/preload2.js' }
    }
  }
}

Next you need to updated your background.js file to use preload.js in the web preferences like this:

// src/background.js

const win = new BrowserWindow({
  width: 800,
  height: 600,
  webPreferences: {
    // Use pluginOptions.nodeIntegration, leave this alone
    // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/configuration.html#node-integration for more info
    nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
+   preload: path.join(__dirname, 'preload.js')
  }
})

Once you have that complete you can create the preload.js file in your src directory

Then add this to the file:

// src/preload.js

import { ipcRenderer } from 'electron'
window.ipcRenderer = ipcRenderer

Note: You need to make sure your preload.js file is in the src folder and not dist_electron

To test and make sure the preload file is working you can also create an alert in the preload.js file

// src/preload.js

import { ipcRenderer } from 'electron'
window.ipcRenderer = ipcRenderer

alert("It Worked!") // Remove this line once you confirm it worked

When you have verified that your preload script is working correctly, you can access the ipcRenderer from your vue app.

like this:

// src/App.vue

<template>
     \\ Some html
</template>

<script>
  export default {
    name: "App",
    methods: {
      test(){
        window.ipcRenderer.send(channel, args...) // or any other ipcRenderer method you want to invoke
    }
};
</script>

Sources:

Lateralus
  • 792
  • 9
  • 19
  • 1
    Worked for me but this is some crazy boilerplate... – Victorio Berra Nov 13 '20 at 17:47
  • 2
    This solution comes from the Vue CLI Plugin Electron Builder documentation. https://nklayman.github.io/vue-cli-plugin-electron-builder/guide/guide.html#preload-files It took a while to read through it, but it was pretty straight forward after reading it a few times. – Lateralus Nov 14 '20 at 01:42
  • Thanks, yeah that doc is helpful, I wish they would show an example of using the shared windowed object like you did, thats a bit of a gap in that section IMO. – Victorio Berra Nov 14 '20 at 02:49
  • 3
    not work `console.log( window.ipcRenderer)` = undefinded :| – A1Gard May 12 '21 at 00:54
  • @A1Gard Can you ask a new question with the issue you are experiencing and link it here. I will do my best to assist. – Lateralus May 13 '21 at 01:06
  • @Lateralus, this works great for on and send, but what about invoke? I can't figure out how to add it properly to preload.js in a way that allows the render process to receive a valid ressponse. – PJM Design Oct 31 '21 at 21:55
3

What worked for me was setting the electron window's contextIsolation to false.

So in your main.js wherever you create a BrowserWindow it would look like this:

const win = new BrowserWindow({
  webPreferences: {
    contextIsolation: false,
    preload: path.join(__dirname, 'preload.js'),
  },
})

And then in preload.js you can simply do

const { ipcRenderer } = require('electron')
window.ipcRenderer = ipcRenderer

And then you'll have access to ipcRenderer anywhere in your vue code.

It seems that in the current version of electron contextIsolation defaults to true, which makes the window that preload.js sees different from the one your vue app sees.

Marplebot
  • 61
  • 5
  • 1
    Don't do this, you are turning of security measures and using a non-recommended approach of deliberating the IPC use for the Renderer process, while it should to be able to use just same previously defined IPC names. – Rafael Tavares Sep 02 '21 at 19:26
0

You need to set nodeIntegration to true.

This enables NodeJs in the renderer process (i.e the front-end) so you can use stuff like fs (FileSystem) and other NodeJs-only features in your Vue code.

As ipcRenderer requires NodeJs's environnement too (__dirname is a global variable for NodeJs only), it needs to be activated.

Oussama
  • 402
  • 4
  • 5
  • Hi @Oussama!! Thank you for helping!! I updated my post with Update 1) – Raphael10 Aug 27 '20 at 12:20
  • Hi, yes you're correct for the preload.js solution, it's more secure but the downside is you're going to put logic inside public/ folder which is not very ideal. As for the error, since you're using `vue-cli-plugin-electron-builder`, i suggest you try adding your configuration inside a `vue.config.js` as stated here in the docs : https://nklayman.github.io/vue-cli-plugin-electron-builder/guide/guide.html#preload-files And, just to be sure, make sure you are running your app using `yarn electron:serve` and not in the browser. – Oussama Aug 27 '20 at 12:48
  • I've been using yarn electron:serve when trying to run my app. I updated my post with update 2). Thank you for helping. – Raphael10 Aug 27 '20 at 13:36