src/renderers/markdown.js
function remarkCustomTags(customTags) {
	return ast =>
		visit(ast, 'html', node => {
			if (node.value.startsWith('<x-')) {
				// Parse tag’s HTML
				const dom = parse5.parseFragment(unescapeMarkdown(node.value));
				const tagNode = dom.childNodes[0];
				if (!tagNode) {
					throw new Error('Cannot parse custom tag:', node.value);
				}
				let { tagName, attrs } = tagNode;
				const childNode = tagNode.childNodes[0];
				attrs.push({
					name: 'children',
					value: childNode ? childNode.value.trim() : null,
				});
				tagName = tagName.replace(/^x-/, '');

				// Check tag function
				const tagFunction = customTags[tagName];
				if (!tagFunction || !_.isFunction(tagFunction)) {
					throw new Error(
						`Custom tag "${tagName}" is not defined or is not a function.`
					);
				}

				// Unzip attributes
				attrs = attrs.reduce((attrsObj, attr) => {
					attrsObj[attr.name] = attr.value;
					return attrsObj;
				}, {});

				// Render
				let result;
				try {
					result = tagFunction(attrs) || '';
				} catch (exception) {
					result = errorInlineHtml(
						`Error while rendering custom tag <x-${tagName}>: ${
							exception.message
						}`,
						{ block: true }
					);
				}
				node.value = result.toString().trim();
			}
		});
}
function remarkCustomTags(customTags) {
	return ast =>
		visit(ast, 'html', node => {
			if (node.value.startsWith('<x-')) {
				// Parse tag’s HTML
				const dom = parse5.parseFragment(unescapeMarkdown(node.value));
				const tagNode = dom.childNodes[0];
				if (!tagNode) {
					throw new Error('Cannot parse custom tag:', node.value);
				}
				let { tagName, attrs } = tagNode;
				const childNode = tagNode.childNodes[0];
				attrs.push({
					name: 'children',
					value: childNode ? childNode.value.trim() : null,
				});
				tagName = tagName.replace(/^x-/, '');

				// Check tag function
				const tagFunction = customTags[tagName];
				if (!tagFunction || !_.isFunction(tagFunction)) {
					throw new Error(
						`Custom tag "${tagName}" is not defined or is not a function.`
					);
				}

				// Unzip attributes
				attrs = attrs.reduce((attrsObj, attr) => {
					attrsObj[attr.name] = attr.value;
					return attrsObj;
				}, {});

				// Render
				let result;
				try {
					result = tagFunction(attrs) || '';
				} catch (exception) {
					result = errorInlineHtml(
						`Error while rendering custom tag <x-${tagName}>: ${
							exception.message
						}`,
						{ block: true }
					);
				}
				node.value = result.toString().trim();
			}
		});
}
Remark plugin for custom tags: <x-foo data-value="42"/>

@param {Object} customTags
@return {Function}