Skip to content

Instantly share code, notes, and snippets.

@Rich-Harris
Last active October 19, 2018 06:24
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Rich-Harris/587df5060fc895cda607139e7d41b95c to your computer and use it in GitHub Desktop.
Save Rich-Harris/587df5060fc895cda607139e7d41b95c to your computer and use it in GitHub Desktop.
SSR and web components

Responding to this Twitter convo

Let's say I'm building a blog post template page using from kind of front-end framework — for the sake of argument, we'll use Svelte. On this page, we'll have a share button that lets people share the blog post via Twitter.

Twitter makes this straightforward using their tweet web intent — just create an <a> tag with the appropriate href, and Twitter will take it from there. This is great — no need to faff around with an API.

But constructing the URL gets a bit unwieldy, so we'd like to turn it into a component:

<TwitterShare
	text="Server-side rendering of web components is hard"
	url="https://gist.github.com/Rich-Harris/587df5060fc895cda607139e7d41b95c"
	via='sveltejs'
/>

When we render this on the server, this is the markup that gets generated:

<a svelte-3655588705="" target="_blank" href="https://twitter.com/intent/tweet?text=Server-side%20rendering%20of%20web%20components%20is%20hard&amp;url=https%3A%2F%2Fgist.github.com%2FRich-Harris%2F587df5060fc895cda607139e7d41b95c&amp;via=sveltejs">Share on Twitter</a>

Needless to say, this works everywhere, with or without JavaScript. If we do have JavaScript, then we can enhance the experience by creating a nicely-sized and -positioned popup window rather than opening a new tab. That's a good example of progressive enhancement.

You can see a demo of it here. Notice that we have encapsulated styles, and we can do sophisticated things like client-side hydration using a universal codebase.

As far as I'm aware, you simply cannot do this with web components. At a bare minimum, you need some JavaScript to hydrate the <twitter-share> custom element.

@GianlucaGuarini
Copy link

GianlucaGuarini commented Jul 30, 2017

I guess third party web components were never meant to be rendered on the server. In your case for example you will be able to enhance your app simply by adding a fallback <noscript> in your <twitter-share> in the same way we do for <video> tags having src not supported files

<!-- twitter-share will be rendered by third party scripts -->
<twitter-share url="https://gist.github.com/Rich-Harris/587df5060fc895cda607139e7d41b95c">
<noscript>
  <a target="_blank" href="https://twitter.com/intent/tweet?text=Server-side%20rendering%20of%20web%20components%20is%20hard&amp;url=https%3A%2F%2Fgist.github.com%2FRich-Harris%2F587df5060fc895cda607139e7d41b95c&amp;via=sveltejs">Share on Twitter</a>
</noscript>
</twitter-share>


<!-- don't we already do the same for video tags already -->
 <video width="320" height="240" controls>
  <source src="movie.mp4" type="video/mp4">
  <source src="movie.ogg" type="video/ogg">
Your browser does not support the video tag. but you can download it <a  href="movie.mp4">here</a>
</video> 

@GianlucaGuarini
Copy link

So basically WC will allow you to enhance your Svelte/Vue/React/Skate app adding custom tags that might be rendered on the client by third party scripts leaving you the possibility to eventually progressively enhance them however you like, isn't that cool?!

@Rich-Harris
Copy link
Author

Thanks. You could do it that way, but rendering the tag yourself independently of the component (including all the data-driven href generation that the component is supposed to do for you) so that you can inject it as a fallback seems thoroughly object-defeating - the whole point of the component is that it worries about that stuff for me.

@treshugart
Copy link

treshugart commented Jul 30, 2017

Hey, so we're working pretty hard on this over at skatejs/ssr.

To get something going, the following should do it:

require("@skatejs/ssr/register");
const render = require("@skatejs/ssr");

class TwitterShare extends HTMLElement {
  connectedCallback() {
    const text = this.getAttribute("text");
    const url = this.getAttribute("url");
    const via = this.getAttribute("via");
    if (!this.shadowRoot) {
      this.attachShadow({ mode: "open" });
    }
    this.shadowRoot.innerHTML = `
      <a target="_blank" href="https://twitter.com/intent/tweet?text=${text}&amp;url=${url}&amp;via=${via}"><slot>Share on Twitter</slot></a>
    `;
  }
}

customElements.define("twitter-share", TwitterShare);

render(new TwitterShare()).then(console.log);

This outputs: http://jsbin.com/nizumum/edit?html,output.

As we've discussed via Twitter, styles aren't scoped yet as lack of JavaScript isn't a priority for us right now. We'll be working on this as soon as we've finished of being able to fully reverse engineer the composed DOM (which is almost there, just need to handle default slot content).

The idea behind all of this is to eventually make a spec proposal for declarative shadow roots based on the ideas it presents.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment