So let's dive into what the problem is.
This includes interactivity, rendering content, and even just downloading a working site.
So why should we care?
Well, the best summary is that the faster the site, the higher the conversion rates are for your organization, whatever that org, that conversion metric may be.
And this is just a small sampling of case studies that go into depth of why performance matters.
But let's dig into just a little bit more of what we can do to improve performance and ultimately your site's conversion rates.
And one could say that this overwhelming collection of knowledge, just to make a website work is a whole other problem.
There's already a never ending list of optimizations developers should just know.
But one reoccurring aspect is that the advice is often focused on how to improve your code.
So let's just say that you've done every single optimization possible to your code to make it as, to make a fast site.
There still may be a problem.
The biggest culprit of website performs issues often comes from third-party scripts.
first-party scripts is a code that you can improve.
So if third-party scripts are basically somebody else's code you don't have control over.
Some common examples include Google tag manager, Google analytics, Hotjar and countless others.
And your organization may have many valid reasons of why third-party scripts are necessary.
The data collected from your services can help inform decision across your entire organization.
But as you can imagine, there are consequences to loading as many services, as you can think of.
Now, according to HTTP Archive, the median for a mobile page will request 10 third-party scripts and nine first-party scripts.
This difference increases as we move up to the 90th percentile as 34 third-party scripts and 33 first-party scripts are requested.
And Lighthouse is something that is used as a catchall term for measuring site performance, but it's specifically a tool built into Chrome dev tools.
Lighthouse only uses lab data from predefined networks and devices to measure performance in a controlled environment.
And it's great to use throughout development, but when it comes to measuring production site performance, I'd recommend testing against tools that measure the real user metrics.
A great tool that does this includes Google's page speed insights, which uses both lab data and real world data collected from users.
Other great services include webpagetest.org and SpeedCurve dot com.
Now a common problem is that performance issues may not surface during development.
For example, a website's performance on a new MacBook pro with local host network is not the same as your end users, which commonly could be on a mid-market phone using your site from a bus or a subway.
The real, the real world will see a lower performing website compared to the same site during development.
And when you run these tools it is easy to see some of the biggest performance killers are from third-party scripts.
You're basically giving these scripts the same level of access to window and document as your own code.
So when your code is executing, It's now only one of many scripts competing with the same resources.
And worse yet is that your code is not given any priority over third-party scripts.
So they're all equally fighting on the main thread.
So the state is that you just can't opt, opt out of running third-party scripts.
That's an organizational requirement.
You can't modify or improve another service's code because that's out of your control and hosted on another Server.
And you can't prioritize your code to run faster than theirs.
So some of the possible solutions could be a Web worker.
A Web worker are able to run scripts in the background on a separate thread from the main thread.
And the main thread is what runs the UI.
What window and document are running from.
The more work you that's doing on the main thread, the more, the more that it slows down.
So it's natural to think let's just run third-party scripts from a Web worker, right?
Wouldn't it be great to unblock the main thread and move the third-party scripts over to a background thread?
This diagram shows the two examples.
While the bottom example has two threads.
With two threads, we're able to leave the main thread unblocked while the worker thread handles the heavy work.
But there's a challenge in that the Web workers cannot access the main thread's window or document.
These globals do not exist in the worker environment, which is a problem because third-party scripts often access window and run many different DOM operations.
And you can communicate between the main thread and the worker thread, but sending data between the two must be asynchronous, meaning that even if you didn't have an abstraction layer to communicate, you always need some sort of promise or callback to send that data.
Remember, we cannot change third-party script code.
We can't call up Google and tell them they need to rewrite all their scripts so they have async Dom operations.
That's just not going to happen.
So to recap web workers would be ideal solution to run within a background thread, but we can't change third-party script code.
And the communication between the threads must be async.
This is where Partytown comes in.
It allows us to run third-party script codes from within a Web worker.
This is also so the main thread's dedicated to your code, and third-party script's run from the Web worker and doesn't allow them to affect the performance of their main UI thread.
And it does this lazily after your code has finished loading.
Here's a generalized diagram of how a Web worker is able to help reduce the bottleneck on the main thread.
Now, the main thread is dedicated to your code while the worker can handle third-party scripts in the background.
And it's difficult to have a general rule to measure the performance improvement from Partytown.
The short answer is 'well it depends".
It depends on the number of third-party scripts your site uses and what those third-party scripts are doing.
There really isn't a one size fits all answer.
But in this test example, this page itself does nothing.
It easily scores 100, but if we add five third-party scripts, then this script drops to 77.
When we run those same five scripts with Partytown on this page, that does nothing, it's back to 100.
So again, this is a site that does absolutely nothing.
There's a drastic improvement that can happen with Partytown.
So, how does this all work?
The script element has a type attribute that in many cases is just not included.
However, if you add the type attribute, the browser will only execute the script if it's a type that it recognizes.
But if we change a type to something, the browser does not recognize such as "text/partytown", then the browser completely skips over the script and does not execute it.
Next Partytown is able to use the simple querySelector to find all the scripts that were not executed on the main thread, but instead should be executed on the Web worker.
This first example here shows a common script being executed on the main thread.
The second example shows how adding the attribute text/partytown will instead move it to a Web worker.
This also allows developers to specifically pick and choose which scripts should and should not run within the Web worker.
There's also no loader pre-processor or build step required to make this system work.
Since it's only changing an HTML attribute, it's not tied to a specific technology like Web pack, React, PHP or whatever.
And because it's not built for one tool, it makes it easier to integrate into any webpage.
Remember that a Web worker does not have access to window or document.
If we were to run this code, like console.logging document.title from within a Web worker, it would throw an error that it basically has no idea what document is.
But our third-party scripts are packed full of DOM operations like this and calls like this need to be able to just work because we can't change how third-party scripts are written.
So somehow the Web worker needs to be able to access the main thread's window and document.
And this is where parttown's proxies come into play.
So instead of trying to reimplement the DOM, Partytime will proxy any DOM operations so that it can run the same command on the main thread, then return the value.
And going back to our restrictions is that the communication between the threads is async.
And when the code is executing, it needs to work exactly the same as if it was on the main thread.
We cannot modify this code.
When their script calls document.title, it's a blocking call and the getter is expecting a value.
It's not expecting the getter return a promises, it's expecting the string value of document.title.
This is actually the biggest challenge, which Partytown has to solve.
As we talked about earlier, no matter what we do, sending messages between the main thread and the worker thread requires an asynchronous task.
Basically we have to use the post message API and then listening for those messages from the opposite thread, which is entirely asynchronous.
This is the other important piece of the puzzle, which allows a Web worker to communicate with the main thread synchronously.
In the diagram, the worker thread calls a getter expecting a value, but our proxy will fire off a synchronous XHR request which is intercepted by a service worker so that it can talk to the main thread.
And in the end, the synchronous XHR request returns a getter ... the getter's value.
And according to the third-party script code that ran within the Web worker, it was one synchronous task.
It has no idea that it even ran onto a Web worker.
It is just running from the main thread, according to its own code.
And because Partytown proxies every single call to main, it can whitelist what the scripts can and cannot do.
Or return empty values it shouldn't be reading.
For example, reading navigator.userAgent, or reading document.cookies could return an empty string, or it could also even prevent calls like the dreaded document.write.
And Partytown also comes with a debug build that helps developers better inspect what third-party scripts are doing.
Depending on your code you can decide which type of your calls should be logged.
You can see exactly what values are being passed to and from the third party script.
And what commands are even using.
Again, Partytown isn't tied to one framework and it can be used from any one of them or no framework at all.
But the Partytown site itself also documents the easiest way to integrate it into existing projects.
This includes nextJS and Gatsby providing their own way to run their script components using the worker strategy solution, which you guessed it, is just Partytown.
But there's also integrations for any React app, Angular app, Astro, and many more.
If you don't see your favorite framework here, our doc sites would love a pull request from you adding it.
So we had only a short amount of time to go over all this, but there's so much more abut partytime that we can talk about, including how we can use Atomics and shared array buffers rather than service workers.
And also nothing is not without tradeoff.
Partytown is no different.
So I encourage you to take a look at our docs and figure out what's-if this is a good fit for your site or not.
So please try it out and see if it's a good fit for your website.
It's open source and MIT licensed, but we'd also love to get your help in testing the many third-party script scenarios that exists out in the wild.
And Partytown itself in built and maintained by our team builder.io.
Really this entire project came up as we've been building out Qwik, which is a framework around the idea of using resumability rather than hydration.
Now quick deserves many of its own presentations, but the short story is that no matter how fast we make Qwik, websites still have an unsolved problem with third party scripts.
And that's why builder.io has been investing in both open source projects like these.
We wanna make sure that the sites we're building can continue to scale without sacrificing performance.
No matter if it's first-party code or third-party code.
That's all I have for today.
And thank you for attending.
Improve your Lighthouse scores with Partytown
Adam Bradley Director of Technology Builder.io@adamdbradley
So what’s the problem?
- More JS. More problems.
- Heavy and bloated scripts
- Increases time to interactivity
- Increases network payload
- Reduces rendering performance
Why should sites care about performance?
- Faster Site === Higher Conversion Rates
- Case Studies:
- Every 100ms faster ➡ 1% more conversions (Mobify)
- 50% faster ➡ 12% more sales (AutoAnything)
- 20% faster ➡ 10% more conversions (Furniture Village)
- 40% faster ➡ 15% more signups (Pinterest)
- 850ms faster ➡ 7% more conversions (COOK)
- 1 second slowness ➡ 10% less users (BBC)
Improve page performance
- Plenty of advice and recommendations
- Never ending list of optimizations to learn
... of how you improve YOUR code
Biggest performance culprit?
Third Party Scripts
What are Third-Party Scripts?
- Ads, Analytics, Tag Managers, Social, Videos, etc.
- Embedding another service’s code into your site
- No control
- Organizational decision to include
logos of many 3rd parties like Google Ads, facebook pixel, Hubspot
First-party vs third-party requests
Localhost !== Real World
- Issues may not surface during development
- “Works [fast] on my machine!”
- Local development
- MacBook Pro
- Mid-market phone
- Used on a bus
Negative effects from third-party scripts
- Competes with your code
- Same level of execution priority
- Shares window and document on the main thread
- Contributes to layout thrashing
The state of third-party scripts
- ❌ You can’t opt-out
- ❌ You can’t improve their performance
- ❌ You can’t prioritize your code
Possible solution? Web workers
Illustration of processes running in series on a CPU, and in parallel, one on the main thread and a second on a worker thread
Challenge with web workers
- No access to
- All DOM operations will fail
- Asynchronous communication
- Scripts don’t “just work”
- Workers ideal for running third-party scripts
- Can’t change third-party code
- Communication between threads must be async
- Run third-party scripts from a web worker
- Main thread dedicated to your code
- Minimizes third-party scripts from blocking main thread
- Lazily loaded after your code has finished loading
Single threads vs. multi-threaded
Two illustrations of your code and third party code running. On the left is a single inverted bottle. Blue and red coloured balls representing your code and 3rd party scripts both flow though the same bottle neck. On the right, your code is in a bottle labelled main thread 3rd party scripts in a bottle labelled worker thread.
Empty page demo
- Empty HTML page
- No first-party scripts
- Five third-party scripts
- Without Partown
To the left of the list is a lighthouse performance score of 77
- Same empty HTML page
- Same third-party scripts
- With Partytown enabled
To the left of this is a lighthouse performance score of 100
- Executes JS on the main thread
- Known type attribute values
- Excluding “type” will also execute the JS
<script type="text/partytown" src="https://third-party-script.com/"><script>
- Partytown does not automatically change scripts
- Developers pick and choose scripts to run with Partytown
- Not driven by a specific framework or build-tool
- Just change the script “type” attribute
Worker missing globals
Uncaught ReferenceError: document is not defined at blob: null/900c9c11-9...0-9c4c6e89a8e4:1:14
Proxying the DOM
Two columns separated by a dotted line. The left is titled 'Worker thread' an arrow points down from document.title to Worker proxy. Them another arrow points down from worker proxy to a second instance of document.title. The second column is titled Main thread. In it appears document.title and bidirectional arrows point from worker proxy in the left column to document.title in the right column.
Same illustration as above with the bidirectional arrows highlighted.
console.log( document. title );
Communicating between threads
diagram that Adam describes.
- Allow or do not allow specific API calls
- Whitelisting commands
- Return empty string for navigator.userAgent or document.cookie
- Prevent document.write()
Debugging and logging
- Partytown also comes with a Debug build
- Log all activity
- See what DOM operations are called
- See the values its sending and receiving
Screen shot of integrations page for Partytown
So much more to talk about...
- Shared Array Buffers
- Forwarding main thread events
- CORS and proxying requests
- Intense UI updates
Try it out!
- Open-source (MIT)
- Need your help testing!
- Get help on Discord
Created and maintained by:
<script type="text/partytown"> console.log ("Thank you!"); </script>