Skip to content

Instantly share code, notes, and snippets.

@Dabolus
Last active June 26, 2020 01:39
Show Gist options
  • Save Dabolus/314bd939959ebe68f57f1dcebe120a7e to your computer and use it in GitHub Desktop.
Save Dabolus/314bd939959ebe68f57f1dcebe120a7e to your computer and use it in GitHub Desktop.
A simple guide that explains how to deploy PWA Starter Kit to Firebase

Note: this guide explains step by step how to add Firebase to PWA Starter Kit.

If you already have some Firebase knowledge and you just want to get everything ready out of the box, you might want to checkout the Firebase branch on the PWA Starter Kit repo, that already contains all the needed files.

Deploying prpl-server to Firebase

Firebase Hosting alone is not sufficient for hosting the prpl-server build since it requires some server-side processing of the user agent string. Instead, you will have to use Firebase Functions for that.

  1. Sign up for a Firebase account

  2. Head over the Firebase Console and create your project. Make note of the project ID associated with your app

  3. Install Firebase Command Line Tools:

    $ npm i -g firebase-tools
  4. Log into your Firebase account from the CLI by running:

    $ firebase login
  5. Set the default Firebase project for your PWA. You can do that by either running this command:

    $ firebase use --add <project-id>

    Or by creating a file named .firebaserc in the root of your application, containing:

    {
      "projects": {
        "default": "<project-id>"
      }
    }
  6. Create a file named firebase.json in the root of you PWA and paste this JSON in it:

    {
      "hosting": {
        "public": "build",
        "ignore": [
          "firebase.json",
          "**/.*"
        ],
        "rewrites": [
          {
            "source": "**",
            "function": "app"
          }
        ],
        "headers": [
          {
            "source" : "**/service-worker.js",
            "headers" : [
              {
                "key" : "Service-Worker-Allowed",
                "value" : "/"
              },
              {
                "key" : "Cache-Control",
                "value" : "no-cache"
              }
            ]
          }
        ]
      }
    }

    The public field tells Firebase that the build folder should be statically served through its CDN. The rewrites field tells Firebase to send each request to the app function - we will create it later. The headers field tells Firebase to add the Service-Worker-Allowed header when the user requests a service-worker.js file

  7. Create a functions folder. This folder will contain the code executed by Firebase Functions. You will need to create two files in it:

    1. package.json
      {
        "name": "functions",
        "description": "Cloud Functions for Firebase",
        "scripts": {
          "serve": "firebase serve --only functions",
          "shell": "firebase functions:shell",
          "start": "npm run shell",
          "deploy": "firebase deploy --only functions",
          "logs": "firebase functions:log"
        },
        "dependencies": {
          "express": "^4.16.4",
          "firebase-functions": "^2.1.0",
          "prpl-server": "^1.3.0",
          "rendertron-middleware": "^0.1.3"
        },
        "private": true
      }
    2. index.js
      const functions = require('firebase-functions');
      const prpl = require('prpl-server');
      const express = require('express');
      const rendertron = require('rendertron-middleware');
      
      const app = express();
      
      const rendertronMiddleware = rendertron.makeMiddleware({
        proxyUrl: 'https://render-tron.appspot.com/render',
        injectShadyDom: true,
      });
      
      app.use((req, res, next) => {
        req.headers['host'] = '<YOUR HOST URL HERE>';
        return rendertronMiddleware(req, res, next);
      });
      
      app.get('/*', prpl.makeHandler('./build', require('./build/polymer.json')));
      
      exports.app = functions.https.onRequest(app);
      This is the main file for our Firebase Functions. First, we create an Express app. Then, we add a rendertron middleware, that will server side render our PWA for crawler bots, and we give to PRPL Server the responsability to answer to any other GET request. Last but not least, we tell Firebase to make our Express app handle each request to the app function.
  8. Install the npm dependencies inside the functions directory:

    $ cd functions && npm i
  9. Now that the Firebase Functions part is ready, we have to setup our build to make it work correctly with it. Since Firebase Functions cannot require files that are not inside the functions directory, we need to build the PWA and make Gulp move the files needed by PRPL Server to the functions directory, while leaving the static assets in the build directory. To do that, we are going to add a new task to gulpfile.js:

    /**
     * Builds the Firebase-ready version of the PWA, moving the necessary
     * files to the functions folder to be used by PRPL Server
     */
    gulp.task('firebase', () => {
      // These are the files needed by PRPL Server, that are going to be moved to the functions folder
      const filesToMove = [ 'build/polymer.json', 'build/**/index.html', 'build/**/push-manifest.json' ];
      // Delete the build folder inside the functions folder
      return del('functions/build')
        .then(() =>
          // Copy the files needed by PRPL Server
          new Promise((resolve) =>
            gulp
              .src(filesToMove, { base: '.' })
              .pipe(gulp.dest('functions'))
              .on('end', resolve)))
        // Delete them from the original build
        .then(() => del(filesToMove));
    });
  10. Optional: If you also need to use Firebase Auth, you have to make sure to tell the Service Worker to ignore its reserved namespace. Add the following code to your sw-precache-config.js to tell your Service Worker to use index.html as a fallback for everything but Firebase reserved namespace:

    module.exports = {
      ...
      navigateFallback: '/index.html',
      navigateFallbackWhitelist: [ /^\/[^\_]+\/?/ ]
    };
  11. Add a script named build:firebase in your project root's package.json:

    "build:firebase": "polymer build --auto-base-path && gulp firebase"
    
  12. Now that you have everything set up, just run the build:firebase script to build the PWA for Firebase:

    $ npm run build:firebase
  13. Finally, deploy your PWA to Firebase:

    $ firebase deploy --only functions,hosting
@testforautism
Copy link

I am getting the foll error on running npm run build:firebase. Plz help
@Dabolus

npm ERR! file /home/nitin/fb/package.json
npm ERR! code EJSONPARSE
npm ERR! JSON.parse Failed to parse json
npm ERR! JSON.parse Unexpected string in JSON at position 1165 while parsing '{
npm ERR! JSON.parse "name": "@polymer/tfa",
npm ERR! JSON.parse "version":'
npm ERR! JSON.parse Failed to parse package.json data.
npm ERR! JSON.parse package.json must be actual JSON, not just JavaScript.

npm ERR! A complete log of this run can be found in:
npm ERR! /home/nitin/.npm/_logs/2019-04-04T16_39_00_707Z-debug.log

@rickymarcon
Copy link

rickymarcon commented Apr 8, 2019

@Dabolus You can refactor the following:

req.headers['host'] = '<YOUR HOST URL HERE>';

to

req.headers['host'] = `${process.env.GCLOUD_PROJECT}.firebaseapp.com`;

I also found out you'll run into CORS issues if you change use any other firebase region that is not us-central1 for the Cloud Function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment