{"id":21305982,"url":"https://github.com/whitetigle/fable-pwa","last_synced_at":"2025-07-11T21:31:23.424Z","repository":{"id":144167002,"uuid":"130320176","full_name":"whitetigle/fable-pwa","owner":"whitetigle","description":"Use Fable to create your next Pogressive Web Application","archived":false,"fork":false,"pushed_at":"2018-05-02T15:24:01.000Z","size":3081,"stargazers_count":25,"open_issues_count":0,"forks_count":0,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-06T09:11:26.479Z","etag":null,"topics":["fable","fsharp","pwa"],"latest_commit_sha":null,"homepage":"https://whitetigle.github.io/fable-pwa/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/whitetigle.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-04-20T06:40:50.000Z","updated_at":"2024-02-11T13:54:22.000Z","dependencies_parsed_at":null,"dependency_job_id":"697c207a-4032-445c-ac37-8b2aa314ad70","html_url":"https://github.com/whitetigle/fable-pwa","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/whitetigle/fable-pwa","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/whitetigle%2Ffable-pwa","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/whitetigle%2Ffable-pwa/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/whitetigle%2Ffable-pwa/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/whitetigle%2Ffable-pwa/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/whitetigle","download_url":"https://codeload.github.com/whitetigle/fable-pwa/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/whitetigle%2Ffable-pwa/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264902316,"owners_count":23681047,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fable","fsharp","pwa"],"created_at":"2024-11-21T16:20:33.899Z","updated_at":"2025-07-11T21:31:23.414Z","avatar_url":"https://github.com/whitetigle.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 1. Fable Simple PWA\n\nOk so you want to build a Progressive Web application using [Fable](http://fable.io/)? Then you're in the right place!\n\nGrab your mobile phone and browse to: [https://whitetigle.github.io/fable-pwa/](https://whitetigle.github.io/fable-pwa/)\n\nOr scan \n\n![QRCode](qrcode.png)\n\n## 1.1. Table of contents\n\u003c!-- TOC --\u003e\n\n- [1. Fable Simple PWA](#1-fable-simple-pwa)\n  - [1.1. Table of contents](#11-table-of-contents)\n  - [1.2. Requirements](#12-requirements)\n  - [1.3. Build \u0026 Run](#13-build--run)\n  - [1.4. How it works](#14-how-it-works)\n    - [1.4.1. manifest.webmanifest](#141-manifestwebmanifest)\n    - [1.4.2. Service Workers](#142-service-workers)\n      - [1.4.2.1. service-worker.js](#1421-service-workerjs)\n        - [1.4.2.1.1. What's inside](#14211-whats-inside)\n      - [1.4.2.2. register-service-worker.js](#1422-register-service-workerjs)\n        - [1.4.2.2.1. Fable/F# way](#14221-fablef-way)\n        - [1.4.2.2.2. Classic JS approach](#14222-classic-js-approach)\n  - [1.5. Life of a Service Worker](#15-life-of-a-service-worker)\n    - [1.5.1. Offline first](#151-offline-first)\n    - [1.5.2. Hey, I did that but my display just did not update!](#152-hey-i-did-that-but-my-display-just-did-not-update)\n  - [1.6. Security considerations](#16-security-considerations)\n    - [1.6.1. Prepare your SSL certificate.](#161-prepare-your-ssl-certificate)\n    - [1.6.2. CORS](#162-cors)\n    - [1.6.3. Foreign fetch](#163-foreign-fetch)\n    - [1.6.4. Modern Internet](#164-modern-internet)\n  - [1.7. Development guidelines](#17-development-guidelines)\n    - [1.7.1. Use Service workers ONLY when you need them](#171-use-service-workers-only-when-you-need-them)\n    - [1.7.2. Fable and webpack](#172-fable-and-webpack)\n    - [1.7.3. Avoid localhost:8080 nightmare](#173-avoid-localhost8080-nightmare)\n    - [1.7.4. Unregister a service worker](#174-unregister-a-service-worker)\n    - [1.7.5. Remotely debug your web app](#175-remotely-debug-your-web-app)\n    - [1.7.6. sync your static needs](#176-sync-your-static-needs)\n  - [1.8. Try the live sample!](#18-try-the-live-sample)\n    - [1.8.1. Check how it works](#181-check-how-it-works)\n    - [1.8.2. Update this sample to your needs](#182-update-this-sample-to-your-needs)\n      - [1.8.2.1. From docs to public](#1821-from-docs-to-public)\n      - [1.8.2.2. Cache paths](#1822-cache-paths)\n      - [1.8.2.3. Change watched file for webpack dev server](#1823-change-watched-file-for-webpack-dev-server)\n  - [1.9. To be continued](#19-to-be-continued)\n\n\u003c!-- /TOC --\u003e\n\n## 1.2. Requirements\n\n* [dotnet SDK](https://www.microsoft.com/net/download/core) 2.0 or higher\n* [node.js](https://nodejs.org) 6.11 or higher\n* [yarn](https://yarnpkg.com)\n\nAlthough is not a Fable requirement, on macOS and Linux you'll need [Mono](http://www.mono-project.com/) for other F# tooling like Paket or editor support.\n\n## 1.3. Build \u0026 Run\n\n* Install JS dependencies: `yarn install`\n* **Move to `src` folder**: `cd src`\n* Install F# dependencies: `dotnet restore`\n* Start Fable daemon and [Webpack](https://webpack.js.org/) dev server: `dotnet fable yarn-start`\n* In your browser, open: http://localhost:8080/\n\n\u003e `dotnet fable yarn-start` (or `npm-start`) is used to start the Fable daemon and run a script in package.json concurrently. It's a shortcut of `yarn-run [SCRIPT_NAME]`, e.g. `dotnet fable yarn-run start`.\n\nIf you are using VS Code + [Ionide](http://ionide.io/), 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.\n\nAny 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.\n\n## 1.4. How it works\n\nLet'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. \n\nSo 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). \n\nFor 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**.\n\nSo your web site/app will run just like a **native app** with its icon and all.\n\n(More information from [Mozilla documentation](https://developer.mozilla.org/en-US/Apps/Progressive))\n\n### 1.4.1. manifest.webmanifest\n\nThe manifest file is a JSON file that allosw you to control how your app appears to the user. It's pretty easy to understand.\n\n```json\n{\n  \"short_name\": \"MyFablePWA\",\n  \"name\": \"Fable powered PWA\",\n  \"start_url\": \"/index.html\",\n  \"icons\": ...\n}\n```\n\n**This is also this file that will allow to add a nice shortcut to your app.**\n\nThere are other fields you can add and which are very well documented [here](https://developer.mozilla.org/en-US/docs/Web/Manifest)\n\n### 1.4.2. Service Workers\n\nEnter our nice workers that make the magic possible.\nYou can read [awesome doc on Mozilla's site](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers)\n\nBut I will try to sum things up for you here.\nBasically, let's say a service Worker allows you to\n\n1. to manage your cache and allow offline use of your app\n2. proxy all outgoing request\n\n#### 1.4.2.1. service-worker.js\n\nSo, the ``service-worker.js`` file is where all the plumbing happens:\n\n1. **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)\n2. **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.\n3. **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.\n\n##### 1.4.2.1.1. What's inside\n**A cache name**: \n\n```js\nvar CACHE_NAME = 'my-fable-app-cache-0.1';\n```\n\n**A list of assets to cache**: \n\n```js\nvar resourcesToCache = [\n    '/',\n    'https://cdn.polyfill.io/v2/polyfill.js?features=es6,fetch',    \n    '/index.html',\n    '/bundle.js'\n];\n```\n\nIn this list you should add all the local and remote assets you would need and you'd like to cache.\n\n**Our plumbing event listeners**: \n\n```js\nself.addEventListener('install', function(event) {...\n\nself.addEventListener('activate', function(event) {...\n\nself.addEventListener('fetch', function(event) {...\n```\n\nNow we only need to register our ``service-worker.js`` file to make it working.\n\n#### 1.4.2.2. register-service-worker.js\n\nThere are two ways of doing that.\n\n##### 1.4.2.2.1. Fable/F# way \n\n  We can simply register the service worker file from ``App.fs``:\n\n```fsharp\n\n// start our app only if our service worker registered well\nopen Fable.PowerPack\nopen Fable.Import\n\npromise {\n\n    try\n      // register service worker\n      let! _ = \"/service-worker.js\" |\u003e Browser.navigator.serviceWorker.register\n\n      // start app only if registration works\n      init()\n\n    with exn -\u003e printfn \"%s\" exn.Message\n}\n|\u003e Promise.start\n```\nand\n\n```html\n\u003cbody\u003e\n  ...no need to call any extra script to register the service-worker.js file\n  \u003cscript src=\"bundle.js\"\u003e\u003c/script\u003e\n\u003c/body\u003e\n```\n\n\n##### 1.4.2.2.2. Classic JS approach\n\nor the js way which implies 2 things:\n\n1. we have a ``register-service-worker.js`` file ready\n2. we call it before ``bundle.js`` from our index.html file\n\nSo it would look like:\n\n```html\n\u003cbody\u003e\n  ...\n  \u003cscript src=\"register-service-worker.js\"\u003e\u003c/script\u003e\n  \u003cscript src=\"bundle.js\"\u003e\u003c/script\u003e\n\u003c/body\u003e\n```\n\nand \n\n```js\nif ('serviceWorker' in navigator) {\n    navigator.serviceWorker.register('/service-worker.js')\n    .then(function() {\n        console.info('service worker succesfully registered');\n    }).catch(function(e) {\n        console.error(e, 'service worker registration failed');\n    });\n}\n```\n\n\u003e If we take alook at [CanIUse](https://caniuse.com/#feat=serviceworkers)  I think we can safely assume that service workers are now taken care of in every modern browsers.\n\n## 1.5. Life of a Service Worker\n\nThere are many great articles to read about ServiceWorkers and Progressive Web Apps. There's especially something which you should be aware of:\n\n### 1.5.1. Offline first\n\nNow you've installed your Service Worker, it will always be there making sure everything's running smooth without any Internet connection.\nWhat 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:\n\n\u003e **service-worker.js**: Change the cache label/version number \n\n### 1.5.2. Hey, I did that but my display just did not update!\n\nYes, 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.**.\n\nIt's because to ensure the app consistency for the client, we can't just update the app while it's running.\nSo 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.\n\nSo update your app, change your cache version number, close all your tabs to kill the app and clean your browser cache.\n\n**Reload**: now you've got your new version available.\nThere are other strategies, but I won't dig into the details here. Please read [this](https://redfin.engineering/service-workers-break-the-browsers-refresh-button-by-default-here-s-why-56f9417694) and [this](https://redfin.engineering/how-to-fix-the-refresh-button-when-using-service-workers-a8e27af6df68) if you want more information. (It's worth the read!)\n\n## 1.6. Security considerations\n\n### 1.6.1. Prepare your SSL certificate.\nPWA work only through https. Period. \nOn your desktop browser you won't really get problems with SSL certificates because it will try to load any missing part.\nBut on the mobile side, you will if you the certificates you provide are not complete: you absolutely need a **FULL and VALID certificate**.\n\nSo go on a SSL test site like this [one](https://www.ssllabs.com/ssltest/) and make sure your certificate passes the tests.\n\n### 1.6.2. CORS\nSince you will make remote calls using fetch, you'll probably end up with CORS issues.\nSo prepare your servers, load balancers and all to handle cross origin requests.\n\n### 1.6.3. Foreign fetch\nI have not yet taken time to dig into that, but Foreign Fetch should be there to help avoir cross origin related problems.\n\n### 1.6.4. Modern Internet\n\nWell 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 :)\n\n## 1.7. Development guidelines\n\nThere are a few things you should know to avoid losing too much time solving issues during your development with Fable.\n\n### 1.7.1. Use Service workers ONLY when you need them\n\nJust 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.\n\nIf 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.**\n\nThe 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.\n\n### 1.7.2. Fable and webpack\n\nWhen you develop using the watch mode (localhost:8080), changes in your ``service-worker.js`` file won't be reflected unless you stop webpack.\n\nTo avoid this behaviour, we add the following line to ``App.fs``, so that the service-worker file gets watched too.\n\n```f#\nimportAll \"../docs/service-worker.js\"\n```\n\n### 1.7.3. Avoid localhost:8080 nightmare\n\nDy 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!\n\nBecause, as you now understand, once it's started, **a Serice Worker will remain alive until you actually unregister it.**\n\nSo it means it will outlive your development cycle and remain there forever **at the address you hosted it to**.\nDon't be afraid. Relax. Just unregister your service worker it, refresh and now you can see your actual project on screen.\n\n### 1.7.4. Unregister a service worker\n\nIt's easy:\n\n1. *Chrome*: go to the Application tab and unregister the service worker\n2. *Firefox*: browse to [about:serviceworkers](about:serviceworkers) and there you'll be able to unregister them\n\n### 1.7.5. Remotely debug your web app\n\nIt's not really linked to PWAs but if you're using Firefox, use its great [WebIDE](https://developer.mozilla.org/en-US/docs/Tools/WebIDE) which will help you understand what's goin on on your remote device by making the whole developer console available on your desktop.\n\nIt's called Remote Debugging and you should definitely use it!\n\n### 1.7.6. sync your static needs\n\nIf 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.\n\n\u003e For instance don't call ``\u003cscript src=\"greatJSLib.js\"\u003e\u003c/script\u003e`` in your index.html and ``greatJSLib.min.js`` in your cache list or else it will cache a file you won't be using!\n\n## 1.8. Try the live sample!\n\nGrab your mobile phone and browse to: [https://whitetigle.github.io/fable-pwa/](https://whitetigle.github.io/fable-pwa/)\n\n### 1.8.1. Check how it works\n\nIf you use chrome, just open the Developer Tools and click on the ``Application`` tab. There you'll see:\n\n1. your Service Worker \n2. the possible errors encountered while fetching data.\n\nIf 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).\n\n### 1.8.2. Update this sample to your needs\n\n#### 1.8.2.1. From docs to public\nThe current project will build to the ``docs`` folder to enable GitHub hosting.\n\nDon't forget to modify the ``webpack.config.js file`` like this in order to allow the building of ``bundle.js`` into the public folder:\n\nchange \n\n```json\nvar outputFolder = \"./docs\";\n```\n\nto \n\n```json\nvar outputFolder = \"./public\";\n```\n\n#### 1.8.2.2. Cache paths\nIf 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. \n\n```js\nvar resourcesToCache = [\n    '/fable-pwa/',\n    'icons/android-icon-144x144.png',\n    ...\n    'https://cdn.polyfill.io/v2/polyfill.js?features=es6,fetch',\n    '/fable-pwa/index.html',\n    '/fable-pwa/bundle.js'\n];\n```\n\nSo just remove these in order to get the sample working on your host:\n\n```js\nvar resourcesToCache = [\n    '/',\n    'icons/android-icon-144x144.png',\n    ...\n    'https://cdn.polyfill.io/v2/polyfill.js?features=es6,fetch',\n    '/index.html',\n    '/bundle.js'\n];\n```\n#### 1.8.2.3. Change watched file for webpack dev server\n\nSimply change this:\n\n```f#\nimportAll \"../docs/service-worker.js\"\n```\n\nto \n\n```f#\nimportAll \"../public/service-worker.js\"\n```\n\nor any path so that the service-worker gets watched.\n\n\u003e **Remember**: changes won't take any effect unless you change the cache version number in ``service-worker.js``\n\n## 1.9. To be continued\nI will definitely update this project with latest news and information I get from my ongoing PWA projects.\nThanks for reading and have fun deploying your pwa app!","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwhitetigle%2Ffable-pwa","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwhitetigle%2Ffable-pwa","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwhitetigle%2Ffable-pwa/lists"}