为之漫笔

The PRPL Pattern for Progressive Web Applications using Angular 6+

原文链接: itnext.io

PRPL pattern is a new technique used for building modern progressive web applications.

It is meant to serve to a client (CSS, JS and other static assets) only to what will be used by the client on a current requested page. Upon that, resource will be cached.

  • It increases application startup speed
  • Does not spend time on unnecessary compiling of not used JS
  • Faster initial interaction with application
  • Decreases bytes exchanged between server and client

PRPL stands for:

  • Push critical resources for the initial URL route
  • Render initial route
  • Pre-cache remaining routes
  • Lazy-load and create remaining routes on demand

For more details:

Angular

As Angular framework consists of all the necessary pieces here, we will build a simple application that uses this pattern.

Angular features we are going to use are:

  • Service workers
  • Lazy loading
  • Building & bundling with angular-cli

Application

We are going to build a mobile only Cleaning App ( Order cleaning service ). We borrowed sketch from: www.sketchappsources.com

Sketch has been made by Max Khomenko

We will use Figma to load Sketch and export necessary assets: https://www.figma.com/file/VdyF2hYyzFtpFmb7fXzGPc0v/Cleaning-App

Libraries & techniques

For the sake of the size and performances, we will not use any UI libraries or any third party components.

  • We will copy content of normalize.css into our main style.css
  • We will use Flex as method for element positioning.

I will not go into detail about writing HTML, CSS and TypeScript in this tutorial, all that can be found on GitHub link at the end of this article.

Structure

App structure will look like diagram bellow:

Bootstrapping

Create a new Angular application with ( before doing this, we will need to install @angular/cli_@6.0.8 version, because in latest release 6.1.1 there is a problem with_ @angular/pwa module):

ng new cleaning-app --routing -p capp --skip-git && cd cleaning-app

Then we will add PWA tooling to our application with ( as for angular-cli, we will install 0.6.8 version of this module, because in latest release there is a bug.):

ng add @angular/pwa@0.6.8 --project cleaning-app

Within adding this library, we will get a couple of files in our repository. We will need to alter some of them.

We generated an icon and replaced the default ones in src/assets/icons. We have changed a few things in src/manifest.json ( name, short_name and theme_color to match our app ). At the end it looks like this:

{  "name": "Cleaning Service",  "short_name": "CA",  "theme_color": "#48b3e2",  "background_color": "#fafafa",  "display": "standalone",  "scope": "/",  "start_url": "index.html",  "icons": [...]}

Modules

Than, we will add five modules in our application:

ng generate module Step1 --routingng generate module Step2 --routingng generate module Step3 --routingng generate module Final --routingng generate module Shared

Routing

We will lazy load all modules:

import { NgModule } from '@angular/core';import { Routes, RouterModule } from '@angular/router';const routes: Routes = [  {    path: '',    pathMatch: 'full',    redirectTo: 'step1'  },  {    path: 'step1',    loadChildren: 'src/app/step1/step1.module#Step1Module'  },  {    path: 'step2',    loadChildren: 'src/app/step2/step2.module#Step2Module'  },  {    path: 'step3',    loadChildren: 'src/app/step3/step3.module#Step3Module'  },  {    path: 'final',    loadChildren: 'src/app/final/final.module#FinalModule'  }];@NgModule({  imports: [RouterModule.forRoot(routes, { useHash: true })],  exports: [RouterModule]})export class AppRoutingModule { }

State

We can identify a possible state from the app sketch. Our application has one flow and for it we will use only one service and one model.

We need a service to keep the state of the application (selected options). That will be done through Offer service.

ng generate service shared/services/Offer

We will create Offer model, as well

ng generate class shared/services/Offer --type model

with all the necessary attributes.

export class Offer {  total = 0;
beds = 0;
baths = 0;
windows: boolean;
ironing: boolean;
fridge: boolean;
oven: boolean;
when: Date;
time: string;
location: string;
name: string;
city: string;
mobile: string;
email: string;
notes: string;
waitingAtHome: boolean;
keysAndSecurity: boolean;
keyUnderDoormat: boolean;}

It is important that we can dump complete state after the end of the process.

In offer.service.ts we created all necessary set and unset methods for Offer attributes.

Shared

Shared module, as it names implies, will hold shared components

As we identified it on application sketch, we will have four shared components: Header, Input, Button and Step component. We will create them in Shared module:

ng generate component shared/components/Header -c 'OnPush' --module shared/shared.module.ts --export
ng generate component shared/components/Input -c 'OnPush' --module shared/shared.module.ts --export
ng generate component shared/components/Button -c 'OnPush' --module shared/shared.module.ts --export
ng generate component shared/components/Step -c 'OnPush' --module shared/shared.module.ts --export

Marking component with OnPush means that this component is a dumb component, and change detection will be triggered only upon Input change.

Step 1

We will need to have an initial component for each step, we will call that component Home.

ng generate component step1/components/Home --module step1/step1.module.ts

We can notice on app sketch that we have one repeating component (decrease and increase buttons), so we will create it in our step1 module.

ng generate component step1/components/controls --module step1/step1.module.ts -c 'OnPush'

All components that are not bellow routes, should be dumb components and marked with OnPush.

Our final design for Step 1 looks like:

Step 2

Again, we are creating a home component for this module.

ng generate component step2/components/Home --module step2/step2.module.ts

After adding all the necessary methods to offer service, and HTML with CSS, our component looks like:

Step 3

As for step 2, we are creating a home component for this module.

ng generate component step3/components/Home --module step3/step3.module.ts

After putting all the necessary styling, it looks like this now:

Final step

Again, as for all from above we are creating a home component.

ng generate component final/components/Home --module final/final.module.ts

We identified Option component on this page, and we will create it with:

ng generate component final/components/option --module final/final.module.ts -c 'OnPush'

After all the necessary components styled and imported, it looks like:

Compression

Because angular-cli does not do gzip compression of JS and CSS files out of the box, we will achieve that with gulp script which we will trigger after ng build command.

Offline

Application works in an offline mode, but there is no business logic written for this ( aggregating orders in local storage for example).

Performance and Testing with Lighthouse

Our goal was to serve html to our clients in the first second, and we have achieved that, as you can see on the Lighthouse page.

As you may have noticed, we’ve had a few issues with Accessibility ( color of the header and scaling option ) but that is due to the design of the app, and I didn’t want to change that now.

Repository & Demo

Demo app is deployed with now.sh ( and it should be opened with mobile phone ): https://cleaning-app-sjgglugavc.now.sh

All source code can be found on GitHub: https://github.com/vladotesanovic/cleaning-app

Closing note

If someone is interested to finish ( sponsor ) and commercialize this application, please let me know. Maybe we can create something good out of it. :)