Use Trunk to build and deploy the web app (#69)

This commit is contained in:
Red Artist 2022-08-19 21:03:23 +05:30 committed by GitHub
parent e1e4fb8113
commit d12c19da37
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 148 additions and 1772 deletions

45
.github/workflows/pages.yml vendored Normal file
View file

@ -0,0 +1,45 @@
name: Github Pages
# By default, runs if you push to master. keeps your deployed app in sync with master branch.
on:
push:
branches:
- master
# to only run when you do a new github release, comment out above part and uncomment the below trigger.
# on:
# release:
# types:
# - published
permissions:
contents: write # for committing to gh-pages branch.
jobs:
build-github-pages:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2 # repo checkout
- uses: actions-rs/toolchain@v1 # get rust toolchain for wasm
with:
profile: minimal
toolchain: stable
target: wasm32-unknown-unknown
override: true
- name: Rust Cache # cache the rust build artefacts
uses: Swatinem/rust-cache@v1
- name: Downlaod and Install Trunk binary
run: wget -qO- https://github.com/thedodd/trunk/releases/latest/download/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf-
- name: Build # build
# "${GITHUB_REPOSITORY#*/}" evaluates into the name of the repository
# using --public-url something will allow trunk to modify all the href paths like from favicon.ico to repo_name/favicon.ico .
# this is necessary for github pages where the site is deployed to username.github.io/repo_name and all files must be requested
# relatively as eframe_template/favicon.ico. if we skip public-url option, the href paths will instead request username.github.io/favicon.ico which
# will obviously return error 404 not found.
run: ./trunk build --release --public-url "${GITHUB_REPOSITORY#*/}"
- name: Deploy
uses: JamesIves/github-pages-deploy-action@v4
with:
folder: dist
# this option will not maintain any history of your previous pages deployment
# set to false if you want all page build to be committed to your gh-pages branch history
single-commit: true

View file

@ -33,8 +33,8 @@ jobs:
with:
profile: minimal
toolchain: stable
target: wasm32-unknown-unknown
override: true
- run: rustup target add wasm32-unknown-unknown
- uses: actions-rs/cargo@v1
with:
command: check
@ -66,7 +66,7 @@ jobs:
profile: minimal
toolchain: stable
override: true
- run: rustup component add rustfmt
components: rustfmt
- uses: actions-rs/cargo@v1
with:
command: fmt
@ -82,14 +82,14 @@ jobs:
profile: minimal
toolchain: stable
override: true
- run: rustup component add clippy
components: clippy
- uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
wasm_bindgen:
name: wasm-bindgen
trunk:
name: trunk
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@ -97,7 +97,9 @@ jobs:
with:
profile: minimal
toolchain: 1.61.0
target: wasm32-unknown-unknown
override: true
- run: rustup target add wasm32-unknown-unknown
- run: ./setup_web.sh
- run: ./wasm_bindgen_check.sh
- name: Downlaod and Install Trunk binary
run: wget -qO- https://github.com/thedodd/trunk/releases/latest/download/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf-
- name: Build
run: ./trunk build

1
.gitignore vendored
View file

@ -1 +1,2 @@
/target
/dist

View file

@ -1,6 +1,5 @@
[package]
name = "eframe_template"
default-run = "eframe_template_bin"
version = "0.1.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
edition = "2021"
@ -8,13 +7,6 @@ rust-version = "1.60"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[[bin]]
name = "eframe_template_bin"
path = "src/main.rs"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
egui = "0.18.0"

View file

@ -15,20 +15,15 @@ Start by clicking "Use this template" at https://github.com/emilk/eframe_templat
Change the name of the crate: Chose a good name for your project, and change the name to it in:
* `Cargo.toml`
* Change the `package.name` from `eframe_template` to `your_crate`
* Change the `package.name` from `eframe_template` to `your_crate`.
* Change the `package.authors`
* Change the `package.default-run` from `eframe_template_bin` to `your_crate_bin` (note the `_bin`!)
* Change the `bin.name` from `eframe_template_bin` to `your_crate_bin` (note the `_bin`!)
* `main.rs`
* Change `eframe_template::TemplateApp` to `your_crate::TemplateApp`
* `docs/index.html`
* Change the `<title>`
* Change the `<script src=…` line from `eframe_template.js` to `your_crate.js`
* Change the `wasm_bindgen(…` line from `eframe_template_bg.wasm` to `your_crate_bg.wasm` (note the `_bg`!)
* `docs/sw.js`
* Change the `'./eframe_template.js'` to `./your_crate.js` (in `filesToCache` array)
* Change the `'./eframe_template_bg.wasm'` to `./your_crate_bg.wasm` (in `filesToCache` array)
* Remove the web build of the old name: `rm docs/eframe_template*`
* `index.html`
* Chage the `<title>eframe template</title>` to `<title>your_crate</title>`. optional.
* `assets/sw.js`
* Change the `'./eframe_template.js'` to `./your_crate.js` (in `filesToCache` array)
* Change the `'./eframe_template_bg.wasm'` to `./your_crate_bg.wasm` (in `filesToCache` array)
### Learning about egui
@ -50,38 +45,29 @@ On Fedora Rawhide you need to run:
`dnf install clang clang-devel clang-tools-extra speech-dispatcher-devel libxkbcommon-devel pkg-config openssl-devel libxcb-devel`
For running the `build_web.sh` script you also need to install `jq` and `binaryen` with your packet manager of choice.
### Web Locally
### Compiling for the web
You can compile your app to [WASM](https://en.wikipedia.org/wiki/WebAssembly) and publish it as a web page.
Install [jq](https://stedolan.github.io/jq/download/).
We use [Trunk](https://trunkrs.dev/) to build for web target.
1. Install Trunk with `cargo install --locked trunk`.
2. run `trunk serve` to build and serve on `http://127.0.0.1:8080`. will rebuild automatically if you edit the project.
3. open `http://127.0.0.1:8080/index.html#dev` in a browser. see the warning below.
Make sure you are using the latest version of stable rust by running `rustup update`.
> `assets/sw.js` script will try to cache our app, and loads the cached version when it cannot connect to server allowing your app to work offline (like PWA).
> appending `#dev` to `index.html` will skip this caching, allowing us to load the latest builds during development.
You can compile your app to [WASM](https://en.wikipedia.org/wiki/WebAssembly) and publish it as a web page. For this you need to set up some tools. There are a few simple scripts that help you with this:
```sh
./setup_web.sh
./start_server.sh
./build_web.sh --optimize --open
```
* `setup_web.sh` installs the tools required to build for web
* `start_server.sh` starts a local HTTP server so you can test before you publish
* `build_web.sh` compiles your code to WASM and puts it in the `docs/` folder (see below) and `--open` opens the result in your default browser.
The finished web app is found in the `docs/` folder (this is so that you can easily share it with [GitHub Pages](https://docs.github.com/en/free-pro-team@latest/github/working-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site)). It consists of three files:
* `index.html`: A few lines of HTML, CSS and JS that loads your app. **You need to edit this** (once) to replace `eframe_template` with the name of your crate!
* `your_crate_bg.wasm`: What the Rust code compiles to.
* `your_crate.js`: Auto-generated binding between Rust and JS.
### Web Deploy
1. Just run `trunk build --release`.
2. It will generate a `dist` directory as a "static html" website
3. Upload the `dist` directory to any of the numerous free hosting websites including [GitHub Pages](https://docs.github.com/en/free-pro-team@latest/github/working-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site).
4. we already provide a workflow that auto-deploys our app to github pages if you enable it.
> To enable Github Pages, you need to go to Respository -> Settings -> Pages -> Source -> set to `gh-pages` branch and `/` (root).
>
> If `gh-pages` is not availabe in `Source`, just create and push a branch called `gh-pages` and it should be available.
You can test the template app at <https://emilk.github.io/eframe_template/>.
### Web testing/development
Open `index.html#dev` to disable caching, which makes development easier.
## Updating egui
As of 2022, egui is in active development with frequent releases with breaking changes. [eframe_template](https://github.com/emilk/eframe_template/) will be updated in lock-step to always use the latest version of egui.

2
Trunk.toml Normal file
View file

@ -0,0 +1,2 @@
[build]
filehash = false

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

Before

Width:  |  Height:  |  Size: 314 KiB

After

Width:  |  Height:  |  Size: 314 KiB

View file

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

View file

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

28
assets/manifest.json Normal file
View file

@ -0,0 +1,28 @@
{
"name": "Egui Template PWA",
"short_name": "egui-template-pwa",
"icons": [
{
"src": "./icon-256.png",
"sizes": "256x256",
"type": "image/png"
},
{
"src": "./maskable_icon_x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "./icon-1024.png",
"sizes": "1024x1024",
"type": "image/png"
}
],
"lang": "en-US",
"id": "/index.html",
"start_url": "./index.html",
"display": "standalone",
"background_color": "white",
"theme_color": "white"
}

View file

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 128 KiB

View file

@ -22,4 +22,4 @@ self.addEventListener('fetch', function (e) {
return response || fetch(e.request);
})
);
});
});

View file

@ -1,78 +0,0 @@
@echo off
SET script_path=%~dp0
cd %script_path%
SET OPEN=0
SET OPTIMIZE=0
:do_while
IF (%1) == () GOTO end_while
IF %1 == -h GOTO print_help
IF %1 == --help GOTO print_help
IF %1 == --optimize (
SET OPTIMIZE=1
SHIFT
GOTO do_while
)
IF %1 == --open (
SET OPEN=1
SHIFT
GOTO do_while
)
echo Unknown command "%1"
:end_while
@REM call this first : `./setup_web.bat`
for %%I in (.) do SET FOLDER_NAME=%%~nxI
@REM assume crate name is the same as the folder name
SET CRATE_NAME=%FOLDER_NAME%
@REM for those who name crates with-kebab-case
SET CRATE_NAME_SNAKE_CASE=%FOLDER_NAME:-=_%
@REM This is required to enable the web_sys clipboard API which egui_web uses
@REM https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Clipboard.html
@REM https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html
SET RUSTFLAGS=--cfg=web_sys_unstable_apis
@REM Clear output from old stuff:
DEL /F docs\%CRATE_NAME_SNAKE_CASE%_bg.wasm
echo Building rust...
SET BUILD=release
cargo build -p %CRATE_NAME% --release --lib --target wasm32-unknown-unknown
@REM Get the output directory (in the workspace it is in another location)
FOR /F "delims=" %%i IN ('cargo metadata --format-version=1 ^| jq --raw-output .target_directory') DO SET TARGET=%%i
echo Generating JS bindings for wasm...
SET TARGET_NAME=%CRATE_NAME_SNAKE_CASE%.wasm
wasm-bindgen "%TARGET%\wasm32-unknown-unknown\%BUILD%\%TARGET_NAME%" --out-dir "docs" --no-modules --no-typescript
IF %OPTIMIZE% == 1 (
echo Optimizing wasm...
@REM to get wasm-opt: apt/brew/dnf install binaryen
@REM add -g to get debug symbols :
wasm-opt "docs\%CRATE_NAME%_bg.wasm" -O2 --fast-math -o "docs\%CRATE_NAME%_bg.wasm"
)
echo Finished: docs/%CRATE_NAME_SNAKE_CASE%.wasm"
IF %OPEN% == 1 start http://localhost:8080/index.html
GOTO end_program
:print_help
echo build_web.sh [--optimize] [--open]
echo --optimize: optimize the generated WASM for performance
echo --open: open the result in a browser
GOTO end_program
:end_program

View file

@ -1,77 +0,0 @@
#!/usr/bin/env bash
set -eu
CRATE_NAME=${PWD##*/} # assume crate name is the same as the folder name
script_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )
cd "$script_path"
OPEN=false
OPTIMIZE=false
while test $# -gt 0; do
case "$1" in
-h|--help)
echo "build_web.sh [--optimize] [--open]"
echo " --optimize: enable optimization step"
echo " --open: open the result in a browser"
exit 0
;;
-O|--optimize)
shift
OPTIMIZE=true
;;
--open)
shift
OPEN=true
;;
*)
break
;;
esac
done
./setup_web.sh
CRATE_NAME_SNAKE_CASE="${CRATE_NAME//-/_}" # for those who name crates with-kebab-case
# This is required to enable the web_sys clipboard API which egui_web uses
# https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Clipboard.html
# https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html
export RUSTFLAGS=--cfg=web_sys_unstable_apis
# Clear output from old stuff:
rm -f "docs/${CRATE_NAME_SNAKE_CASE}_bg.wasm"
echo "Building rust…"
BUILD=release
cargo build -p "${CRATE_NAME}" --release --lib --target wasm32-unknown-unknown
# Get the output directory (in the workspace it is in another location)
TARGET=$(cargo metadata --format-version=1 | jq --raw-output .target_directory)
echo "Generating JS bindings for wasm…"
TARGET_NAME="${CRATE_NAME_SNAKE_CASE}.wasm"
WASM_PATH="${TARGET}/wasm32-unknown-unknown/${BUILD}/${TARGET_NAME}"
wasm-bindgen "${WASM_PATH}" --out-dir docs --no-modules --no-typescript
if [[ "${OPTIMIZE}" == true ]]; then
echo "Optimizing wasm…"
# to get wasm-opt: apt/brew/dnf install binaryen
wasm-opt "docs/${CRATE_NAME}_bg.wasm" -O2 --fast-math -o "docs/${CRATE_NAME}_bg.wasm" # add -g to get debug symbols
fi
echo "Finished: docs/${CRATE_NAME_SNAKE_CASE}.wasm"
if [[ "${OPEN}" == true ]]; then
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
# Linux, ex: Fedora
xdg-open http://localhost:8080/index.html#dev
elif [[ "$OSTYPE" == "msys" ]]; then
# Windows
start http://localhost:8080/index.html#dev
else
# Darwin/MacOS, or something else
open http://localhost:8080/index.html#dev
fi
fi

View file

@ -1,3 +0,0 @@
This folder contains the files required for the web app.
The reason the folder is called "docs" is because that is the name that GitHub requires in order to host a web page from the `master` branch of a repository. You can test the `eframe_template` at <https://emilk.github.io/eframe_template/>.

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -1,27 +0,0 @@
{
"name": "Egui Template PWA",
"short_name": "egui-template-pwa",
"icons": [{
"src": "./icon-256.png",
"sizes": "256x256",
"type": "image/png"
},
{
"src": "./maskable_icon_x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "./icon-1024.png",
"sizes": "1024x1024",
"type": "image/png"
}
],
"lang": "en-US",
"id": "/index.html",
"start_url": "./index.html",
"display": "standalone",
"background_color": "white",
"theme_color": "white"
}

View file

@ -6,11 +6,29 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<head>
<!-- change this to your project name -->
<title>eframe template</title>
<!-- config for our rust wasm binary. go to https://trunkrs.dev/assets/#rust for more customization -->
<link data-trunk rel="rust" data-wasm-opt="2" />
<!-- this is the base url relative to which other urls will be constructed. trunk will insert this from the public-url option -->
<base data-trunk-public-url />
<link data-trunk rel="icon" href="assets/favicon.ico">
<link data-trunk rel="copy-file" href="assets/sw.js" />
<link data-trunk rel="copy-file" href="assets/manifest.json" />
<link data-trunk rel="copy-file" href="assets/icon-1024.png" />
<link data-trunk rel="copy-file" href="assets/icon-256.png" />
<link data-trunk rel="copy-file" href="assets/icon_ios_touch_192.png" />
<link data-trunk rel="copy-file" href="assets/maskable_icon_x512.png" />
<link rel="manifest" href="manifest.json">
<link rel="apple-touch-icon" href="icon_ios_touch_192.png">
<meta name="theme-color" media="(prefers-color-scheme: light)" content="white">
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#404040">
<link rel="apple-touch-icon" href="icon_ios_touch_192.png">
<style>
html {
@ -99,76 +117,21 @@
<body>
<!-- The WASM code will resize the canvas dynamically -->
<!-- the id is hardcoded in main.rs . so, make sure both match. -->
<canvas id="the_canvas_id"></canvas>
<div class="centered" id="center_text">
<p style="font-size:16px">
Loading…
</p>
<div class="lds-dual-ring"></div>
</div>
<script>
// The `--no-modules`-generated JS from `wasm-bindgen` attempts to use
// `WebAssembly.instantiateStreaming` to instantiate the wasm module,
// but this doesn't work with `file://` urls. This example is frequently
// viewed by simply opening `index.html` in a browser (with a `file://`
// url), so it would fail if we were to call this function!
//
// Work around this for now by deleting the function to ensure that the
// `no_modules.js` script doesn't have access to it. You won't need this
// hack when deploying over HTTP.
delete WebAssembly.instantiateStreaming;
</script>
<!-- this is the JS generated by the `wasm-bindgen` CLI tool -->
<script src="eframe_template.js"></script>
<script>
// We'll defer our execution until the wasm is ready to go.
// Here we tell bindgen the path to the wasm file so it can start
// initialization and return to us a promise when it's done.
console.debug("loading wasm…");
wasm_bindgen("./eframe_template_bg.wasm")
.then(on_wasm_loaded)
.catch(on_wasm_error);
function on_wasm_loaded() {
console.debug("wasm loaded. starting app…");
// This call installs a bunch of callbacks and then returns:
wasm_bindgen.start("the_canvas_id");
console.debug("app started.");
document.getElementById("center_text").remove();
}
function on_wasm_error(error) {
console.error("Failed to start: " + error);
document.getElementById("center_text").innerHTML = `
<p>
An error occurred during loading:
</p>
<p style="font-family:Courier New">
${error}
</p>
<p style="font-size:14px">
Make sure you use a modern browser with WebGL and WASM enabled.
</p>`;
}
</script>
<!--Register Service Worker-->
<!--Register Service Worker. this will cache the wasm / js scripts for offline use (for PWA functionality). -->
<!-- Force refresh (Ctrl + F5) to load the latest files instead of cached files -->
<script>
// We disable caching during development so that we always view the latest version.
if ('serviceWorker' in navigator && window.location.hash !== "#dev") {
window.addEventListener('load', function() {
window.addEventListener('load', function () {
navigator.serviceWorker.register('sw.js');
});
}
</script>
</body>
</html>
<!-- Powered by egui: https://github.com/emilk/egui/ -->
<!-- Powered by egui: https://github.com/emilk/egui/ -->

View file

@ -1,7 +0,0 @@
@REM Pre-requisites:
rustup target add wasm32-unknown-unknown
cargo install wasm-bindgen-cli
cargo update -p wasm-bindgen
@REM For local tests with `./start_server`:
cargo install basic-http-server

View file

@ -1,9 +0,0 @@
#!/usr/bin/env bash
set -eu
# Pre-requisites:
rustup target add wasm32-unknown-unknown
cargo install wasm-bindgen-cli --version 0.2.82
# For local tests with `./start_server`:
cargo install basic-http-server

View file

@ -2,25 +2,3 @@
mod app;
pub use app::TemplateApp;
// ----------------------------------------------------------------------------
// When compiling for web:
#[cfg(target_arch = "wasm32")]
use eframe::wasm_bindgen::{self, prelude::*};
/// This is the entry-point for all the web-assembly.
/// This is called once from the HTML.
/// It loads the app, installs some callbacks, then returns.
/// You can add more callbacks like this if you want to call in to your code.
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub fn start(canvas_id: &str) -> Result<(), eframe::wasm_bindgen::JsValue> {
// Make sure panics are logged using `console.error`.
console_error_panic_hook::set_once();
// Redirect tracing to console.log and friends:
tracing_wasm::set_as_global_default();
eframe::start_web(canvas_id, Box::new(|cc| Box::new(TemplateApp::new(cc))))
}

View file

@ -14,3 +14,18 @@ fn main() {
Box::new(|cc| Box::new(eframe_template::TemplateApp::new(cc))),
);
}
// when compiling to web using trunk.
#[cfg(target_arch = "wasm32")]
fn main() {
// Make sure panics are logged using `console.error`.
console_error_panic_hook::set_once();
// Redirect tracing to console.log and friends:
tracing_wasm::set_as_global_default();
eframe::start_web(
"the_canvas_id", // hardcode it
Box::new(|cc| Box::new(eframe_template::TemplateApp::new(cc))),
)
.expect("failed to start eframe");
}

View file

@ -1,9 +0,0 @@
@echo off
@REM Starts a local web-server that serves the contents of the `doc/` folder,
@REM which is the folder to where the web version is compiled.
echo "open http://localhost:8080"
(cd docs && basic-http-server --addr 127.0.0.1:8080 .)
@REM (cd docs && python3 -m http.server 8080 --bind 127.0.0.1)

View file

@ -1,12 +0,0 @@
#!/usr/bin/env bash
set -eu
PORT=8080
# Starts a local web-server that serves the contents of the `doc/` folder,
# which is the folder to where the web version is compiled.
echo "open http://localhost:$PORT/index.html#dev"
(cd docs && basic-http-server --addr 127.0.0.1:$PORT .)
# (cd docs && python3 -m http.server $PORT --bind 127.0.0.1)

View file

@ -1,32 +0,0 @@
#!/usr/bin/env bash
set -eu
script_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )
cd "$script_path"
./setup_web.sh
CRATE_NAME=${PWD##*/} # assume crate name is the same as the folder name
# This is required to enable the web_sys clipboard API which eframe web uses
# https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Clipboard.html
# https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html
export RUSTFLAGS=--cfg=web_sys_unstable_apis
echo "Building rust…"
BUILD=debug # debug builds are faster
cargo build --lib --target wasm32-unknown-unknown
TARGET="target"
echo "Generating JS bindings for wasm…"
rm -f "${CRATE_NAME}_bg.wasm" # Remove old output (if any)
TARGET_NAME="${CRATE_NAME}.wasm"
wasm-bindgen "${TARGET}/wasm32-unknown-unknown/$BUILD/$TARGET_NAME" \
--out-dir . --no-modules --no-typescript
# Remove output:
rm -f "${CRATE_NAME}_bg.wasm"
rm -f "${CRATE_NAME}.js"