Skip to content
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

Yew for some reason interacts poorly with the twttr widgets API #2484

Closed
udoprog opened this issue Mar 2, 2022 · 3 comments
Closed

Yew for some reason interacts poorly with the twttr widgets API #2484

udoprog opened this issue Mar 2, 2022 · 3 comments
Labels

Comments

@udoprog
Copy link

udoprog commented Mar 2, 2022

Problem
When I try to use yew (through trunk) and build a component which makes use of the twitter factory API, any use of the twitter API causes it to do nothing and produce a Promise which never resolves.

Steps To Reproduce

Cargo.toml:

[package]
name = "twitter"
version = "0.0.0"
edition = "2021"

[dependencies]
yew = "0.19.3"

index.html:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <script src="https://platform.twitter.com/widgets.js"></script>
    <title>Tweet</title>
</head>

<body>
</body>

<script>
    setTimeout(() => {
        console.log("show the tweet");
        let element = document.getElementById("tweet");
        twttr.widgets.createTweet('20', element).then(console.log, console.error);
    }, 1000);
</script>
</html>

src/main.rs:

use yew::prelude::*;

struct App;

impl Component for App {
    type Message = ();
    type Properties = ();

    fn create(_: &Context<Self>) -> Self {
        Self
    }

    fn view(&self, _: &Context<Self>) -> Html {
        html! {
            <div id={"tweet"}></div>
        }
    }
}

fn main() {
    yew::start_app::<App>();
}

Run with:

trunk serve

With the above, the tweet is expected to render after a 1 second delay. It doesn't! Just doing this however works as expected:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <script src="https://platform.twitter.com/widgets.js"></script>
    <title>Tweet</title>
</head>

<body>
    <div id="tweet"></div>
</body>

<script>
    setTimeout(() => {
        console.log("show the tweet");
        let element = document.getElementById("tweet");
        twttr.widgets.createTweet('20', element).then(console.log, console.error);
    }, 1000);
</script>
</html>

The issue seems to trigger when loading into the WASM application. Everything produced by trunk seems to work up until the point we load into the WASM instance through __wbindgen_start. Simply commenting it out allows it to work again:

index-<hash>.js while trunk serve is running (note that you also have to add a <div id="tweet"></div> in index.html):

    // wasm.__wbindgen_start();

Beyond that, I do not have the necessary understanding of WASM to troubleshoot this. And there's the issue that it's an issue triggered somewhere within Twitters widgets API.

Environment:

  • Yew version: 0.19.3
  • Rust version: 1.59.0
  • Build tool, if relevant: trunk
  • Browser and version: google chrome 98.0.4758.102 and firefox 97.0.1.
@udoprog udoprog added the bug label Mar 2, 2022
@rand0m-cloud
Copy link

Well, I've copied your files into a test directory and I can't get the tweet to load on the correct HTML. But after some playing I realized, this is an issue of when your script is ran. I'm not too familiar with the all inner-workings but the solution is to add defer to the script tag. The script runs before Yew has a chance to update the page. Add a console.log("the element", element); to spot the issue.

@WorldSEnder
Copy link
Member

WorldSEnder commented Mar 11, 2022

Yew clears the element where you mount the app, by default the <body>. In this case, the tweet loads, then the yew app start and the element gets removed, then replaced with an empty one rendered by yew.

The reconciler of yew does not try to preserve elements that exist before an app is mounted, it's concerned with managing the virtual dom as returned from component view functions. Modifying the same part of the DOM that yew expects to have control over will lead to poor results. There are safety hatches, in the form of VNode::VRef that lets you insert html elements that you can freely control (or pass to the twitter API for that matter) without yew interfering.

@udoprog
Copy link
Author

udoprog commented Mar 11, 2022

So I still don't know why, but it's clear now that it's some form of ordering issue as @rand0m-cloud pointed to above. Twitter has a loader snippet which defines the twttr.ready function and some more state (twttr._e), but apparently is required to smooth out any ordering differences. Adding it to the page solves the issue for me:

<script>window.twttr = (function(d, s, id) {
  var js, fjs = d.getElementsByTagName(s)[0],
    t = window.twttr || {};
  if (d.getElementById(id)) return t;
  js = d.createElement(s);
  js.id = id;
  js.src = "https://platform.twitter.com/widgets.js";
  fjs.parentNode.insertBefore(js, fjs);

  t._e = [];
  t.ready = function(f) {
    t._e.push(f);
  };

  return t;
}(document, "script", "twitter-wjs"));</script>

The twttr._e attribute seems to be used internally in the obfuscated widgets library. And it simply seems like it must be defined for it to work properly which admittedly is kinda weird since it's defined outside of the library itself (but that's an issue for Twitter to figure out).

So doesn't seem like it's a yew issue to me but just the first setup where I encountered this particular issue so I'm closing this. Thanks for the help everyone!

@udoprog udoprog closed this as completed Mar 11, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants