shikiphp

Transformers

Transformers let you reshape the highlighted output at every stage — from the raw source string, through the token grid, to the final HAST tree and HTML. shikiphp ports Shiki's transformer model hook-for-hook, so transformers behave the same as their JavaScript counterparts.

Pass them through the transformers option:

use Shikiphp\Shikiphp;
use Shikiphp\Transformer\Notation\NotationHighlight;
use Shikiphp\Transformer\Notation\NotationDiff;

echo Shikiphp::codeToHtml($code, [
    'lang'         => 'js',
    'theme'        => 'vitesse-dark',
    'transformers' => [
        new NotationHighlight(),
        new NotationDiff(),
    ],
]);

The pipeline

Each transformer is an object implementing Shikiphp\Transformer\Transformer. The pipeline runs the hooks in order over the build of a single block:

HookSignatureWhen
preprocess(string $code, array &$options, $context): ?stringbefore tokenization; may rewrite the source and mutate options
tokens(array $tokens, $context): ?arrayafter tokenization, on the 2D ThemedToken grid
root(Element $el, $context): ?Elementon the HAST root
pre(Element $el, $context): ?Elementon the <pre> element
code(Element $el, $context): ?Elementon the <code> element
line(Element $el, int $line, $context): ?Elementper line element (1-based)
span(Element $el, int $line, int $col, Element $lineEl, ThemedToken $token, $context): ?Elementper token span
postprocess(string $html, array $options, $context): ?stringon the final serialized HTML string

Every hook is optional. Returning a value replaces the input; returning null keeps it unchanged.

enforce() controls ordering across transformers: 'pre' runs first, 'post' runs last, and null (the default) runs in between in registration order.

Writing a transformer

Extend Shikiphp\Transformer\AbstractTransformer so you only override the hooks you need; the rest stay no-ops. The transformer context exposes addClassToHast() for adding classes to elements.

use Shikiphp\Hast\Element;
use Shikiphp\Transformer\AbstractTransformer;
use Shikiphp\Transformer\TransformerContext;

final class AddLanguageClass extends AbstractTransformer
{
    public function pre(Element $element, TransformerContext $context): ?Element
    {
        if ($context->lang !== null) {
            $context->addClassToHast($element, 'language-' . $context->lang);
        }

        return $element;
    }
}

Built-in transformers

Notation transformers

These read special // [!code …] comments in the source (the comment syntax of the language is detected automatically) and translate them into classes, removing the comment itself. They are direct ports of the @shikijs/transformers notation set.

NotationHighlight

Shikiphp\Transformer\Notation\NotationHighlight// [!code highlight] (or // [!code hl]) adds the highlighted class to its line.

console.log('kept plain')
console.log('this line is highlighted') 

NotationDiff

Shikiphp\Transformer\Notation\NotationDiff// [!code ++] marks a line as added (diff add), // [!code --] as removed (diff remove).

console.log('old line') 
console.log('new line') 

NotationFocus

Shikiphp\Transformer\Notation\NotationFocus// [!code focus] adds the focused class.

console.log('focus on me') 

NotationErrorLevel

Shikiphp\Transformer\Notation\NotationErrorLevel// [!code error], // [!code warning], and // [!code info] add the highlighted class plus error / warning / info.

throwSomething() // [!code error]
maybeRisky()     // [!code warning]

NotationWordHighlight

Shikiphp\Transformer\Notation\NotationWordHighlight// [!code word:foo] (optionally // [!code word:foo:2] to limit to the next N lines) wraps each occurrence of foo in a span carrying highlighted-word.

const config = loadConfig()

Meta transformers

These read the code-fence meta string supplied through the meta option (meta['__raw']), the way Shiki reads the text after a fenced code block's language.

MetaHighlight

Shikiphp\Transformer\MetaHighlight — highlights lines named in a meta range like {1,3-5}, adding the highlighted class.

echo Shikiphp::codeToHtml($code, [
    'lang'         => 'js',
    'theme'        => 'nord',
    'meta'         => ['__raw' => '{1,3-5}'],
    'transformers' => [new MetaHighlight()],
]);

MetaWordHighlight

Shikiphp\Transformer\MetaWordHighlight — highlights words named in the meta string with /word/ syntax, adding highlighted-word.

Whitespace and structure

RenderWhitespace

Shikiphp\Transformer\RenderWhitespace — wraps space and tab characters in spans (classes space and tab by default) so they can be rendered visibly. The constructor takes (classSpace, classTab, position) where position is 'all' (default), 'boundary', 'trailing', or 'leading'.

CompactLineOptions

Shikiphp\Transformer\CompactLineOptions — applies classes to specific lines from a list of {line, classes} entries.

new CompactLineOptions([
    ['line' => 1, 'classes' => 'highlighted'],
    ['line' => 3, 'classes' => ['highlighted', 'error']],
]);

RemoveNotationEscape

Shikiphp\Transformer\RemoveNotationEscape — unescapes the [\!code …] form used to write a literal notation comment without triggering a notation transformer.

RemoveLineBreak

Shikiphp\Transformer\RemoveLineBreak — removes the line-break text nodes between lines (Shiki's transformerRemoveLineBreak).

StyleToClass

Shikiphp\Transformer\StyleToClass — replaces every inline style attribute with a generated class and collects a stylesheet you fetch with getCSS(). Class names hash the style with Shiki's cyrb53, so output is byte-identical to @shikijs/transformers.

use Shikiphp\Transformer\StyleToClass;

$styleToClass = new StyleToClass();

$html = Shikiphp::codeToHtml($code, [
    'lang'         => 'php',
    'theme'        => 'github-dark',
    'transformers' => [$styleToClass],
]);

$css = $styleToClass->getCSS(); // collected stylesheet for the classes above

The constructor takes (classPrefix, classSuffix, classReplacer); the default prefix is __shiki_.

On this page