In the previous chapter, Defensive Language Fallback, we ensured our application wouldn't crash if the syntax highlighter encountered an error. We built a robust "spare tire."
Now, we need to make sure the application is fast.
Syntax highlighting engines are heavy. They include definitions for hundreds of languages (Python, Java, Rust, SQL, etc.).
If we load this entire engine before we show anything on the screen, the user will stare at a blank terminal for several seconds. This is called "blocking the render." It feels like the app is frozen.
To solve this, we use Async Highlighter Loading. Think of it like a busy restaurant:
In our code:
We use two modern React features to achieve this:
Promise: A guarantee that a value (the highlighter) will arrive in the future.use Hook: A function that pauses a component until a Promise is ready.Suspense: A wrapper that defines what to show while the component is paused.Let's visualize the flow when a user asks to render a file.
Let's break down the implementation into three small parts.
We have a utility function getCliHighlightPromise. This function starts loading the highlighting library immediately but returns a Promise.
// utils/cliHighlight.ts
let highlightPromise: Promise<Highlighter> | null = null;
export function getCliHighlightPromise() {
if (!highlightPromise) {
// Start loading the heavy library
highlightPromise = loadHeavyLibrary();
}
return highlightPromise;
}
Explanation: We ensure we only load the library once (Singleton pattern). We return the "ticket" (Promise), not the library itself immediately.
Inside our Highlighted component, we use the use hook. This is where the magic happens.
// Inside Highlighted component
function Highlighted({ code, language }) {
// 1. Get the promise
const promise = getCliHighlightPromise();
// 2. PAUSE execution here until promise resolves!
const hl = use(promise);
// 3. Use the highlighter (only runs after loading finishes)
return hl.highlight(code, { language });
}
Explanation: When React hits use(promise), if the promise is pending, it stops rendering this component immediately and tells the parent, "I'm not ready."
This is the parent component, HighlightedCodeFallback. It wraps the worker in a <Suspense> boundary.
// Inside HighlightedCodeFallback component
export function HighlightedCodeFallback({ code, language }) {
// Define the "Menu" (what to show while waiting)
const plainText = <Ansi>{code}</Ansi>;
return (
<Suspense fallback={plainText}>
{/* React tries to render this. If it pauses,
it shows the fallback above. */}
<Highlighted code={code} language={language} />
</Suspense>
);
}
Explanation:
<Highlighted />.<Highlighted /> says "I'm paused via use".<Suspense> catches that signal and renders fallback={plainText} instead.<Highlighted /> with colors.You don't need to do anything special to trigger this. Just using the component works:
// Your Application
<HighlightedCodeFallback
code="const x = 1;"
filePath="test.ts"
/>
What the user sees:
const x = 1; appears in white (Plain Text).The application remains responsive the entire time.
In this chapter, we learned:
use: A hook that lets us wait for async data inside a component.Suspense: A boundary that handles the "Loading..." state for us automatically.We now have a system that is Robust (Chapter 1) and Fast (Chapter 2). But how exactly do we draw these colors to a command-line interface instead of a web browser?
Next, we will look at Ink Terminal Rendering.
Generated by Code IQ