Custom Rich Text Serializer not able to get spans to work

Describe your question/issue in detail

I'm using Svelte 4.2.17
I needed to apply custom styling to the rich text fields. Doing straight text content works fine, but when the content is slightly more complex (list items with spans), I'm only able to get the text for each list item.

Below is the errant code in richTextSerializer.js (Full code also provided):
This works, but only displays the text:

list: (node) => {
	console.log('list:', { node });
	const listItems = node.node.items.map(item => {
		const content = item.text;
		console.log('listItem:', content);
		return `<li>${content}</li>`;
	}).join('');
	return `<ul class="list-disc">${listItems}</ul>`;
}

This does not work. Getting RangeError: Maximum call stack size exceeded:

list: (node, type) => {
	console.log('list:', {node});
	const listItems = node.node.items.map(item => {
		const content = prismicR.serialize([{ ...item, type: 'list-item' }], richTextSerializer);
		console.log('listItem:', content);
		return `<li>${content}</li>`;
	}).join('');
	return `<ul class="list-disc">${listItems}</ul>`;
},

Full code:
richTextSerializer.js

import { prismicH } from '$lib/prismicio.js';
import * as prismicR from '@prismicio/richtext';

export const richTextSerializer = prismicR.wrapMapSerializer({
	heading1: (type, node, content) => {
		return `<h1 class="mb-4 font-bold text-regal-blue text-5xl">${content}</h1>`;
	},
	heading2: (type, node, content) => {
		return `<h2 class="mb-4 font-bold text-regal-blue text-4xl">${content}</h2>`;
	},
	heading3: (type, node, content) => {
		return `<h3 class="mb-4 font-bold text-regal-blue text-3xl">${content}</h3>`;
	},
	// list: (node, type) => {
	// 	console.log('list:', {node});
	// 	const listItems = node.node.items.map(item => {
	// 		const content = prismicR.serialize([{ ...item, type: 'list-item' }], richTextSerializer);
	// 		console.log('listItem:', content);
	// 		return `<li>${content}</li>`;
	// 	}).join('');
	// 	return `<ul class="list-disc">${listItems}</ul>`;
	// },
	list: (node) => {
		console.log('list:', { node });
		const listItems = node.node.items.map(item => {
			const content = item.text;
			console.log('listItem:', content);
			return `<li>${content}</li>`;
		}).join('');
		return `<ul class="list-disc">${listItems}</ul>`;
	},

	paragraph: (type, node, content) => {
		console.log('paragraph:', { type });
		return `<p class="text-cyan">${type.text}</p>`;
	},
	'list-item': (type, node, content) => {
		console.log('list-item:', { type });
		return `<li>${type.node.items}</li>`;
	},
});

export const asHTML = (richText) => {
	return prismicR.serialize(richText, richTextSerializer).join('');
};

BasicGrid/index.svelte

<script>
	import { PrismicImage } from '@prismicio/svelte';
	import { asHTML } from '$lib/richTextSerializer';

	/** @type {import("@prismicio/client").Content.BasicGridSlice} */
	export let slice;
</script>

<section data-slice-type={slice.slice_type} data-slice-variation={slice.variation}
				 class="section--description heal-process container mx-auto px-3 py-16 sm:px-0 relative bg-white">
	<div class="container mx-auto">
		<h2 class="mb-4 font-bold text-regal-blue text-4xl">{slice.primary.grid_title}</h2>
		<div class="grid grid-cols-2 place-content-evenly">
			<div id="description" class="pe-4">
				{@html asHTML(slice.primary.grid_content)}
			</div>
			<PrismicImage field={slice.primary.grid_image} class="block mx-auto" />
		</div>
	</div>
</section>

This is the console.log output for the list:

{
    "type": "group-list-item",
    "node": {
        "type": "group-list-item",
        "items": [
            {
                "type": "list-item",
                "text": "Health",
                "spans": [
                    {
                        "start": 0,
                        "end": 6,
                        "type": "hyperlink",
                        "data": {
                            "link_type": "Web",
                            "url": "https://wellwardmed.com/services/health.html",
                            "target": "_blank"
                        }
                    },
                    {
                        "start": 0,
                        "end": 6,
                        "type": "strong"
                    }
                ],
                "direction": "ltr"
            },
            {
                "type": "list-item",
                "text": "Energy",
                "spans": [
                    {
                        "start": 0,
                        "end": 6,
                        "type": "hyperlink",
                        "data": {
                            "link_type": "Web",
                            "url": "https://wellwardmed.com/services/energy.html",
                            "target": "_blank"
                        }
                    },
                    {
                        "start": 0,
                        "end": 6,
                        "type": "strong"
                    }
                ],
                "direction": "ltr"
            },
            {
                "type": "list-item",
                "text": "Activity",
                "spans": [
                    {
                        "start": 0,
                        "end": 8,
                        "type": "hyperlink",
                        "data": {
                            "link_type": "Web",
                            "url": "https://wellwardmed.com/services/activity.html",
                            "target": "_blank"
                        }
                    },
                    {
                        "start": 0,
                        "end": 8,
                        "type": "strong"
                    }
                ],
                "direction": "ltr"
            },
            {
                "type": "list-item",
                "text": "Lifestyle",
                "spans": [
                    {
                        "start": 0,
                        "end": 9,
                        "type": "hyperlink",
                        "data": {
                            "link_type": "Web",
                            "url": "https://wellwardmed.com/services/lifestyle.html",
                            "target": "_blank"
                        }
                    },
                    {
                        "start": 0,
                        "end": 9,
                        "type": "strong"
                    }
                ],
                "direction": "ltr"
            }
        ]
    },
    "children": [],
    "key": "86"
}

I found a solution that worked for me, so I wanted to post my solution in case anyone else was having an issue. I didn't think the instructions were clear at first, and the examples I saw were for Vue and React, not Svelte.

I ended up making svelte components for the different elements, e.g. heading3, list, hyperlink, etc. My richTextComponents.js file is used to export richTextComponents (key is the prismic name for the type and the value is my svelte component. Finally, in the BasicGrid slice I created, I removed {@html asHTML(slice.primary.grid_content)}, which was in the video explaining how to create a serializer, and used PrismicRichText with the components prop equaling my richTextComponents.

lib/components/richtext/Hyperlink.svelte

<script lang="ts">
	export let node: any;
	const linkResolver = (doc: any) => '/' + doc.uid;
	const url = linkResolver(node.data);
</script>

<a href="{node.data.url}" target="{node.data.target}">
	<slot />
</a>

lib/richTextComponents.js

import Heading3 from '$lib/components/richtext/Heading3.svelte';
import Hyperlink from '$lib/components/richtext/Hyperlink.svelte';
import List from '$lib/components/richtext/List.svelte';

export const richTextComponents = {
	heading3: Heading3,
	hyperlink: Hyperlink,
	list: List,
};

lib/slices/BasicGrid/index.svelte

<script>
	import { PrismicImage, PrismicRichText } from '@prismicio/svelte';
	import { richTextComponents } from '$lib/richTextComponents.js';

	/** @type {import("@prismicio/client").Content.BasicGridSlice} */
	export let slice;
</script>

<section data-slice-type={slice.slice_type} data-slice-variation={slice.variation}
				 class="section--description heal-process container mx-auto px-3 py-16 sm:px-0 relative bg-white">
	<div class="container mx-auto">
		<h2 class="mb-4 font-bold text-regal-blue text-4xl">{slice.primary.grid_title}</h2>
		<div class="grid grid-cols-2 place-content-evenly">
			<div id="description" class="pe-4">
				<!-- removed {@html asHTML(slice.primary.grid_content)} -->
				<PrismicRichText field={slice.primary.grid_content} components={richTextComponents} />.
			</div>
			<PrismicImage field={slice.primary.grid_image} class="block mx-auto" />
		</div>
	</div>
</section>
1 Like