Throughout this tutorial, we'll see how to use Electron and Vue.js to build cross-platform desktop apps for the major operating systems such as Windows, Linux, and MAC.

In a previous article, we have used Angular as the framework for structuring the code of our application. Now, we'll see how to use Vue.js instead.

Thanks to Electron, you can build fully-fledged desktop apps using web technologies only. No more Java or C++, plus you'll have access to the native APIs of the underlying system so you can integrate your app with any required system service.

A plethora of apps are being built using Electron, you can check some of them from this link.

Now, let's start building our demo desktop app with Vue.

Prerequisites

You can follow this tutorial comfortably if you have the following prerequisites:

  • First, Node and NPM are required for developing and building your application. You can download both of them from the official website. You can also use a package manager for installing Node in your system or better yet use nvm for installing and switching between multiple versions,
  • You also need to be familiar with JavaScript, HTML, and CSS,
  • You also need to have a working knowledge of Vue.js.

Introducing Vue.js

Vue.js is a modern JavaScript framework for building user interfaces. It's designed to be progressive and incrementally adoptable.

Vue deals with the view layer of your application but also provides many other third-party libraries for routing and state management.

You can use Vue to add a bit of structured JavaScript code to your app or you can build fully-fledged Single Page applications using modern features such as single-file components and a bunch of supporting libraries.

Installing Vue CLI

As we mentioned previously, you can incrementally use Vue.js in your project which means, you can start with a single <script> tag in your HTML document:

<script src="https://cdn.jsdelivr.net/npm/vue"></script>

If you want to build fully-fledged SPAs, you can use the Vue CLI which allows you to quickly generate a Vue project and work with it without needing to deal with complex configurations such as Webpack.

The Vue CLI has the following features:

  • Feature-rich: Vue CLI has support for Babel, TypeScript, ESLint, PostCSS, PWA, Unit Testing and End-to-end testing.
  • Extensible: Vue CLI provides a plugin system which allows developers to build and share reusable code for solving common web development problems.
  • No need to eject: Unlike create-react-app, the Vue CLI is fully configurable without the need for ejecting.
  • Future-ready: Vue CLI enables you to use native ES2015 code for modern browsers.

You can install Vue CLI from npm using the following command:

$ npm install -g @vue/cli

As the time of this writing, @vue/cli v3.10.0 is installed.

Note: You may need to add sudo before the previous command for installing packages globally in Linux or macOS or use a command prompt with administrator rights in Windows. You can also just fix your npm premissions. If you installed Node and NPM on your system using NVM, this will be automatically handled for you.

Creating a Vue Project

After installing the Vue CLI, let's proceed to create a Vue project. Open a new terminal and run the following command:

$ vue create electron-vue

You'll be prompted to pick a preset. You can choose either the default preset which has Babel and ESlint or you can select the needed features manually. Let's pick this last option which is Manually select features.

You'll be presented with many features that you can add to your project. Use an arrow to move to the Router feature and then press space to select it.

Next, you'll be prompted if you would like to Use history mode for router? You can say n for this.

Next, you'll be prompted to Pick a linter / formatter config. You can simply pick ESLint with error prevention only.

Next, for Pick additional lint features: Simply pick Lint on save.

Next, you'll be asked of Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? Choose In package.json.

Finally, you'll be prompted if you would like to Save this as a preset for future projects? (y/N) You can say N for this.

Note: You can freely choose the features and configuration options that suit your needs. None of the decisions above affect how you can integrate Electron with Vue.

The Vue CLI will create a project in the electron-vue project, initialize a git repository and install the Vue CLI plugins and npm dependencies.

Let's serve our application locally, just to make it works as expected:

$ cd electron-vue
$ npm run serve

You'll be able to visit your application using a web browser by going to the http://localhost:8080/ address. You should see the following page:

You can see that our app has already routing configured with two examples home and about pages.

Installing Electron into your Vue Application

After creating our Vue project, let's now install Electron in our Vue project using the following commands:

$ npm install --save-dev electron@latest

By using the --save-dev switch, Electron will be installed as a development dependency in your project.

As of this writing, electron v6.0.1 will be installed.

Bootstrapping the Electron App

After installing Electron, you need to add some code to bootstrap your Electron app and create a GUI window where the Vue app will be opened.

Go ahead and create a main.js file inside your Vue project and add the following code:

const { app, BrowserWindow } = require('electron');

const url = require("url");
const path = require("path");

let mainWindow

function createWindow() {
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true
    }
  })

  mainWindow.loadURL(
    url.format({
      pathname: path.join(__dirname, `./dist/index.html`),
      protocol: "file:",
      slashes: true
    })
  );
  mainWindow.on('closed', function () {
    mainWindow = null
  })
}
console.log(app);
app.on('ready', createWindow)

app.on('window-all-closed', function () {
  if (process.platform !== 'darwin') app.quit()
})

app.on('activate', function () {
  if (mainWindow === null) createWindow()
})

This code will allow us to spawn a GUI window using some built-in Electron APIs such as BrowserWindow and the loadURL() method which loads the index.html file from the dist folder.

Note: Both the dist folder and index.html file are not present until you build your Vue project for the first time

Next, you need to set the entry point of your project to the main.js file in the package.json file as follows:

{
  "name": "electron-vue",
  "version": "0.1.0",
  "private": true,
  "main": "main.js",
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  /* [...] */
}

Head over to the official docs for more information about the main key.

Next, you need a start script in the package.json file which can be used to build the Vue project and start the Electron application:

{
  "name": "electron-vue",
  "version": "0.1.0",
  "private": true,
  "main": "main.js",
  "scripts": {
    "start" : "vue-cli-service build && electron .",
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  // [...]
}

We can test if this is working by simply running the following command:

$ npm start

Your Vue app will be built in the dist/ folder and should be opened inside a native GUI window using Electron.

This is a screenshot of the result:

You can notice that Electron has failed to load many resources of your Vue app which prevents the app from displaying inside the GUI. Let's solve this.

The errors are due to how the various paths to the app resources are defined in the index.html file.

If you take a look at the dist/index.html file, you'll see something like the following:

<script  src=/js/chunk-vendors.e9885347.js></script>  
<script  src=/js/app.f0025aa3.js>  </script>

You can see that the resources files are supposed to be loaded from absolute paths so Electron will not be able to load them instead we need to change them to relative paths.

We can do that using a vue.config.js file.

The vue.config.js file is an optional configuration file that will be automatically loaded by @vue/cli-service if there is one in your project root where the package.json file. This will allow configuring many aspects of your project without dealing directly with the Webpack configuration file.

Make sure you are inside the root folder of your project and create a vue.config.js file:

$ touch vue.config.js

Next, add the following configuration option:

module.exports  = {
    publicPath: process.env.NODE_ENV  ===  'production'  ?  './'  :  '/'
}

This will tell Vue to use a relative path in production and an absolute path in development.

Now, re-run the npm start command again.

Electron will be able to load the Vue application correctly:

We already have a simple app with two pages and navigation between them.

We can click on the Home and About links to navigate between the home and about pages.

Typically, in a desktop application we have navigation in the top menu bar so let's change our application to be able to navigate from the menu.

We can create a menu using Menu.buildFromTemplate() method. First, go to the main.js file and import Menu as follows:

const { app, BrowserWindow, Menu } =  require('electron');

Next, define the createMenu() method as follows:

function createMenu() {

  var menu = Menu.buildFromTemplate([
      {
          label: 'Menu',
          submenu: [
              {label:'Home',
                click(){
                  console.log("Navigate to Home");
                }
            
              },
              {label:'About',                 
              
               click(){
                console.log("Navigate to About");
              }},
              {label:'Exit',                 
               click() { 
                app.quit() 
              }}
          ]
      }
  ])
  Menu.setApplicationMenu(menu); 
}

We first call the buildFromTemplate() method which takes a template which is simply an array of options for constructing a MenuItem.

The label key is used to specify the name for each menu in the top bar. Here, we simply create one menu with the Menu name.

The submenu key takes an array of items. Each item defines the sub-menu items that will be shown when you click on the label.

In our example, we create three menu items. The exit, home and about items.

Next, we call the setApplicationMenu() to set the menu for our application. This will replace the default menu which comes with Electron with our menu.

At this point, only the exit menu-item works, the home and about menu simply display a message in the console.

The Menu class exists in the main process while the Vue router used for navigation inside our Vue app exists in the renderer process so we'll need to use the Inter-Process communication.

Specifically, we need to call a renderer method from the main process i.e the click() method of the menu items. For this, we can use the webContents.send() method.

In your menu template, change the click() method of the home and about items:

      submenu: [
        {
          label: 'Home',
          click() {
            console.log("Navigate to Home");
            mainWindow.webContents.send('goToHome');

          }

        },
        {
          label: 'About',

          click() {
            console.log("Navigate to About");
            mainWindow.webContents.send('goToAbout');
          }
        },

We simply use the webContents.send() method to send a custom goToHome message when we click on the home item or a goToAbout message when we click on the about item.

Next, we need to listen for these messages in our renderer process i.e the Vue application.

Open the src/App.vue file and a <script> tag and the following code:

<script>
const electron = window.require("electron")


export default {
  mounted: function(){
    electron.ipcRenderer.on('goToHome', ()=>{
      this.$router.push('/');
    });
    electron.ipcRenderer.on('goToAbout', ()=>{
      this.$router.push('/about');
    });
  }
}

</script>

We first import electron using the window.require() method. Next, in the mounted() lifecycle method of the App component we call the ipcRenderer.on() method to listen for the goToHome and goToAbout messages sent from the main process. Depending on the message we navigate the user either to home or about pages using the $router.push() method.

That's it! We can now use our native menu to navigate inside our Vue app.

You can find the source code of this project from this GitHub repository.

You can also use vue-cli-plugin-electron-builder which allows you to integrate Electron with Vue and relieves you from doing all the previous configurations manually.

Conclusion

In this tutorial, we've introduced the Vue framework for building progressive web apps and the Electron platform for building cross-platform desktop apps with web technologies. We've also seen how to integrate Vue with Electron to create our example desktop application.

You can also check electron-vue, an Electron and Vue.js quick-start boilerplate that provides scaffolding using Vue CLI, common Vue plugins, electron-packager and electron-builder, unit and end to end testings, and vue-devtools, etc.

After developing your application, you need to package it for distribution which can be done using different tools such as electron-builder and electron-packager.