Ok so you want to build a Progressive Web application using Fable? Then you're in the right place!
Grab your mobile phone and browse to: https://whitetigle.github.io/fable-pwa/
Or scan
- 1. Fable Simple PWA
- dotnet SDK 2.0 or higher
- node.js 6.11 or higher
- yarn
Although is not a Fable requirement, on macOS and Linux you'll need Mono for other F# tooling like Paket or editor support.
- Install JS dependencies:
yarn install
- Move to
src
folder:cd src
- Install F# dependencies:
dotnet restore
- Start Fable daemon and Webpack dev server:
dotnet fable yarn-start
- In your browser, open: http://localhost:8080/
dotnet fable yarn-start
(ornpm-start
) is used to start the Fable daemon and run a script in package.json concurrently. It's a shortcut ofyarn-run [SCRIPT_NAME]
, e.g.dotnet fable yarn-run start
.
If you are using VS Code + Ionide, you can also use the key combination: Ctrl+Shift+B (Cmd+Shift+B on macOS) instead of typing the dotnet fable yarn-start
command. This also has the advantage that Fable-specific errors will be highlighted in the editor along with other F# errors.
Any modification you do to the F# code will be reflected in the web page after saving. When you want to output the JS code to disk, run dotnet fable yarn-build
and you'll get a minified JS bundle in the public
folder.
Let's say that a Progressive Web Application is simply a web app you want your user to access at the best conditions: obviously making it more responsive thanks to caching strategies for instance.
So when your user goes to your web site, let's say https://myNextAwesomeFable.App.net, he'll be offered the choice to add the web site/app to his system/home screen (desktop/mobile).
For instance on Android, a shortcut will be added and then when the use taps on the icon of this app, the web site will run in its own context and load assets from a local cache thanks to a service-worker. This context is called App Shell.
So your web site/app will run just like a native app with its icon and all.
(More information from Mozilla documentation)
The manifest file is a JSON file that allosw you to control how your app appears to the user. It's pretty easy to understand.
{
"short_name": "MyFablePWA",
"name": "Fable powered PWA",
"start_url": "/index.html",
"icons": ...
}
This is also this file that will allow to add a nice shortcut to your app.
There are other fields you can add and which are very well documented here
Enter our nice workers that make the magic possible. You can read awesome doc on Mozilla's site
But I will try to sum things up for you here. Basically, let's say a service Worker allows you to
- to manage your cache and allow offline use of your app
- proxy all outgoing request
So, the service-worker.js
file is where all the plumbing happens:
- install the worker: usually it's here we cache our assets. Once the worker is installed it will stay there until the end of the world. (I'll get back to that later)
- activate the worker: once it's up and running, the web worker will update its cache only when we tell him to do so, meaning when we change the cache name for instance.
- handle events: there are several events that can be handled by the worker. The most known being the fetch event which will gracefully allow your app to make its call to, let's say, your server or CDNs.
A cache name:
var CACHE_NAME = 'my-fable-app-cache-0.1';
A list of assets to cache:
var resourcesToCache = [
'/',
'https://cdn.polyfill.io/v2/polyfill.js?features=es6,fetch',
'/index.html',
'/bundle.js'
];
In this list you should add all the local and remote assets you would need and you'd like to cache.
Our plumbing event listeners:
self.addEventListener('install', function(event) {...
self.addEventListener('activate', function(event) {...
self.addEventListener('fetch', function(event) {...
Now we only need to register our service-worker.js
file to make it working.
There are two ways of doing that.
We can simply register the service worker file from App.fs
:
// start our app only if our service worker registered well
open Fable.PowerPack
open Fable.Import
promise {
try
// register service worker
let! _ = "/service-worker.js" |> Browser.navigator.serviceWorker.register
// start app only if registration works
init()
with exn -> printfn "%s" exn.Message
}
|> Promise.start
and
<body>
...no need to call any extra script to register the service-worker.js file
<script src="bundle.js"></script>
</body>
or the js way which implies 2 things:
- we have a
register-service-worker.js
file ready - we call it before
bundle.js
from our index.html file
So it would look like:
<body>
...
<script src="register-service-worker.js"></script>
<script src="bundle.js"></script>
</body>
and
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(function() {
console.info('service worker succesfully registered');
}).catch(function(e) {
console.error(e, 'service worker registration failed');
});
}
If we take alook at CanIUse I think we can safely assume that service workers are now taken care of in every modern browsers.
There are many great articles to read about ServiceWorkers and Progressive Web Apps. There's especially something which you should be aware of:
Now you've installed your Service Worker, it will always be there making sure everything's running smooth without any Internet connection. What does it mean? Well it means that you nay not see anything changing the next time you update your app. Why? Because it's been cached already. So here is the most important things you should always do:
service-worker.js: Change the cache label/version number
Yes, because you will have to force the service worker to update and you can't do it just by hitting the refresh button of your browser..
It's because to ensure the app consistency for the client, we can't just update the app while it's running. So you've updated your app and now you need to upgrade your service worker to the new version. It means you need to remove the old version first.
So update your app, change your cache version number, close all your tabs to kill the app and clean your browser cache.
Reload: now you've got your new version available. There are other strategies, but I won't dig into the details here. Please read this and this if you want more information. (It's worth the read!)
PWA work only through https. Period. On your desktop browser you won't really get problems with SSL certificates because it will try to load any missing part. But on the mobile side, you will if you the certificates you provide are not complete: you absolutely need a FULL and VALID certificate.
So go on a SSL test site like this one and make sure your certificate passes the tests.
Since you will make remote calls using fetch, you'll probably end up with CORS issues. So prepare your servers, load balancers and all to handle cross origin requests.
I have not yet taken time to dig into that, but Foreign Fetch should be there to help avoir cross origin related problems.
Well it should be a surprise for a web developer but now that browser vendors are deprecating http requests in favor of https, the whole pipeline must be ready for that :)
There are a few things you should know to avoid losing too much time solving issues during your development with Fable.
Just code your app, update, test it with your dotnet fable yarn-start
like you would usually do and enable/register your service worker only when you actually need to use/test it.
If you don't do that, you'll end up screaming because you don't see your changes reflected in your browser because, as you now understand, once it's started, a Serice Worker will remain alive until you actually unregister it.
The same goes when you want to update your app: disable Service Worker, make your changes, test them and then when you're done, uncomment your registration code.
When you develop using the watch mode (localhost:8080), changes in your service-worker.js
file won't be reflected unless you stop webpack.
To avoid this behaviour, we add the following line to App.fs
, so that the service-worker file gets watched too.
importAll "../docs/service-worker.js"
Dy default, we use localhost:8080
to work on our fable apps. If you decide to work on a new project, you may experience something weird: you actually see the last project you were working on!
Because, as you now understand, once it's started, a Serice Worker will remain alive until you actually unregister it.
So it means it will outlive your development cycle and remain there forever at the address you hosted it to. Don't be afraid. Relax. Just unregister your service worker it, refresh and now you can see your actual project on screen.
It's easy:
- Chrome: go to the Application tab and unregister the service worker
- Firefox: browse to about:serviceworkers and there you'll be able to unregister them
It's not really linked to PWAs but if you're using Firefox, use its great WebIDE which will help you understand what's goin on on your remote device by making the whole developer console available on your desktop.
It's called Remote Debugging and you should definitely use it!
If you load things from index.html
, make sure the assets you're loading are reflected in your service-worker.js
file in the resourcesToCache list.
For instance don't call
<script src="greatJSLib.js"></script>
in your index.html andgreatJSLib.min.js
in your cache list or else it will cache a file you won't be using!
Grab your mobile phone and browse to: https://whitetigle.github.io/fable-pwa/
If you use chrome, just open the Developer Tools and click on the Application
tab. There you'll see:
- your Service Worker
- the possible errors encountered while fetching data.
If you get some errors, go to the Network
tab and check what's going there. It's often a problem with ressources we can't load for whatever reason (often CORS).
The current project will build to the docs
folder to enable GitHub hosting.
Don't forget to modify the webpack.config.js file
like this in order to allow the building of bundle.js
into the public folder:
change
var outputFolder = "./docs";
to
var outputFolder = "./public";
If you pick a look at the resourcesCache variable in service-worker.js
, you'll see that I've added /fable-pwa/
in front of my ressources.
var resourcesToCache = [
'/fable-pwa/',
'icons/android-icon-144x144.png',
...
'https://cdn.polyfill.io/v2/polyfill.js?features=es6,fetch',
'/fable-pwa/index.html',
'/fable-pwa/bundle.js'
];
So just remove these in order to get the sample working on your host:
var resourcesToCache = [
'/',
'icons/android-icon-144x144.png',
...
'https://cdn.polyfill.io/v2/polyfill.js?features=es6,fetch',
'/index.html',
'/bundle.js'
];
Simply change this:
importAll "../docs/service-worker.js"
to
importAll "../public/service-worker.js"
or any path so that the service-worker gets watched.
Remember: changes won't take any effect unless you change the cache version number in
service-worker.js
I will definitely update this project with latest news and information I get from my ongoing PWA projects. Thanks for reading and have fun deploying your pwa app!