Adventures in Rendering Off the Main Thread

Introduction

Simon MacDonald, a seasoned web developer, kicks off the session with a humorous anecdote about his early days building web applications using CGI bin, Perl, and even compiled C applications. He expresses his enthusiasm for JavaScript while acknowledging the tendency of developers to overuse it. Simon emphasizes the importance of considering alternatives to client-side rendering, particularly for performance and accessibility reasons.

The Importance of the Main Thread

Simon delves into the crucial role of the main thread in web development, outlining its responsibilities: parsing HTML, applying CSS, executing JavaScript, and handling user events. He stresses that long-running JavaScript processes on the main thread can negatively impact user experience and lead to performance issues. The talk highlights the significance of maintaining a smooth 60 frames per second for animations and the limited time available per frame (approximately 10 milliseconds) to execute all necessary tasks.

The Problem with Client-Side Rendering (CSR)

Simon critiques the current state of web development, where many applications rely heavily on client-side rendering, leading to large JavaScript bundles and potential failure points. He points out that a failure rate of 1-2% for JavaScript, though seemingly small, can have significant consequences when multiplied across numerous page views. He cites various reasons for JavaScript failures, including network errors, corporate firewalls, ad blockers, users with JavaScript disabled, and older browsers.

Adventure 1: Server-Side Rendering (SSR)

As a solution to the challenges posed by CSR, Simon advocates for server-side rendering. He explains the basic process of SSR, where the server generates the HTML, sends it to the client, and then loads the JavaScript for interactivity. He debunks the misconception of SSR being slow, attributing this perception to legacy systems and highlighting the speed of modern cloud databases. Simon emphasizes the benefits of SSR, including faster time to first interaction, improved SEO, and compatibility with older devices.

Adventure 2: Web Components for Reusable Components

Moving beyond SSR, Simon explores the use of web components for creating reusable UI elements. He questions the reliance on heavyweight JavaScript frameworks for this purpose, given the availability of web components as a native browser solution. The talk introduces the core APIs of web components: custom elements for defining new HTML tags, Shadow DOM for encapsulation, and slots and templates for component composition. Simon argues that web components, being lightweight and standards-based, offer a more efficient approach to building reusable components.

Addressing the JavaScript Dependency of Web Components

Acknowledging that web components still require JavaScript for registration and some functionalities, Simon presents Enhance, an open-source project he contributes to. Enhance enables server-side rendering of web components, allowing developers to write standard HTML that is progressively enhanced with JavaScript on the client-side. This approach ensures a functional experience even if JavaScript fails to load, aligning with the principle of graceful degradation.

Adventure 3: Offloading with Web Workers

In the final segment, Simon introduces web workers as a mechanism to offload computationally intensive tasks from the main thread. He explains that web workers run in separate threads, have access to a subset of browser APIs, and communicate with the main thread through message passing. Simon demonstrates a practical example of using a web worker to handle saving data to an API, highlighting the performance benefits. He advises using web workers strategically for specific tasks rather than as a default solution.

Conclusion

Summarizing the key takeaways, Simon reiterates the importance of reducing JavaScript bundle size, reusing platform features, and offloading tasks to web workers. He encourages developers to embrace a progressive enhancement mindset, prioritizing HTML and using JavaScript judiciously for enhancing functionality. Simon concludes by promoting Enhance as a tool for building performant and resilient web applications with server-side rendered web components.

It is true, I used to build web applications using CGI bin, so we used Perl back in the day, but even before Perl, we actually used C applications, like compiled C applications to build web apps, so that will tell you how old I am if you can get in on that.

Anyway, I'm here to talk to you about, my adventures in rendering off the main thread.

We had this wonderful, introduction, so I really don't have to get into myself, even though I could probably spend 45 minutes of doing that.

This is my infinite assignment slide, where I, it's the slide from the talk from before, from the talk from before, 42 talks deep.

Anyway, if you need to get a hold of me, the best thing to do is just to hit me up on Mastodon, @macdonst, that's where I am everywhere.

I don't go on Twitter anymore, it's a tire fire, please for your own safety and sanity get the hell off that thing.

This is my first time in Sydney, but also I was born in Sydney, and let's just make that make sense.

So top right hand corner of the screen, we have Sydney, Nova Scotia, which is the place of my birth.

Yes, I am Canadian, you probably noticed that because I have a funny accent.

I'm speaking English to you.

If I spoke like fluent Cape Bretonese, you would not have an idea of what I'm saying.

How do I explain it?

Okay, take pirate and Scottish and jam them together and you have Cape Bretonese.

I don't think there is whoops, I went in the wrong direction.

I don't think there are two places you could get that are further apart than Sydney, Nova Scotia and Sydney, Australia.

And I just wanted to bring this up because it has brought me no end of joy in my entire life.

Because once every couple of years in my hometown, something like this would happen.

Or something like this would happen.

Or something like this would happen.

I have felt a connection to this city my entire life.

So I'm really excited to be here.

Anyway let me just, before we get into this talk, let me just say how much that I love JavaScript.

I love JavaScript.

I write it every day.

It is awesome.

JavaScript didn't pay for my house, but it really did help me with the down payment, so I really appreciate JavaScript.

However, as developers, I think sometimes we like JavaScript just a little too much.

It gets excessive sometimes, folks, and we might talk about that a little bit here.

Alright, why do we care about the main thread?

Like, why is this so important to us as web developers?

The main thread, it has a lot of responsibilities.

What it does first is it parses the HTML, so the HTML gets downloaded, it's parsed, we turn those into some DOM elements, and then the next thing that happens is it grabs the CSS for you, so it's going to parse all of that CSS, apply that to our DOM elements, and then it's going to download and, execute the JavaScript, so it basically has to parse the JavaScript, then execute the JavaScript, and then All user events have to go on the main thread, so it has to, keep all that aside to be able to respond to any user events.

So that means that any, long running JavaScript process in your main thread is going to block things for your users, and it's going to make the experience, terrible for them.

A couple of important numbers I always like to think of.

The first is 60 frames per second.

Anytime we're doing animations on the web, we want to make sure they're running at least 60 frames per second.

Otherwise, they look a little janky, people start to get that uncanny valley going on, and they realize that, ugh, it's not as great as it could be.

And in order for you to actually do 60 frames per second of animation, that means for each tick, you have 16.

7 milliseconds in order to do everything.

So the browser has to do everything and under that time.

And that includes executing scripts, recalculating styles, doing layouts, all that other sorts of fun stuff.

And to tell you the truth, it's not even 16.

7 seconds because there's still browser overhead in there.

So you've basically got about 10 milliseconds on every tick in order to get everything done.

Otherwise, you get some pretty bad experiences.

So anytime you have slow JavaScript or some really expensive CSS properties for animation, it can result in kind of that browser jank.

So you want to avoid that as much as possible.

All right, so I'm already getting like dry mouth up here, because I don't know, I've been speaking for, I don't know, how many years, Tommy?

We met over, 10, 12 years ago, and still get freaked out every time I get up here.

Honestly, I think I would have preferred to do the bridge climb yesterday, but anyway.

Alright, let's go to the current state of the art when it comes to most applications these days.

And you see something like this, 2.

1 megabytes transferred, 6.

3 megabytes of resources coming across, and I'm sorry, this is, that's, I'm not even gonna name and shame, but that is for a landing page, folks, that is way too much resources to come along.

Ah, man, that's crazy.

But if you take a look at, typically what we have for an index.

Html, basically what a lot of the modern JavaScript frameworks do is they have, a div, which you have your insertion point.

And that is where all of the client side rendering is going to happen.

It'll just be entered into that point.

But, what if, also we have our script, which is our module, which is all of our bundle of JavaScript resources.

And that bundle can be quite large.

And you have to ask yourself, What if JavaScript fails?

If something goes wrong in this case, what happens?

And I, hopefully this is not, I did a translation of that, bad trot, hopefully that's not offensive, but if it is, eh, whatever, I'll get cancelled in Australia.

I'm still big in Luxembourg.

But, yeah, that's what happens.

If you CSR everything, and the previous speaker was just talking about how important it is to have a fallback, If you do everything in CSR, there is no fallback.

You're dead in the water.

Don't even, I could talk about 20 minutes about how one time I tried to buy a pair of pants from Nordstroms and it just would not work because of client side rendering, but that is a whole other talk.

Alright, you're probably sitting here in the audience thinking, yeah, JavaScript never fails, I don't really have to worry about this, guy's crazy.

I am, but that's not the point.

The point is, the failure rate for JavaScript is anywhere between 1 and 2%.

So all of the studies that I've seen in the past decade or so are estimating it between 1 and 2%.

And that's oh yeah, that's not that bad.

But that's not like every user, that's every page view.

So every page view, you have one or two percent chance of something going wrong.

And that adds up over time.

And I've seen a recent study that it's up to three percent.

And honestly, with the size of the JavaScript payloads that we have, I'm not surprised that it is growing over these past ten years.

Now, what are some possible failure reasons for JavaScript?

Very easily, there's did the HTTP request for your JavaScript file fail?

It could be a network error.

That makes a lot of sense.

Was your JavaScript blocked by some sort of corporate firewall?

I've worked for some really paranoid companies.

I'm not actually going to tell you the name of the company.

I'll just give you the initials.

It's IBM.

And, they were like very, Ooh, yeah, I can't have anything come through their firewall, and they actually end up blocking themselves sometimes.

It was actually quite hilarious.

Could something be interfering with your JS?

I don't know, if you have an ISP that used to, mess with your DNS or inject some scripts in there, or if you're at an airport, do you ever be on airport Wi Fi?

They're always injecting stuff in there and messing up your JavaScript, so that's not great.

And this is where most people think, this is, those crazy people that are out there surfing the web with JavaScript turned off.

And it's that's where we think, oh, that's when JavaScript fails.

It's no, that's only one small reason.

But some people do that for very good reasons.

Now, my favorite one is I have a 19 year old daughter.

She has X number of gigabytes of data.

But as she gets closer to running out of data, IOS will automatically flip on data saver mode.

And what that does is stops downloading JavaScript.

So she will like, about a week left in her billing cycle, she'll be like texting me going Dad, my phone doesn't work anymore.

And it's it's because lots of the sites that she's trying to visit.

She's not getting the JavaScript, so everything seems broken to her.

And this is happening to all of your users.

There could be plugins that are messing with your JavaScript.

I don't know, has anybody actually surfed the web with an ad blocker anymore?

Yeah, there's nobody's raising their hand.

Wait, there's one hand in the back.

You are a, you, sir, you are brave.

That is amazing.

Also, don't do that.

That is a bad idea.

I've got three layers of ad blockers at this point in time, because I don't trust any of that stuff.

And I will back right away from a site that's oh, please disable your ad blocker.

Nope.

Alright, I'm done.

Anyway, so sometimes ad blockers will interfere with the JavaScript on the site, but that's probably protecting you from something malicious, eh, don't do it.

And then, yeah, I don't know, of course there's just the side of the ad blocker.

My favorite one was, Canadian Tire.

That is a giant corporation in Canada.

They don't just sell tires, they sell pretty much everything.

It's an old company.

But during the pandemic, trying to order anything from them, and it just would not work.

It was great.

So you couldn't actually order anything.

It was so fun.

And it was all because of a broken card experience.

And then there are some folks that are still running older browsers and, JavaScript could fail because they're not supporting a particular, the question mark to do chaining.

It could not be in their browser.

And all of a sudden, everything fails for them.

There's lots and lots of reasons for a JavaScript fail, and it adds up to that 1 2%.

And when it comes to the failure rates, you think oh, it's not that big a deal, it's only a couple of people, but as your site becomes more popular, as you get more users, this relatively rare occurrence happens all of the time, so you have to be able to deal with it.

And when you're building, a purely client side rendered application, and that only uses JavaScript on the front end, you're often left with something like this.

There's just continuous spinners, the data never loads, and your users never buy your FizzBud widget or whatever you're trying to sell them at that point in time.

And that really leaves people sad.

Anyway, terrible.

So if you're not scared about this right now, hopefully I've scared the crap out of you and you're going to look into it.

Anyway, you can lose a lot of money or, have terrible SEO scores if you don't account for this type of thing, which leads us to our first adventure in rendering off the main thread.

And the best way that I know to avoid JS failures is to reduce JS bundle size and stay within our performance budget.

And, let's just forget about client side rendering altogether and let's talk about doing server side rendering to begin with.

Alright, so a quick overview of SSR.

Actually, anybody in the React Server Components, talk yesterday?

People.

Okay.

Yeah.

They didn't invent that.

That's been around for a really long time.

, sorry, I'm not trying to crap over river cell, but sometimes there's these breathy things.

There's, oh yes, this is so amazing.

This is this new functionality that we've had.

And it's ah, dude that's been around for the web is around for over 25 years.

That's, how it was done from day one.

Anyway, quick overview.

You click on something, you make an HTTP request, and the browser's gonna respond with some accessible HTML to you.

The HTML ends up in your client, at that point in time, if you've done your job correctly, people can start viewing and interacting with your site.

They may not be able to, submit a cart, but they can start doing things.

And then, there's another HTTP request for all of the other resources, the CSS and JavaScript, and eventually the JavaScript ends up on your page where it is, parsed and executed so you can add interactivity.

Now, anytime that you're building an application, you want to take into account the law of powers.

And basically that states that anytime you're building an application, you should always reach for the language with the least amount of power that can get the job done.

And generally, that's HTML.

And again, this is how the web's built.

You start with HTML.

Then you go to CSS, and then finally, if you can't deal with those things, you go to JavaScript.

It's not JavaScript, Sorry, that JavaScript.

It's a tongue twister, what can I say?

You want to leverage the way that the web was originally architected.

And by, using server side rendering, you get some benefits, you can get to time to first interaction a little bit faster because the HTML is already, able to be viewed and interacted with, and if you're using forms and issuing some client side rendering, you can submit those even without JavaScript.

You get better SEO because, Google can get in there and just view your site without even having JavaScript.

And then, of course, you get the ability that things are going to, work on older devices.

I'm from Canada, we had Research in Motion, BlackBerry, and they had this, really terrible tablet that, they thought they were going to take the world by storm and it ended up, selling for five bucks apiece because they were, lots of the websites that we write today on this tablet.

But we can still view because it's just HTML.

It's amazing.

Anyway.

You might be asking, is server side rendering slow?

And it's no, it's not.

It is not at all.

Now the time to first byte may be a little bit slower because you have to render all of your HTML on the server and then send it down.

But when it comes to time first interactivity, it will be a lot better, and a lot of your modern functional web applications are using cloud functions to deliver HTML, and they're generally backed by like modern databases, like again, I'm old, I'm so sorry, I'm like stupid old, I remember when we had to set up socket pools to Oracle databases, and if your socket went down, you could be waiting two minutes for that socket to reconnect in order to query the data, and that's why servers are, everybody move Let's go to client side rendering instead.

To use modern cloud databases to query data, it's under 10 milliseconds.

It's not even worth thinking about at this point in time.

It's just ridiculous.

So you can get that data in that very first section and then create your HTML and send down like something working to your client immediately.

And that's like a much better way of doing things in my personal opinion.

The first lesson is, render things off the main thread by leveraging your server.

Your server is far more powerful than any of your client's applications.

I know lots of us are probably running M2 MacBooks and we have all these wonderful things, but that's not your typical customer.

They have, feature phones or they have some older Android phones and they don't have the processing power that you do on your desktop.

By targeting HTML first and server side rendering, you're going to make your application a lot more performant.

Alright.

Our second adventure is like, how can we compose these applications using reusable components?

And, that means modern JavaScript frameworks like React, Vue, Angular, etc.

But, the question, can we use a component framework when we're server side rendering?

And, it'd be stupid if we couldn't, so yeah, the answer is yes.

But now I have to ask you a question.

There's a but here.

Alright, who here in the audience has used jQuery in an application?

Yeah, okay, some of you didn't raise your hand, but I guess you're just too lazy to do it, because I know you've all done it.

How many of you were starting an application today would reach for jQuery as that solution?

Yeah, not one of you.

And that is not to bash on jQuery because it was an amazing solution, it did a fantastic job of smoothing over all the difficulties, or all of the differences between all the web browsers, but now we have evergreen web browsers that are much better at implementing all of the APIs.

But also, one of the main things we used jQuery for was the query selectors.

It was awesome.

You could, select by class, or ID, or whatever.

And that have informed, modern DOM APIs.

And that's why we have querySelector, querySelectorAll, all of those fun things.

One of the main reasons to use jQuery just no longer exists, because it's built into the platform.

Ask yourself this question.

This is probably the most important thing I will leave you with today.

When you're starting a new application now, and we have a component framework that is built into the platform, why am I reaching for a heavyweight JavaScript solution?

It's like, why do I go to these frameworks, other than the fact that I already know them?

Or, why do these heavyweight frameworks not look at things like web components and start transitioning away from the mountains and mountains of JavaScript that they need in order to create reusable components and instead leverage what's in the platform?

Because that's what we're supposed to be doing.

These frameworks are supposed to get smaller over time, not bigger over time.

Yeah.

Anyway, my question, why use a, a large heavyweight framework when web components exist?

I love web components.

Just came from another talk that was on web components.

They're pretty great.

Mostly, there's three APIs that you can really count on.

First off is the custom elements.

That allows you to define new reusable custom elements, just HTML elements themselves.

You can use the Shadow DOM in order to encapsulate styles and behavior into your components so that they don't leak into the rest of your application.

And finally, you can use slots and templates in order to compose larger components out of smaller leaf components.

So anyway, They're fantastic.

And if we were to take a look at a very, small Hello World component, it's not super fancy, but basically, what you need to do in order to start a web component, in this case, is we're defining this template.

So this template, we're going to be able to reuse this over and over again, we can create many Hello Worlds, or it might be many buttons, it might be many tabs, whatever your component is about.

And then basically, when we start up the, we define a class for hello world, we're going to attach the shadow DOM, which is a whole other thing that I won't even get into right now.

But basically, I'm going to clone that template, put it into the shadow DOM, and it's going to show up into my web component, which is controlled by this little bit here, custom elements defined.

So anytime you see the tag hello world, I want you to execute this class, hello world, and create this component for me.

And so that's a really amazing way of making reusable components, and you're using the platform to do it, and not a giant, framework that you're going to have to, I don't know, put in 2.

1 megabytes of JavaScript just to show a Hello World?

Yeah, I don't really want that.

And what that looks like in your HTML is just something like this.

You have your Hello World tag, and quick note.

Any web component has to have a dash in it, so generally it's hello, it's first word, dash, second word, you can have multiple words if you want, but it has to have a dash in it, you cannot have a single word web component, those are reserved for possible future HTML elements, I don't know, search, which just recently came out.

So quite simply, you just, have your tag, you pass in our slot, which is what to actually show, and it just adds, hello, and then the span for the slot in there, That's, pretty cool.

That's great.

A lot of you folks were smart.

You can see the problem in this.

Am I contradicting myself?

Because web components need JavaScript, don't they?

There was that line right there, custom elements define.

So without that, we don't get these custom elements.

And to that I say, yeah, shit, you caught me out.

But, That is where this open source project that I work on, it's called Enhance.

I won't delve too deeply into it, but basically what Enhance allows you to do is to write web components that can be server side rendered.

So you just write basic HTML functionality, you use web standards, you use what is in the platform, and then you progressively enhance these web components.

And so what Enhance allows you to do, write your web component like normal, extend our base class, it has a render method, and that render method will be run on the server, so you'll get working HTML delivered to your client, and then, once the JavaScript loads on the client, if it ever does, because, JavaScript can fail, the component is enhanced with additional behavior.

But, at least, you have something working from the very beginning, because you're going to write good HTML, good semantic HTML.

Anyway.

So our second lesson in rendering off the main thread is to use what the browser already provides you.

Do not, go ahead and try to reinvent what the browser already does.

It's really good at going backwards and forwards, doing all that routing.

It has custom elements.

It's got all that fun stuff for you.

And I don't know if it's the same thing in Australia, but in Canada, we have this thing, Reduce, Reuse, Recycle.

I don't know, oh, cool, Tommy says it is.

Yeah, this is where my metaphor breaks down.

And unfortunately, we, can't do the, recycle.

But we'll try to, offload some things.

And this is where we're getting, into, the second, last third of the talk.

And you're finally getting, you're saying to yourself, Oh, finally, he's going to talk about what I came here for.

But here's the thing.

Everything that I talked about before, It's going to help you not get into the situation where you have to use this last thing.

So anyway, web workers are fantastic.

So it's this way that the browser allows you to spin up a separate thread in order to be able to offload, some computations to it.

Now it has a lot of the browser APIs so it can do fetch or any other kind of network request.

It can use the WebCrypto API, no, not to do mining, to actually do authentication.

And it has access to IndexedDB.

And it's the same IndexedDB that runs on the main thread, so you can pass data back and forth that way if you need to.

However, it can't directly manipulate the DOM.

And has anybody here ever done any Android or iOS programming?

A few of you have.

So that's really not uncommon for a separate thread not to be able to access what's on the main thread.

In fact, in iOS and Android, that main thread is actually called the UI thread for that reason.

And that's the only one that you can actually do repainting in your applications.

Communication between our main thread and the browser and our web worker is done through a callback called postMessage.

And in postMessage, a very bad example here that I will show you, we have our two threads, main thread and our web worker.

And the user clicks on a button to do something like save a new to do.

And it's like, all right, so we're firing that on click event in our main thread.

PostMessage is then going to call over to our WebWorker, where there is an on message, event, listener.

And it's going to be like, okay, I get this from the main thread.

I'm going to save this into our database.

I'm going to do that network call.

I'm going to, toss things into IndexedDB.

Whatever you need to do at that point.

And then once I have that response, I'm going to call PostMessage back.

To our main thread, where we have another onMessage event listener, and that's going to be like, OK, everything's good, I'm going to update our to do list, and say that, yes, this has been properly saved.

That is a really just quick way of showing how to do this.

Oh, five minutes!

Oh, there is no way I'm getting through all this.

Alright, folks!

There's going to be a lot of code, and I'm going to go through it fast, but don't worry, there is a link at the end to the slides, and you will get it all, so do not worry too much about it.

So If we have this to do API, and I, you know what, I am getting old because man, these are like little tiny things on my slides, how do I look up here?

Alright, so we're defining, anytime we come in to save to do, we're going to check to see if there's already a WebWorker running, if there's not, we're going to create one.

And then once we've done that, we're going to post a message, we're going to say that to do that we've got off of our form, we're going to post that over to the WebWorker to do some saving.

And then from there, we have our web workers, we've implemented our onMessage callback, and once the onMessage callback is called, we're going to take that data that we got from the main thread, and then we're just going to do a fetch to our backend API in order to save that into our database.

Once we get the response back from that fetch, we are just going to post that message, the result of what's come back from the database, over to the main thread again.

So we're just doing that little ping pong there.

In our to do API, we have this thing which is fun, it's called a store.

Basically we're just going to, create this kind of proxy object where we can put all of our to dos in there.

When we get in a new result, we're going to push that onto our to dos, and then add the copy of things back into store's to do.

And for the store API itself, basically we're creating a proxy object where we have our subscription, so anybody who cares about the to dos list being updated can subscribe to changes for it.

And then of course, we, need to be able to do an unsubscribe as well.

And from there, any time that any changes happen on our store, we're going to go through the list of all the folks that are listening and we're just going to notify them of any changes.

And from there, we have this really fun, handler here.

We just set.

Any time anything is set on our store, so we're overriding set, we're just going to go through and just, do that notify for folks.

And then of course, the way that this is all possible is by, wrapping it in a proxy.

Now our to do list web component, so we could do something very simple here.

When we, set up our to do list, we have a constructor where we instantiate our API, and then we're going to bind that update method to our component because, this and JavaScript is hard, what can I tell you?

in our connectedCallback.

We're going to subscribe to the store so that anytime there's changes to the to dos, we're going to get notified of it.

And our disconnected callback, that's where we clean things up because we don't want any kind of memory leaks.

And then anytime we update anything, like we get a new to do, we're going to go through this whole step of just, changing our UI.

And then the web component is going to take care of making sure everything, it gets updated correctly.

I got a couple of minutes.

I'll just look over there really quick, see if I can do this.

Alright, so Protip folks, work with really good designers because they are worth their weight in gold.

Pro Protip, marry a great designer because they sometimes come a little bit cheaper that way.

That's not been my experience, I've actually had to pay more for that.

Anyway, so if I have a new to do, you can see in the Web Inspector itself, we have this little thing here where we've added a new to do, there's a little sprocket or widget next to it, and that's just basically saying that, it's happening in a Web Worker, and I can't even, I can't spell it the best of days, so again, this is all happening, all that network connection, connectivity is happening through the Web Worker, and if I go over here and there's this little button to turn off JavaScript, and you can see things get red, I can still, I'll put in one more to do.

And in this case, the entire page refreshed.

Because I don't have access to JavaScript anymore.

I don't have, I can't do a post message to my web worker.

But since I've written semantic HTML, since I've set things up as a form, I can still do an old fashioned form post to my backend API to save the to do.

Get back that information, completely refresh the page.

So this whole thing is going to work with JavaScript and without.

And if I just go back over here, turn JavaScript back on, because otherwise slides won't work without it, and I can delete things, and it happens to the worker again.

All right.

All right.

Our third lesson is, Anytime you have something that's, super expensive in JavaScript, think about using a WebWorker in order to offload that from your main thread.

It will really help.

But definitely don't start, don't run to this.

This should not be your first thing.

Master the other two, paths that I talked about first before going this direction.

Now, It may seem to be like a ton of work to, do things this way, but if start with HTML and progressively enhance things, you're going to always have a working application rather than running to a heavyweight framework and trying to, do graceful degradation when something goes wrong in JavaScript.

That is the much harder path.

Anyway.

In conclusion to all this, first thing is reduce the amount of JavaScript required to build your HTML.

Reuse what the platform already provides for you.

And offload any expensive or time consuming tasks to a web worker.

And really, to really sum it up, I would say use JavaScript, Not a lot, mostly for progressive enhancement.

And that's a hot tip to Michael Pollan in one of his books.

Alright, so again, I went through that code super fast, so that's the thing for the slides, and everybody's grabbing their phones now, alright.

And, if you're interested in that open source project that I was talking about that is Enhance, that is what we use to server side render our web components.

And to all of you, I say thank you so much for having me.

It's been great to be in Sydney, not, the real Sydney, like the second Sydney.

I, I know.

I don't even know which one came first.

When was Sydney founded?

Oh, come on, . I know when my Sydney was founded because I looked it up five minutes before getting on stage.

Anyway, thank you all so much for coming.

ADVENTURES IN RENDERING OFF THE MAIN THREAD

Simon MacDonald

Head of Developer Experience
Begin Corp

{Adventures in Rendering Off the Main Thread}

1
Text on a dark background with blurred lines of code.

Simon MacDonald

@macdonst@mastodon.online

Simon MacDonald on stage in front of a large display screen with a smaller inset image of him speaking.
INTERNATIONAL CONVENTION CENTRE
A map showing a route from the Sydney Nova Scotia to Sydney, Australia. Several news items appear in succession describing passengers who flew to Nova Scotia instead of Australia.

I ❤️ JS

Meme/image of a man pouring a large container labeled "JAVASCRIPT" on a salad, with the word "DEVELOPERS" positioned to appear as though it is coming out from the container and "WEBSITE" at the bottom.

Chapter 1

Why do we care about the main thread?

Shrug emoji

Main Thread Responsibilities

  • Parses HTML ✔️
  • Builds the DOM ✔️
  • Parses CSS ✔️
  • Executes JavaScript ✔️
  • Responds to User Events ✔️

60 fps

16.7 ms

State of the Art

A screenshot showing “2.1 MB transferred” and “6.3 MB resources”

index.html


<!DOCTYPE html>
<html lang="en">
<head>
  <title>My App</title>
</head>
<body>
  <div id="app"></div>
  <script type="module" src="/src/main.js"></script>
  <noscript>Bad luck / uflaks</noscript>
</body>
</html>

But what if JavaScript fails?

A speech bubble containing the text "But what if JavaScript fails?" with a thinking face emoji below it.

JavaScript never fails, right?

12
Speech bubble with text "JavaScript never fails, right?" and a smirking emoji.

Failure Rate

1-2%

Failure Reasons

  • Did the HTTP request for your fail?
  • Is something interfering with your JS?
  • Was the JS blocked by the corporate firewall?
14
Three colorful speech bubbles containing text related to HTTP and JS issues.

Reasons

  • Ad blocker?
  • Does a browser plugin mess with your JS?
  • Is data saver mode turned on?
  • Is something interfering with your JS?
  • Has the user switched off JS?
  • JS blocked by the corporate firewall?
14
Man speaking at a podium labeled "International Convention Centre"

Failure Rates

Page Loads JavaScript Failures
100 1-2
1,000 10-20
10,000 100-200
100,000 1,000-2,000
1,000,000 10,000-20,000
A graphic representation of a web browser window with three sections: one large section on the right displaying loading spinners.
An emoji with a worried expression and a blue tear drop

Adventure

1.

Reduce

Reduce your JavaScript footprint by server side rendering HTML.

A visual timeline showing the sequence of steps: 1. A HTML5 logo with accompanying text "The server sends a 'ready to be rendered' HTML response to the browser". 2. An HTTP icon with text "The browser renders the HTML page which is now interactive for the user". 3. An HTTP icon with text "The browser requests JavaScript code from the server". 4. A JavaScript logo with text "Our JavaScript is parsed and our page is progressively enhanced to be even more interactive".

SSR Benefits

  • Time to First Interaction is fast
  • Better SEO
  • Works on older devices and slow network connections
An image shows a collection of old mobile phones, including models from brands such as Nokia and Sony Ericsson.

Isn't SSR slow ?

No

The first lesson in rendering off the main thread is to leverage your server to do the initial render

A speech bubble with text and an emoji of a smiling face with glasses and buck teeth.

Adventure

1.

Reduce

Reduce your JavaScript footprint by server side rendering HTML

2.

Reuse

Compose our application with re-usable components

Can I still use a component framework when using SSR?

An emoji of a person with short hair raising one hand.

Yes

But...

Why use a framework when Web Components exist?

  • Custom Elements ➡ define components
  • Shadow DOM ➡ encapsulate styles
  • Slots and Templates ➡ re-use
A geometric logo with a gradient design in shades of blue and green.

Hello World Web Component


    const template = document.createElement('template');
    template.innerHTML = `
      <h1>
        Hello <slot name="name"></slot>
      </h1>
    `;

    class HelloWorld extends HTMLElement {
      constructor() {
        super();
        const shadow = this.attachShadow({ mode: 'open' });
        shadow.appendChild(template.content.cloneNode(true));
      }
    }

    customElements.define('hello-world', HelloWorld);
  

Hello World Web Component


<hello-world>
  <span slot="name">
    Web Directions Summit
  </span>
</hello-world>
<hello-world>
  <span slot="name">Sydney</span>
</hello-world>
    
The slide contains two side-by-side sections. The left section shows HTML code examples inside a code editor interface with three round buttons in the top left corner. The right section displays the rendered output of this code, formatted as text.

Aren't you contradicting yourself? Web components need JavaScript, right?

Illustration of a person with dark skin and long black hair, wearing a purple shirt, raising their left hand.
An image of a smiling face emoji with a drop of sweat on its forehead, indicating a mix of happiness and relief.

WELCOME TO

ENHANCE

Two cartoon characters with wings.

WHAT IS ENHANCE?

Enhance is a web standards-based HTML framework. It’s designed to provide a dependable foundation for building lightweight, flexible, and future-proof web applications.

Author

Enhance allows developers to write components as pure functions that return HTML. Then render them on the server to deliver complete HTML immediately available to the end user.

Standards

Enhance takes care of the tedious parts, allowing you to use today’s Web Platform standards more efficiently. As new standards and platform features become generally available, Enhance will make way for them.

Progressive

Enhance allows for easy progressive enhancement so that working HTML can be further developed to add additional functionality with JavaScript.

The second lesson in rendering off the main thread is to reuse what the browser already provides

A smiley face emoji wearing glasses placed below the speech bubble.

Adventure

1.

Reduce

Reduce your JavaScript footprint by server side rendering HTML

2.

Reuse

Use the platform to provide a reusable component architecture with Web Components

3.

Recycle Offload

Expensive or time consuming tasks to web workers

Web Workers

Web Workers makes it possible to run a script operation in a background thread separate from the main execution thread of a web application.

35
Diagram illustrating the communication between Main Thread and Web Worker using postMessage(). The Main Thread has two events: 'onclick Event' (user clicks a button to save a new todo) and 'onMessage Event' (updates Store and emits an event for web components to update). The Web Worker has an 'onMessage Event' that sends a new todo to the server and awaits the response. Arrows indicate the flow of messages between the Main Thread and Web Worker.

Todo API


    // Todo API
    let worker
    export default function saveTodo (todo) {
        if (!worker) {
            worker = new Worker('worker.mjs')
            worker.onmessage = mutate
        }

        worker.postMessage({
            data: todo
        })
    }

    const store = Store()
    function mutate(result) {
        const copy = Array.from(store.todos)
    }
    

Todo API

let worker
export default function saveTodo (todo) {
    if (!worker) {
        worker = new Worker('worker.mjs')
        worker.onmessage = mutate
    }
    worker.postMessage({
        data: todo
    })
}
const store = Store()
function mutate(result) {
    const copy = Array.from(store.todos)
    copy.push(result)
    store.todos = copy
}

Web Worker


// worker.js
self.onmessage = async function message({ data }) {
  try {
    const result = await (await fetch(
      `/todos/${data.key}`, {
        body: payload,
        credentials: 'same-origin',
        headers: {
          'Content-Type': 'application/json'
        },
        method: 'POST'
      }
    )).json()

    self.postMessage(result)
  } catch (err) {
    console.error(err)
  }
}

Todo API



const store = Store()
function mutate(result) {
  const copy = Array.from(store.todos)
  copy.push(result)
  store.todos = copy
}

Store API


// store.js
function subscribe (fn, props) {
   return listeners.push(fn)
}

An illustration of a code snippet demonstrating the Store API with functions for subscribing, unsubscribing, and notifying listeners.

Store API



    function unsubscribe (fn) {
      return listeners.splice(listeners.indexOf(fn), 1)
    }
  
A code snippet block illustrating functions for subscribing, unsubscribing, and notifying listeners in a store.js file.

Store API


    function notify () {
        listeners.forEach(fn => {
            fn(payload)
        })
    }
    

Store API


const handler = {
    set: function (obj, prop, value) {
        if (obj[prop] !== value) {
            obj[prop] = value
            set(notify)
        }
        return true
    }
}

Todo List Web Component

// todo-list.js
class TodosList extends HTMLElement {
  constructor () {
    super()
    this.api = API()
    this.update = this.update.bind(this)
  }

  connectedCallback () {
    this.api.subscribe(this.update, [ 'todos' ])
  }

  disconnectedCallback () {
    this.api.unsubscribe(this.update)
  }
}

Todo List Web Component


    connectedCallback () {
        this.api.subscribe(this.update, ['todos'])
    }
Code snippet demonstrating the definition of a custom HTML element class named "TodosList" that interacts with an API.

Todo List Web Component



    disconnectedCallback () {
      this.api.unsubscribe(this.update)
    }

  

Todo List Web Component


    update ({ todos }) {
        const activeTodos = todos.filter(t => !t.completed)
        const completedTodos = todos.filter(t => t.completed)
        this.activeTodosList.todos = activeTodos
        this.completedTodosList.todos = completedTodos
    }
    
Screenshot of a web browser displaying a "Todos" application interface and Chrome Developer Tools' Network tab.

The third lesson in rendering off the main thread is to use web workers for expensive operations after you've mastered the first two lessons!

An emoji with glasses and buck teeth inside a speech bubble.

In conclusion...

Reduce: the amount of JavaScript required to build your HTML

Reuse: what the platform already provides

Offload: expensive or time consuming tasks to web workers

Use JavaScript Not a lot Mostly for progressive enhancement

h/t Michael Pollan

Slides

https://slides.com/simonmacdonald/web-directions-summit-adventures-in-rendering-off-the-main-thread-3c8214
45

ENHANCE

The HTML framework

Read the Docs: enhance.dev/docs

Join our Discord: enhance.dev/discord

Follow us on Mastodon: fosstodon.org/@enhance_dev

A slide with a cartoon character at the top. A three-item list with titles linking to respective URLs and a QR code below the list. The slide also contains an illustrative background with mountains, grassy hills, a rainbow, and clouds.

Thanks!