Remark Plugin Implementation Plan for Astro Content
Sources
Implementation Plan for Remark and Rehype Plugins in Astro
1. Configuration Setup
1.1 Astro Config Integration
typescript
// astro.config.mjs
import { defineConfig } from 'astro/config';
import remarkCallouts from './src/utils/markdown/remark-callout-handler';
import remarkAsf from './src/utils/markdown/remark-asf';
import remarkBacklinks from './src/utils/markdown/remark-backlinks';
import remarkImages from './src/utils/markdown/remark-images';
export default defineConfig({
markdown: {
remarkPlugins: [
remarkCallouts,
[remarkAsf, { /* options */ }],
remarkBacklinks,
remarkImages
],
rehypePlugins: [
// Add any rehype plugins here
]
}
});
2. Plugin Implementation
2.1 Base Plugin Structure
typescript
// src/utils/markdown/remark-callout-handler.ts
import { visit } from 'unist-util-visit';
import type { Plugin } from 'unified';
import type { Root } from 'mdast';
import { detectMarkdownCallouts } from './callouts/detectMarkdownCallouts';
import { transformCalloutStructure } from './callouts/transformCalloutStructure';
const remarkCallouts: Plugin<[], Root> = () => {
return (tree) => {
visit(tree, 'blockquote', (node, index, parent) => {
// 1. Detect callout content
const calloutData = detectMarkdownCallouts(node);
if (!calloutData) return;
// 2. Transform node structure
transformCalloutStructure(node, calloutData);
});
return tree;
};
};
export default remarkCallouts;
2.2 Detection Phase
typescript
// src/utils/markdown/callouts/detectMarkdownCallouts.ts
export function detectMarkdownCallouts(node: Node) {
// Extract first line text
const firstLine = node.children[0]?.value || '';
const calloutMatch = firstLine.match(/^\[!(\w+)\]\s*(.*)$/);
if (!calloutMatch) return null;
// Capture all content
const content = node.children
.slice(1)
.map(child => child.value)
.join('\n');
return {
type: calloutMatch[1],
title: calloutMatch[2] || calloutMatch[1],
content
};
}
2.3 Transformation Phase
typescript
// src/utils/markdown/callouts/transformCalloutStructure.ts
export function transformCalloutStructure(node: Node, data: CalloutData) {
// Convert blockquote to callout
node.type = 'callout';
node.data = {
hName: 'article',
hProperties: {
className: ['callout', `callout-${data.type.toLowerCase()}`],
'data-type': data.type
}
};
// Structure content
node.children = [
{
type: 'element',
tagName: 'header',
properties: { className: ['callout-header'] },
children: [{ type: 'text', value: data.title }]
},
{
type: 'element',
tagName: 'div',
properties: { className: ['callout-content'] },
children: node.children.slice(1) // Preserve original content structure
}
];
}
3. Integration with Content Collections
3.1 Collection Configuration
typescript
// src/content/config.ts
import { defineCollection, z } from 'astro:content';
const vocabularyCollection = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
slug: z.string().optional(),
aliases: z.array(z.string()).default([])
})
});
export const collections = {
vocabulary: vocabularyCollection
};
3.2 Page Integration
astro
---
// src/pages/more-about/[vocabulary].astro
import { getCollection, type CollectionEntry } from 'astro:content';
import Layout from '@layouts/Layout.astro';
import OneArticleOnPage from '@components/articles/OneArticleOnPage.astro';
export async function getStaticPaths() {
const vocabularyEntries = await getCollection('vocabulary');
return vocabularyEntries.map(entry => ({
params: { vocabulary: entry.data.slug || entry.id },
props: { entry }
}));
}
const { entry } = Astro.props;
const { Content } = await entry.render();
---
<Layout title={entry.data.title || entry.id}>
<OneArticleOnPage
title={entry.data.title || entry.id}
content={Content}
/>
</Layout>
4. Component Styling
4.1 Base Callout Styles
css
/* src/styles/callouts.css */
.callout {
border-left: 4px solid var(--callout-color);
margin: 1.5rem 0;
padding: 1rem;
background: var(--callout-bg);
}
.callout-header {
font-weight: 600;
margin-bottom: 0.5rem;
color: var(--callout-header-color);
}
.callout-content {
color: var(--callout-content-color);
}
/* Type-specific styles */
.callout-note {
--callout-color: #3b82f6;
--callout-bg: #eff6ff;
}
.callout-warning {
--callout-color: #f59e0b;
--callout-bg: #fffbeb;
}
5. Debug Utilities
5.1 AST Debug Component
astro
---
// src/components/Debug.astro
interface Props {
ast: any;
}
const { ast } = Astro.props;
---
{import.meta.env.DEV && (
<pre class="debug-ast">
{JSON.stringify(ast, null, 2)}
</pre>
)}
Implementation Steps
- Phase 1: Base Setup
- Configure remark plugins in astro.config.mjs
- Create basic plugin structure
- Implement detection logic
- Phase 2: Transformation
- Implement node transformation
- Add proper HAST conversion
- Test with simple callouts
- Phase 3: Content Integration
- Update collection configuration
- Modify page component
- Test with actual content
- Phase 4: Styling & Polish
- Add base callout styles
- Implement type-specific styles
- Add debug utilities
Success Criteria
- Callouts are properly detected in markdown
- AST transformation preserves all content
- Styling is applied correctly
- Debug tools show proper transformation
- Content collections work seamlessly