WIP: feat(webserver): Vendor and reimplement in leptos

This commit is contained in:
Tristan Daniël Maat 2025-11-24 03:29:18 +08:00
parent aeba7301b0
commit daec74e73f
Signed by: tlater
GPG key ID: 02E935006CF2E8E7
12 changed files with 2976 additions and 163 deletions

153
flake.lock generated
View file

@ -136,50 +136,6 @@
"type": "github" "type": "github"
} }
}, },
"dream2nix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"purescript-overlay": "purescript-overlay",
"pyproject-nix": "pyproject-nix"
},
"locked": {
"lastModified": 1735160684,
"narHash": "sha256-n5CwhmqKxifuD4Sq4WuRP/h5LO6f23cGnSAuJemnd/4=",
"owner": "nix-community",
"repo": "dream2nix",
"rev": "8ce6284ff58208ed8961681276f82c2f8f978ef4",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "dream2nix",
"type": "github"
}
},
"fenix": {
"inputs": {
"nixpkgs": [
"tlaternet-webserver",
"nixpkgs"
],
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1737181903,
"narHash": "sha256-lvp77MhGzSN+ICd0MugppCjQR6cmlM2iAC5cjy2ZsaA=",
"owner": "nix-community",
"repo": "fenix",
"rev": "ac79bb490b8c1af4bbc587b84c76f9527d6b14f7",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"flake-compat": { "flake-compat": {
"flake": false, "flake": false,
"locked": { "locked": {
@ -349,50 +305,6 @@
"type": "github" "type": "github"
} }
}, },
"purescript-overlay": {
"inputs": {
"flake-compat": [
"deploy-rs",
"flake-compat"
],
"nixpkgs": [
"tlaternet-webserver",
"dream2nix",
"nixpkgs"
],
"slimlock": "slimlock"
},
"locked": {
"lastModified": 1728546539,
"narHash": "sha256-Sws7w0tlnjD+Bjck1nv29NjC5DbL6nH5auL9Ex9Iz2A=",
"owner": "thomashoneyman",
"repo": "purescript-overlay",
"rev": "4ad4c15d07bd899d7346b331f377606631eb0ee4",
"type": "github"
},
"original": {
"owner": "thomashoneyman",
"repo": "purescript-overlay",
"type": "github"
}
},
"pyproject-nix": {
"flake": false,
"locked": {
"lastModified": 1702448246,
"narHash": "sha256-hFg5s/hoJFv7tDpiGvEvXP0UfFvFEDgTdyHIjDVHu1I=",
"owner": "davhau",
"repo": "pyproject.nix",
"rev": "5a06a2697b228c04dd2f35659b4b659ca74f7aeb",
"type": "github"
},
"original": {
"owner": "davhau",
"ref": "dream2nix",
"repo": "pyproject.nix",
"type": "github"
}
},
"root": { "root": {
"inputs": { "inputs": {
"deploy-rs": "deploy-rs", "deploy-rs": "deploy-rs",
@ -401,48 +313,7 @@
"foundryvtt": "foundryvtt", "foundryvtt": "foundryvtt",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"sonnenshift": "sonnenshift", "sonnenshift": "sonnenshift",
"sops-nix": "sops-nix", "sops-nix": "sops-nix"
"tlaternet-webserver": "tlaternet-webserver"
}
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1737140097,
"narHash": "sha256-m4SN8DeKzsP10EQFS7+2zgGfCrMhTfTt1H0QRNesD08=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "f61bfa4d7feb84d07538d361fe77d34a29e3b375",
"type": "github"
},
"original": {
"owner": "rust-lang",
"ref": "nightly",
"repo": "rust-analyzer",
"type": "github"
}
},
"slimlock": {
"inputs": {
"nixpkgs": [
"tlaternet-webserver",
"dream2nix",
"purescript-overlay",
"nixpkgs"
]
},
"locked": {
"lastModified": 1688756706,
"narHash": "sha256-xzkkMv3neJJJ89zo3o2ojp7nFeaZc2G0fYwNXNJRFlo=",
"owner": "thomashoneyman",
"repo": "slimlock",
"rev": "cf72723f59e2340d24881fd7bf61cb113b4c407c",
"type": "github"
},
"original": {
"owner": "thomashoneyman",
"repo": "slimlock",
"type": "github"
} }
}, },
"sonnenshift": { "sonnenshift": {
@ -501,28 +372,6 @@
"type": "github" "type": "github"
} }
}, },
"tlaternet-webserver": {
"inputs": {
"dream2nix": "dream2nix",
"fenix": "fenix",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1737271785,
"narHash": "sha256-yVdaaawYK1/q9V5btfGpxVCQBdyQx1WcFHYO0yX5bP8=",
"ref": "refs/heads/master",
"rev": "5d3d84836101ec9b9867a5f754c9ee1b9d4dc538",
"revCount": 76,
"type": "git",
"url": "https://gitea.tlater.net/tlaternet/tlaternet.git"
},
"original": {
"type": "git",
"url": "https://gitea.tlater.net/tlaternet/tlaternet.git"
}
},
"utils": { "utils": {
"inputs": { "inputs": {
"systems": "systems" "systems": "systems"

View file

@ -30,17 +30,6 @@
## Services ## Services
tlaternet-webserver = {
url = "git+https://gitea.tlater.net/tlaternet/tlaternet.git";
inputs = {
nixpkgs.follows = "nixpkgs";
dream2nix.inputs = {
nixpkgs.follows = "nixpkgs";
purescript-overlay.inputs.flake-compat.follows = "deploy-rs/flake-compat";
};
};
};
foundryvtt = { foundryvtt = {
url = "github:reckenrode/nix-foundryvtt"; url = "github:reckenrode/nix-foundryvtt";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
@ -184,6 +173,8 @@
minecraft = nixpkgs.legacyPackages.${system}.mkShell { minecraft = nixpkgs.legacyPackages.${system}.mkShell {
packages = nixpkgs.lib.attrValues { inherit (nixpkgs.legacyPackages.${system}) packwiz; }; packages = nixpkgs.lib.attrValues { inherit (nixpkgs.legacyPackages.${system}) packwiz; };
}; };
webserver = self.packages.${system}.webserver.devShell;
}; };
}; };
} }

1
pkgs/packages/webserver/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
target/

2610
pkgs/packages/webserver/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,61 @@
[package]
name = "tlaternet-webserver"
version = "0.2.0"
edition = "2024"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
axum = { version = "0.8.7", optional = true }
console_error_panic_hook = { version = "0.1.7", optional = true }
leptos = "0.8.3"
leptos_axum = { version = "0.8.3", optional = true }
leptos_meta = "0.8.3"
leptos_router = "0.8.3"
tokio = { version = "1.48.0", features = ["rt-multi-thread"], optional = true }
wasm-bindgen = { version = "=0.2.100", optional = true }
[features]
hydrate = [
"leptos/hydrate",
"dep:console_error_panic_hook",
"dep:wasm-bindgen",
]
ssr = [
"dep:axum",
"dep:tokio",
"dep:leptos_axum",
"leptos/ssr",
"leptos_meta/ssr",
"leptos_router/ssr",
]
[profile.wasm-release]
inherits = "release"
opt-level = 'z'
lto = true
codegen-units = 1
panic = "abort"
[package.metadata.leptos]
output-name = "tlaternet-webserver"
site-root = "target/site"
site-pkg-dir = "pkg"
style-file = "style/main.scss"
assets-dir = "public"
site-addr = "127.0.0.1:3000"
reload-port = 3001
browserquery = "defaults"
env = "DEV"
bin-features = ["ssr"]
bin-default-features = false
lib-features = ["hydrate"]
lib-default-features = false
lib-profile-release = "wasm-release"
# [Optional] Command to use when running end2end tests. It will run in the end2end dir.
# [Windows] for non-WSL use "npx.cmd playwright test"
# This binary name can be checked in Powershell with Get-Command npx
end2end-cmd = "npx playwright test"
end2end-dir = "end2end"

View file

@ -0,0 +1,5 @@
max_width = 100
tab_spaces = 2
attr_value_brace_style = "WhenRequired"
macro_names = [ "leptos::view", "view" ]
closing_tag_style = "SelfClosing"

View file

@ -0,0 +1,143 @@
{
lib,
stdenvNoCC,
fetchFromGitHub,
symlinkJoin,
makeBinaryWrapper,
cargo-leptos,
dart-sass,
llvmPackages,
mkShell,
clangStdenv,
rust-analyzer,
rustc,
rustfmt,
leptosfmt,
cargo,
clippy,
writers,
nix-update,
nixfmt-rfc-style,
}:
let
cargoMetadata = lib.pipe ./Cargo.toml [
builtins.readFile
builtins.fromTOML
];
bulma = stdenvNoCC.mkDerivation (drv: {
pname = "bulma";
version = "1.0.4";
src = fetchFromGitHub {
owner = "jgthms";
repo = "bulma";
rev = "${drv.version}";
hash = "sha256-hlejqBI6ayzhm15IymrzhTevkl3xffMfdTasZ2CmAas=";
};
installPhase = ''
mkdir -p $out/node_modules/bulma/
cp -r ./sass $out/node_modules/bulma/
'';
});
fontsource-fonts = stdenvNoCC.mkDerivation (drv: {
pname = "fontsource-fonts";
version = "";
});
# Hack to allow importing sass *without* resorting to using `npm`
# and dealing with the insanity that is `package.json` files.
#
# dart-sass *in theory* supports completely arbitrary logic for
# package importing via the `pkg:` url prefix, but unfortunately
# currently the only implementation of it that is available in the
# upstream binary *requires* using a `node_modules` file in the
# repository root. See here:
# https://sass-lang.com/documentation/at-rules/use/#node-js-package-importer
#
# This wouldn't be so bad if it supported an environment variable or
# command line arg to specify what the repo root should be, but it
# doesn't; so instead we use the load-path to specify a package
# import root.
#
# As a consequence, we cannot use the `pkg:` prefix, so package
# imports are indistinguishable from relative path imports. This
# isn't the end of the world, but:
#
# TODO(tlater): See if we can talk to upstream about an
# implementation better suited for use with nix, perhaps in
# dart-sass (add an env variable?) or in cargo-leptos (add a config
# option that can also be set with an env variable, and use the sass
# protocol instead of the raw exe?).
dart-sass-with-packages =
let
packages = symlinkJoin {
name = "sass-packages";
paths = [ bulma ];
stripPrefix = "/node_modules";
};
in
symlinkJoin {
inherit (dart-sass) version;
pname = "dart-sass-with-packages";
paths = [ dart-sass ];
nativeBuildInputs = [ makeBinaryWrapper ];
postBuild = ''
wrapProgram $out/bin/sass \
--add-flag --load-path=${packages}
'';
};
in
symlinkJoin {
inherit (cargoMetadata.package) version;
pname = cargoMetadata.package.name;
paths = [ bulma ];
passthru = {
devShell = mkShell.override { stdenv = clangStdenv; } {
packages = [
cargo-leptos
dart-sass-with-packages
# lld is exposed as ld by the clangStdenv, adding it
# explicitly with bintools makes it work
llvmPackages.bintools
rust-analyzer
rustc
rustfmt
leptosfmt
cargo
clippy
];
};
updateScript =
writers.writeNuBin "update-bulma"
{
makeWrapperArgs = [
"--prefix"
"PATH"
":"
(lib.makeBinPath [
nix-update
nixfmt-rfc-style
])
];
}
''
(nix-update --flake --format
--subpackage bulma
--subpackage fontsource-fonts
--subpackage fontsource-scss
webserver)
'';
};
}

View file

@ -0,0 +1,2 @@
[rustfmt]
overrideCommand = ["leptosfmt", "--stdin", "--rustfmt"]

View file

@ -0,0 +1,71 @@
use leptos::prelude::*;
use leptos_meta::{provide_meta_context, MetaTags, Stylesheet, Title};
use leptos_router::{
components::{Route, Router, Routes},
StaticSegment,
};
pub fn shell(options: LeptosOptions) -> impl IntoView {
view! {
<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
<meta charset="utf-8" />
<meta name="description" content="tlater.net homepage" />
<meta name="author" contnet="Tristan Daniël Maat" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<AutoReload options=options.clone() />
<HydrationScripts options />
<MetaTags />
</head>
<body>
<App />
</body>
</html>
}
}
#[component]
pub fn App() -> impl IntoView {
// Provides context that manages stylesheets, titles, meta tags, etc.
provide_meta_context();
view! {
// injects a stylesheet into the document <head>
// id=leptos means cargo-leptos will hot-reload this stylesheet
<Stylesheet id="leptos" href="/pkg/tlaternet-webserver.css" />
// sets the document title
<Title text="Welcome to Leptos" />
// content for this welcome page
<Router>
<main>
<Routes fallback=|| "Page not found.".into_view()>
<Route path=StaticSegment("") view=HomePage />
</Routes>
</main>
</Router>
}
}
/// Renders the home page of your application.
#[component]
fn HomePage() -> impl IntoView {
view! {
<section class="section">
<div class="container">
<h1 class="title has-text-weight-normal is-family-monospace">
<span id="typed-welcome">tlater.net</span>
</h1>
<hr />
<div class="columns">
<div class="column content" />
<div class="column content" />
</div>
</div>
</section>
}
}

View file

@ -0,0 +1,9 @@
pub mod app;
#[cfg(feature = "hydrate")]
#[wasm_bindgen::prelude::wasm_bindgen]
pub fn hydrate() {
use crate::app::*;
console_error_panic_hook::set_once();
leptos::mount::hydrate_body(App);
}

View file

@ -0,0 +1,38 @@
#[cfg(feature = "ssr")]
#[tokio::main]
async fn main() {
use axum::Router;
use leptos::logging::log;
use leptos::prelude::*;
use leptos_axum::{LeptosRoutes, generate_route_list};
use tlaternet_webserver::app::*;
let conf = get_configuration(None).unwrap();
let addr = conf.leptos_options.site_addr;
let leptos_options = conf.leptos_options;
// Generate the list of routes in your Leptos App
let routes = generate_route_list(App);
let app = Router::new()
.leptos_routes(&leptos_options, routes, {
let leptos_options = leptos_options.clone();
move || shell(leptos_options.clone())
})
.fallback(leptos_axum::file_and_error_handler(shell))
.with_state(leptos_options);
// run our app with hyper
// `axum::Server` is a re-export of `hyper::Server`
log!("listening on http://{}", &addr);
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
axum::serve(listener, app.into_make_service())
.await
.unwrap();
}
#[cfg(not(feature = "ssr"))]
pub fn main() {
// no client-side main function
// unless we want this to work with e.g., Trunk for pure client-side testing
// see lib.rs for hydration function instead
}

View file

@ -0,0 +1,33 @@
@use "bulma/sass/utilities/initial-variables" as iv with (
$black: #0f0f0f,
$grey-darker: #11151c,
$grey-light: #dddddd,
$white: #ffffff,
$orange: #d26937,
$yellow: #b58900,
$green: #2aa889,
$cyan: #599cab,
$blue: #195466,
$red: #dc322f,
);
iv.$family-sans-serif: "Nunito", iv.$family-sans-serif;
iv.$family-monospace: "Hack", iv.$family-monospace;
@forward "bulma/sass/utilities/functions";
@forward "bulma/sass/utilities/derived-variables";
@forward "bulma/sass/utilities/controls";
@forward "bulma/sass/base";
@forward "bulma/sass/themes";
@forward "bulma/sass/elements/content";
@forward "bulma/sass/elements/title";
@forward "bulma/sass/grid/columns";
@forward "bulma/sass/helpers/typography";
@forward "bulma/sass/layout/container";
@forward "bulma/sass/layout/section";