Creating a Custom 'code-block' Builder Component with QWIK

When building web applications, especially documentation or blogs, syntax highlighting for code blocks is essential. In this post, we'll walk you through creating a custom 'code-block' builder component using QWIK and integrating syntax highlighting using rehype.

1. Creating the 'code-block' Component

First, let's create the 'code-block' component that will take in the content and language as props and return the highlighted code.

import { component$, useSignal, useTask$ } from "@builder.io/qwik";
import rehypeHighlight from "rehype-highlight";
import { unified } from "unified";
import rehypeParse from "rehype-parse";
import rehypeSanitize from "rehype-sanitize";
import rehypeStringify from "rehype-stringify";
type MyProps = { content: string; language: string };

async function highlight(content: string, language: string) {
  // Escape angle brackets using HTML character entities
  const escapedContent = content.replace(/</g, "<").replace(/>/g, ">");

  const file = await unified()
    .use(rehypeParse, { fragment: true })
    .use(rehypeSanitize)
    .use(rehypeHighlight)
    .use(rehypeStringify)
    .process(
      `<pre><code class="language-${language}">${escapedContent}</code></pre>`
    );

  return String(file);
}

export default component$((props: MyProps) => {
  const highlightedContent = useSignal("");

  useTask$(async () => {
    if (!props.content) return;
    const content = await highlight(props.content, props.language);
    highlightedContent.value = content;
  });
  return (
    <div>
      <pre dangerouslySetInnerHTML={highlightedContent.value} />
    </div>
  );
});

Processing with rehype:

Create a rehype processor using unified().

The processor is set up with a series of plugins using the .use() method.

Parsing: The rehypeParse plugin is used to convert the HTML string (your wrapped content) into a syntax tree. This tree represents the structure of the HTML and can be manipulated or analyzed.

Sanitizing: The rehypeSanitize plugin cleans the parsed HTML to ensure it doesn't contain any malicious or unwanted content. This is especially important if you're dealing with user-generated content or content from untrusted sources.

Highlighting: The rehypeHighlight plugin is where the magic happens. It looks for <code> elements in the syntax tree that have a language class (like language-js). For each such element, it uses Prism.js to apply syntax highlighting. This involves wrapping different parts of the code in <span> elements with classes indicating their syntactic role, e.g., <span class="token keyword">const</span>.

Stringifying: Finally, the rehypeStringify plugin converts the modified syntax tree back into an HTML string. This string now contains your code with added syntax highlighting.

Output: The function returns the highlighted HTML string.

In summary, parsing turns strings into structured data, sanitizing ensures the data is safe, and stringifying turns structured data back into strings.

In the component, it then uses the highlighted HTML string and sets it as the inner HTML of a <pre> element, allowing it to be rendered with the applied syntax highlighting.

2. Registering the 'code-block' Component


To make the 'code-block' component available in Builder.io's visual editor, you need to register it.

import type { RegisteredComponent } from "@builder.io/sdk-qwik";
import codeBlock from "./code-block/code-block";

export const CUSTOM_COMPONENTS: RegisteredComponent[] = [
  {
    component: codeBlock,
    name: "code-block",
    inputs: [
      { name: "content", type: "longText" },
      { name: "language", type: "string" },
    ],
  },
];

3. Adding the Highlight.js Stylesheet


For the syntax highlighting to display correctly, you need to include the stylesheet for Highlight.js. Add the following link to your root.tsx:

import { component$ } from "@builder.io/qwik";
import {
  QwikCityProvider,
  RouterOutlet,
  ServiceWorkerRegister,
} from "@builder.io/qwik-city";
import { RouterHead } from "./components/router-head/router-head";

import "./global.css";

export default component$(() => {
  return (
    <QwikCityProvider>
      <head>
        <meta charSet="utf-8" />
        <link rel="manifest" href="/manifest.json" />
        <link
          rel="stylesheet"
          href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/base16/summerfruit-dark.min.css"
        />
        <RouterHead />
        <ServiceWorkerRegister />
      </head>
      <body lang="en">
        <RouterOutlet />
      </body>
    </QwikCityProvider>
  );
});

Conclusion


With these steps, you've successfully created and registered a custom 'code-block' builder component in QWIK with syntax highlighting. This will enhance the readability of your code snippets and provide a better user experience for your readers.

Takeaways

Rehype is a powerful tool for processing HTML through a unified interface. While you've used it for parsing, sanitizing, highlighting, and stringifying HTML, there are many other use cases for rehype given its extensible nature. Here are some other potential use cases:

  • Transforming HTML:
  • Minifying HTML: Removing unnecessary whitespace, comments, and other redundant content to reduce the size of the HTML.
  • Optimizing media elements: Automatically adding loading="lazy" to <img> tags for lazy loading or converting images to modern formats like WebP.
  • Adding, removing, or modifying attributes: For example, adding rel="noopener" to external links for security.
  • Content Extraction:
  • Extracting metadata: Pulling out metadata like <meta> tags, <title>, or microdata.
  • Scraping content: Extracting specific content from web pages, such as article text, images, or links.
  • Content Enhancement:
  • Embedding content: Automatically converting URLs to embedded content, like turning YouTube links into embedded video players.
  • Adding tooltips: Automatically adding tooltips to specific terms or links in the content.
  • Generating a table of contents: Based on headings (<h1>, <h2>, etc.) in the document.
  • Accessibility Improvements:
  • Checking for accessibility issues: Identifying missing alt attributes, incorrect heading levels, or other accessibility concerns.
  • Automatically adding ARIA roles and attributes to improve accessibility.
  • Syntax Highlighting for Other Languages: While you've used it for JavaScript, rehype can be used to highlight syntax for a wide range of programming and markup languages.
  • Math Rendering: Converting LaTeX or MathML representations into rendered math using libraries like KaTeX or MathJax.
  • Link Manipulation:
  • URL rewriting: Modifying links based on certain criteria, like adding tracking parameters or converting relative URLs to absolute ones.
  • Link checking: Identifying broken links within the content.
  • Integrating with Other Systems:
  • Markdown to HTML conversion: Using remark (a sibling tool of rehype) to convert Markdown to HTML, then processing the HTML with rehype.
  • Combining with CSS or JS processors: For example, inlining critical CSS or JS into the HTML.
  • Custom Plugins: The real power of rehype lies in its plugin architecture. You can create custom plugins to perform any specific transformation or analysis on HTML content that suits your needs.

These are just a few examples, and the possibilities are vast given the flexibility and extensibility of rehype. The ecosystem around rehype and the unified collective provides a wide range of plugins, and if you can't find one that fits your needs, you can always create your own!