Building a Desktop App with Electron and Vue.js
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.
What is Vue.js?
Vue.js is a JavaScript framework known for its simplicity and flexibility in building interactive user interfaces. With features like reactive data binding, component-based architecture, and official routing and state management libraries, Vue.js offers a user-friendly approach for efficiently creating modern web applications.
What is Electron?
The Electron app framework enables the development of cross-platform desktop apps utilizing HTML, CSS, and JavaScript. It uses the Chromium rendering engine and Node.js runtime for native-like performance on Windows, macOS, and Linux. Electron grants access to native desktop features and a diverse range of plugins, simplifying the development of modern desktop applications.
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 operating system service.
A plethora of apps are being built using Electron, you can check some of the Electron apps from this link.
Now, let's start building our demo desktop app with Vue.
How to get started? Prerequisites
You can follow this tutorial comfortably if you have the following prerequisites:
- Our first step should be getting Node and NPM. 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.
Introduction to Vue.js
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:
html<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: supports Babel, TypeScript, ESLint, PostCSS, PWA, Unit Testing and End-to-end testing.
- Extensible: 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: enables you to use native ES2015 code for modern browsers.
To get started, you can install the CLI from npm using the following command:
bash$ 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 new Vue Project
After installing the CLI, let's proceed to create a Vue project. Open a new terminal or Visual Studio Code and run the following command:
bash$ 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 command line interface plugins and npm dependencies.
Let's serve our application locally, just to make it works as expected:
bash$ 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:
Image loading...
You can see that our app has already routing configured with two examples home and about pages.
Installing Electron into your new Vue Application
After creating our project, let's now install Electron in our Vue project using the following commands:
bash$ 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 Application
After the installation is complete, you need to add some code to bootstrap your Electron application 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:
javascriptconst { 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 andindex.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:
json{ "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:
javascript{ "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:
bash$ 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:
Image loading...
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:
html<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:
bash$ touch vue.config.js
$
Next, add the following configuration option:
bashmodule.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:
Image loading...
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:
jsconst { app, BrowserWindow, Menu } = require('electron');
Next, define the createMenu()
method as follows:
jsfunction 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:
jssubmenu: [ { 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:
js<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.
Let's sum it up
In this tutorial, we've introduced the Vue framework for building progressive web apps and the Electron for building cross-platform desktop apps with web technologies such as HTML, CSS. 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.