Notes: How the browser renders a page and its influence on the evolution of JavaScript use in the browser.
In this write-up, we shall try to understand some behaviors in the browser rendering process. And in the process, understand how that has influenced JavaScript tools and techniques.
What is a browser renderer process?
A web browser renderer process is responsible for rendering output onto the web page.
The renderer process is part of the browser’s architecture.
Each tab on a browser has one renderer process.
The main thread in the renderer process executes tasks that lead to a rendered output on the page.
The list of tasks to be executed in the thread are:
- Parsing: Html parsing, CSS parsing
- Evaluate Script
- Style Calculation
- Layout
- Paint
- Composting
“The renderer process handles everything inside of a tab including your JavaScript code”
Evaluating the script involves compiling the script and executing it.
How does the browser initiate the process of rendering a page?
Generating a web page starts with a network request for the HTML file. If the response is an HTML file, then the next step would be to pass the data to the renderer process.
Html parsing
The first task in the renderer process is Html parsing. This task starts after the first data arrives. You can see this in the browser timeline after the ‘Receive Data’ event for Html is triggered.
Html parsing involves converting valid Html tags into DOM nodes.
In older browsers, when the parser comes across an element that requests critical resources like stylesheet and JavaScript, the parser pauses and waits for the resources to download before proceeding with parsing. Example HTML elements are script, link, etc.
Modern browsers, however, have preload scanner, which allows the browser to download critical resources without blocking the parser. An example of optimization that enables faster page load.
Critical rendering path
Good user experience on the browser often means delivering a web page fast to the user.
Some of the tools used to measure how fast a web page is available to the user are:
- First paint
- First contentful paint.
Optimizing the critical rendering path allows the browser to paint the page as quickly as possible by delivering the first paint as soon as possible.
First paint is the first time any pixel becomes visible to the user.
Resources that are critical to delivering fast page load are Html, CSS, and JavaScript.
Why is JavaScript vital when optimizing page loads?
The JavaScript engine is responsible for evaluating JavaScript.
Note: Because JavaScript can make updates to the DOM, all other tasks in the render process pause until JavaScript completes execution. The renderer process gives control to the JavaScript engine at this point.
This behavior of JavaScript is called parser blocking.
Minimize JavaScript parser blocking by minding the location of script tags
Move all script tags to the end of the Html body.
Minimize overhead from network requests to get multiple JavaScript files
While moving script tags to the bottom of the Html body minimizes blocking parsing as much as possible, it does not fix the overhead from network requests to fetch JavaScript code. And as web apps became more complex, web applications contained multiple JavaScript files.
The first tools that helped fix the issue were the early concatenating tools.
Modern JavaScript tools that perform this are called bundlers.
Making Html script tags nonblocking
Grouping script elements and concatenating them does not mean the DOM will be parsed completely before JavaScript is evaluated by the engine.
How to make Html script elements nonblocking
Some of the ways to make Html script elements nonblocking include:
- Adding defer or async attribute to the script element
- Using script loaders. E.g Requirejs, curljs.
Most script loaders use either of two techniques to achieve non-blocking:
- Dynamic script elements
- XMLHttpRequest Script Injection
It is important to note that script loaders were an early workaround. Modern web browsers have the defer and async attributes that solve this.
Async
The async attribute on the script element allows for JavaScript to be downloaded and evaluated parallel to the parsing. The assumption is that the executed JavaScript will not alter the DOM.
Defer
The defer attribute on the script element allows for JavaScript to be downloaded parallel to the parsing, but will only execute just before the DOMContentLoaded event is triggered. By the time the JavaScript code gets executed, all DOM parsing would have been completed.
DOMContentLoaded event
Script loaders are not module loaders but serve a similar purpose
There was no well-defined JavaScript module system when script loaders first came out. Websites were still getting more complex. And that meant downloading multiple external JavaScript files. Script loaders didn’t have to handle the dependency graph of all the JavaScript files.
As the use of JavaScript in the browser evolved, there was a need for a standard module system. In this system, each file encapsulates a module; with dependencies defined, private and public content. Module loaders download JavaScript files written in the specified module format. An example is RequireJS which loads AMD modules.
Module loaders download multiple JavaScript files defined as modules in a non-blocking way.
Module loading does not work with the CommonJS module system. The CommonJS module system is not compatible with browsers.
Module bundlers
With the creation of the CommonJS module system, we could write highly reusable JavaScript. And with the NPM registry, you can have access to existing solutions.
But the CommonJS module system is not compatible with the web browser.
Module bundlers help concatenate JavaScript codes written in CommonJS into a single file called a bundle. The bundle can run in web browsers.
Even though the organization of JavaScript code changed, the need to minimize blocking remains.
Single bundles fix the issue of network request overhead.
What happens when the bundled code is too large? Remove dead JavaScript code with tree shaking.
Minimize the time it takes to request a large bundled code by gzipping.
How do you solve for the increased execution time during rendering? Use code-splitting to make JavaScript code available only when they are needed.
Subscribe to my newsletter
Read articles from Richard Terungwa Kombol directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by