Title image of Prism Js syntax highlighting in Blazor

Prism Js syntax highlighting in Blazor

27 May 2022

·
Blazor

Code is really hard to read 🧐

Syntax highlighting is crucial for any website showing code. It instantly makes every blog, forum, or documentation site ten times better. Add some color to it and incomprehensible code suddenly becomes readable. Your readers are actually able to see what you’re doing 🥳

Prism is the most popular way of doing it. It’s a javascript library used in millions of websites with tones of themes to choose from.

I recently tried using the library in a Blazor project and it took some work. So this post is about the steps needed to get it working.

Post objective: Get PrismJs working in Blazor.

Method #1 (Not perfect)

All you need to do in any other website is put your code in between some tags. Bring in the Prism JS/CSS and it just works. But Blazor-rendered content is slightly different.

With Blazor we need to explicitly tell Prism to highlight our code. We do this through the Prism API that we can call using Blazor JS Interop.

Prism has a method to highlight all code on the page called ‘highlightAll’. First, we need to add the Javascript method to your index.html:

<script>
	window.highlightAll = () => {
		Prism.highlightAll();
	}
</script>

Then call it when the component is rendered:

<h1>Hello, world!</h1>

<pre><code class="language-json">@exampleJson</code></pre>


@code{
	private string exampleJson = @"{
	""message"": ""this is an example message"",
	""an_array"": [{
			""blogAwesome"": true,
			""reason"": ""best content""
		}
	]
}";

	protected override async Task OnAfterRenderAsync(bool firstRender)
	{
		if (firstRender)
		{
			await JS.InvokeVoidAsync("highlightAll");
		}
	}
}

And our code is pretty!

Json with syntax highlighting inside a blazor app

The problem

There’s a problem with this approach: Blazor doesn’t like Javascript modifying the DOM.

When JS modifies an element in the DOM Blazor loses track of it (something about a shadow dom bla bla bla). This means Blazor can’t update it when the data is changed.

We can show this with two buttons that update the JSON data.

Here’s what’s supposed to happen when Prism isn’t involved:

Data changes like it’s supposed to

Unfortunately when we add Prism the data doesn’t change:

Data not changing

Data doesn’t change 😔

So this method only works for data that doesn’t change. For data that changes we need to try something else.

Method #2

For this second method, we’re going to stop using the Prism highlight-all method. Instead, we’re going to highlight the code individually before it gets rendered.

The Prism API method for this is ‘highlight’. So we wrap that around a function that we can call from C#:

window.highlight = (code) => {
    return Prism.highlight(code, Prism.languages.json, 'json');
}

The HTML stays the same with the buttons to update the data:

<h1>Hello, world!</h1>

<div class="mb-3">
	<button type="button" class="btn btn-primary" @onclick="SetJson1">Set Json 1</button>
	<button type="button" class="btn btn-secondary" @onclick="SetJson2">Set Json 2</button>
</div>

@if(exampleJson.Value != null){
	<pre class="language-json"><code class="language-json">@exampleJson</code></pre>
}

A few changes need for the code section of the razor page:

@code {
	private MarkupString exampleJson;

	public async Task SetJson1()
	{
		exampleJson = await PrettifyJson(@"{ ""message"": ""this is json number 1!"", ""an_array"": [{ ""blogAwesome"": true, ""reason"": ""best content"" } ] }");
	}

	public async Task SetJson2()
	{
		exampleJson = await PrettifyJson(@"{ ""message"": ""this is json number 2!!"" }");
	}

	private async Task<MarkupString> PrettifyJson(string json)
	{
		// Cheaty way to format json
		using var jDoc = JsonDocument.Parse(json);
		var prettyJson = JsonSerializer.Serialize(jDoc,
			new JsonSerializerOptions
				{
					WriteIndented = true
				});


		return new MarkupString(await JS.InvokeAsync<string>("highlight", prettyJson));
	}
}

The private variable exampleJson has been switched from a string to a MarkupString. This is so we can render raw HTML without Blazor trying to encode it first.

The Highlight Javascript method we added is called when either button is clicked. We use it to transform the JSON data into highlighted HTML. That is then stored in the exampleJson variable and rendered onto the screen.

And it works!

Successful data changing in Blazor

Such pretty colors 😍