<p v-html="post.description"></p>
<script>
import {parse} from 'himalaya'
import Product from '~/components/Product/Product.vue';
const hasProduct = parsedHtml => {
let has = false;
if (parsedHtml.children) {
parsedHtml.children.forEach(ch => {
if(has) return;
if (ch.tagName == 'product') {
has = true;
} else {
has = hasProduct(ch);
}
});
}
return has;
};
const toElement = (h, products) => parsedHtml => {
if (parsedHtml.type == 'Text') {
return [h('span', {
domProps: {
innerHTML: parsedHtml.content
},
})];
}
if (parsedHtml.tagName == 'product') {
let product = products.find(product => product.slug == parsedHtml.attributes.slug);
if (!product) return '';
return [h('div', {
'class': ['catalog', 'catalog_view_row'],
}, [
h('div', {
'class': ['catalog__cell'],
}, [
h(
'product',
{props: {product}}
)
])
])];
}
let has = hasProduct(parsedHtml);
return h(
has ? 'div' : parsedHtml.tagName,
{
'class': parsedHtml.attributes.className,
'attrs': parsedHtml.attributes,
},
parsedHtml.children
? parsedHtml.children.map(toElement(h, products))
: []
)
};
export default {
components: {
Product,
},
props: [
'html',
'products',
],
render(h) {
let tree = parse(`<div>${this.html}</div>`);
return tree.map(toElement(h, this.products))[0];
}
};
</script>
let match = article.content.match(/\$product\[([^\]]+)\]/g);
if(match != null && match.length) {
let promises = match.map(async (m) => {
let slug = m.match(/\[(.+)\]/)[1];
try {
let product = await ctx.app.$api.product(slug);
if(product) products.push(product);
} catch (e) {
}
});
await Promise.all(promises);
}
Vue нам предоставляет широкие возможности рендеринга компонента с помощью метода render. Сначала ищем все упоминания указателей на компоненты в пришедшем html и получаем по api данные, нужные для отображения этого компонента. Далее разбиваем весь html на дерево. В этом нам помогла библиотека himalaya. И затем собираем обратно html заменяя указатели на уже готовые компоненты.
import { compile } from 'vue-template-compiler';
import * as transpile from 'vue-template-es2015-compiler';
function toFunction(code) {
return `(function(){${code}})`;
}
export default () => {
const vueTemplate = '<div>{{album_1}}</div>'; //тут html для vue шаблона без оборачивания в template
const { render, staticRenderFns } = compile(vueTemplate, { preserveWhitespace: false });
return transpile(`(function(){
return {staticRenderFns:[${staticRenderFns.map(toFunction)}],render:${toFunction(render)}};
})()`);
}