How to Measure JavaScript and CSS Code Coverage in Safari Web Inspector

Modern web applications rely on complex build systems, single-page application (SPA) bundles, component libraries, and global CSS stylesheets. As features evolve, legacy CSS rules, uninvoked polyfills, unused utility functions, and dormant vendor libraries accumulate within production bundles. This dead code inflates network transfer sizes, wastes parsing and compilation time on mobile CPU threads, and increases total memory consumption without providing any value to the user.

To help developers streamline web payloads, Safari Web Inspector provides specialized code coverage and profiling instrumentation. By measuring JavaScript and CSS code coverage directly in Safari on macOS, developers can audit runtime asset execution, identify precisely what percentage of each bundle is actually utilized during user sessions, and pinpoint dead code for tree-shaking and dynamic code-splitting.

Understanding Code Coverage Profiling in WebKit

Code coverage analysis tracks execution and evaluation at the byte and line level:

  • JavaScript Code Coverage: Tracks which functions, blocks, and statements were compiled and executed by WebKit’s JavaScriptCore (JSC) engine during page load and subsequent user interactions. Unexecuted functions (such as unopened modal handlers or inactive admin routes) are highlighted as unused.
  • CSS Code Coverage: Evaluates loaded stylesheets against the active Document Object Model (DOM). Selectors that never matched any rendered DOM element are flagged as unused rules.

Safari Web Inspector calculates two primary metrics for every resource:

  • Total Bytes: The raw uncompressed size of the script or stylesheet.
  • Unused Bytes (% Unused): The exact volume and percentage of code that was parsed into memory but never executed or applied during the recorded session.

Accessing Code Coverage in Safari Web Inspector

To inspect code coverage, launch Safari on macOS and open the Web Inspector on your target website:

  1. Ensure the Develop menu is enabled in Safari under Safari > Settings > Advanced by ticking Show features for web developers.
  2. Right-click on the webpage and select Inspect Element (or press Option + Command + I).
  3. In Web Inspector, switch to the Timelines tab.
  4. In the left sidebar, click the Edit button at the top and ensure JavaScript Allocations and Type Profiler / Code Coverage instruments are activated.
  5. Alternatively, navigate to the Debugger tab and expand the Type Profiler & Code Coverage options located in the navigation controls.

Recording and Analysing a Code Coverage Session

To gather accurate coverage data, capture both initial page load and representative user interactions:

  1. Open the Code Coverage tool in Web Inspector.
  2. Click the red Record button in the toolbar.
  3. Reload the webpage (press Command + R) to capture scripts and stylesheets evaluated during initial page hydration and DOM construction.
  4. Interact with the primary features of the page: click navigation menus, open dropdowns, scroll to lazy-loaded sections, and trigger common modal workflows.
  5. Click the Stop Recording button once the interaction cycle is complete.

Web Inspector populates a structured table listing all evaluated resources:

URL                                      Type    Total Bytes   Unused Bytes   Coverage
https://example.com/assets/app.js        JS      842 KB        589 KB         30.0%
https://example.com/assets/vendor.js     JS      1.2 MB        890 KB         25.8%
https://example.com/styles/main.css      CSS     180 KB        115 KB         36.1%
https://example.com/styles/theme.css     CSS     45 KB         12 KB          73.3%

Examining Source Files Line by Line

Selecting any script or stylesheet in the coverage list opens the source file directly in the Web Inspector editor with visual gutter annotations:

  • Green Highlight / Left Indicator: Represents executed JavaScript statements or matched CSS selectors. These lines contributed directly to rendering or interaction.
  • Red Highlight / Greyed Out: Highlights code that was loaded over the network but never executed. In JavaScript files, entire functions, callback blocks, or unused third-party helpers will show red bars. In CSS stylesheets, unreferenced class names, outdated media queries, and print rules will be marked as inactive.

Strategies to Eliminate Unused Code Identified in Safari

Once high-percentage unused bundles are identified, apply targeted optimization patterns:

1. Implementing Dynamic Code-Splitting in JavaScript

If large libraries (such as heavy charting engines, rich-text editors, or PDF generators) appear largely red on initial page load, replace static ES module imports with dynamic asynchronous imports:

// Inefficient static import
// import { Chart } from 'chart.js';

// Optimised dynamic import triggered only on user action
document.getElementById('viewAnalyticsBtn').addEventListener('click', async () => {
  const { Chart } = await import('chart.js');
  renderAnalyticsChart();
});

This defers script loading until strictly necessary, drastically reducing initial bundle size and lowering unused byte percentages.

2. Purging Unused CSS Selectors

If global stylesheets show over 60% unused bytes, your application is shipping monolithic style bundles across disparate templates. Use automated purging tools (such as PurgeCSS or CSS Modules) within your build pipeline (Webpack, Vite, or PostCSS) to remove unmatched utility classes, or split CSS into template-specific stylesheets.

3. Utilizing Critical Path CSS

Extract above-the-fold styling into an inline <style> block within the document head, and asynchronously defer non-critical styling using rel="preload":

<link rel="preload" href="/styles/non-critical.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/styles/non-critical.css"></noscript>

Auditing Single-Page Applications vs Static Pages

When auditing Single-Page Applications (React, Vue, Angular, or Svelte), avoid testing only the homepage. Perform multiple discrete recordings: one for the landing page, one for an authentication flow, and one for deep application dashboards. This prevents mistakenly purging route-specific code that is genuinely required on secondary application screens.

Get the best tech tips delivered straight to your inbox.

Join thousands of readers mastering Apple, Google, Microsoft, and Linux.