Progressive Web Apps or PWA’s are the it thing at the moment. Why wouldn’t it be? The promise of a website behaving like a native app, without all the hassles.

PWA’s allow you to “install” your website on the user’s homescreen, work without an internet connection and even send push notifications to users. You can also cache everything to your heart’s content, including API calls with IndexedDB.

I’ll run you through the simple setup I used to get things going rather quickly with Laravel.

To view service worker information in browser when testing, open up devtools and hit the application tab in Chrome.

What do I need for a PWA?

To make sure your website behaves like a PWA, please read the checklist.

Here’s the quick and easy summary:

  • HTTPS
  • manifest.json
  • service worker
  • Responsive design

Setting things up

Firstly you’re going to want to run npm install --save-dev sw-precache-webpack-plugin. This package uses sw-precache to generate our service worker. If you’re worried about losing control, don’t worry - you can use the importScripts option to include any custom logic that you need.

Service Workers provide us with the black magic that we want. You can read more about them here.

To store information from API calls, I used localforage. It’s a nice little library to interact with IndexedDB or WebSQL.

So once you’ve got sw-precache-webpack-plugin installed, it’s time to customize our Laravel project.

Step 1: Update Laravel Mix

You’re going to have to copy webpack’s config file and place it at the root of your project directory. You should be able to find it at:

node_modules/laravel-mix/setup/webpack.config.js.

Once that’s done, you’ll need to update your package.json file to reference the new location. Otherwise our changes won’t affect anything. It’s up to you to decide which build step to include it in. You’ll see the development, watch and production scripts currently have the location set to:

--config=node_modules/laravel-mix/setup/webpack.config.js.

Change that to look like this --config=webpack.config.js.

Diving into webpack.config.js

At the top of the file you’ll find all the packages that are being imported. Add the sw-precache plugin here. It should look something like this now:

let path = require('path');
let glob = require('glob');
let webpack = require('webpack');
let Mix = require('laravel-mix').config;
let webpackPlugins = require('laravel-mix').plugins;
let dotenv = require('dotenv');
let SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin'); //Our magic

Then above this line module.exports.plugins = plugins;, I’ve added the plugin:

plugins.push(
    new SWPrecacheWebpackPlugin({
        cacheId: 'pwa',
        filename: 'service-worker.js',
        staticFileGlobs: ['public/**/*.{css,eot,svg,ttf,woff,woff2,js,html}'],
        minify: true,
        stripPrefix: 'public/',
        handleFetch: true,
        dynamicUrlToDependencies: {
            '/': ['resources/views/welcome.blade.php'],
            '/articles': ['resources/views/articles.blade.php']
        },
        staticFileGlobsIgnorePatterns: [/\.map$/, /mix-manifest\.json$/, /manifest\.json$/, /service-worker\.js$/],
        runtimeCaching: [
            {
                urlPattern: /^https:\/\/fonts\.googleapis\.com\//,
                handler: 'cacheFirst'
            },
            {
                urlPattern: /^https:\/\/www\.thecocktaildb\.com\/images\/media\/drink\/(\w+)\.jpg/,
                handler: 'cacheFirst'
            }
        ],
        importScripts: ['./js/push_message.js']
    })
);

The sw-precache package is pretty well documented, so I won’t go into too much depth. I will give a quick rundown of some of the options seen above.

  • staticFileGlobs: The files that we want cached.
  • dynamicUrlToDependencies: Map the routes to the absolute path of our files. Don’t forget this
  • runtimeCaching: Allows us to save 3rd party libraries in cache.
  • importScripts: This includes our custom logic to the generated service worker.

That’s it! When compiling your assets, the service-worker.js should show up in your public folder.

Step 2: The manifest file

The manifest file also sits in your public folder as manifest.json. This file gives you a bit more control on the behaviour of your app. You can read more about it here. But as a quick overview, here’s an example:

{
  "short_name": "Shots",
  "name": "Shots App",
  "background_color": "#2196F3",
  "orientation": "portrait",
  "icons": [
    {
      "src": "icons/icon-36.png",
      "sizes": "36x36",
      "type": "image/png"
    },
    {
      "src": "icons/icon-48.png",
      "sizes": "48x48",
      "type": "image/png"
    },
    {
      "src": "icons/icon-72.png",
      "sizes": "72x72",
      "type": "image/png"
    },
    {
      "src": "icons/icon-96.png",
      "sizes": "96x96",
      "type": "image/png"
    },
    {
      "src": "icons/icon-144.png",
      "sizes": "144x144",
      "type": "image/png"
    },
    {
      "src": "icons/icon-168.png",
      "sizes": "168x168",
      "type": "image/png"
    },
    {
      "src": "icons/icon-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "icons/icon-256.png",
      "sizes": "256x256",
      "type": "image/png"
    }
  ],
  "start_url": "/?launcher=true",
  "display": "standalone"
}

  • short_name: The name shown on the homescreen of the mobile device.
  • name: The name shown on the install banner/popup.
  • background_color: The color that is shown just before you app launches.
  • orientation: Enforces the orientation to be used.
  • start_url: The default page to load when our app launches.
  • display: ‘standalone’ or ‘browser’, where browser adds an address bar.
  • icons: These are the images for our apps icon on the homescreen. Catering for most screen sizes.

These are just a few of the options that are available to you.

Step 3: Check for PWA support

In the page that you define your layout, include the following:

if ('serviceWorker' in navigator && 'PushManager' in window) {
    window.addEventListener('load', function() {
        navigator.serviceWorker.register('/service-worker.js').then(function(registration) {
            // Registration was successful
            console.log('ServiceWorker registration successful with scope: ', registration.scope);
        }, function(err) {
            // registration failed :(
            console.log('ServiceWorker registration failed: ', err);
        });
    });
}

This snippet will check for service worker and push notification support and if true, it will load our service worker file.

Done

This is basically all you need to get the PWA behaviour. The rest is up to you to customize and configure based on requirements.

The documenation will provide you with all the finer details that you’d need.

Gotchas

When testing with devtools I found that sometimes I had to open and close it to see the latest changes. There is the option on Service Workers in the Application tab, that let’s you update on reload.

localhost is treated as secure (https). This is worth noting when testing on mobile devices. The secure path created with ngrok didn’t work. You’ll have to connect your mobile device with a USB cable to Chrome. The settings are under devtools More tools -> Remote Devices.

Conclusion

This is the quickest way to get started and seeing PWA behaviour in action with Laravel. I didn’t want to go through the finer details as it’ll be cumbersome writing about all the possible issues you might encounter. If you do find yourself stuck on something, refer to the documenation, it really is your best friend.

Goodluck!