C# Source Generator for JavaScript Interop in Blazor

David Pine

5 minute read

The Blazorators logo


Blazorators is a C# source generator that creates fully functioning Blazor JavaScript interop code, targeting either the IJSInProcessRuntime or IJSRuntime types. All of this code relies on the output of one of my side projects named blazorators. While writing my O’Reilly “Learning Blazor: Build Single-Page Apps with WebAssembly and C#” book, I discovered an impactful way to use both TypeScript type declarations from common JavaScript APIs and C# source generators to generate the JavaScript interop code. This is covered in chapter seven, so I don’t want to spoil it too much, but rather entice you to want to read the book. I’ve been using this approach in my Blazor apps for a while now and I’m excited to share it with you. Let’s get started!

Check out my new Twitter account (@blazorbits) for Blazor tips and tricks! Below you’ll see the Progress Telerik REPL (Read Evaluate Print Loop) for Blazor, select the Preview tab and watch the Blazor WebAssembly app perform a .NET restore and builds the app. Once the app loads, select a Natural voice from the voice selection control if it is available to you. If it’s not available to you, that’s not a big deal either. Select the Speak button and the app will speak the text you entered in the Text-to-speech control. You can also select the Stop button to stop the speech.

Explore the app source code

when you create your own REPL session you’re free to consume public NuGet packages, this app uses the Blazor.SpeechSynthesis.WebAssembly NuGet package 📦. Explore the Code tab thoroughly and look through the app’s source code. You’ll see that this app does the following:

  • _Main.razor: The app’s main page, that showing a loading indicator and immediately navigates to the /speak route.
  • TextToSpeech.razor: The page that renders the Text-to-speech text input element and the browser-native speech synthesis control buttons.
  • TextToSpeech.razor.cs: The page’s code-behind that contains the TextToSpeech component’s logic.
  • Startup.cs: The app’s startup class that configures services, calling a source-generated dependency injection specific extension method named `AddSpeechServices.

Notice the SpeechSynthesisVoice[], SpeechSynthesisUtterance, and ISpeechSynthesisService types. These are all from the Blazor.SpeechSynthesis.WebAssembly NuGet package 📦. The SpeechSynthesisVoice[] type is an array of SpeechSynthesisVoice objects that represent the available voices on the user’s device. The SpeechSynthesisUtterance type is a class that represents the text to be spoken and the voice to be used. The ISpeechSynthesisService type is an interface that defines the GetVoicesAsync and an in-process Speak method that is synchronous.

The Speak function takes a SpeechSynthesisUtterance object and a callback function that is invoked when the speech is complete, delivering the total elapsed time in milliseconds.

There is also a way to listen for changes in the browser-native window.speechSynthesis voices that are available to the JavaScript context. Provide a callback to the ISpeechSynthesisService.OnVoicesChanged event and it will be invoked when the window.speechSynthesis voiceschanged event is fired. This is useful if you want to update the UI with the available voices. All of this functionality is source-generated by the Blazor.SourceGenerator project.

What’s next?

This project was originally written as part of a Microsoft hackathon. It’s been refined, but is still a brute-force effort and could stand to be optimized. As such I’m working to reimplement the parsing approach. It currently makes an HTTP request to a raw Github link with the lib.dom.d.ts file with all of the DOM type declarations in it. The file is itself, generated, and is over 18 thousand lines of code. It uses Regex expressions to parse on-demand single hierarchical type dependency graphs as readonly record struct values. These values were modeled as

Patent pending, not… ☹️

I was working with Stephen Toub (the patent lead for the .NET team), and we filed it for a patent. I was disappointed to learn that it wasn’t filed as the lawyers reviewed it and determined that it wasn’t patentable. I’m not sure why, but I’m glad to share it with you. I hope you find it useful. If you have any questions, please feel free to reach out to me:

The community is excited about this project, and I’m excited to see what you build with it. Here’s what some of the community members have said on Twitter:

The plan

I’m rewriting the TypeScript abstract syntax tree parser to C#, essentially porting it one-to-one. When this is done, the parsing will be much more accurate and flexible. Likewise, our friends over on Twitter, and some of the community members on YouTube have been kind to me as well. Thank you for your kind words, they mean the world to me.

This is a pretty amazing project. Hope this makes it into a serious project at Microsoft! Thank you!


I think this package has a lot of potential. It makes the bridge between JS and C# in Blazor much less painful. I love it! Well done, David.

Jiří Novotný

Additional resources



comments powered by Disqus