Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Progressive Web Apps with React.js and Firebase
Search
Jimmy Moon
May 20, 2017
250
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Progressive Web Apps with React.js and Firebase
Jimmy Moon
May 20, 2017
More Decks by Jimmy Moon
See All by Jimmy Moon
Operating an AI Infrastructure Through MCP Apps on Agents
ragingwind
0
50
Coding as Prompting Since 2025
ragingwind
0
940
Edge Computing for WebApp
ragingwind
0
160
Micro-Saas for developer who want a new
ragingwind
0
130
How to use The Fourth Language
ragingwind
0
250
Please Use The Fourth Language - WebAssembly
ragingwind
0
270
Head topics in Javascript, 2020
ragingwind
1
750
Recap Modern WebAssembly in CDS 2019
ragingwind
0
370
Today headlines in Javascript, 2019
ragingwind
0
570
Featured
See All Featured
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
28
3.6k
Reality Check: Gamification 10 Years Later
codingconduct
0
2.3k
Information Architects: The Missing Link in Design Systems
soysaucechin
1
1.2k
Dominate Local Search Results - an insider guide to GBP, reviews, and Local SEO
greggifford
PRO
0
340
Why Mistakes Are the Best Teachers: Turning Failure into a Pathway for Growth
auna
0
300
Measuring Dark Social's Impact On Conversion and Attribution
stephenakadiri
2
290
The AI Search Optimization Roadmap by Aleyda Solis
aleyda
1
6.2k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
232
55k
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
11
1k
Producing Creativity
orderedlist
PRO
348
41k
A designer walks into a library…
pauljervisheath
211
25k
The Limits of Empathy - UXLibs8
cassininazir
1
690
Transcript
PWA w/React.js & Firebase
+JimmyMoon @ragingwind
- Simple PWA with React.js - App Shell Architecture -
Web Manifest - Service Worker - Firebase - Tuning on PRPL - Auditing by Lighthouse
- create-react-app - (react-scripts < 0.9.5) - Webpack > 2.4.x
- PWA features - Minimal snippets - One of the app for guiding PWA
None
create-react-app
> yarn init && tree . └── package.json
> touch public/index.html && tree . └── public └── index.html
<!--fit in mobile-optimized --> <meta name="viewport" content="width=device-width, initial-scale=1" /> <!--support
tool bar color --> <meta name="theme-color" content="#0A0A0A" /> ./public/index.html
> mkdir -p src/components/ > touch ./src/components/App.js \ ./src/main.js
> tree . └── src ├── components │ └── App.js
└── main.js
> yarn add react react-dom
import React from 'react'; import ReactDOM from 'react-dom'; import App
from './App.js'; ReactDOM.render(<App />, document.getElementById('app')); ./src/main.js
import React from 'react'; class App extends React.Component { render()
{ return ( <div><h1>Hello World !! </h1> </div> ); } } ./src/components/App.js
... <body> <!--root element --> <div id="app"> </div> </body> ...
./public/index.htm
> yarn add --dev webpack \ webpack-dev-server > touch webpack.config.js
module.exports = { entry: { main: ['./src/main.js'], }, output: {
path: path.resolve( __dirname, './build'), filename: '[name].js' } ... }; ./webpack.config.js
module.exports = { ... devServer: { contentBase: './public', inline: true,
host: 'localhost', port: 8080 } }; ./webpack.config.js
... <!--hard code main script --> <script type="text/javascript" src="main.js"> </script>
... ./public/index.html
> yarn add --dev babel-core \ babel-loader \ babel-preset-react-app >
touch .babelrc
{ // babel preset for react app // made by
facebook "presets": ["react-app"] } ./.babelrc
module.exports = { ... module: { loaders: [{ test: /\.(js|jsx)$/,
include: path.resolve( __dirname, './src'), loaders: 'babel-loader' }] } }; ./webpack.config.js
{ ... // add dev server and build script to
package.json "scripts": { "start": "NODE_ENV=development webpack-dev-server", "build": "rm -rf build && NODE_ENV=production webpack -p" }, ... } ./package.json
> yarn start
None
App Shell Architecture
None
None
> yarn add material-ui \ react-tap-event-plugin > touch ./src/components/AppShell.js
import reactTabEventPlugin from 'react-tap-event-plugin'; reactTabEventPlugin(); ./src/main.js
class AppShell extends React.Component { render() { return ( <div
id="content"> {React.cloneElement(this.props.children)} </div> ); } }; ./src/components/AppShell.js
import {MuiThemeProvider, getMuiTheme} from 'material-ui/styles'; import {AppBar, Drawer, MenuItem} from
'material-ui'; ./src/components/AppShell.js
constructor(props) { // drawer open flag this.state = {open: false};
} ./src/components/AppShell.js
render() { return ( <MuiThemeProvider> <div> <AppBar /> <Drawer open={this.state.open}>
<MenuItem primaryText={'Main'} /> </Drawer> ... </div> </MuiThemeProvider> ); } ./src/components/AppShell.js
... handleDrawerToggle = () => this.setState({open: !this.state.open}) handleRequestChange = open
=> this.setState({open: open}) ... ./src/components/AppShell.js
class App extends React.Component { render() { return ( <AppShell>
<div><h1>Hello World !! </h1> </div> </AppShell> ); } } ./src/components/AppShell.js
None
Web Manifest
> npm install -g pwa-manifest-cli > pwa-manifest ./public \ --icons=ICONPATH_LOCAL_OR_HTTP
None
None
None
> tree -L 3 ./public ├── favicon.ico ├── icon-144x144.png ├──
icon-192x192.png ├── icon-512x512.png ├── index.html └── manifest.json
{ "name": "react-pwa", "short_name": "react-pwa", "icons": [ { "src": "icon-144x144.png",
"sizes": "144x144", "type": "image/png" }, ... ], "start_url": "./?utm_source=web_app_manifest", "display": "standalone", "orientation": "natural", "background_color": "#FFFFFF", "theme_color": "#3F51B5" } ./public/manifest.json
<link rel="manifest" href="manifest.json"> ./public/index.html
None
Service Worker
None
> yarn add --dev \ sw-precache-webpack-plugin-loader \ copy-webpack-plugin
const CopyWebpackPlugin = require('copy-webpack-plugin'); const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin-loader'); ./webpack.config.json
plugins: [ new CopyWebpackPlugin([{ context: './public', from: '*.*' }]) ]
./webpack.config.json
plugins: [ new SWPrecacheWebpackPlugin({ staticFileGlobs: [CACHED_FILES_GLOBS], logger: function () {},
filename: 'sw.js' }) ] ./webpack.config.json
<script> if ('serviceWorker' in navigator) { window.addEventListener('load', function() { navigator.serviceWorker.register('sw.js');
}); } </script> ./public/index.html
None
More views, React Routing
> yarn add react-router-dom > touch ./src/components/Home.js > tree ./src/components
├── App.js ├── AppShell.js └── Home.js
import {Card, CardTitle, CardText} from 'material-ui/Card'; ./src/components/Home.js
class Home extends React.Component { render () { return (
<Card> <CardTitle title="Hello! World" /> <CardText> ... </CardText> </Card> ) } } ./src/components/Home.js
import {HashRouter as Router, Route} from 'react-router-dom'; import Home from
'./Home'; ./src/components/App.js
class App extends React.Component { render() { return ( <div><h1>Hello
World !! </h1> </div> <Router> <AppShell> <div> <Route exact path="/" component={Home} /> </div> </AppShell> </Router> ); } } ./src/components/App.js
None
> touch ./src/components/Users.js > touch ./src/components/Notification.js > tree ./src/components ├──
App.js ├── AppShell.js ├── Home.js ├── Users.js └── Notification.js
import React from 'react'; class Users extends React.Component { render()
{ return ( <div> Users </div> ); } } export default Users; ./src/components/Users.js
import React from 'react'; class Notification extends React.Component { render()
{ return ( <div>Notification </div> ); } } export default Notification; ./src/components/Notification.js
import Users from './Users'; import Notification from './Notification'; ./src/components/App.js
<Router> <AppShell> <div> <Route exact path="/" component={Home} /> <Route path="/users"
component={Users} /> <Route path="/notification" component={Notification} /> </div> </AppShell> </Router> ./src/components/App.js
import {Link} from 'react-router-dom'; <Drawer> ... <MenuItem primaryText={'Users'} containerElement={<Link to={'/Users'}
/>} onClick={this.handleDrawerToggle} /> <MenuItem primaryText={'Notification'} containerElement={<Link to={'/Notification'} />} onClick={this.handleDrawerToggle} /> </Drawer> ./src/components/AppShell.js
None
Firebase Hosting with HTTPS
None
> npm install -g firebase-tools
None
> tree -L 1 ./ -a -I node_modules ├── .babelrc
├── .firebaserc ├── firebase.json ├── package.json ├── public ├── src ├── webpack.config.js └── yarn.lock
{ "hosting": { "public": "build" } } ./firebase.json
{ "projects": { "default": "YOUR-PROJECT-NAME" } } ./.firebaserc
None
> yarn build > tree -L 1 ./ -a -I
node_modules ├── favicon.ico ├── icon-144x144.png ├── icon-192x192.png ├── icon-512x512.png ├── index.html ├── main.js ├── manifest.json ├── notification.js └── sw.js
Firebase Realtime Database
None
None
None
class Users extends React.Component { constructor() { super(); this.state =
{ users: [] }; } } ./src/components/Users.js
componentDidMount() { const databaseURL = 'https: //YOUR_DATABASE_URL'; fetch(`${databaseURL}/data.json/`).then(res => {
if (res.status !== 200) { throw new Error(res.statusText); } return res.json(); }).then(users => this.setState({users: users})) } ./src/components/Users.js
render() { const users = () => { return Object.keys(this.state.users).map(id
=> { const user = this.state.users[id]; return ( <User key={id} name={user.name} email={user.email} /> ); }); } return (<div>{users()} </div>); } ./src/components/Users.js
None
None
plugins: [ new SWPrecacheWebpackPlugin({ ... runtimeCaching: [{ urlPattersn: /https:\/\/.+.firebaseio.com/, handler:
'networkFirst' }] }) ] ./webpack.config.js
None
Firebase Cloud Messaging
> yarn add firebase
None
{ gcm_sender_id: "103953800507", ... }; ./manifest.json
import firebase from 'firebase'; ./src/components/Notification.js
var config = { apiKey: "AIzasdakagskgei@#9412i8123WWEeFwyuOk4af3vhYFw", authDomain: "YOURPROJECT.firebaseapp.com", databaseURL: "https:
//YOURPROJECT.firebaseio.com", projectId: "YOURPROJECT", storageBucket: "YOURPROJECT.appspot.com", messagingSenderId: "2595534347235" }; ./src/components/Notification.js
class Notification extends React.Component { static firebaseApp; constructor(props) { super(props);
if (!Notification.firebaseApp) { firebase.initializeApp(config); } } } ./src/components/Notification.js
class Notification extends React.Component { constructor(props) { ... this.state =
{ token: '', message: '' }; } } ./src/components/Notification.js
componentDidMount() { const messaging = firebase.messaging(); messaging.onMessage(this.handlePushMessage); messaging.requestPermission() .then(() =>
messaging.getToken()) .then(token => this.setState({token: token})) .catch(err => { throw new Error(err); }); } ./src/components/Notification.js
handlePushMessage = noti => { this.setState({ message: `${noti.title}: ${noti.body}` });
} ./src/components/Notification.js
render() { return ( <div> <Card> <CardHeader title={'Token'} subtitle={this.state.token} />
<CardHeader title={'Message'} subtitle={this.state.message} /> </Card> </div> ); } ./src/components/Notification.js
> touch ./src/firebase-messaging-sw.js > tree -L 1 ./src -I node_modules
├── components ├── firebase-messaging-sw.js └── main.js
importScripts('https: // www.gstatic.com/firebasejs/3.5.2/firebase-app.js'); importScripts('https: // www.gstatic.com/firebasejs/3.5.2/firebase-messaging.js'); ./src/firebase-messaging-sw.js
var firebaseConfig = { ... }; firebase.initializeApp(firebaseConfig); const messaging =
firebase.messaging(); messaging.setBackgroundMessageHandler(({data} = {}) => { const title = data.title || 'Title'; const opts = Object.assign({body: data.body || 'Body'}, data); return self.registration.showNotification(title, opts); }); ./src/firebase-messaging-sw.js
plugins: [ new CopyWebpackPlugin([{ ... }, { from: './src/firebase-messaging-sw.js', to:
'firebase-messaging-sw.js', }]), ] ./webpack.config.js
> npm install -g fcm-cli
None
None
None
> fcm send --server-key SERVER_KEY:bKFlxhMqS_PYGlCw4VTV \ --to TOKEN:A91bFmi_DEQFPQ2FMf_7V8NcEGGqkAZEV- \ --notification.title
hi \ --notification.body 'Hello World'
None
Tuning on PRPL Pattern
(P)ush critical resources for the initial route (R)ender initial route
and get it interactive as soon as possible, (P)re-cache components for the remaining routes (L)azy-load, and create next routes on demand by user
None
None
Kicking Mega Bundle
None
None
Breaking down Common Chunks
module.exports = { entry: { main: ['./src/main.js'], vendor: ['react', 'react-dom']
}, plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: 'vendor' }), ] } ./webpack.config.js
<script type="text/javascript" src="vendor.js"> </script> <script type="text/javascript" src="main.js"> </script> ./public/index.html
None
None
Routes based Code-splitting
> yarn add --dev babel-plugin-syntax- dynamic-import
{ "plugins": [ "syntax-dynamic-import" ] } ./.babelrc
export default (getComponent) => ( class AsyncComponent extends React.Component {
componentWillMount() { if (!this.state.Component) { getComponent().then(Component => { AsyncComponent.Component = Component this.setState({ Component }) }); } }. ... } ); ./src/components/AsyncComponent.js
import asyncComponent from './AsyncComponent'; const Home = asyncComponent(() => {
return import( /* webpackChunkName: "home" */ './Home') .then(module => module.default); }); const Users = ...' const Notification = ...; ./src/components/App.js
None
None
Tree-shaking, Dead-code elimination Live-code Importing
None
import {Card, CardHeader} from 'material-ui'; import {Card, CardHeader} from 'material-ui/Card';
> yarn add --dev babel-preset-es2015 babel- plugin-transform-imports
{ "presets": [["es2015", {"modules": false}], "react-app"] } ./.babelrc
"plugins": [ [ "transform-imports", { "material-ui": { "transform": "material-ui/${member}", "preventFullImport":
true }, "react-router-dom": { ...}, "lodash": { ...} } ] ] ./.babelrc
None
494 kB
None
Extract common parts Shared Common Chunk
plugins: [ new webpack.optimize.CommonsChunkPlugin({ children: true, async: 'common', minChunks: 2
}), ] ./webpack.config.js
<script type="text/javascript" src="common.js"> </script> ./public/index.html
None
None
None
None
Control resources pushing Preload, Prefetch
> yarn add --dev html-webpack-plugin preload-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin'); const PreloadWebpackPlugin = require('preload-webpack-plugin'); ./webpack.config.js
plugins: [ new HtmlWebpackPlugin({ filename: 'index.html', template: './public/index.html', favicon: './public/favicon.ico'
}), new PreloadWebpackPlugin({ include: ['vendor', 'main', 'common', 'home'] }), ] ./webpack.config.js
<script type="text/javascript" src="vendor.js"> </script> <script type="text/javascript" src="main.js"> </script> <script type="text/javascript"
src="common.js"> </script> ./public/index.html
None
plugins: [ new PreloadWebpackPlugin({ rel: 'prefetch', include: ['users', 'notification'] }),
] ./webpack.config.js
None
None
More Optimization
class LazySidebarDrawer extends React.Component { componentDidMount() { let frameCount =
0; const open = () => (frameCount ++ > 0) ? this.props.onMounted() : requestAnimationFrame(open); requestAnimationFrame(open); } render() { return ( <Drawer open={this.props.open} docked={false} onRequestChange={this.props.onRequestChange}> ... </Drawer> ); } }
render() { return ( ... {<LazySidebarDrawer />} ... ); }
./src/components/AppShell.js
None
> 144 kB
None
None
> yarn add react-lite
More plugins for Webpack
Auditing by Lighthouse
None
And more ...
None