Are you concerned about accessibility? Do you want your site to be blazingly fast and accessible, even from some random guy with a 2G connection on the North Pole? Are you afraid about being sued by Facebook? (JK probably not happening tho.)

Then you might wanna take a look to Preact, a lightweight alternative to React — and why not add learning about progressive web apps on the mix? This blog post attempts to show you how these two things works together by creating a little webapp to search TV shows.

Before we get started with Preact, lets start with the project configuration.

Setting up Babel and Webpack 2

A little note here. There are some Preact boilerplates / starter kits out there, but most of them are meant for complex apps and thus they install a lot of useful-yet-not-always-needed libraries and modules. I asked @_developit if he knew about a minimal set up for Preact apps and he came back to me with this helpful piece of art. You can skip this step and use his approach if you want to.

If you stick to my approach, first let’s create our project structure:

1

2

3

4

5

.

├──index.html

├──package.json

├──public

├──src

The directory
src/ will contain the source code for our Preact app,
public/ will hold all the builds for JS and CSS,
index.html will contain the app barebones and
package.json is all about our dependencies (and maybe some scripts).

Also, let’s get started by filling our
index.html with some code like this:

1

2

3

4

5

6

7

8

9

10

<!DOCTYPE html>

<html>

<head>

<title>TVApp</title>

</head>

<body>

<div id="root">

</div>

</body>

</html>

Now let’s add some modern-js-fatigue-frontend-tooling. Read this carefully because your brains may fall out:

Notice that I’m using Yarn, because I like live on the edge. You can use

npm install--save-dev

if you are not ready for this level of awesomeness yet.

If you are already a pro setting up your webpack and babel configs, feel free to skip to the next section. If you are not ready yet, after this you might end up using webpack a lot, or starting your very own js fatigue post. Either way you are gonna get something from it.

Let’s start creating the infamous webpack.config.js file:

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

varwebpack=require('webpack')// require webpack of course

varpath=require('path')// path is nice to have

module.exports={

entry:path.join(__dirname,'src/index.js'),// our entry file

output:{

filename:'bundle.js',// we are creating this file after bundling

path:path.join(__dirname,'public'),// this will be the folder for bundle.js

publicPath:'/static/'// we are gonna serve the bundle from here using webpack-dev-server

},

module:{

rules:[

{

test:/\.jsx?/i,

loader:'babel-loader',

options:{

presets:[

'es2015'

],

plugins:[

['transform-react-jsx',{pragma:'h'}]

]

}

}

]

}

}

I tried to keep things minimal, so no fancy configs, but this will do the work. The only “confusing” thing here is the
path vs
publicPath thing. For now just think this: for the final (production) bundle we are throwing that file on the public directory, but for webpack-dev-server (a little server that we are gonna use to work on dev) we are serving from
/static/.

Now we need to tweak a little bit our package json in order to look like this:

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

{

"name":"moovieapp",

"private":true,

"dependencies":{

},

"devDependencies":{

"babel-core":"^6.23.1",

"babel-loader":"^6.3.2",

"babel-preset-es2015":"^6.22.0",

"webpack":"^2.2.1",

"webpack-dev-server":"^2.3.0",

"babel-plugin-transform-react-jsx":"^6.23.0"

}

}

We have just added the “babel” key telling babel “hey we are using this preset”. You can move this thing from here into a
.babelrc file if you want to.

And we are done. Yes, that was it, at least for now. A really painful experience right? (</sarcasm>)

So, let’s test our setup! we can create an
index.js file on our
src/ folder with the following content:

JavaScript

1

2

3

document.addEventListener('DOMContentLoaded',event=>(

console.log('test')

))

I use
DOMContentLoaded to wait for everything to load. You can use whatever you want (e.g. If you are into lispy things:
(()=>(console.log('test')()))

Next, let’s tweak our
index.html:

1

2

3

4

5

<body>

<div id="root">

</div>

<script src="/static/bundle.js"></script>

</body>

Here we just added the
<script> tag to load
bundle.js from
/static/.

Let’s take this for a run by doing
node_modules/.bin/webpack-dev-server on our console:

Webpack should be running a server on your port 8080 in localhost and you should see the test message on your console! Pretty neat, huh?

That’s it for the configs, at least for now. Let’s step into the preact app now!

Getting started with Preact

We already have all the configs that we need for running Preact on our webpack config on the rules key:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

...

{

test:/\.jsx?/i,

loader:'babel-loader',

options:{

presets:[

'es2015'

],

plugins:[

['transform-react-jsx',{pragma:'h'}]

]

}

},

...

You could move all the presets/plugins stuff into a
babel.rc file if you want to, but im trying to keep things (and files) minimal here. With this config we can use ES6 and Preact components.

If you are wondering about that
'h' after the keyword
pragma, we will see that soon.

Let’s write our first/main component under
src/compontents/App.js:

1

2

3

4

5

6

7

8

9

10

11

import{h,Component}from'preact'

exportdefaultclassAppextendsComponent{

render(){

return(

<div>

Hi

</div>

)

}

}

And now let’s get that rendered on our page by updating the
index.js file so it looks like this:

1

2

3

4

5

6

import{h,render}from'preact'

import App from'./components/App'

document.addEventListener('DOMContentLoaded',event=>(

render(<App/>,document.getElementById('root'))

))

Now if you reload the page you should see our
App component saying hi!

As mentioned earlier, you might have noticed that
h here and there on the configs and the components. That’s a pretty common thing on Preact. You can read more about it here.

Working on our App

Now that everything is up and running, we can get down into some code. For this app I’ve just created a few components. Let’s go through each one:

This is how
App.js looks now:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

import{h,Component}from'preact'

import SearchBar from'./SearchBar'

import SearchesList from'./SearchesList'

exportdefaultclassAppextendsComponent{

constructor(){

super()

this.state={

searches:[]

}

this.addSearch=this.addSearch.bind(this)

}

addSearch({search,results}){

this.setState((prevState)=>{

const{searches}=prevState

searches.push({search,results})

return{searches:searches}

})

}

render(props,state){

return(

<div>

<SearchBar

addSearch={this.addSearch}

/>

<SearchesList

searches={state.searches}

/>

</div>

)

}

}

If you know React, this should be pretty straightcoforward: we have a constructor function and we keep a
searches collection on the app state.

The
addSearch method gets and object with a
search and
result keys as a parameter (thanks to destructuring) and we use the
setState function to push the new search with its result to the new state.

Finally our render it’s just a
<div> with 2 components
SearchBar which will use
addSearch as a callback later and
SearchesList which will display the searches on our state.

Now, let’s create all the other components on the
src folder.

The file
SearchBar.js should look like this:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

import{h,Component}from'preact'

import request from'superagent'

exportdefaultclassSearchBarextendsComponent{

constructor(){

super()

this.doSearch=this.doSearch.bind(this)

}

doSearch(){

const{addSearch}=this.props

const{text}=this.state

consturl=`http://api.tvmaze.com/search/shows?q=${text}`

request

.get(url)

.end((err,res)=>{

if(!err){

addSearch({

search:text,

results:res.body

})

}else{

console.error('Something is wrong with the API :(')

}

})

}

render(props,{text}){

return(

<div>

<input value={text}onInput={this.linkState('text')}/>

<button onClick={this.doSearch}>

Search

</button>

</div>

)

}

}

This component is an text input with a button to perform searches. It’s in charge of dispatching an API call and setting the results on a search within a
searches collection on the App state. For that, we have the
doSearch method that just gets the
addSearch function from the props (the one that we sent from the
App component) and the text input from the state. Then, it performs an Ajax call to tvmaze’s API. After that, if everything was ok, we call
addSearch with our text as the
search key and the
results. You can see that I’m using superagent to handle Ajax calls, mostly because I’m super lazy right now. Feel free to use whatever you want, but if you want to use super agent as well, just run
yarn add superagent on your terminal.

The render method has the
input tag with the
text from our state as a value and a really nice feature from Preact called
linkState, which allows us to update the
text key of our state on each change on our input element.

And we have also a
button that triggers the
doSearch function when it’s clicked. Notice that we had to bind that function to the component’s
this on the
constructor function so it could access the state and props of the component.
The file
SearchesList.js is simpler:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

import{h,Component}from'preact'

import SearchItem from'./SearchItem'

constSearchesList=({

searches

})=>(

<div>

{

searches.map((search)=>

<SearchItem search={search}/>)

}

</div>

)

export defaultSearchesList

Since it is just a presentational component, it doesn’t need
state or
lifecycle functions so we can just write it as a plain function that gets our searches from the state and maps them into a SearchItem.

And
SearchItem.js:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

import{h,Component}from'preact'

constSearchItem=({

search

})=>(

<div>

<h1>{search.search}</h1>

<ul>

{

search.results.map(({show})=>(

<li>

<h5>{show.name}</h5>

<img src={show.image.medium}/>

<div dangerouslySetInnerHTML={{__html:show.summary}}/>

</li>

))

}

</ul>

</div>

)

export defaultSearchItem

This is also a presentational component so it’s also a function. Here we render a
<div> with a title that should be the searched text (that’s what
serach.search contains, sorry about the confusing naming) and we map the results inside a list showing the
show name,
image and
summary (we use
dangerouslySetInnerHTML because it comes down with it’s own html tags).

With all this new files and changes let’s go back to our
localhost:8080 and we should see our app up and running:

Going offline

We have our app working nicely despite not having any styling, but what happens if we want to remember the description of a show we looked up before on the subway and we don’t have any connection?

That’s a shame we want our app to be available anywhere! That’s where service workers shine. Let’s get started making our app progressive.

First thing about progressive web apps is that they use a
manifest.json file where we store some data about the app, much like when you work on chrome extensions. Ours should be something like:

1

2

3

4

5

6

7

8

9

{

"name":"TVApp",

"short_name":"TVApp",

"start_url":"/",

"scope":"/",

"display":"standalone",

"background_color":"#2196F3",

"theme_color":"#2196F3"

}

And that manifest should be located on the root of our app. You could set more things in here like icons and such… feel free to tweak it around!

Next step, add the manifest on the
<head> of our
index.html like so:

1

2

3

4

<head>

<title>TVApp</title>

<link rel="manifest"href="/manifest.json">

</head>

Now we can add our service worker to cache our assets, let’s create a
sw.js file also on the root of our app.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

varCACHE_NAME='tv-app-v1'

varurlsToCache=[

'/',

'/index.html',

'/static/bundle.js'

]

self.addEventListener('install',function(event){

// Perform install steps

event.waitUntil(

caches.open(CACHE_NAME)

.then(function(cache){

console.log('Opened cache')

returncache.addAll(urlsToCache)

})

)

})

self.addEventListener('fetch',function(event){

event.respondWith(

caches.match(event.request)

.then(function(response){

// Cache hit - return response

if(response){

returnresponse

}

returnfetch(event.request)

}

)

)

})

There’s a lot going on here. Let’s split that into different parts:

First we declare the cache name, that can be whatever you want. After that, we declare an array of files that we want to store in our service worker cache. Since we are using the webpack dev server, we cache the
/static/bundle.js, but if you are in production you probably want to add
/public/bundle.js here.

Second, we open the cache and add all our url’s on the installation step of our service worker.

Third, every time our app does a
fetch, we check if our cache contains the thing that the app is trying to fetch. If it does, we return the cached content. Otherwise, we retrieve the data from the network.

That’s it for now! Now we just need to install this service worker, in order to do that let’s add this on our
index.html.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

<!DOCTYPE html>

<html>

<head>

<title>TVApp</title>

<link rel="manifest"href="/manifest.json">

</head>

<body>

<div id="root">

</div>

<script>

if('serviceWorker'innavigator){

navigator.serviceWorker.register('/sw.js',{scope:'/'})

.then(function(registration){

console.log('Service Worker Registered');

});

navigator.serviceWorker.ready.then(function(registration){

console.log('Service Worker Ready');

});

}

</script>

<script src="/static/bundle.js"></script>

</body>

</html>

Here, we ask if the browser supports
serviceWorker and if it does we grab our
sw.js and we install it.

Let’s take it for a ride! If you are using Chrome you can reload the page, then go to your dev tools on
Application->Service Workers and there you’ll see your service worker registered.

Check that offline box and see how our app now even loads without connection.

But we still have a problem: we can see our app assets and if we perform a search without connection it’s gonna fail because it will try to use the network. Fixing that up is not really hard. We just need to make some tweaks on 2 files.

First on
sw.js:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

self.addEventListener('fetch',function(event){

vardataUrl='http://api.tvmaze.com/search/shows?q='

if(event.request.url.indexOf(dataUrl)>-1){

event.respondWith(

caches.open(CACHE_NAME).then(function(cache){

returnfetch(event.request).then(function(response){

cache.put(event.request.url,response.clone())

returnresponse

})

})

)

}

else{

event.respondWith(

caches.match(event.request)

.then(function(response){

if(response){

returnresponse

}

returnfetch(event.request)

}

)

)

}

})

In our
fetch event listener, we added an if, so if the request is for our API, we open the cache and then we store the result in there. Otherwise, we proceed as before returning assets from our cache or the network.

Now we have our requests on our cache, let’s read them from there. For that, let’s go to our
SearchBar.js on the
doSearch method:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

doSearch(){

const{addSearch}=this.props

const{text}=this.state

consturl=`http://api.tvmaze.com/search/shows?q=${text}`

if('caches'inwindow){

caches.match(url).then((res,err)=>{

if(res){

res.json().then((json)=>{

addSearch({

search:text,

results:json

})

})

}

})

}

request

.get(url)

.end((err,res)=>{

if(!err){

addSearch({

search:text,

results:res.body

})

}else{

console.error('Something is wrong with the API :(')

}

})

}

Here, we aso added another piece: checking if we have the request on our cache. If we do, we add the search from there, otherwise we go to the network again.

Let’s try our app again!

So, let’s say we have internet connection so we search for Batman shows:

But 5 hours later you are talking with your friend about the old Batman show while climbing a mountain and you don’t remember who was interpreting Batman on that show (how could you?). Now, you can check that on our app even without connection! (if you don’t have any mountain near you, you can try clicking the offline box on your chrome dev tools.)

That’s it! You’ve got your first Progressive Preact app up and running using Webpack 2! Probably there are bugs and things to improve. Feel free to hack around the repo and send PRs! Here you can find the source code.

Some final thoughts about Preact and Progressive web apps.

At first, I wasn’t really sure about Preact (or any other React alternative that matters), but after using it a bit I found it really useful. Specially when you want the React goodies, but you can’t / shouldn’t afford the bandwidth to send React on your build, like working on widgets or landing pages. Also, it plays really nice with progressive web apps and I found that the community is great!

And, for progressive web apps, I really thing that’s gonna play a major part on the future of web development. There are a lot of things to use on the ServiceWorker API, but it’s not standard yet. Keep an eye on this page to stay tuned.