Diffsby The Pierre Computer Co.

Overview

Diffs is in early active development—APIs are subject to change.

Diffs is a library for rendering code and diffs on the web. This includes both high-level, easy-to-use components, as well as exposing many of the internals if you want to selectively use specific pieces. We‘ve built syntax highlighting on top of Shiki which provides a lot of great theme and language support.

We have an opinionated stance in our architecture: browsers are rather efficient at rendering raw HTML. We lean into this by having all the lower level APIs purely rendering strings (the raw HTML) that are then consumed by higher-order components and utilities. This gives us great performance and flexibility to support popular libraries like React as well as provide great tools if you want to stick to vanilla JavaScript and HTML. The higher-order components render all this out into Shadow DOM and CSS grid layout.

Generally speaking, you‘re probably going to want to use the higher level components since they provide an easy-to-use API that you can get started with rather quickly. We currently only have components for vanilla JavaScript and React, but will add more if there‘s demand.

For this overview, we‘ll talk about the vanilla JavaScript components for now but there are React equivalents for all of these.

Rendering diffs

It‘s in the name, it‘s probably why you‘re here. Our goal with visualizing diffs was to provide some flexible and approachable APIs for how you may want to render diffs. For this, we provide a component called FileDiff (available in both JavaScript and React versions).

There are two ways to render diffs with FileDiff:

  1. Provide two versions of a file or code snippet to compare
  2. Consume a patch file

You can see examples of these approaches below, in both JavaScript and React.

Installation

Package Exports

The package provides several entry points for different use cases:

React API

You can import the React components from @pierre/precision-diffs/react

We offer a variety of components to render diffs and files. Many of them share similar types of props, which you can find documented in Shared Props.

Components

The React API exposes four main components: MultiFileDiff (compare two file versions), PatchDiff (render from a patch string), FileDiff (render a pre-parsed FileDiffMetadata), and File (render a single code file without diff).

Shared Props

The three diff components (MultiFileDiff, PatchDiff, and FileDiff) share a common set of props for configuration, annotations, and styling. The File component has similar props but uses LineAnnotation instead of DiffLineAnnotation (no side property).

Vanilla JS API

You can import the vanilla JavaScript classes, components and methods from @pierre/precision-diffs

We offer two components, FileDiff for rendering diffs, and File for rendering plain files. Typically you'll want to interface with these as they'll handle all the complicated aspects of syntax highlighting and themeing for you.

Components

The Vanilla JS API exposes two core components: FileDiff (compare two file versions or render a pre-parsed FileDiffMetadata) and File (render a single code file without diff).

Props

Both FileDiff and File accept an options object in their constructor. The File component has similar options but excludes diff-specific settings and uses LineAnnotation instead of DiffLineAnnotation (no side property).

Custom Hunk Separators

If you want to render custom hunk separators that won't scroll with the content, there are a few tricks you will need to employ. See the following code snippet:

Renderers

Note: For most use cases, you should use the higher-level components like FileDiff and File (vanilla JS) or the React components (MultiFileDiff, FileDiff, PatchDiff, File). These renderers are low-level building blocks intended for advanced use cases.

These renderer classes handle the low-level work of parsing and rendering code with syntax highlighting. Useful when you need direct access to the rendered output as hast nodes or HTML strings for custom rendering pipelines.

DiffHunksRenderer

Takes a FileDiffMetadata data structure and renders out the raw hast elements for diff hunks. You can generate FileDiffMetadata via parseDiffFromFile or parsePatchFiles utility functions.

FileRenderer

Takes a FileContents object (just a filename and contents string) and renders syntax-highlighted code as hast elements. Useful for rendering single files without any diff context.

Utilities

You can import these utility functions from @pierre/precision-diffs

These utilities can be used with any framework or rendering approach.

diffAcceptRejectHunk

Programmatically accept or reject individual hunks in a diff. This is useful for building interactive code review interfaces, AI-assisted coding tools, or any workflow where users need to selectively apply changes.

When you accept a hunk, the new (additions) version is kept and the hunk is converted to context lines. When you reject a hunk, the old (deletions) version is restored. The function returns a new FileDiffMetadata object with all line numbers properly adjusted for subsequent hunks.

disposeHighlighter

Dispose the shared Shiki highlighter instance to free memory. Useful when cleaning up resources in single-page applications.

getSharedHighlighter

Get direct access to the shared Shiki highlighter instance used internally by all components. Useful for custom highlighting operations.

parseDiffFromFile

Compare two versions of a file and generate a FileDiffMetadata structure. Use this when you have the full contents of both file versions rather than a patch string.

parsePatchFiles

Parse unified diff / patch file content into structured data. Handles both single patches and multi-commit patch files (like those from GitHub PR .patch URLs).

preloadHighlighter

Preload specific themes and languages before rendering to ensure instant highlighting with no async loading delay.

registerCustomTheme

Register a custom Shiki theme for use with any component. The theme name you register must match the name field inside your theme JSON file.

Styling

Diff and code are rendered using shadow DOM APIs. This means that the styles applied to the diffs will be well isolated from your page's existing CSS. However, it also means if you want to customize the built-in styles, you'll have to utilize some custom CSS variables. These can be done either in your global CSS, as style props on parent components, or on the FileDiff component directly.

Advanced: Unsafe CSS

For advanced customization, you can inject arbitrary CSS into the shadow DOM using the unsafeCSS option. This CSS will be wrapped in an @layer unsafe block, giving it the highest priority in the cascade. Use this sparingly and with caution, as it bypasses the normal style isolation.

We also recommend that any CSS you apply uses simple, direct selectors targeting the existing data attributes. Avoid structural selectors like :first-child, :last-child, :nth-child(), sibling combinators (+ or ~), deeply nested descendant selectors, or bare tag selectors—these are susceptible to breaking in future versions or in edge cases that may be difficult to anticipate.

We cannot currently guarantee backwards compatibility for this feature across any future changes to the library, even in patch versions. Please reach out so that we can discuss a more permanent solution for the style change that you're looking for.

Worker Pool

You can import the worker utilities from @pierre/precision-diffs/worker

By default, syntax highlighting runs on the main thread using Shiki. If you are rendering large files or many diffs this can cause a bottleneck on your javascript thread resulting in jank or unresponsiveness. To work around this we've provided some APIs to run all syntax highlighting in worker threads. The main thread will still attempt to render plain text synchronously and then apply the syntax highlighting when we get a response from the worker threads.

Basic usage differs a bit depending on if you're using React or Vanilla JS apis, so continue reading for more details.

Setup

One unfortunate side effect of using Web Workers is that different bundlers and environments require slightly different approaches to create a Web Worker. Basically you will need to create a function that spawns a worker that's appropriate for your environment and bundler and then pass that function to our provided APIs.

Lets begin with that workerFactory function. We've provided some examples for common use cases below.

Side note, we've only tested the Vite and NextJS examples, the rest were generated by AI. If any of them are incorrect, please let us know

Vite

NextJS

Important: Workers only work in client components. Make sure your function has the 'use client' directive if using App Router.

Webpack 5

esbuild

Rollup / Static Files

If your bundler doesn't have special worker support, build and serve the worker file statically:

Vanilla JS (No Bundler)

For projects without a bundler, host the worker file on your server and reference it directly:

Usage

With your workerFactory function created, you can integrate it with our provided APIs. In React, you'll want to pass this workerFactory to a <WorkerPoolContextProvider> so all components can inherit the pool automatically. If you're using the Vanilla JS APIs we provide a getOrCreateWorkerPoolSingleton helper that ensures a single pool instance that you can then manually pass to all your File/FileDiff instances.

Note: When using the worker pool, theme settings are controlled by the pool manager, not individual component props. Any theme options passed to File or FileDiff components will be ignored. To change the theme, use the setTheme() method on the pool manager. All connected instances will automatically re-render with the new theme.

React

Wrap your component tree with WorkerPoolContextProvider from @pierre/precision-diffs/react. All FileDiff and File components nested within will automatically use the worker pool for syntax highlighting.

The WorkerPoolContextProvider will automatically spin up or shut down the worker pool based on its react lifecycle. If you have multiple context providers, they will all share the same pool, and termination won't occur until all contexts are unmounted.

Important: The worker pool only works in client components. Make sure your provider is in a component with the 'use client' directive if you're using NextJS App Router. Workers cannot be instantiated during server-side rendering.

To change themes dynamically, use the useWorkerPool() hook to access the pool manager and call setTheme().

Vanilla JS

Simply use getOrCreateWorkerPoolSingleton to spin up a singleton worker pool. Then pass that as the second argument to File and/or FileDiff. When you are done with the worker pool, you can use terminateWorkerPoolSingleton to free up resources.

To change themes dynamically, call workerPool.setTheme(theme) on the pool instance.

API Reference

These methods are exposed for advanced use cases. In most scenarios, you should use the WorkerPoolContextProvider for React or pass the pool instance via the workerPool option for Vanilla JS rather than calling these methods directly.

Architecture

The worker pool manages a configurable number of worker threads that each initialize their own Shiki highlighter instance. Tasks are distributed across available workers, with queuing when all workers are busy.

SSR

You can import the SSR utilities from @pierre/precision-diffs/ssr

The SSR API allows you to pre-render file diffs on the server with syntax highlighting, then hydrate them on the client for full interactivity.

Usage

Each preload function returns an object containing the original inputs plus a prerenderedHTML string. This object can be spread directly into the corresponding React component for automatic hydration.

Important: The inputs used for pre-rendering must exactly match what's rendered in the client component. This is why we recommend spreading the entire result object—it ensures the client receives the same inputs that were used to generate the prerendered HTML.

Server Component

Client Component

Preloaders

We provide several preload functions to handle different input formats. Choose the one that matches your data source.

preloadFile

Preloads a single file with syntax highlighting (no diff). Use this when you want to render a file without any diff context. Spread into the File component.

preloadFileDiff

Preloads a diff from a FileDiffMetadata object. Use this when you already have parsed diff metadata (e.g., from parseDiffFromFile or parsePatchFiles). Spread into the FileDiff component.

preloadMultiFileDiff

Preloads a diff directly from old and new file contents. This is the simplest option when you have the raw file contents and want to generate a diff. Spread into the MultiFileDiff component.

preloadPatchDiff

Preloads a diff from a unified patch string for a single file. Use this when you have a patch in unified diff format. Spread into the PatchDiff component.

preloadPatchFile

Preloads multiple diffs from a multi-file patch string. Returns an array of results, one for each file in the patch. Each result can be spread into a FileDiff component.