Finnian Anderson

My hackerspace

Deploying a Vue PWA to Now

There are lots of strange things in that title. Vue? PWA? Now? Let's break it down.

tl;dr

I made a PWA using the Star Wars API and it's pretty quick: swapi.finnian.app

VueJS

Vue is a relatively new front end JavaScript framework. To quote their documentation:

Vue (pronounced /vjuː/, like view) is a progressive framework for building user interfaces. Unlike other monolithic frameworks, Vue is designed from the ground up to be incrementally adoptable. The core library is focused on the view layer only, and is easy to pick up and integrate with other libraries or existing projects. On the other hand, Vue is also perfectly capable of powering sophisticated Single-Page Applications when used in combination with modern tooling and supporting libraries.

I've been getting to grips with it recently and am really enjoying writing apps with it. It's incredibly powerful and lightweight and although there are some headaches to start with, they're easy to overcome once you wrap your head around the concepts.

I've been taking this course on Udemy and I would highly recommend it. At the time of writing, it's still on sale for $11 (94% off - originally $190!).

PWAs (Progressive Web Apps)

According to Google, a PWA (or Progressive Web App) "are user experiences that have the reach of the web, and are...":

Reliable - Load instantly and never show the downasaur, even in uncertain network conditions.

Fast - Respond quickly to user interactions with silky smooth animations and no janky scrolling.

Engaging - Feel like a natural app on the device, with an immersive user experience.

What does this mean in the real world? They need to be fast. Really fast. Users will notice the difference if your app is a PWA. They will go "woah, that was quick". And if they don't, it's not fast enough 😉

PWAs are installed onto the user's device as if they were a native app - on the home screen. From their perspective, it is an app. They use technologies such as service workers to ensure the app always loads quickly, even if the network is slow - by caching assets and keeping data around for reuse. They also allow developers to access other on-device APIs such as push notifications.

If you're interested in learning more about PWAs, I'd recommend having a browse through some of the dev.to articles on the subject. There are lots of different examples, hints/tips and use-cases on there.

Now

Now is a deployment service from the folk at Zeit. It's a really slick solution to deploying your apps as all you need is a package.json file, Dockerfile or even just static HTML. Zeit handles everything, all you need to do to get your site live is run:

$ now

Pretty cool.

I've adapted things slightly to allow deployment of a Vue app. More on that later!

Building the app

For this example, I chose to build the app using data from an API I knew didn't require any authentication, to make things easier. SWAPI (Star Wars API) is a web service developed by Paul Hallett which lets you query anything about the Star Wars universe.

Side note: I met Paul when he was working at Twilio way back in 2014. He came to the Raspberry Pi offices in Cambridge to encourage us to use Twilio for our Young Rewired State Festival of Code hacks - read more on that here.

The app is very simple. It presents you with a dropdown and a text field to allow you to search for whatever you want.

When the user types in the search box, the results are retrieved from the API and suggested to them. They can then select one and have the information about that particular entity displayed to them.

Now we can see the power of Vue at work. All the code I had to write for this fits into 32 lines of Vue-flavoured HTML, 97 lines of JS and 78 lines of CSS. In fact, all of the app code compiles down to just 46.5kB and only 226kB when you include Vue's own code and the image. That's how much code gets shipped to the browser 😍. Remember, PWAs need to load near-instantaneously!

The code to call the API is very simple:

search: _debounce(function () {  
  this.results = []

  const query = this.query
  const entity = this.selectedEntity

  axios.get(`https://swapi.co/api/${entity}/?search=${query}`)
    this.results = res.data
  }, err => {
    throw err
  })
}, 500)

I've used lodash's _debounce method to prevent too much spamming of the API. Essentially, it waits to see if the user types anything else in 500ms before sending the request.

<ul>  
  <li v-for="result in results.results">
    <a @click="selectResult(result)">
      {{getName(result)}}
    </a>
  </li>
</ul>  

The results are then rendered in a list with Vue's v-for syntax. Much nicer than writing a loop with jQuery and dynamically appending to the DOM! Much more performant too ❤️

There's also a @click event handler to perform an action when the user clicks the link. In my case, this is the code that fires to show the additional information on the right (or below on mobile).

That's pretty much all there is to it. There's a few more lines of code to handle animations and strip out attributes we don't need but the code above is the most important part. The only other vaguely interesting thing I added was a listener for the escape key, so we can reset the view.

created () { // vue lifecycle hook  
  document.addEventListener('keydown', this.handleKeydown)
},
methods: {  
  clearResults () {
    this.selectedResult = null
    this.query = null
    this.results = []
  },
  handleKeydown (e) {
    if (e.keyCode === 27) { // esc
      this.clearResults()
    }
  }
}

The entity selection dropdown is created as soon as the page loads and is pre-filled dynamically with the available entities from the API. By doing it like this, I won't need to update any code if the API adds (or removes) entities.

Deployment!

Alright, so the app is built. Now to deploy it.

This part of the project took the longest. I needed to fiddle around with Now's configuration a little bit, but I finally found a solution that works.

Here's my now.json:

{
  "name": "Star Wars API Vue PWA",
  "type": "npm"
}

And the important parts of package.json:

{
  "scripts": {
    "start": "serve -n dist",
    "build": "NODE_ENV=production node build/build.js",
    "dev": "node build/dev-server.js"
  },
  "dependencies": {
    "serve": "^9.4.0" // used for serving the static files
  }
}

To run the app in development with hot reloading etc, I use npm run dev.
Now executes npm build && npm start to get the app up and running in production mode. This takes about a minute as it needs to install all the dependencies and compile the production assets.

Now (pun intended), my deployment command is simply:

$ now
> Deploying ~/Documents/code/vue-pwa-example under finniananderson
> Synced 4 files (479.77KB) [3s]
> https://starwarsapivuepwa-sugnckyqnl.now.sh [in clipboard] (sfo1) [2s]
> Building…
> ▲ npm install
> ▲ npm run build
> Total precache size is about 226 kB for 5 resources.
> Time: 7604ms
>   Build complete.
> ▲ Snapshotting deployment
> Verifying instantiation in sfo1
> [0] vue-pwa-example@1.0.0 start
> [0] serve -n dist
> [0] INFO: Accepting connections at http://localhost:5000
> ✔ Scaled 1 instance in sfo1 [27s]
> Success! Deployment ready

That's it! The app is deployed and available at the URL it gave me.

I also aliased the domain so it's easier to remember:

$ now alias https://starwarsapivuepwa-sugnckyqnl.now.sh swapi-pwa
> Assigning alias swapi-pwa to deployment starwarsapivuepwa-sugnckyqnl.now.sh
> Previous deployment starwarsapivuepwa-nhgzovjlod.now.sh downscaled
> Success! swapi-pwa.now.sh now points to starwarsapivuepwa-sugnckyqnl.now.sh [5s]

The app is live: swapi-pwa.now.sh

Now for a Lighthouse test. Lighthouse is an open source web app performance testing tool. It measures all sorts of metrics and gives a website a score based on all of them. Here's the one for this PWA:

Pretty pleased with that!

Here is a video demo of how the PWA works on iOS 11.4

Configuring CI / CD

Alrighty, so we've got an app which we're happy with. Hopefully, we've been following TDD principles and writing tests as we go along. For my app, I've written some basic tests. They aren't very extensive and I could do with writing some proper E2E tests too, but the basic unit tests are here: test/unit/specs/Hello.spec.js.

We need to run these tests a lot, for instance:

  • continuously in development (try npm run unit-watch!)
  • on push (for every commit)

Automating the latter is what I did next. Thankfully, Travis is designed to do exactly this - trigger 'builds' whenever something happens in your repo. It's very easy to setup so I won't cover that here, but checkout the .travis.yml to see what's going on.

I've configured Travis to do a few things. The first of these is, of course, to run all the unit tests. Those are easy to setup since they don't rely on external services, they're just for testing the nuts and bolts of your application.

The second thing Travis does is run the E2E tests. You may have heard these called 'view tests' too. The trouble is, these kinds of tests need to fire up a web browser to run through their test cases. This is a slight problem with Travis since it doesn't support virtual browsers.

Luckily, there is a free-for-open-source service called Sauce Labs which does exactly that. It's a bit tricky to configure but if you copy my setup it should work without too many issues. The trick is to use the access token from your Sauce Labs account and set it as an environment variable in the Travis config. I did this in the Travis UI itself rather than the yml, unlike the username env var. Then you will need to modify your nightwatch.conf.js file to dynamically set the username/password/url for the selenium browser (local in dev, Sauce Labs in CI).

It's also important to set the capabilities in Nightwatch to ensure the tunnel to Sauce Labs works correctly. Now, when your tests are run in Travis, they should all pass correctly.

Running E2E tests on Travis

The final thing that Travis does for me is to upload a coverage report to CodeCov, to generate the little badge in the README:

# .travis.yml
after_script:  
  - npm run report-coverage
// package.json
{
  "scripts": {
    "report-coverage": "codecov"
  }
}

All done! Now when we push to a branch, a whole load of stuff gets kicked off, including an automatic temporary deployment to Now for QA/review:

Ignore the deployment failure, I simply had too many instances running (the free tier only allows three)

Taking it further

Although I'm happy with the performance of the app, I'd still like to improve on the loading speed if I can.
I'd also like to add some extra eye candy in the form of loading animations and transitions.
The unit tests aren't complete and the E2E's are severely lacking, I simply wanted to see how easy it would be to create a PWA and ship it. This whole project only took about half a day and I've learnt a lot in this short time.

Now also offers a way of deploying (in Heroku terminology) review apps for each pull request in your repository. I'll be giving this a go in the next week or so and will update this post with how it went. [Done! 🚀]

A big thanks must go to the team behind VueJS for creating such an amazing framework, one I actually enjoy using. I've hated frontend work for ages, but Vue is really nice and I'm starting to come around to it again.

Also a round of applause to Paul for putting all the time and energy into build a first-class REST API. It's a joy to work with!

Feel free to play with the app at swapi.finnian.app 🚀

As always, the code is available on GitHub.

Thanks for reading!

Finnian Anderson

Node.js, MongoDB, Express, PHP, MySQL, Raspberry Pi, Arduino and other such things. Sailing instructor and Docker fan!

Suffolk, UKhttps://finnian.io

Subscribe to Finnian Anderson

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!