-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Change the app struct to be a real handle to an Yew app instance and make it possible to destroy a running app #1825
Conversation
This is of course a breaking change. |
So @siku2 comment made me look at the App interface again, and I realize that with this PR you can mount an I think the solution is to only return an instance of an |
Oh, you're totally right! How about we use #1652 and make the functions return a handle which can be used to destroy the app. |
That would basically be the same as the changes I'm doing now. If you give me 20 minutes then I have some code that we can discuss ;) |
So I have created my new suggestion for the public App interface here Skybox-Technologies@81fce4b Basically the api is as follows: @siku2 this does pretty much what you suggest just with other function names. However I think it makes sense to keep the mounting/creating App fns under App (as this commit does) |
I do prefer the function interface for simplicity though. yew::start_app::<Comp>();
// vs
yew::App::<Comp>::mount(); |
Hmm. I don't agree. I look at the API as: yew::start_app::<Comp>(); // Quick way and we don't care about getting a handle to the app
let app = yew::App::<Comp>::mount(); // Explicit way - we want to use the App instance for something. But that said, I don't feel super strongly about it. |
BTW, since the Right now I'm very inclined to just follow #1652 |
Yes the App struct wraps the scope, so you can't call destroy twice on it (since it isn't Copy, unlike Scope). It makes for a nicer API IMO |
I've fixed the tests and examples, so I'll pull in the new branch to this PR (not because it's final in any way) |
That's a good point, I think it's fine to keep the handle around but I still want to simplify the mounting process. Either we remove the public constructors from the handle altogether or we remove the convenience functions. Personally, I strongly believe that the functions are more convenient and look nicer. |
Fair enough. So we keep the functions and remove the public constructors. Do you want to rename |
Please do, yeah |
On it ;) |
Also, I introduced the function: /// Returns the app's component link.
pub fn get_component_link(&self) -> ComponentLink<COMP> {
self.scope.clone()
} Which I just realized probably should be substituted by impl let app_handle = yew::start_app::<Comp>();
app_handle.send_message(Comp::Message::Hello); |
@siku2 How strongly do you feel about the names of the functions yew::start_app::<Comp>();`
// and
start_app_with_props<COMP>(props: COMP::Properties) They respectively call App::<COMP>::mount_to_body()`
// and
App::<COMP>::mount_to_body_with_props(props) if we keep the names and want to provide the same functionality as fn start_app_in_element_with_props(...) -> AppHandle |
Feel free to change the names as you see fit. |
The function get_component_link and destroy on App does not require Component::Properties to be Default. So move them to their own impl block.
Co-authored-by: Simon <simon@siku2.io>
* app.rs thorugh to public API: only make it possible to create an instance of an App by mounting it. And only make it possible to mount an App by creating it. This prevents double mounting. * Change Scope::mount_in_place to take &self instead of self, because it doesn't have too.
Currently we call some global initialization code in std::panic::set_hook(Box::new(console_error_panic_hook::hook)); |
After thinking about it I think we should inline (get rid of) the let scope = Scope::new();
// ... do something
Self { scope } which also looks cleaner in my opinion. As for the panic hook, we should just move that to the |
Fair enough, I can do that.
That's doesn't really solve the underlying problem, since with this PR we make multiple concurrent Yew apps first class citizens and thus any global init code shouldn't be called multiple times just because you start multiple apps. However I'll do what you suggest, I just think we should have a note saying that this is maybe not the best approach. |
Co-authored-by: Simon <simon@siku2.io>
Co-authored-by: Simon <simon@siku2.io>
To me the only problematic part was that the hook was registered in a struct constructor instead of a global function since semantically a constructor shouldn't have visible side effects in the global scope. There's nothing wrong with calling |
Which could be a bit confusing for the user
I don't think we should make an asymmetric solution where we only set the panic hooks sometimes. What if, we have a if !panic_hook_is_set.get() {
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
panic_hook_is_set.set(true);
} in addition we add a function: pub fn set_custom_panic_hook(hook: Box<dyn Fn(&PanicInfo<'_>) + Sync + Send + 'static>) {
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
panic_hook_is_set.set(true);
} So to summarize: If the panic hook is not set, we set it to what we usually do, however if it is set (either by the user or us) then we don't |
I like that solution! |
This commit adds the function set_custom_panic_hook. In addition Yew will now check if a custom panic is registered when one of start_app* functions are called, if that is not the case a default panic hook is registered.
Co-authored-by: Simon <simon@siku2.io>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you! I'm very excited about this change.
I originally posted this as a discussion thread, however since I already have a working implementation that I think is ready - I thought it would be better in a PR form.
Motivation
Currently there is no way to unmount a Yew app after it has been started, which is useful/necessary if you want to use Yew to create standalone widget/components in a larger non-Yew project.
Our use case:
We are currently creating a Yew app that we want to distribute to our partners as a widget/plugin to be used on their own website.
Our current approach is to expose an initialization JS function that takes an html element which then mounts the yew app with App::mount_with_props, and HOPE that they don't call the initialization function twice. However we have found that this is pretty hard to control with single page websites, e.g. React website, especially when we don't control the surroundings of our Yew app. For this reason we would like to introduce a way to unmount/destroy an Yew app cleanly so we can expose the same functionality in our API.
Description
This PR have changed quite a bit with the input of @siku2 since I created it, so I thought it required a new description (I've kept the old one below).
app
and the structapp::App
has respectively been renamedapp_handle
andapp_handle::AppHandle
.AppHandle
. Specifically the following has been changedstart_app<COMP>()
has changed toyew::start_app<COMP>() -> AppHandle<COMP>
start_app_with_props<COMP>(props: COMP::Properties)
has changed tostart_app_with_props<COMP>(props: COMP::Properties) -> AppHandle<COMP>
App::mount(self, element: Element) -> ComponentLink<COMP>
has been moved and renamed tostart_app_in_element<COMP>(element: Element) -> AppHandle<COMP>
App::mount_with_props(self, element: Element, props: COMP::Properties) -> ComponentLink<COMP>
has been moved and renamed tostart_app_with_props_in_element<COMP>(element: Element, props: COMP::Properties) -> AppHandle<COMP>
App::mount_as_body(self) -> ComponentLink<COMP>
has been moved and renamed tostart_app_as_body<COMP>() -> AppHandle<COMP>
mount_as_body_with_props(self, props: COMP::Properties) -> ComponentLink<COMP>
has been moved an renamed tostart_app_with_props_as_body<COMP>(props: COMP::Properties) -> AppHandle<COMP>
app_handle::AppHandle
. AnAppHandle
instance can only be created by mounting a yew application and it does not implementClone
, thus there can only be oneAppHandle
per mounted Yew application. TheApphandle
implements one public function:AppHandle::destroy(self)
, which consumes the uniqueAppHandle
and schedules the app'sScope
to be destroyed.AppHandle
now implementsDeref<Target = Scope<COMP>>
, thus making it possible to get a reference to the app's scope and send messages to the app.Unfortunately because we have changed the naming scheme of how to mount an Yew application, this PR now touches A LOT of files, e.g. all the yew examples. However all those changes are very trivial and mostly boils down to calling the same function but with a different name.
OLD Description
OLD Implementation proposal
Our proposal is to use underutilized yew::app::App struct by:
mount(self) -> ComponentLink<..>
tomount(self) -> self
, i.e. store the resulting Scope in the App struct instead of returning it.get_component_link(&self) -> ComponentLink<..>
on App, to compensate for the changes in the function signatures in point 1.destroy(self)
on App that consumes the app and schedules a destruction of the inner scope of the app.By doing the above we effectively change the App struct from being a "initialize and consume struct" to be a real handle to a specific instantiation of a Yew app.
Test
In the discussion post I promised to create new tests for this functionality, but I have not found any good way to test it.
If someone can point me in the right direction or have any ideas for one or more tests, I'll happily write them.
I have however created a new example called dyn_create_destroy_apps that showcases how to dynamically create and destroy instances of yew apps.
Checklist
cargo make pr-flow