Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/plugins/renderer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,6 @@ export = (ctx: Hexo) => {

renderer.register('njk', 'html', nunjucks, true);
renderer.register('j2', 'html', nunjucks, true);

renderer.register('mdx', 'html', require('./mdx'), false);
};
31 changes: 31 additions & 0 deletions lib/plugins/renderer/mdx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { evaluate } from '@mdx-js/mdx';
import { h, Fragment } from 'preact';
import render from 'preact-render-to-string';
import type { StoreFunctionData } from '../../extend/renderer';

async function mdxRenderer(data: StoreFunctionData): Promise<string> {
const { text, path } = data;

try {
// Evaluate MDX content with Preact JSX runtime
const { default: Content } = await evaluate(text, {
Fragment,
jsx: h,
jsxs: h,
development: false
});

// Render the MDX component to HTML string
const html = render(h(Content, {}));

return html;
} catch (error) {
const fileInfo = path ? ` in ${path}` : '';
throw new Error(`MDX compilation error${fileInfo}: ${error.message}\n${error.stack || ''}`);
}
}

// Disable Nunjucks processing for MDX files
mdxRenderer.disableNunjucks = true;

export = mdxRenderer;
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
],
"license": "MIT",
"dependencies": {
"@mdx-js/mdx": "^3.1.1",
"abbrev": "^3.0.0",
"bluebird": "^3.7.2",
"fast-archy": "^1.0.0",
Expand All @@ -60,6 +61,8 @@
"moment-timezone": "^0.5.46",
"nunjucks": "^3.2.4",
"picocolors": "^1.1.1",
"preact": "^10.28.1",
"preact-render-to-string": "^6.6.5",
"pretty-hrtime": "^1.0.3",
"strip-ansi": "^7.1.0",
"tildify": "^2.0.0",
Expand Down
104 changes: 104 additions & 0 deletions test/scripts/renderers/mdx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import r from '../../../lib/plugins/renderer/mdx';

describe('mdx', () => {
it('should render basic MDX content', async () => {
const result = await r({ text: '# Hello World' });
result.should.include('<h1>Hello World</h1>');
});

it('should render MDX with bold text', async () => {
const result = await r({ text: 'This is **bold** text.' });
result.should.include('<strong>bold</strong>');
});

it('should render MDX with links', async () => {
const result = await r({ text: '[Link](https://example.com)' });
result.should.include('<a href="https://example.com">Link</a>');
});

it('should render MDX with multiple elements', async () => {
const mdxContent = `# Title

This is a paragraph with **bold** and *italic* text.

- List item 1
- List item 2

[Link](https://example.com)`;

const result = await r({ text: mdxContent });
result.should.include('<h1>Title</h1>');
result.should.include('<strong>bold</strong>');
result.should.include('<em>italic</em>');
result.should.include('<li>List item 1</li>');
result.should.include('<a href="https://example.com">Link</a>');
});

it('should handle errors gracefully', async () => {
try {
// Invalid MDX syntax - JSX that can't be evaluated
await r({ text: '<Component with invalid syntax', path: 'test.mdx' });
throw new Error('Should have thrown an error');
} catch (error) {
error.message.should.include('MDX compilation error');
error.message.should.include('test.mdx');
}
});

it('should have disableNunjucks flag set', () => {
r.disableNunjucks.should.be.true;
});

it('should render JSX elements', async () => {
const jsxContent = `# JSX Test

<div className="custom">
This is a **JSX element**.
</div>`;

const result = await r({ text: jsxContent });
result.should.include('<h1>JSX Test</h1>');
result.should.include('<div class="custom">');
result.should.include('<strong>JSX element</strong>');
});

it('should render JSX with expressions', async () => {
const jsxWithExpressions = `# Dynamic Content

<div className="year">
Year: {2024}
</div>`;

const result = await r({ text: jsxWithExpressions });
result.should.include('<h1>Dynamic Content</h1>');
result.should.include('<div class="year">');
result.should.include('Year: 2024');
});

it('should render JSX with inline styles', async () => {
const jsxWithStyles = `<div style={{color: 'red', padding: '10px'}}>
Styled content
</div>`;

const result = await r({ text: jsxWithStyles });
result.should.include('color:red');
result.should.include('padding:10px');
result.should.include('Styled content');
});

it('should render custom components', async () => {
const customComponent = `export const Alert = ({children, type}) => (
<div className={\`alert-\${type}\`}>
{children}
</div>
);

<Alert type="warning">
This is a **warning**!
</Alert>`;

const result = await r({ text: customComponent });
result.should.include('alert-warning');
result.should.include('<strong>warning</strong>');
});
});