Working with Markdown in TypeScript: From Parsing to Custom Plugins
Markdown remains one of the most popular markup languages for documentation, blogs, and educational platforms. When developing in TypeScript, there is a need not only to display Markdown but also to flexibly manage the conversion process. In this article, we will cover three key libraries that will help you integrate Markdown into your TypeScript project and show you how to create your own extensions.
Why is TypeScript Important for Working with Markdown?
TypeScript adds strict typing, which is critical when processing user content. You know exactly what data comes from Markdown files and can avoid runtime errors. Additionally, TypeScript simplifies creating plugins and middleware for parsers.
Overview of Popular Libraries
- marked — the fastest and simplest parser. Minimal configuration, excellent performance.
- unified / remark — an ecosystem for syntactic analysis. Allows you to create complex transformation chains (AST -> HTML -> React).
- showdown — a lightweight alternative with extension support, well-documented.
Example 1: Basic Parsing with marked
The marked library is ideal for a quick start. Install it via npm:
npm install markedTypes for TypeScript are included. Usage example:
import { marked } from 'marked';
const markdownContent = `# Hello, TypeScript!This is **bold text** and `code`.- List item- Another one`;
const html = marked.parse(markdownContent);console.log(html);// Output: <h1>Hello, TypeScript!</h1>// <p>This is <strong>bold text</strong> and <code>code</code>.</p>// <ul><li>List item</li><li>Another one</li></ul>For asynchronous work, use marked.parseAsync(). This is useful when loading files from the network.
Example 2: Custom Renderer with Typing
Sometimes you need to change the default behavior. For example, add classes to links or process images. Let's create a custom renderer in TypeScript:
import { marked, Renderer } from 'marked';
interface CustomRenderer extends Renderer { link(href: string, title: string | null, text: string): string;}
const renderer: Partial<CustomRenderer> = { link({ href, title, text }) { return `<a href="${href}" target="_blank" rel="noopener noreferrer" class="external-link">${text}</a>`; }};
marked.use({ renderer });
const markdown = 'Visit [PythonLib](https://pythonlib.ru)';const result = marked.parse(markdown);// <a href="https://pythonlib.ru" target="_blank" class="external-link">PythonLib</a>The Partial<CustomRenderer> type ensures we only override the necessary methods.
Example 3: Working with AST via unified and remark
If you need full control over the document structure, use unified and remark. Installation:
npm install unified remark-parse remark-stringify remark-htmlExample of parsing into a syntax tree (AST) and back:
import { unified } from 'unified';import remarkParse from 'remark-parse';import remarkStringify from 'remark-stringify';import remarkHtml from 'remark-html';
const markdown = '# HeadingThis is **important**.';
const file = await unified() .use(remarkParse) // Parse into AST .use(remarkStringify) // Back to Markdown (for verification) .process(markdown);
console.log(String(file)); // Original Markdown
const htmlFile = await unified() .use(remarkParse) .use(remarkHtml) // Convert to HTML