Skip to content

Commit

Permalink
feat: pwa
Browse files Browse the repository at this point in the history
- setup PWA
  • Loading branch information
denchiklut committed Mar 5, 2023
1 parent 8d89680 commit b2af112
Show file tree
Hide file tree
Showing 18 changed files with 386 additions and 19 deletions.
2 changes: 1 addition & 1 deletion .node-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v18.12.1
v18.14.0
12 changes: 4 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@ You can use this project as a boilerplate for your SSR applications. Feel free t

## Features

- [x] Server side render
- [x] `SSR`
- [x] `HMR`
- [x] Code splitting
- [x] `SPA` mode
- [x] `Webpack 5`
- [x] `React 18`
- [x] `Polyfills`
- [x] `PWA`

## Startup project

Expand Down Expand Up @@ -41,10 +40,7 @@ Add `.env` file based on `.env.example`

Note that additionally there will be few useful `global variables` available for you.

- IS_DEV
- IS_PROD
- IS_SERVER
- IS_SPA
- `IS_DEV`, `IS_PROD`, `IS_SERVER`, `IS_SPA`

### Step 3. Starting the project

Expand All @@ -66,4 +62,4 @@ To start the project in **Prod** mode, run the command
yarn start
```

Ready! The app will start on `http://localhost:5000`
Ready! The app will start on `http://localhost:3000`
260 changes: 260 additions & 0 deletions assets/offline/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link href="../icons/favicon.ico" rel="icon" />
<link href="../icons/maskable.png" rel="apple-touch-icon" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Lato:wght@400;700&display=swap"
rel="stylesheet"
/>
<style>
html {
height: 100%;
font-size: 10px;
}

body {
background-color: #f2f4f6;
margin: 0;
color: #242629;
width: 100%;
height: 100%;
display: flex;
text-align: center;
place-items: center;
}

.content {
width: 100%;
}

@media only screen and (max-width: 767px) {
.content {
padding: 16px;
}
}

.modem {
margin-bottom: 24px;
}

.title {
font-weight: 500;
font-size: 24px;
line-height: 32px;
margin: 0;
}

.description {
font-size: 14px;
line-height: 20px;
margin-top: 8px;
}

.button {
display: inline-flex;
place-content: center;
box-sizing: border-box;
margin: 24px 0;
text-decoration: none;
font-family: Lato, 'Trebuchet MS', sans-serif;
font-size: 12px;
line-height: 1.75;
letter-spacing: 0.02857em;
text-transform: uppercase;
min-width: 64px;
padding: 6px 16px;
border-radius: 4px;
transition: 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
color: white;
background-color: black;
box-shadow: #00000033 0 3px 1px -2px, #00000024 0 2px 2px 0, #0000001f 0 1px 5px 0;
}

.button:hover {
box-shadow: #00000033 0 2px 4px -1px, #00000024 0 4px 5px 0, #0000001f 0 1px 10px 0;
}

@media only screen and (max-width: 480px) {
.button {
width: 100%;
}
}
</style>
<meta charset="utf-8" />
<meta name="robots" content="noindex,nofollow" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>No internet connection</title>
</head>
<body>
<section class="content">
<div class="modem">
<svg width="180" height="180" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="90.5" cy="89.5" r="82.5" fill="#E3FBFD" />
<path
d="M171.982 102.5C165.747 141.887 131.64 172 90.5 172S15.253 141.887 9.02 102.5h162.963z"
fill="#9BCEE1"
/>
<path
d="M47.446 48.872c-.74-8.292 5.792-15.434 14.117-15.434 8.326 0 14.858 7.142 14.118 15.434L73.96 68.148H49.167l-1.721-19.276z"
fill="#6FB4D0"
/>
<path
opacity=".2"
d="M61.563 33.438c8.326 0 14.858 7.142 14.118 15.434L73.96 68.148H62.803c1.86-24.173-1.24-34.71-1.24-34.71z"
fill="#014266"
/>
<path
d="M51.647 33.438c.206.62.495 2.231 0 3.719M47.307 36.537c.62.62 2.232 1.86 3.72 1.86M49.167 34.678l1.24 2.479"
stroke="#BEE0ED"
stroke-linecap="round"
/>
<g filter="url(#filter0_d)">
<path
d="M59.703 34.678c-4.958 4.338-5.578 11.776-3.718 27.892"
stroke="#9BCEE1"
stroke-linecap="round"
/>
</g>
<path
d="M62.183 34.678c.826 2.479 1.735 11.528 1.24 27.892"
stroke="#9BCEE1"
stroke-linecap="round"
/>
<path
d="M76.882 40.203c-.55.354-2.038 1.035-3.603.924M72.801 36.77c-.446.755-1.246 2.624-.876 4.065M75.064 38.11l-2.093 1.816M64.11 44.848c.224.363.56 1.317.107 2.235M66.002 47.651c-.208.373-.879 1.13-1.9 1.18M60 47.1c.37.46 1.316 1.367 2.147 1.327M61.62 44.726l1.358 2.417M46.598 50.99c.59.281 1.95 1.19 2.678 2.58M45.82 56.265c.877-.016 2.888-.317 3.92-1.389M45.77 53.636l2.647.827"
stroke="#BEE0ED"
stroke-linecap="round"
/>
<g filter="url(#filter1_d)">
<path
d="M64.662 34.678c6.096 3.033 9.483 7.28 6.096 28.512"
stroke="#9BCEE1"
stroke-linecap="round"
/>
</g>
<path
d="M75.826 59.08c-.628.176-2.252.39-3.714-.176M72.935 54.6c-.648.59-1.963 2.14-2.032 3.627M74.705 56.544l-2.535 1.122"
stroke="#BEE0ED"
stroke-linecap="round"
/>
<path
d="M55.812 28.441c.372 3.591 2.763 5.734 3.912 6.357 1.352.659 4.588 1.819 6.714 1.188 2.125-.631 4.812-2.296 4.633-4.846-1.953-.345-3.471.645-3.986 1.184.276-.371.812-1.745.756-4.273-1.465-.26-3.484 1.902-4.31 3.014-.096-.646-.466-2.348-1.183-3.986-1.236 1.293-1.833 3.243-1.977 4.057-.299-.682-1.629-2.176-4.559-2.695z"
fill="#BEE0ED"
/>
<path d="M46 63h31l-3.78 26H50.535L46 63z" fill="#9BCEE1" />
<path
d="M47.5 69L77 63l-1.5 10c-5.5-1.5-10-2.5-18-3.5s-10-.5-10-.5z"
fill="#6FB4D0"
/>
<path d="M45 61h33l-1 8H46l-1-8z" fill="#9BCEE1" />
<path
opacity=".16"
d="M27.444 131c-2.486 0-4.444-2.015-4.444-4.5s1.947-4.5 4.432-4.5h130.104c2.485 0 4.464 2.015 4.464 4.5s-1.954 4.5-4.439 4.5H27.443z"
fill="#014266"
/>
<path
d="M112.255 91.33l7.66-1.22-.886-5.568c-2.251 1.52-4.728 2.758-7.263 3.72l.489 3.067z"
fill="#60696C"
/>
<path
d="M155.079 123.318H30.252A6.25 6.25 0 0124 117.07V95.094a6.25 6.25 0 016.252-6.252h124.83a6.251 6.251 0 016.252 6.252v21.976a6.255 6.255 0 01-6.255 6.248z"
fill="#fff"
/>
<path
d="M106.83 32.61a3.877 3.877 0 00-3.22 4.44l8.157 51.212c2.535-.96 5.011-2.2 7.262-3.72l-7.758-48.712a3.88 3.88 0 00-4.441-3.22z"
fill="#9CABB1"
/>
<path
d="M131.164 88.842h7.757v-5.637c-2.462 1.147-5.103 1.98-7.757 2.531v3.106z"
fill="#60696C"
/>
<path
d="M135.042 30a3.877 3.877 0 00-3.878 3.88v51.856c2.654-.548 5.295-1.385 7.757-2.53V33.878a3.88 3.88 0 00-3.879-3.878zM53.592 126.263v-2.945H36.354v.819c5.855.083 11.653.782 17.172 2.689.043-.181.066-.367.066-.563z"
fill="#9CABB1"
/>
<path
d="M36.354 124.14v2.126a2.515 2.515 0 002.514 2.514h12.21a2.51 2.51 0 002.448-1.951c-5.52-1.91-11.317-2.609-17.172-2.689z"
fill="#DDE2E3"
/>
<path
d="M148.977 126.263v-2.945h-17.238v.819c5.855.083 11.653.782 17.172 2.689.043-.181.066-.367.066-.563z"
fill="#9CABB1"
/>
<path
d="M131.738 124.14v2.126a2.515 2.515 0 002.514 2.514h12.21a2.51 2.51 0 002.448-1.951c-5.519-1.91-11.317-2.609-17.172-2.689zM146.755 113.373H39.304A3.304 3.304 0 0136 110.069v-9.765A3.304 3.304 0 0139.304 97h107.451a3.304 3.304 0 013.304 3.304v9.765a3.304 3.304 0 01-3.304 3.304z"
fill="#DDE2E3"
/>
<path d="M125 108.5v-6a1.5 1.5 0 00-3 0v6a1.5 1.5 0 003 0z" fill="#F13E32" />
<path d="M131 108.5v-6a1.5 1.5 0 00-3 0v6a1.5 1.5 0 003 0z" fill="#9CABB1" />
<path
d="M137 108.5v-6a1.5 1.5 0 00-3 0v6a1.5 1.5 0 003 0zM143 108.5v-6a1.5 1.5 0 00-3 0v6a1.5 1.5 0 003 0z"
fill="#FE9247"
/>
<path d="M113.5 103h-65a2.5 2.5 0 100 5h65a2.5 2.5 0 100-5z" fill="#60696C" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M144.062 37.678a2 2 0 01-.022 2.828l-13.637 13.42a2 2 0 11-2.806-2.852l13.637-13.419a2 2 0 012.828.023z"
fill="#F13E32"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M144.782 53.288a2 2 0 01-2.819.233l-14.755-12.495a2 2 0 112.585-3.052l14.755 12.495a2 2 0 01.234 2.819z"
fill="#F13E32"
/>
<defs>
<filter
id="filter0_d"
x="50.52"
y="34.178"
width="13.683"
height="36.892"
filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB"
>
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feColorMatrix
in="SourceAlpha"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
/>
<feOffset dy="4" />
<feGaussianBlur stdDeviation="2" />
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0" />
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow" />
<feBlend in="SourceGraphic" in2="effect1_dropShadow" result="shape" />
</filter>
<filter
id="filter1_d"
x="60.162"
y="34.177"
width="16.438"
height="37.512"
filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB"
>
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feColorMatrix
in="SourceAlpha"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
/>
<feOffset dy="4" />
<feGaussianBlur stdDeviation="2" />
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0" />
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow" />
<feBlend in="SourceGraphic" in2="effect1_dropShadow" result="shape" />
</filter>
</defs>
</svg>
</div>
<h1 class="title">No internet connection</h1>
<p class="description">Check your internet and reload the page</p>
<a class="button" href="/">Reload</a>
</section>
</body>
</html>
12 changes: 10 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,16 @@
"@loadable/server": "^5.15.3",
"classnames": "^2.3.2",
"core-js": "^3.27.2",
"debug": "^4.3.4",
"dotenv": "^16.0.3",
"morgan": "^1.10.0",
"postcss": "^8.4.21",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router": "^6.8.1",
"react-router-dom": "^6.8.1",
"winston": "^3.8.2"
"winston": "^3.8.2",
"workbox-window": "^6.5.4"
},
"devDependencies": {
"@babel/cli": "^7.20.7",
Expand All @@ -56,6 +58,7 @@
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^14.4.3",
"@types/classnames": "^2.3.0",
"@types/debug": "^4.1.7",
"@types/express": "^4.17.17",
"@types/jest": "^29.4.0",
"@types/loadable__component": "^5.13.4",
Expand Down Expand Up @@ -117,6 +120,11 @@
"webpack-dev-middleware": "^6.0.1",
"webpack-dev-server": "^4.11.1",
"webpack-hot-middleware": "2.25.3",
"webpack-node-externals": "^3.0.0"
"webpack-node-externals": "^3.0.0",
"workbox-precaching": "^6.5.4",
"workbox-recipes": "^6.5.4",
"workbox-routing": "^6.5.4",
"workbox-strategies": "^6.5.4",
"workbox-webpack-plugin": "^6.5.4"
}
}
27 changes: 27 additions & 0 deletions pwa/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"short_name": "SSR app",
"name": "SSR app",
"start_url": ".",
"display": "standalone",
"theme_color": "#efefef",
"background_color": "#f2f4f6",
"icons": [
{
"src": "icons/192.png",
"sizes": "192x192"
},
{
"src": "icons/512.png",
"sizes": "512x512"
},
{
"src": "icons/360.svg",
"sizes": "360x360"
},
{
"src": "icons/maskable.png",
"sizes": "853x853",
"purpose": "any maskable"
}
]
}
12 changes: 12 additions & 0 deletions pwa/sw.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { NetworkOnly } from 'workbox-strategies'
import { setDefaultHandler } from 'workbox-routing'
import { precacheAndRoute } from 'workbox-precaching'
import { googleFontsCache, imageCache, offlineFallback } from 'workbox-recipes'

declare const self: ServiceWorkerGlobalScope

precacheAndRoute(self.__WB_MANIFEST)
googleFontsCache()
setDefaultHandler(new NetworkOnly())
imageCache({ maxEntries: 60 })
offlineFallback()
3 changes: 3 additions & 0 deletions src/client/utils/bootstrap.util.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import type { ReactNode } from 'react'
import { loadableReady } from '@loadable/component'
import { createRoot, hydrateRoot } from 'react-dom/client'
import { registerSW } from 'utils/pwa.util'

export const bootstrap = (app: ReactNode) => {
const container = document.getElementById('root') as HTMLElement

if (IS_SPA) createRoot(container).render(app)
else loadableReady(() => hydrateRoot(container, app))

if (IS_PROD) registerSW()
}
1 change: 1 addition & 0 deletions src/client/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './lazy.util'
export * from './pwa.util'
export * from './bootstrap.util'
Loading

0 comments on commit b2af112

Please sign in to comment.