examples: add PWA w/ Dioxus CLI template (#977)
* examples: add PWA w/ Dioxus CLI template * ci: properly set workspace and dependencies
This commit is contained in:
parent
b4af761038
commit
459d8d69a7
|
@ -23,6 +23,7 @@ members = [
|
|||
"packages/signals",
|
||||
"packages/hot-reload",
|
||||
"docs/guide",
|
||||
"examples/PWA-example",
|
||||
]
|
||||
|
||||
# This is a "virtual package"
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
[package]
|
||||
name = "dioxus-pwa-example"
|
||||
version = "0.1.0"
|
||||
authors = ["Antonio Curavalea <one.kyonblack@gmail.com>"]
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dioxus = { path = "../../packages/dioxus", version = "^0.3.0"}
|
||||
dioxus-web = { path = "../../packages/web", version = "^0.3.0"}
|
||||
|
||||
log = "0.4.6"
|
||||
|
||||
# WebAssembly Debug
|
||||
wasm-logger = "0.2.0"
|
||||
console_error_panic_hook = "0.1.7"
|
|
@ -0,0 +1,42 @@
|
|||
[application]
|
||||
|
||||
# App (Project) Name
|
||||
name = "dioxus-pwa-example"
|
||||
|
||||
# Dioxus App Default Platform
|
||||
# desktop, web, mobile, ssr
|
||||
default_platform = "web"
|
||||
|
||||
# `build` & `serve` dist path
|
||||
out_dir = "dist"
|
||||
|
||||
# resource (public) file folder
|
||||
asset_dir = "public"
|
||||
|
||||
[web.app]
|
||||
|
||||
# HTML title tag content
|
||||
title = "dioxus | ⛺"
|
||||
|
||||
[web.watcher]
|
||||
|
||||
# when watcher trigger, regenerate the `index.html`
|
||||
reload_html = true
|
||||
|
||||
# which files or dirs will be watcher monitoring
|
||||
watch_path = ["src", "public"]
|
||||
|
||||
# include `assets` in web platform
|
||||
[web.resource]
|
||||
|
||||
# CSS style file
|
||||
style = []
|
||||
|
||||
# Javascript code file
|
||||
script = []
|
||||
|
||||
[web.resource.dev]
|
||||
|
||||
# Javascript code file
|
||||
# serve: [dev-server] only
|
||||
script = []
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 Dioxus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,44 @@
|
|||
# Dioxus PWA example
|
||||
|
||||
This is a basic example of a progressive web app (PWA) using Dioxus and Dioxus CLI.
|
||||
Currently PWA functionality requires the use of a service worker and manifest file, so this isn't 100% Rust yet.
|
||||
|
||||
It is also very much usable as a template for your projects, if you're aiming to create a PWA.
|
||||
|
||||
## Try the example
|
||||
|
||||
Make sure you have Dioxus CLI installed (if you're unsure, run `cargo install dioxus-cli`).
|
||||
|
||||
You can run `dioxus serve` in this directory to start the web server locally, or run
|
||||
`dioxus build --release` to build the project so you can deploy it on a separate web-server.
|
||||
|
||||
## Project Structure
|
||||
```
|
||||
├── Cargo.toml
|
||||
├── Dioxus.toml
|
||||
├── index.html // Custom HTML is needed for this, to load the SW and manifest.
|
||||
├── LICENSE
|
||||
├── public
|
||||
│ ├── favicon.ico
|
||||
│ ├── logo_192.png
|
||||
│ ├── logo_512.png
|
||||
│ ├── manifest.json // The manifest file - edit this as you need to.
|
||||
│ └── sw.js // The service worker - you must edit this for actual projects.
|
||||
├── README.md
|
||||
└── src
|
||||
└── main.rs
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
If you're just getting started with PWAs, here are some useful resources:
|
||||
|
||||
* [PWABuilder docs](https://docs.pwabuilder.com/#/)
|
||||
* [MDN article on PWAs](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps)
|
||||
|
||||
For service worker scripting (in JavaScript):
|
||||
|
||||
* [Service worker guide from PWABuilder](https://docs.pwabuilder.com/#/home/sw-intro)
|
||||
* [Service worker examples, also from PWABuilder](https://github.com/pwa-builder/pwabuilder-serviceworkers)
|
||||
|
||||
If you want to stay as close to 100% Rust as possible, you can try using [wasi-worker](https://github.com/dunnock/wasi-worker) to replace the JS service worker file. The JSON manifest will still be required though.
|
|
@ -0,0 +1,30 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{app_title}</title>
|
||||
<script>
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register(
|
||||
'/sw.js'
|
||||
);
|
||||
}
|
||||
</script>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta charset="UTF-8" />
|
||||
{style_include}
|
||||
</head>
|
||||
<body>
|
||||
<div id="main"></div>
|
||||
<script type="module">
|
||||
import init from "/{base_path}/assets/dioxus/{app_name}.js";
|
||||
init("/{base_path}/assets/dioxus/{app_name}_bg.wasm").then(wasm => {
|
||||
if (wasm.__wbindgen_start == undefined) {
|
||||
wasm.main();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{script_include}
|
||||
</body>
|
||||
</html>
|
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
After Width: | Height: | Size: 47 KiB |
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"name": "Dioxus",
|
||||
"icons": [
|
||||
{
|
||||
"src": "logo_192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo_512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "logo_512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "any",
|
||||
"purpose": "any"
|
||||
}
|
||||
],
|
||||
"start_url": "/",
|
||||
"id": "/",
|
||||
"display": "standalone",
|
||||
"display_override": ["window-control-overlay", "standalone"],
|
||||
"scope": "/",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff",
|
||||
"short_name": "Dioxus",
|
||||
"description": "Dioxus is a portable, performant, and ergonomic framework for building cross-platform user interfaces in Rust.",
|
||||
"dir": "ltr",
|
||||
"lang": "en",
|
||||
"orientation": "portrait"
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
"use strict";
|
||||
|
||||
//console.log('WORKER: executing.');
|
||||
|
||||
/* A version number is useful when updating the worker logic,
|
||||
allowing you to remove outdated cache entries during the update.
|
||||
*/
|
||||
var version = 'v1.0.0::';
|
||||
|
||||
/* These resources will be downloaded and cached by the service worker
|
||||
during the installation process. If any resource fails to be downloaded,
|
||||
then the service worker won't be installed either.
|
||||
*/
|
||||
var offlineFundamentals = [
|
||||
// add here the files you want to cache
|
||||
'favicon.ico'
|
||||
];
|
||||
|
||||
/* The install event fires when the service worker is first installed.
|
||||
You can use this event to prepare the service worker to be able to serve
|
||||
files while visitors are offline.
|
||||
*/
|
||||
self.addEventListener("install", function (event) {
|
||||
//console.log('WORKER: install event in progress.');
|
||||
/* Using event.waitUntil(p) blocks the installation process on the provided
|
||||
promise. If the promise is rejected, the service worker won't be installed.
|
||||
*/
|
||||
event.waitUntil(
|
||||
/* The caches built-in is a promise-based API that helps you cache responses,
|
||||
as well as finding and deleting them.
|
||||
*/
|
||||
caches
|
||||
/* You can open a cache by name, and this method returns a promise. We use
|
||||
a versioned cache name here so that we can remove old cache entries in
|
||||
one fell swoop later, when phasing out an older service worker.
|
||||
*/
|
||||
.open(version + 'fundamentals')
|
||||
.then(function (cache) {
|
||||
/* After the cache is opened, we can fill it with the offline fundamentals.
|
||||
The method below will add all resources in `offlineFundamentals` to the
|
||||
cache, after making requests for them.
|
||||
*/
|
||||
return cache.addAll(offlineFundamentals);
|
||||
})
|
||||
.then(function () {
|
||||
//console.log('WORKER: install completed');
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
/* The fetch event fires whenever a page controlled by this service worker requests
|
||||
a resource. This isn't limited to `fetch` or even XMLHttpRequest. Instead, it
|
||||
comprehends even the request for the HTML page on first load, as well as JS and
|
||||
CSS resources, fonts, any images, etc.
|
||||
*/
|
||||
self.addEventListener("fetch", function (event) {
|
||||
//console.log('WORKER: fetch event in progress.');
|
||||
|
||||
/* We should only cache GET requests, and deal with the rest of method in the
|
||||
client-side, by handling failed POST,PUT,PATCH,etc. requests.
|
||||
*/
|
||||
if (event.request.method !== 'GET') {
|
||||
/* If we don't block the event as shown below, then the request will go to
|
||||
the network as usual.
|
||||
*/
|
||||
//console.log('WORKER: fetch event ignored.', event.request.method, event.request.url);
|
||||
return;
|
||||
}
|
||||
/* Similar to event.waitUntil in that it blocks the fetch event on a promise.
|
||||
Fulfillment result will be used as the response, and rejection will end in a
|
||||
HTTP response indicating failure.
|
||||
*/
|
||||
event.respondWith(
|
||||
caches
|
||||
/* This method returns a promise that resolves to a cache entry matching
|
||||
the request. Once the promise is settled, we can then provide a response
|
||||
to the fetch request.
|
||||
*/
|
||||
.match(event.request)
|
||||
.then(function (cached) {
|
||||
/* Even if the response is in our cache, we go to the network as well.
|
||||
This pattern is known for producing "eventually fresh" responses,
|
||||
where we return cached responses immediately, and meanwhile pull
|
||||
a network response and store that in the cache.
|
||||
|
||||
Read more:
|
||||
https://ponyfoo.com/articles/progressive-networking-serviceworker
|
||||
*/
|
||||
var networked = fetch(event.request)
|
||||
// We handle the network request with success and failure scenarios.
|
||||
.then(fetchedFromNetwork, unableToResolve)
|
||||
// We should catch errors on the fetchedFromNetwork handler as well.
|
||||
.catch(unableToResolve);
|
||||
|
||||
/* We return the cached response immediately if there is one, and fall
|
||||
back to waiting on the network as usual.
|
||||
*/
|
||||
//console.log('WORKER: fetch event', cached ? '(cached)' : '(network)', event.request.url);
|
||||
return cached || networked;
|
||||
|
||||
function fetchedFromNetwork(response) {
|
||||
/* We copy the response before replying to the network request.
|
||||
This is the response that will be stored on the ServiceWorker cache.
|
||||
*/
|
||||
var cacheCopy = response.clone();
|
||||
|
||||
//console.log('WORKER: fetch response from network.', event.request.url);
|
||||
|
||||
caches
|
||||
// We open a cache to store the response for this request.
|
||||
.open(version + 'pages')
|
||||
.then(function add(cache) {
|
||||
/* We store the response for this request. It'll later become
|
||||
available to caches.match(event.request) calls, when looking
|
||||
for cached responses.
|
||||
*/
|
||||
cache.put(event.request, cacheCopy);
|
||||
})
|
||||
.then(function () {
|
||||
//console.log('WORKER: fetch response stored in cache.', event.request.url);
|
||||
});
|
||||
|
||||
// Return the response so that the promise is settled in fulfillment.
|
||||
return response;
|
||||
}
|
||||
|
||||
/* When this method is called, it means we were unable to produce a response
|
||||
from either the cache or the network. This is our opportunity to produce
|
||||
a meaningful response even when all else fails. It's the last chance, so
|
||||
you probably want to display a "Service Unavailable" view or a generic
|
||||
error response.
|
||||
*/
|
||||
function unableToResolve() {
|
||||
/* There's a couple of things we can do here.
|
||||
- Test the Accept header and then return one of the `offlineFundamentals`
|
||||
e.g: `return caches.match('/some/cached/image.png')`
|
||||
- You should also consider the origin. It's easier to decide what
|
||||
"unavailable" means for requests against your origins than for requests
|
||||
against a third party, such as an ad provider.
|
||||
- Generate a Response programmaticaly, as shown below, and return that.
|
||||
*/
|
||||
|
||||
//console.log('WORKER: fetch request failed in both cache and network.');
|
||||
|
||||
/* Here we're creating a response programmatically. The first parameter is the
|
||||
response body, and the second one defines the options for the response.
|
||||
*/
|
||||
return new Response('<h1>Service Unavailable</h1>', {
|
||||
status: 503,
|
||||
statusText: 'Service Unavailable',
|
||||
headers: new Headers({
|
||||
'Content-Type': 'text/html'
|
||||
})
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
/* The activate event fires after a service worker has been successfully installed.
|
||||
It is most useful when phasing out an older version of a service worker, as at
|
||||
this point you know that the new worker was installed correctly. In this example,
|
||||
we delete old caches that don't match the version in the worker we just finished
|
||||
installing.
|
||||
*/
|
||||
self.addEventListener("activate", function (event) {
|
||||
/* Just like with the install event, event.waitUntil blocks activate on a promise.
|
||||
Activation will fail unless the promise is fulfilled.
|
||||
*/
|
||||
//console.log('WORKER: activate event in progress.');
|
||||
|
||||
event.waitUntil(
|
||||
caches
|
||||
/* This method returns a promise which will resolve to an array of available
|
||||
cache keys.
|
||||
*/
|
||||
.keys()
|
||||
.then(function (keys) {
|
||||
// We return a promise that settles when all outdated caches are deleted.
|
||||
return Promise.all(
|
||||
keys
|
||||
.filter(function (key) {
|
||||
// Filter by keys that don't start with the latest version prefix.
|
||||
return !key.startsWith(version);
|
||||
})
|
||||
.map(function (key) {
|
||||
/* Return a promise that's fulfilled
|
||||
when each outdated cache is deleted.
|
||||
*/
|
||||
return caches.delete(key);
|
||||
})
|
||||
);
|
||||
})
|
||||
.then(function () {
|
||||
//console.log('WORKER: activate completed.');
|
||||
})
|
||||
);
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
// init debug tool for WebAssembly
|
||||
wasm_logger::init(wasm_logger::Config::default());
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
dioxus_web::launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! (
|
||||
div {
|
||||
style: "text-align: center;",
|
||||
h1 { "🌗 Dioxus 🚀" }
|
||||
h3 { "Frontend that scales." }
|
||||
p { "Dioxus is a portable, performant, and ergonomic framework for building cross-platform user interfaces in Rust." }
|
||||
}
|
||||
))
|
||||
}
|
Loading…
Reference in New Issue