framesurge / perseus

A state-driven web development framework for Rust with full support for server-side rendering and static generation.
https://framesurge.sh/perseus/en-US
MIT License
2.18k stars 89 forks source link

Added feature flag for standard wasm build #281

Closed DanHaas closed 1 year ago

DanHaas commented 1 year ago

Named the feature wasm-standard Instead of using not wasm2js. Set it default feature as standard. Useful as we can disable this and then you can load your wasm file in your own way.

arctic-hen7 commented 1 year ago

Hmm, I'm tentative about this. What use case do you have for manually loading your Wasm bundle? Note that you can insert arbitrary scripts into the HTML shell Perseus generates using plugins.

DanHaas commented 1 year ago

We could expose our rust functions through imports to JS.

import init, { add } from './pkg/without_a_bundler.js';

https://rustwasm.github.io/docs/wasm-bindgen/examples/without-a-bundler.html

arctic-hen7 commented 1 year ago

It is not intended that you export functions from Perseus to JS, Perseus is a framework that should let you do almost everything in Rust. Even so, you should still be able to import extra functions if you really want to by just calling bundle.wasm as normal I think...

DanHaas commented 1 year ago

For my use case it was: I had issues with a crate and Perseus compiling, I think this was a bindgen issue but luckily there is a js framework I could use instead of the crate. I wanted to expose my rust function as getters and setters. So I can trigger them in js and return the main meat of the app to rust. I'm sure there are ways around it but for a supported feature of bindgen that's simple to implement it would be a shame not to have the option. I think this is a harsh restraint as there are a lot of developers that like implementing js and this would at least leave the door open even if it's not by default.

arctic-hen7 commented 1 year ago

Okay, got it. But my question stands: what is preventing you from doing this at the moment? I'm happy to merge this if it's impossible to do this right now, but I think you should be able to import directly from the stable path .perseus/bundle.wasm...

DanHaas commented 1 year ago

Please correct me if I'm wrong (this is deep waters for me), I have tried but without the generated wasm interface javascript it would be very tricky. To instantiate a WebAssembly module, without the generated JavaScript I will need to provide implementations of all the JS functions that bindgen would normally provide to the module.

As standard you need to call the generated init function first to actually load the wasm and perform the bindgen setup with required modules.

arctic-hen7 commented 1 year ago

That's not quite what I mean. Perseus uses wasm-bindgen under the hood, which basically exposes an interface of the form init, { my_function_1, my_function_2, ... } through bundle.js. Once someone has called init() (which is async), those other functions are then defined, and can be imported painlessly from /.perseus/bundle.js (be aware that JS imports don't get the benefit of Perseus' relative path support, so if you're deploying at https://example.com/path/, that will be /path/.perseus/bundle.js, you can use perseus::utils::get_path_prefix() to get that if you're unsure).

Since Perseus calls init() automatically, your scripts can import fine: here's an example I mocked up on the core/basic example:

mod error_views;
mod templates;

use perseus::prelude::*;

#[perseus::main(perseus_axum::dflt_server)]
pub fn main<G: Html>() -> PerseusApp<G> {
    PerseusApp::new()
        .template(crate::templates::index::get_template())
        .template(crate::templates::about::get_template())
        .error_views(crate::error_views::get_error_views())
        .index_view(|cx| sycamore::view! { cx,
            html {
                head {

                }
                body {
                    PerseusRoot()
                    script(type = "module") {
                        "import { print_stuff } from \"/.perseus/bundle.js\"; document.addEventListener(\"__perseus_loaded\", () => { print_stuff() })"
                    }
                }
            }
        })
}

#[cfg(client)]
#[wasm_bindgen::prelude::wasm_bindgen]
pub fn print_stuff() {
    perseus::web_log!("Hello, world!");
}

Niftily, Perseus outputs a custom DOM event __perseus_loaded that was intended for exactly this sort of thing! By hooking into that (since it will only run once), we can guarantee that init() is done and that those functions will be defined, and you can use them freely! In the above example, I've put the script in the <body>, but it could just as easily go in the <head>. The only other thing you'll need is to import wasm-bindgen = "0.2" in the cfg(client) section of your Cargo.toml dependencies.

Let me know if this doesn't address your specific use-case, and I'll be more than happy to reconsider merging this!

DanHaas commented 1 year ago

You are completely correct, this works great! Thank you for taking the time to explain and creating the sample. I'll close this shortly but maybe we could add this to the docs?

DanHaas commented 1 year ago

With the event listener we can pop this in plain js file. The whole time my issue was simply not knowing about the event listeners for Perseus.

import { fn_name } from "/.perseus/bundle.js"

document.addEventListener("__perseus_loaded", () => { console.log("Perseus is Loaded"); fn_name(); });

Thanks again.

arctic-hen7 commented 1 year ago

Sure thing, I'd welcome a PR for this!