Orientation
Content Processing
Search Infrastructure
Rendering and UI
Developer Tooling
How It Works
The following files were used as context for generating this wiki page:
Twoslash Highlight is a sophisticated documentation enrichment subsystem that bridges the gap between static code samples and interactive TypeScript analysis. By integrating the twoslash engine directly into the documentation rendering pipeline, it enables features like hover-based type information, automatic error highlighting, and code completion previews. It is designed to work in conjunction with the Fumadocs rehype-code pipeline, transforming standard markdown code blocks into dynamic, information-rich UI elements.
The architecture centers on a Shiki-based transformer factory. When a documentation page is processed, Twoslash interprets special comments within code blocks to perform TypeScript language service operations. The resultant data—containing type definitions, diagnostics, and metadata—is then mapped into HTML structures (via Shiki's renderer) that React components (like the custom Popup) use to present interactive overlays.
A key design decision in this subsystem is the "lazy-loaded" instance pattern. By delaying the initialization of the heavy twoslash engine until requested, the system ensures that build times remain performant and serverless environments remain lightweight. This implementation detail prevents unnecessary instantiation while allowing the system to scale to large documentation sets containing thousands of code snippets.
The transformer is orchestrated by the transformerTwoslash function, which serves as the entry point for Fumadocs code block processing. This function leverages createTransformerFactory to bridge the gap between static Shiki highlighting and dynamic Twoslash analysis.
Sources: packages/twoslash/src/index.ts:31-131
The mechanism for lazy loading is crucial for resource management. The lazyInstance function uses a cached singleton cachedInstance. If the cache is empty, it invokes createTwoslasher, configuring it with a Bundler module resolution strategy. This ensures that the TypeScript compiler options are correctly set for modern documentation environments.
Sources: packages/twoslash/src/index.ts:25-52
Note
The compilerOptions override sets moduleResolution to 100 (Bundler). This is a rigid invariant to ensure that twoslash accurately resolves imports that are standard in modern frontend documentation projects, regardless of the user's specific tsconfig.json settings.
The transformation process relies on rendererRich from @shikijs/twoslash. It defines how Twoslash metadata is converted into HAST (Hypertext Abstract Syntax Tree) nodes that Fumadocs renders.
The core mapping logic intercepts standard HAST tokens and maps them to HAST properties configured via the renderer's hast option.
Sources: packages/twoslash/src/index.ts:54-108
For line-specific queries (such as diagnostics or errors), the code performs an explicit HAST node modification:
const fn = renderer.lineQuery!;
renderer.lineQuery = function (this: ShikiTransformerContext, ...args) {
const result = fn.call(this, ...args);
// Re-wrap to ensure proper styling as a span
const child = result[0].children[0];
result[0].children[0] = {
type: 'element',
tagName: 'span',
children: [child],
};
return result;
};Sources: packages/twoslash/src/index.ts:110-123
The interaction layer is defined in packages/twoslash/src/ui/popup.tsx. It provides a context-aware Popup component that manages hover-state timing with setTimeout to prevent UI jitter.
Sources: packages/twoslash/src/ui/popup.tsx:1-61
The system uses pointer events (onPointerEnter, onPointerLeave) rather than simple onMouseEnter to better handle different input devices, specifically ignoring touch events in the implementation logic to prevent unintentional interactions on mobile devices.
Sources: packages/twoslash/src/ui/popup.tsx:38-46
Performance is maintained via a file-system-based cache for type checking results. This is critical because twoslash runs the TypeScript language server on code snippets, which is computationally expensive.
The createFileSystemTypesCache provides an init, read, and write interface:
SHA256 to create a 12-character identifier based on the source code string..next/cache/twoslash by default, allowing subsequent builds or server restarts to bypass expensive type re-analysis.Sources: packages/twoslash/src/cache-fs.ts:1-50
Warning
While read and write are synchronous, ensure that the cache directory has appropriate write permissions. Failure to write will not crash the compilation but will significantly degrade documentation build performance.
The subsystem uses CSS custom properties and scoped classes to integrate into Fumadocs' theme system. The styles are defined in twoslash.css.
Sources: packages/twoslash/styles/twoslash.css:199-225
The use of currentColor for hover transitions ensures that the interactive UI indicators inherit the document's theme color automatically, preventing manual synchronization overhead in the theme configuration.
Sources: packages/twoslash/styles/twoslash.css:88-101
To integrate Twoslash in a documentation project, developers rely on the transformer export.
import { transformerTwoslash } from 'fumadocs-twoslash';
// Inside your shiki configuration
const transformer = transformerTwoslash({
twoslashOptions: {
// Add custom compiler options here
}
});Sources: packages/twoslash/src/index.ts:31-45
The renderer is inherently decoupled, meaning you can extend the hast configuration if you need to add custom UI triggers or tooltips beyond the standard Popup component, as shown in the renderer setup in the index source file.
Sources: packages/twoslash/src/index.ts:106-107