Enhanced Code Block Rendering System in Astro

Summary

Implemented a comprehensive code block rendering system in Astro that enhances the default markdown code blocks with copy-to-clipboard functionality, language indicators, and support for custom languages through a component-based architecture.

Why Care

This system improves the readability and usability of code blocks throughout the site by providing a consistent styling and interactive features like copy buttons. The component-based approach allows for specialized rendering of custom languages (e.g., litegal, dataview) while maintaining the performance benefits of Shiki syntax highlighting.

Implementation

Changes Made

  • Created a hierarchical component system for code blocks:
    • /site/src/components/codeblocks/BaseCodeblock.astro - Core component with shared functionality
    • /site/src/components/codeblocks/LitegalCodeblockDisplay.astro - Specialized component for litegal language
    • /site/src/components/codeblocks/DataviewCodeblockDisplay.astro - Specialized component for dataview language
  • Implemented a remark plugin for AST transformation:
    • /site/src/utils/markdown/remark-codeblocks.ts - Transforms code blocks to appropriate components
  • Updated Astro configuration:
    • /site/astro.config.mjs - Added custom language definitions and integrated the remark plugin
  • Created an index file for easy component exports:
    • /site/src/components/codeblocks/index.ts - Exports all code block components

Technical Details

Base Code Block Component

The base component (/site/src/components/codeblocks/BaseCodeblock.astro) provides the core functionality:
astro
---
/**
 * BaseCodeblock.astro
 * 
 * Base component for rendering code blocks with a copy button.
 * This component is used by the remark-codeblocks plugin to transform
 * standard code blocks in markdown.
 */

interface Props {
  code: string;
  lang: string;
}

const { code, lang = 'text' } = Astro.props;
---

<div class="codeblock-container">
  <div class="codeblock-header">
    <span class="codeblock-language">{lang}</span>
    <button class="copy-button" aria-label="Copy code to clipboard">
      <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
        <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
        <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
      </svg>
    </button>
  </div>
  
  <pre class="codeblock" data-language={lang}><code set:html={code} /></pre>
  
  <slot />
</div>

<script>
  // Copy button functionality
  // ...implementation details
</script>

<style>
  // Styling for code blocks
  // ...implementation details
</style>

Language-Specific Components

Created specialized components that extend the base component:
astro
// LitegalCodeblockDisplay.astro
---
import BaseCodeblock from './BaseCodeblock.astro';

interface Props {
  code: string;
  lang?: string;
}

const { code, lang = 'litegal' } = Astro.props;
---

<BaseCodeblock code={code} lang={lang}>
  <style>
    /* Add litegal-specific styles here */
    .codeblock--litegal {
      background-color: #f4f4f4;
      border-left: 4px solid #4a9eff;
    }
  </style>
</BaseCodeblock>

Remark Plugin

Implemented a remark plugin (/site/src/utils/markdown/remark-codeblocks.ts) that transforms code blocks in the Markdown AST:
typescript
/**
 * remarkCodeblocks
 * 
 * A remark plugin that transforms code blocks in markdown to use custom Astro components
 * based on the language specified.
 */
const remarkCodeblocks: Plugin<[], Root> = function() {
  return function transformer(tree: Root) {
    visit(tree, 'code', (node: Code, index: number, parent: Parent | null) => {
      if (!parent) return;
      
      const lang = node.lang || 'text';
      
      // Determine which component to use based on language
      let componentName = 'BaseCodeblock';
      if (lang === 'litegal') {
        componentName = 'LitegalCodeblockDisplay';
      } else if (lang === 'dataview') {
        componentName = 'DataviewCodeblockDisplay';
      }
      
      // Create an MDX component node
      const mdxNode: MdxJsxFlowElement = {
        type: 'mdxJsxFlowElement',
        name: componentName,
        attributes: [
          {
            type: 'mdxJsxAttribute',
            name: 'code',
            value: node.value
          },
          {
            type: 'mdxJsxAttribute',
            name: 'lang',
            value: lang
          }
        ],
        children: [],
        data: { _mdxExplicitJsx: true }
      };
      
      // Replace the original code node with our custom component
      parent.children[index] = mdxNode as any;
    });
    
    return tree;
  };
};

Astro Configuration

Updated the Astro configuration (/site/astro.config.mjs) to register custom languages and integrate the remark plugin:
javascript
export default defineConfig({
  // ... other config
  markdown: {
    remarkPlugins: [
      // ... other plugins
      remarkCodeblocks,
    ],
    syntaxHighlight: 'shiki',
    shikiConfig: {
      theme: 'github-dark',
      langs: [
        {
          id: 'litegal',
          scopeName: 'source.litegal',
          grammar: {
            patterns: [
              // Litegal syntax patterns
            ]
          }
        },
        {
          id: 'dataview',
          scopeName: 'source.dataview',
          grammar: {
            patterns: [
              // Dataview syntax patterns
            ]
          }
        }
      ]
    }
  }
});

Integration Points

  • Markdown Processing Pipeline: The code block system integrates with the Astro markdown processing pipeline through the remark-codeblocks plugin.
  • Syntax Highlighting: The system leverages Astro's built-in Shiki syntax highlighting while extending it with custom language support.
  • Component System: The hierarchical component architecture allows for easy extension with new language-specific components.

Design Decisions

  1. Component-Based Architecture: Used a component-based approach to maximize reusability and maintainability.
  2. AST Transformation: Implemented a remark plugin to transform code blocks at the AST level, ensuring proper integration with Astro's markdown processing.
  3. Custom Language Support: Added support for specialized languages through custom components and Shiki grammar definitions.
  4. Copy Button UX: Implemented a copy button with visual feedback to improve user experience.

Future Enhancements

  1. Line Highlighting: Add support for highlighting specific lines in code blocks.
  2. Code Folding: Implement collapsible sections for long code blocks.
  3. Theme Switching: Support multiple syntax highlighting themes.
  4. Interactive Code Blocks: Add support for editable and executable code blocks for certain languages.

Usage Examples

Basic Code Block:
markdown
```javascript
console.log('Hello, world!');
Custom Language Code Block:
markdown
```litegal
function example() {
  return true;
}
The code block rendering system provides a consistent, feature-rich experience for all code blocks throughout the site while maintaining the flexibility to handle specialized languages with custom rendering logic.