JSFiddle - React, Tailwind, and code Playground

JavaScript

// --- парсилка --------------------

var parseStrWithTags = (function() {
	var RX_TPL = /<\s*\/\s*(tags)\s*>|<\s*(tags)(\s+(?:"[^"]*"|'[^']*'|[^>"'])*)?\s*>/.toString().slice(1, -1);

	function parseTags(str, tags, callback) {
		var repl = tags && Object.keys(tags).join('|') || '[\\w-]+';
		var rxTag = new RegExp(RX_TPL.replace(/tags/g, repl), 'gi');
		var m, lastIndex = 0;
		
		while (m = rxTag.exec(str)) {
			var openTag = m[2] && m[2].toLowerCase();
			var closeTag = m[1] && m[1].toLowerCase();
			if (lastIndex < m.index) {
				callback('content', str.substring(lastIndex, m.index));
			}
			lastIndex = rxTag.lastIndex;
			var type = openTag ? 'open' : closeTag ? 'close' : 'end';
			callback(type, m[0], openTag || closeTag, m[3]);
		}
		if (lastIndex < str.length) {
			callback('content', str.substring(lastIndex, str.length));
		}
		callback('end');
	}

	function parseAttrs(attrs) {
		var rxAttrs = /([\w-]+)(?:\s*[:=]\s*("[^"]*"|'[^']*'|[\w-]+))?/g;
		var rxTrim = /^['"]|['"]$/g;
		var m, data = Object.create(null);
		if (attrs) {
			while (m = rxAttrs.exec(attrs)) {
				data[m[1]] = m[2] ? m[2].replace(rxTrim, '') : true;
			}
		}
		return data;
	}

	function parseStr(str, tags) {
		tags = tags || {};
		var data = {}, tag, m, lastIndex = -1, currentTag, stack = [];
		
		parseTags(str, tags, function(type, text, tagName, attrs) {
			switch (type) {
				case 'content': 
					if (currentTag) {
						currentTag.content += text;
					}
					break;
				case 'open':
					tag = {
						tag: tagName,
						attrs: parseAttrs(attrs),
						content: ''
					};
					data[tagName] = data[tagName] || [];
					data[tagName].push(tag);
					if (!tags[tagName].empty) {
						if (currentTag) {
							stack.push(currentTag);
						}
						currentTag = tag;
					}
					break;
				case 'close':
					if (currentTag) {
						if (tagName === currentTag.tag) {
							currentTag = stack.pop();
						} else {
							currentTag.content +=...