loading...
Cover image for Building a Desktop App with Vue: Electron

Building a Desktop App with Vue: Electron

n_tepluhina profile image Natalia Tepluhina Updated on ・8 min read

Desktop applications with Vue (3 Part Series)

1) Building a Desktop App with Vue: Vuido 2) Building a Desktop App with Vue: Electron 3) Building a Desktop App with Vue: NW.js

In my previous article I described building a desktop application with Vue framework using Vuido. It's a great library to create a fast and small-sized desktop app with native platform components but it has its own downsides like no CSS styling or images support. Now it's time to try Electron to build a desktop app.

Electron is an open source library developed by GitHub for building cross-platform desktop applications with HTML, CSS, and JavaScript. Electron accomplishes this by combining Chromium and Node.js into a single runtime and apps can be packaged for Mac, Windows, and Linux

To have a quick start I've used an electron-vue boilerplate by Greg Holguin. It provides a developer with vue-cli scaffolding, common Vue plugins, packager, testing, devtools, and other features.

💻 What are we going to build

We will build the same application as in the previous article: an app to check the weather in the city of user's choice built on top of OpenWeatherMap API.

If you want just to check the final Electron-powered app code, it's here.

🛠️ Installation

An electron-vue boilerplate is built as a template for VueCLI 2.x and includes options to customize an application. So you need to install it globally:

npm install -g vue-cli

If you prefer to use the latest version of VueCLI, you need to install a global bridge too:

npm install -g @vue/cli @vue/cli-init

and then initialize your project:

vue init simulatedgreg/electron-vue weather-app

This will start an installation project with a couple of choices you need to make during it.

Installation

The cool thing is if you need some commonly used plugins and libraries such as axios, you can pick them up during the installation process.

Almost all choices were clear but there was one concern 🤔:

Electron packager choice

I decided to google it and found this useful thread on StackOverflow. According to it, electron-builder seemed to be a better choice for me, so I went with it.

After the project is set up, you need to go to the application folder and run npm install or yarn install and we are ready to go.

🔦 Understanding an application structure

Right after installation is finished you can see two folders inside the src one: main and renderer. First one is required for Electon main process

According to electron-vue docs, in Electron the process that runs package.json’s main script is called the main process. The script that runs in the main process can display a GUI by creating web pages.

There are two files in the main folder: index.js and index.dev.js. First one is your application's main file, the file in which electron boots with. It is also used as webpack's entry file for production. All main process work should start here.

index.dev.jsis used specifically and only for development as it installs electron-debug & vue-devtools. You don't need to touch it while developing an application.

renderer folder is required for renderer process.

Since Electron uses Chromium for displaying web pages, Chromium’s multi-process architecture is also used. Each web page in Electron runs in its own process, which is called the renderer process.

As you may notice, it's a 'normal' Vue application structure with assets and components folders, main.js and App.vue files. Here is the structure of the latter:

<template>
  <div id="app">
    <landing-page></landing-page>
  </div>
</template>

<script>
  import LandingPage from '@/components/LandingPage'

  export default {
    name: 'weather-app',
    components: {
      LandingPage
    }
  }
</script>

<style>
  /* CSS */
</style>

And if you try to run dev task you will have this result:

Start app template

So there is a landing-page component and also devtools opened. Now we can start to change it!

🕹️ Scaffolding an app

Unlike Vuido, an Electron-powered app is built with HTML-tags, not native components. So we will make a structure similar to the usual web app and style it with CSS.

NOTE: I didn't install any CSS framework or component library on purpose: I wanted to compare package size without adding any different dependencies. The only library used in both projects is axios.

The first step was to get rid of landing-page component. Then I added a simple input field and a button:

<div id="app">
    <p>Enter the city name to check current weather in it</p>
    <section class="weather-input">
      <input type="text" v-model="query">
      <button :disabled="!query.length">Check</button>
    </section>
</div>

Now our application looks this way:

App with input only

We have a query property in data to handle the user input and we will make an API call with this query as a parameter.

🔗 Making an API call

I used the OpenWeatherMap current weather API. It gives you a lot of different information, you can check an example of JSON response here.

We already included axios to our application during the installation process. Let's have a look at src/renderer/main.js:

import Vue from 'vue';
import axios from 'axios';
import App from './App';

if (!process.env.IS_WEB) Vue.use(require('vue-electron'));
Vue.http = Vue.prototype.$http = axios;
Vue.config.productionTip = false;

So we can use axios methods as this.$http in the component instance! The only thing we will add here is a base URL for our API calls:

axios.defaults.baseURL = 'http://api.openweathermap.org/data/2.5';

Now in the App.vue we will create a bunch of data properties to display different weather data:

data() {
    return {
      query: '',
      error: false,
      city: '',
      country: '',
      weatherDescription: '',
      temp: null,
      tempMin: null,
      tempMax: null,
      humidity: null,
      icon: '',
    };
},

I've added one additional property comparing to Vuido version and it's an icon. API provides a weather icon but we couldn't use it in Vuido app because currently there is no support for displaying images.

Let's also create a method to fetch our data:

methods: {
  showWeather() {
    this.$http
      .get(`/weather?q=${this.query}&units=metric&&appid=${API_KEY}`)
      .then(response => {
        this.city = response.data.name;
        this.country = response.data.sys.country;
        this.weatherDescription = response.data.weather[0].description;
        this.temp = response.data.main.temp;
        this.tempMin = response.data.main.temp_min;
        this.tempMax = response.data.main.temp_max;
        this.humidity = response.data.main.humidity;
        this.icon = `http://openweathermap.org/img/w/${
          response.data.weather[0].icon
        }.png`;
        this.error = false;
      })
      .catch(() => {
        this.error = true;
        this.city = '';
      });
  },
},

and add it to the click callback of our button:

<button :disabled="!query.length" @click="showWeather">Check</button>

Now if you enter the text into an input field and click the button, you can observe the API call in the Network tab:

API call response

💅 Displaying weather data

Let's add this data to the template:

<template>
  <main id="app">
    <p>Enter the city name to check current weather in it</p>
    <section class="weather-input">
      <input type="text" v-model="query">
      <button :disabled="!query.length" @click="showWeather">Check</button>
    </section>
    <section v-if="error" class="weather-error">
      There is no such city in the database
    </section>
    <section v-if="city.length" class="weather-result">
      <h1>{{city}}, {{country}}</h1>
      <p><em>{{weatherDescription}}</em></p>
      <div class="weather-result__main">
        <img :src="icon" alt="Weather icon">
        <div class="weather-result__temp">
          {{temp}}&deg;C
        </div>
      </div>
      <div class="weather-result__details">
        <p>Min: {{tempMin}}&deg;C</p>
        <p>Max: {{tempMax}}&deg;C</p>
        <p>Humidity: {{humidity}}%</p>
      </div>
    </section>
  </main>
</template>

Our application view:

App without styling

Awesome, we can see an actual weather! But it looks like it's 1999... Let's add some CSS magic to it (actually, a lot of CSS magic)!

<style lang="scss">
* {
  margin: 0;
  padding: 0;
}
html,
body,
#app {
  height: 100%;
}

#app {
  font-family: Arial, Helvetica, sans-serif;
  font-size: 16px;
  padding: 10px;
  background: rgb(212, 228, 239);
  background: -moz-radial-gradient(
    center,
    ellipse cover,
    rgba(212, 228, 239, 1) 0%,
    rgba(134, 174, 204, 1) 100%
  );
  background: -webkit-radial-gradient(
    center,
    ellipse cover,
    rgba(212, 228, 239, 1) 0%,
    rgba(134, 174, 204, 1) 100%
  );
  background: radial-gradient(
    ellipse at center,
    rgba(212, 228, 239, 1) 0%,
    rgba(134, 174, 204, 1) 100%
  );
  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#d4e4ef', endColorstr='#86aecc',GradientType=1 ); /* IE6-9 fallback on horizontal gradient */
}

.weather-input {
  display: flex;
  align-items: center;
  padding: 20px 0;
}

.weather-result {
  text-align: center;
  &__main {
    display: flex;
    align-items: center;
    justify-content: center;
    padding-top: 5px;
    font-size: 1.3rem;
    font-weight: bold;
  }
  &__details {
    display: flex;
    align-items: center;
    justify-content: space-around;
    color: dimgray;
  }
}

.weather-error {
  color: red;
  font-weight: bold;
}

input {
  width: 75%;
  outline: none;
  height: 20px;
  font-size: 0.8rem;
}

button {
  display: block;
  width: 25%;
  height: 25px;
  outline: none;
  border-radius: 5px;
  white-space: nowrap;
  margin: 0 10px;
  font-size: 0.8rem;
}
</style>

And finally we have nice fully-functional app:

App ready

The last thing to do before packaging it is to reduce a window size. If we check a src/main/index.js file, we can find settings for it:

mainWindow = new BrowserWindow({
    height: 563,
    useContentSize: true,
    width: 1000
})

Let's change width to be 450 and height to be 250

📦 Packaging

Great news: you can build your app as a web application! If you run a build:web task, you will find a build web app in a dist folder.

But let's go back to our desktop app and run build task. As a result, you will have a folder named after your platform inside the build folder (for me it's mac) and an application file inside of it. And its size it... wow, 133 Mb!

App size

It's a lot for such a little application! Also, if you try to run it, you can notice it's starting a bit slower than a Vuido-powered app.

Final look:

Final look

🌟 Conclusions

Pros:

  • easy to start
  • good docs
  • provides web app build
  • can be customized via CSS styling

Cons

  • really big package size
  • slower than an application built with native GUI components

Electron-vue is a good option if your application needs a unique appearance and you care less about package size and performance.

Update

If your web application is built with Vue CLI 3, you can simply make it a desktop app with Vue CLI Plugin Electron Builder. You just need to run the following command in your project root folder:

vue add electron-builder

After it's done you will have two additional npm tasks: serve:electron and build:electron to work with a desktop app.

Desktop applications with Vue (3 Part Series)

1) Building a Desktop App with Vue: Vuido 2) Building a Desktop App with Vue: Electron 3) Building a Desktop App with Vue: NW.js

Posted on by:

n_tepluhina profile

Natalia Tepluhina

@n_tepluhina

Vue.js core team member. Google Developer Expert, conference speaker and all this stuff. Needs coffee to operate

Discussion

markdown guide
 

I feel like Vue + Electron may become a really popular approach for developer experience. Hard to beat this productivity.

I hope Electron has a plan for being kind to WebAssembly to improve performance and resource management in the future.

 

I agree. Development experience is great, but the resulting application is resource-expensive indeed.

 

Thanks, Natasha! Very useful article.

Have you tried UPX to compress final binary?

upx.github.io

It can compress final binary up to 2x.

The main disadvantage of Electron is output file size.

 
 

So cool~, I recently developed a static blog writing client based on Vue & Electron, welcome to use! github.com/hve-notes/hve-notes

 

Wow, it looks great! Thank you for sharing!

 
 

Very cool, I built a side-project with this exact setup + Firebase Auth, Storage and Firestore, such good dev experience and love love loved the flexibility of styling!

getbrandy.io

 

Have you (or anyone) tried this Vue CLI v3 plugin for electron? I haven't had a chance to yet:

Greg is suppose to be doing a re-write for Vue CLI v3 but I haven't seen any updates from him lately.

 

No, but thanks for the idea! I'm going to check it :)

 

Good morning, I'm getting an error when editing App.vue file. Error is:

ERROR in ./src/renderer/App.vue?vue&type=script&lang=js& (./node_modules/babel-loader/lib!./node_modules/vue-loader/lib??vue-loader-options!./src/renderer/App.vue?vue&type=script&lang=js&)
Module build failed (from ./node_modules/babel-loader/lib/index.js):
SyntaxError: C:/Users/Kiseliovai/Desktop/weather-app/src/renderer/App.vue: Unexpected token, expected ; (17:9)

15 | }
16 |

17 | data() {
| ^
18 | return {
19 | query: '',
20 | error: false,

Any suggestions?

 

Could you please post the whole script block here? It seems an error is before the data

 

Hi Natalia,

I'm pretty new to Vue js but not new to programming. I have been using Vue tutorials I found here and youtube to nicely study Vue.

Currently, I have an incoming project to build an electron app using Vue js. And by using a plugin from nklayman.github.io/vue-cli-plugin-... most of my Vue apps produced from learning the above tutorials can be nicely turned into Electron app with simple commands.

This coming electron project requires me to implement multiple windows layout and docking mechanism, and after I did some research, I came to golden-layout.com/ library... The integration of this lib for Vue also available at npmjs.com/package/vue-golden-layout

Maybe because I'm very new to Vue, but the description on how to use Vue-golden-layout lib in both NPM site and it's Github repo really confused me. I don't mean to be lazy here. But since I feel that I'm still a newbie in Vue while my Boss is on my tail ... is it possible that you make a very basic article on how to use Golden Layout with Vue?

I will be very very grateful if you can spend a little bit of your time to make one. Since desktop app without multiple windows layout and docking would not 100% complete :D

Or if any Vue experts here already have done such thing, it would be great for me to jump start with your samples...

Regards,
Bromo

 

Thank you for this comment! I will look into it :)

 

Thank you for this example

but how i can update a vue-list element in vue component of electron-vue app?

i have data(){} and return {} object in this data() and my_list for v-for in template, but i cant change template ((((

Vue manuals dont fit for electron cases ((((

 

Excellent article.
I read it very quickly but that would a good tutorial to go through and build my very first app with Vue.
And then move to something else and create my own application. :)

 

Thank you for your feedback! I am happy this article is useful ;)

 
 
 
 

How to change icon desktop app for production?

 

How can i embed machine learning model in an electron desktop application

 

The most important "Con" for this kind of "apps" are the security implications though: Electron is a risk for your users. You would have to constantly update the underlying runtime all the time.

 

Thank you for your comment! It's a good point to have a look at

 

tUx0r this is valid technically, but unnecessarily rude. Please try to deliver this feedback more constructively next time.