Progressive Web Apps, a course of the minor Web Design & Development. It is a minor of the third year from the study CMD.
🚀 Purpose of Project | 😍 Concept | 🔢 Data | 🤓 Technical summary | ⚙️ Installation | 📁 Sources | 👮 License |
In this course I will convert the client side web application previously made Web App From Scratch into a server side rendered application. I also add functionalities based on the Service Worker and turn the application into a Progressive Web App. Ultimately I'm going to implement a series of optimisations to improve the performance of the application. All the basic parts covered in this course are very useful to know when you later choose to make an app using a framework. All these parts are (almost) all automated in a framework and are therefore done for you. So it is helpful to know exactly how those parts work.
This application is a rebuild from the CATCH YOUR LAUGH-application. Here I focus on the client- and serverside rendering and am I able to create a Progressive Web App.
UI screenshot- By clicking on the 'ANOTHER COMBO' button you can see a different combination of a cat image and a joke
- Like your favourite combination of a cat image and a joke
- Create a favourites list with your favourite combinations of a cat image and a joke
To get more information about the app, for example it's APIs, check it here.
This is a Progressive Web App (PWA). A number of characteristics are:
- It has an app-like interface, through the
manifest.json
. In the browser the app is 'installable'. Which means you can add the app to your homescreen and when you launch the app, you have an app-like interface and the default browser interface is disappeared. - Offline work mode - caching manager
service-worker.js
This Progressive Web App is built, using:
For running the server I use the Express framework for Node.js. This is a web framework for Node.js which is good for setting up a server.
- Require Express
const express = require("express");
After that you init your app
const app = express();
Config your Express-app
// Declare your static folder
app.use(express.static('public'));
// Run the server on a port
app.listen(5000, () => console.log(`App is running on port 5000`));
For the templating engine I use EJS. I really like EJS, because it is easy to implement and work with.
An example of how I render a page and pass data with rendering
app.get('/', async (req, res) => {
// Get data through fetch and put in a variable called dataAll
const dataAll = await getData();
// Declare data variables for better use in .ejs files
const catData = dataAll.filteredDataCat;
const jokeData = dataAll.filteredDataJokes;
// Render data
res.render('index.ejs', { catData, jokeData });
});
In the .ejs
file
<img src="<%= catData.url %>" alt="">
<label id="joke" for="joke"><%= jokeData.setup %></label>
<label id="punchline" for="punchline"><%= jokeData.punchline %></label>
For the building I use Gulp. It was for me the first time I used a bundler/tooling. At the beginning I struggled a lot with building/minifying my code, because I didn't get it. But now I understand what tooling is. Tooling is a feature for the developer to smart and fast build your application. As npm script I used a nice script: "dev": "build && nodemon start"
. This a script I used a lot during development. It means that the app firstly is building and secondly it starts the nodemon server. For building the app before deploying, I used npm run build
and then it first empties the folders in /public
and then the files were build (minified). Here are my npm scripts in the package.json.
"start": "node app.js",
"dev": "build && nodemon start",
"build": "npm-run-all build:*",
"prebuild:css": "rimraf \"public/css\"",
"build:css": "npm run prebuild:css && node buildscripts/build-css.js",
"prebuild:js": "rimraf \"public/js\"",
"build:js": "npm run prebuild:js && node buildscripts/build-js.js",
"prebuild:assets": "rimraf \"public/assets\"",
"build:assets": "npm run prebuild:assets && node buildscripts/build-assets.js",
"prebuild:icons": "rimraf \"public/icons\"",
"build:icons": "npm run prebuild:icons && node buildscripts/build-icons.js",
"deploy": "git push && git push heroku master"
const gulp = require('gulp');
const cleanCSS = require('gulp-clean-css');
return gulp
.src('./static/styles/main.css')
.pipe(cleanCSS())
.pipe(gulp.dest('./public/css'));
const gulp = require('gulp');
const concat = require('gulp-concat');
return gulp
.src('./static/scripts/*.js')
.pipe(concat('bundle.min.js'))
.pipe(gulp.dest('./public/js'));
const gulp = require('gulp');
return gulp
.src('./static/assets/*')
.pipe(gulp.dest('./public/assets'));
const gulp = require('gulp');
return gulp
.src('./static/icons/*')
.pipe(gulp.dest('./public/icons/'));
To deploy my application I used Heroku for the first time. It's easy to use, but my deployment had some issues with don't GET
the path
and the favicon
. Now that issue is fixed to replace the PORT
in my app.js
with process.env.PORT || 5000
. Here is a roadmap of deploying your application to Heroku via git command Heroku CLI.
1. heroku create // Create heroku CLI
2. git add . && git commit -m 'Update app' // Add your last changes and commit
3. git push heroku master // Push to heroku master branch
4. catch-your-laugh.herokuapp.com // Build is done and deployed
First Contentful Paint - 0,9 s First Contentful Paint (FCP) is when the browser renders the first bit of content from the DOM, providing the first feedback to the user that the page is actually loading. The question "Is it happening?" is "yes" when the first contentful paint completes.
Speed Index - 0,9 s Speed Index measures how quickly content is visually displayed during page load. Lighthouse first captures a video of the page loading in the browser and computes the visual progression between frames.
Largest Contentful Paint - 1,5 s Largest Contentful Paint (LCP) is a Core Web Vitals metric and measures when the largest content element in the viewport becomes visible. It can be used to determine when the main content of the page has finished rendering on the screen.
Time to Interactive - 1,5 s TTI measures how long it takes a page to become fully interactive. A page is considered fully interactive when the page displays useful content, which is measured by the First Contentful Paint, and event handlers are registered for most visible page elements.
Total Blocking Time - 0 ms The Total Blocking Time (TBT) metric measures the total amount of time between First Contentful Paint (FCP) and Time to Interactive (TTI) where the main thread was blocked for long enough to prevent input responsiveness.
Cumulative Layout Shift - 0,001 CLS measures the sum total of all individual layout shift scores for every unexpected layout shift that occurs during the entire lifespan of the page.
First I was fetching the custom fonts from an API (Google Fonts), but that costs too much. Therefore I choose to use local fonts to reduce the size of the fetched style sheets plus the application does not have to download anything from the internet again. For this optimisation I used Google Webfonts Helper to serve the fonts locally.
After that have done, the performance is improved by 1 point -_- .
Unfortunately I don't have a screenshot of the difference between before and after implementing the building files. I raised at least .5 seconds faster.
Added robots.txt and sitemap.xml, because of SEO. Although it was 100%, it can also be improved ;) . The 2 files add content and intelligence to the application, so a search engine like Google can find better your website.
Before
Because the manifest don't loaded correctly, it wasn't installable. I had to take a look at this, because the service worker didn't work at that moment, the manifest cannot be read.After
Now the manifest.json and service-worker works correctly. So now the pwa statistics are much better 🔥 . But there are some things I don't understand:It says "Is not configured for a custom splash screen. Failures: Manifest does not have background_color
." I don't like and understand this, because I do have a background_color in the manifest. So this is very weird.
Some statistics are things I cannot fix, because I don't have the rights to change them. Like this issue below, it lays on the server.
Because of having not enough time and my application crashed a lot of times, I deciced to only serve an offline page while the user is offline.
At the moment I want to add a new favourite, it is added to a global array. But at the moment when the list where the page lives is /favourites
. When the app starts (and during the app), this page must not be added to the cache. Because when you hit the like button to create a new fav combo and you want to see the fav list, you click on MY FAVOURITES and go to /favourites
. The service worker takes then this page from the cache and you see an empty fav list. Therefore you need to whitelist /favourites
so this page won't be cached, but retrieved from the server. But on the other side I want to see my fav list when I am offline.
All pages will be cached, excluded the /favourites. All pages will be served from the server when online. While the user is offline, then the pages will be shown whose are cached when the user was online. The pages whose are not cached and when the user wants to enter them, he receives an offline page.
Learned:
- Module.exports in Node.js
- Application tab in Chrome Inspector
- Service worker
- What is caching
- Manifest.json
- PWA statistics and performance info (helper to improve)
- Clone the repository:
git clone https://github.com/ralfz123/CATCH-YOUR-LAUGH--PWA.git
- Install dependencies
npm install
- To run the app
npm run start
- To run the app in developer mode (with nodemon)
npm run dev
- Go to localhost in the browser and voilà ✨
http://localhost:5000/
Expand
- UI is like the non-render-server-side app (WAFS)
- static favicon rendered
- Added error state
- Only fetch when hit another combo or first visit site. Not when clicking from
/favourites
to/
Feedback user:- Feedback loader while fetching (combining client to serverside rendering)
- Feedback like
- Serverworker ON and I can not add favourites :(
- Count indexOf object in global array, so the number => id of the object can be shown at detail page
- Deployed app:rocket:
- Fetch on hit button 'Another Combo' --> but page reloads also and url changes...
- Delete fav item
- Delete all favs item
- Render data home
- Not fetch when coming back at home
- Pass data via views
- Server side fetch with npm package
- responsive css added
- gulp (tooling / static site generator)
- minify
- Compiler/builder
- Page renders data
- Add fav and fav list renders fav items
- Detailpage renders data
- Core feature works ✅
- dotenv for api
- Serviceworker works not perfect; CSS doesn't load.
- Cache don't save
/favourites
, because new items are being added through user - Serve offline pages for features that are not cached
- Cache don't save
- Put router in modules
- Put render in modules
- Dark theme client side JS
- Build command in Install proces
- Choice via button to filter on joke type (programming/animals/etc..) --> fetch different APIs
- Put favouritesArray in a db like MongoDB (with session)
Credits to Joost Faber && Koop && Declan for giving interesting lectures about PWA's and JavaScript and how to deal with it.
-
Stackoverflow (n.d.). Searching for answers on dev questions - Stackoverflow. Retrieved 8 March 2021 from https://www.stackoverflow.com
-
npm (n.d.). Package manager with many packages - npm. Retrieved 8 March 2021 from https://www.npmjs.com
-
Node.js (n.d.). A Runtime engine server for Chrome V8 - Node.js. Retrieved 8 March 2021 from https://nodejs.org
-
Express (n.d.). Framework for Node.js - Express. Retrieved 8 March 2021 from https://expressjs.com/
-
Sitepoint (n.d.) Understanding Modules - Sitepoint. Retrieved 9 March 2021 from https://www.sitepoint.com/understanding-module-exports-exports-node-js/
-
Youtube (n.d.) Registering a Service Worker - The Net Ninja. Retrieved 15 March 2021 from https://www.youtube.com/watch?v=6s697AJdlB8
-
Google Developers (n.d.) Service Worker - Google Developers. Retrieved 15 March 2021 from https://developers.google.com/web/fundamentals/primers/service-workers
This project from Ralf has a MIT © License