How to Extend Markdown with Custom Syntax Using Marked.js
This article explains how to create personalized Markdown syntax using the Marked library, covering basic usage, the internal six‑step rendering process, lexical analysis with regex, and step‑by‑step implementation of custom block elements like an alcohol card and video embed.
Why Create a Personalized Markdown Syntax?
Before discussing this topic we introduce our product "BaiPing", a professional whisky community where articles are stored in Markdown. When product managers saw other editors supporting rich media, they asked us to add similar capabilities, which led us to explore extending Markdown.
Simple Introduction to Marked Usage
We show a simple Markdown snippet and install Marked via npm, then convert it to HTML with a few lines of code.
# Hello Marked
My name is Juner.Installation command: npm install marked Conversion code:
import marked from 'marked';
console.log(marked(the_content_above));Deep Dive into Marked
Marked’s rendering process consists of six steps:
Receive the user’s Markdown input;
The lexer analyzes the input and builds a nested token tree;
Each token is produced by matching a regular expression, capturing the matched string as the token’s content;
Before exporting the final token tree, a pass applies custom processing such as lowering heading levels;
The renderer receives the token tree, traverses it, and assigns rendering functions to each token;
Each rendering function receives a token and generates the corresponding HTML fragment.
We illustrate the process with a flowchart:
Lexical Analysis Process
Marked’s lexer uses regular expressions to match block‑level and inline syntax. For example, a paragraph token is created by matching text until a blank line, and the lexer recursively processes the remaining source.
function lex(src) {
const tokens = [];
// ... lexical analysis logic ...
return tokens;
}Regex Matching Principle
The blockquote rule matches lines starting with ">" after applying the paragraph regex. The heading rule matches lines beginning with one or more "#" characters.
Extending Marked with Custom Syntax
Marked follows the single‑responsibility and open‑closed principles, allowing extensions via marked.use({...}). We define two custom block syntaxes: an alcohol card and a video embed.
Alcohol card syntax starts with @alcohol followed by a JSON object; video syntax starts with v[] and a URL to an MP4 file.
const alcoholPattern = /^@alcohol (.+)(?:
|$)/;
const videoPattern = /^v\[]\((https?:\/\/.+\.mp4)\)(?:
|$)/;Tokenizer Functions
Each tokenizer returns an object containing at least type and raw. The alcohol tokenizer also parses the JSON and stores it in data.
function alcoholTokenizer(src) {
const match = alcoholPattern.exec(src);
if (match) {
const json = match[1].trim();
try {
return {
type: 'alcohol',
raw: match[0],
text: json,
data: JSON.parse(json)
};
} catch (err) {
console.error(err);
}
}
return null;
} function videoTokenizer(src) {
const match = videoPattern.exec(src);
if (match) {
return {
type: 'video',
raw: match[0],
text: match[1],
link: match[1]
};
}
return null;
}Renderer Functions
The alcohol renderer outputs a section with an image, name, and score; the video renderer outputs a <video> element with a source.
function alcoholRenderer(token) {
const { id, name, cover, score } = token.data;
return `
<section id="alcohol-${id}">
<img src="${cover}" alt="">
<div class="message">
<p class="name">${name}</p>
<p class="score">${score}</p>
</div>
</section>`;
} function videoRenderer(token) {
return `
<video controls>
<source src="${token.link}" type="video/mp4">
<p>本视频暂不支持播放</p>
</video>`;
}Complete Code
We register the extensions and parse a sample Markdown string containing both custom syntaxes.
import marked from 'marked';
const alcoholRule = {
name: 'alcohol',
level: 'block',
start(src) { return src.match(/@alcohol {[^
]*}/)?.index; },
tokenizer: alcoholTokenizer,
renderer: alcoholRenderer
};
const videoRule = {
name: 'video',
level: 'block',
start(src) { return src.match(/v\[]\(https?:\/\//)?.index; },
tokenizer: videoTokenizer,
renderer: videoRenderer
};
marked.use({ extensions: [alcoholRule, videoRule] });
const markdown = `@alcohol {"id":12345,"name":"罗曼湖","cover":"http://example.com/example.jpg","score":10}
v[](http://example.com/example.mp4)`;
console.log(marked.parse(markdown));The resulting HTML renders the alcohol card and the video player.
<section id="alcohol-12345">
<img src="http://example.com/example.jpg" alt="">
<div class="message">
<p class="name">罗曼湖</p>
<p class="score">10</p>
</div>
</section>
<video controls>
<source src="http://example.com/example.mp4" type="video/mp4">
<p>本视频暂不支持播放</p>
</video>Conclusion
We have satisfied the product requirement with simple custom syntaxes. More complex elements such as tables, mathematical expressions, or flowcharts would require advanced regular expressions and deeper knowledge of Marked’s internals.
For further exploration, refer to the Extensibility section of the Marked documentation.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
BaiPing Technology
Official account of the BaiPing app technology team. Dedicated to enhancing human productivity through technology. | DRINK FOR FUN!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
