JavaScript usage

Contents

Overview

JavaScript affects more than download size. It impacts:

  • Data usage
  • CPU performance
  • Memory consumption
  • Battery life

For LRO users, JavaScript is often the largest performance bottleneck.

Principle: Keep JavaScript as small and minimal as possible.

Why does this matter for LRO?

JavaScript has four core costs:

  • Download – network transfer
  • Parse – converting code to structure
  • Compile – preparing code for execution
  • Execute – running logic and updating UI

On low-end devices, these steps can be 3–5× slower, significantly delaying interactivity.

Common Problems

Network Issues

  • Large bundles slow down loading
  • Too many files increase requests
  • Third-party scripts add delays

Parse & Compile Issues

Importing entire libraries increases processing time.

Avoid:

// Imports the entire lodash library
// This increases bundle size and adds extra parsing/compilation cost
import _ from "lodash";

Prefer:

// Imports only the required function instead of the whole library
// Reduces bundle size and improves load + execution time
import { debounce } from "lodash-es";

Execution Issues

Frequent DOM updates and heavy loops block the main thread.

Avoid:

// Loop runs many times and updates the DOM on each iteration
// Each update triggers layout recalculation (reflow), which is expensive
for (let i = 0; i < 10000; i++) {
  document.body.innerHTML += "<div></div>"; 
}

Prefer:

// Create a document fragment (in-memory container)
// This avoids direct DOM manipulation inside the loop
const fragment = document.createDocumentFragment();
for (let i = 0; i < 10000; i++) {
  // Create elements and append to fragment instead of the DOM
  fragment.appendChild(document.createElement("div"));
}
// Append all elements to the DOM in a single operation
// This minimizes reflows and improves performance
document.body.appendChild(fragment);

Hidden Costs

  • Memory leaks
  • Battery drain
  • Poor input responsiveness

Core Optimization Techniques

Write Efficient JavaScript

  • Prefer native browser APIs
  • Minimise re-renders
  • Cache DOM queries
  • Use event delegation
// Attach a single event listener to a parent container
document.querySelector("#menu").addEventListener("click", (e) => {
  
  // Check if the clicked element matches the target selector
  if (e.target.matches("button")) {
    
    // Handle click for the specific button
    handleClick(e.target);
  }
});

Use Better Code Architecture

  • Break into small modules
  • Lazy-load non-critical features
  • Separate vendor and app logic
// Load the chart module only when it is actually needed
if (userWantsChart) {
  
  // Dynamically import the chart code (lazy loading)
  // This reduces initial bundle size and improves load performance
  import("./chart.js").then(module => {
    
    // Execute the render function after the module is loaded
    module.render();
  });
}

Set a JavaScript Budget

Metric Target Max
Initial JS <100KB 200KB
Total JS <300KB 500KB
Third-party JS <50KB 100KB
TTI (3G) <3.8s 7.3s
TBT <200ms 600ms

Reduce Bundle Size

  • Remove unused dependencies
  • Analyze bundles
  • Avoid duplicates

Minify Code

Removes unnecessary characters without changing behaviour.

Before minification:

// Readable function with descriptive names and formatting
function calculateTotal(price, tax) {
  return price + tax;
}

After minification:

// Minified version with shortened variable names and no extra spacing
// Functionality remains exactly the same
function a(b,c){return b+c}

Tools for minification:

  • Terser
  • Webpack
  • Rollup
  • Vite

Use Code Splitting

Load only what is needed.

Example: Lazy Loading a Component (React)

// Lazily load the Visualisation component only when it is rendered
// This prevents adding heavy code to the initial bundle
const Visualisation = React.lazy(() => import('./Visualisation'));

Optimise Script Loading

  • Use defer and async
  • Avoid blocking scripts
  • Compress files
// Adding defer attribute to defer particular js file from loading
<script src="main.js" defer></script>

Lazy Load Non-Critical Features

Non-critical features should only be loaded after the user interacts with the page. This reduces unnecessary data usage and CPU processing on initial load.

// Wait for the first user interaction (click) before loading optional functionality
document.addEventListener("click", async () => {
  
  // Dynamically import the module only when needed
  const module = await import("./optional-feature.js");
  
  // Initialize the feature after it is loaded
  module.init();
});

Break Long Tasks

A single task that takes longer than 50 ms to run is classified as a long task. If the user attempts to interact with the page or an important UI update is requested while a long task is running, their experience will be affected. An expected response or visual update will be delayed, resulting in the UI appearing sluggish or unresponsive.

// Function to process a large array of items without blocking the UI
function processItems(items) {
  let i = 0;
  // Chunked processing function
  function chunk() {
    // Process up to 100 items per frame
    for (let j = 0; j < 100 && i < items.length; j++, i++) {
      handle(items[i]); // Process individual item
    }
    // If there are more items, schedule the next chunk
    if (i < items.length) {
      requestAnimationFrame(chunk); // Runs in the next animation frame
    }
  }
  // Start processing the first chunk
  chunk();
}

Choose Lightweight Frameworks

Option Recommendation
Vanilla JS Best for simple features
Svelte Excellent
Preact Lightweight alternative
Vue / React Use with SSR
Angular Use only if justified

Manage Third-Party Scripts

  • Audit regularly
  • Load with async/defer
  • Self-host when possible

Key Takeaways

JavaScript cost = network + processing + execution

Performance improves by:

  • Code splitting
  • Lazy loading
  • Removing unused code
  • Avoiding heavy frameworks

Do / Don't

Do

  • Use small modules
  • Split code by feature
  • Measure performance
  • Remove unused JavaScript

Don't

  • Ship large frameworks unnecessarily
  • Include unused libraries
  • Overuse third-party scripts

Checklist

Metrics & tools

To ensure LRO compliance, continuously monitor:

  • Bundle size (compressed JS)
  • JS execution time
  • Time to Interactive (TTI)
  • Total Blocking Time (TBT)

These metrics reflect real user experience on constrained devices.

Further reading



How can we improve this article?

Your feedback will help improve the framework and its documentation and will only be visible to the development team.