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
- Component-Based Architecture: Used a component-based approach to maximize reusability and maintainability.
- AST Transformation: Implemented a remark plugin to transform code blocks at the AST level, ensuring proper integration with Astro's markdown processing.
- Custom Language Support: Added support for specialized languages through custom components and Shiki grammar definitions.
- Copy Button UX: Implemented a copy button with visual feedback to improve user experience.
Future Enhancements
- Line Highlighting: Add support for highlighting specific lines in code blocks.
- Code Folding: Implement collapsible sections for long code blocks.
- Theme Switching: Support multiple syntax highlighting themes.
- 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.