Blazor WebAssembly and Typescript React In One Application, From a .net Developer

When I embarked on this journey I wanted to create a proof of concept setup where I had react.js components and Blazor components in a single project, using the javascript interop provided with Blazor to communicate between them. I wanted to write my react code in typescript. I wanted my typescript build integrated with the build of the rest of my project, built as part of building the csproj. I found several articles along the way that claimed to be step by step tutorials. None were exactly right and following them didn’t get me where I needed to be, though many were useful in getting to the end result.

Why should you be interested in following this same path? At the heart of things you want to use Blazor and have a need to use javascript at the same time, specifically typescript. I wanted to use react but the steps apply more to Blazor + typescript except for specific package references. You want to do this while retaining a responsive IDE and well integrated development environment.

What are the use cases for such a setup? The simplest is that WebAssembly and, therefore, Blazor WebAssembly cannot do everything that javascript can do. Perhaps you are wanting to migrate from an existing javascript based component library. Maybe you want to use Blazor as a web assembly client while still using your existing component library.

Step by Step Instructions and Sample Code

If you’re like me you probably want to start by cloning the repository, playing with the code, then come back and read the article about what problems I ran into and how to solve them. If that’s the case, here you go, check out BlazorReact on github.

Here is a step by step list of the steps to take manually in order to replicate a project like mine. Steps are discussed and explained below. There are alternatives to the tools and approaches I landed on.

  1. Install vs 2019 v16.6.0+ with the necessary workloads or the dotnet core 3.1.300+

webpack.config.js:

const path = require("path");
const fs = require("fs");module.exports = {
entry: () => fs.readdirSync("./React/").filter(f => f.endsWith(".js")).map(f => `./React/${f}`),
devtool: "source-map",
mode: "development",
output: {
filename: "app.js",
path: path.resolve(__dirname, "./wwwroot/scripts")
},
module: {
rules: [{
test: /\.js$/,
enforce: "pre",
use: ["source-map-loader"]
}]
}
}

At this point you probably have plenty more to do to take this from a POC project to get you started and make it into something you would actually use in your webpack configuration. Getting the babel loader involved in your webpack step to transpile ES versions for compatibility and switching whether to use “development” or “production” built-in optimizations both come to mind.

The Gritty Details

Now for some more details about what all this means, what it does, and why it’s included. I am assuming the basics of creating and rendering a react component and using Visual Studio are already known or can be found elsewhere. I used Visual Studio 2019 Community Edition for this.

Project Setup

The first step, updating or installing vs 2019 16.6.0 or above, is to get the project template for Blazor WebAssembly and the .net core SDK with the tools for it. The workloads I have installed that are relevant are ASP.Net and Web Development, Node.js development, and .Net Core cross-platform development. Ensure that typescript language support is included in your workload options. After those workloads are installed you may want to update your installation of Node and npm and verify that they are available globally from your command line.

You will be prompted by Visual Studio with a yellow bar at the top of your editor to install the nuget package Microsoft.Typescript.MSBuild when you add a typescript file to a project. I didn’t see it the first time and the prompt went away. I created a new project just to get the prompt again so I could see what package it was and install it to the project. This package adds the build targets to integrate typescript into your build as if it were any other file.

Manually adding the two typescript options to the csproj is necessary for your react components to build successfully when importing from ‘react’ and ‘react-dom’ due to the way their @types package is set up. There were other configurations I changed from their defaults that aren’t relevant to getting the build working. Configure those additional options as you see fit.

Restarting Visual Studio after adding those was something that caused me several hours of lost time while figuring this out. It seems as though changing any of the typescript build options in the csproj (not through the properties GUI) will not be reflected in builds performed in Visual Studio until the project is closed and reopened. As far as I could tell this includes adding, removing, or editing a tsconfig file! tsconfig is the default name given to the file that configures how typescript is built, if that is not familiar. We’ve used the same configurations but as supported in a csproj file. I spent a while looking at https://www.typescriptlang.org/docs/handbook/compiler-options-in-msbuild.html to see options that are supported but not in the project properties. It maps the tsconfig options to the csproj options and specifies which are not available.

At this point your project can build typescript and produce output. We still need the react packages and we need some additional tools to make these outputs usable by a browser.

Adding Packages

The npm packages config file is added so that we can use npm as our package manager and node to invoke scripts, such as webpack. I explored using libman as the package manager since that’s what is built into the context menu of a csproj under “manage client side libraries.” Installing packages using libman and the right-click menu they weren’t being picked up by my typescript files. Some googling showed that this solution was meant for “a package or two” and that npm is still the recommended way to manage dependencies and packages. It’s possible to write typescript and build it without these. If you want to create react components, however, you do need the react packages to reference.

Now that there are packages in node_modules they’ll show up in your csproj, since files are added based on folder structure and convention in an SDK-style project. You don’t want your build to try to build anything in your referenced packages, so they need to be ignored. If they are not ignored you’ll get a long list of errors for things like duplicate definitions and conflicts.

Add Webpack for a Usable Build Artifact

Webpack does a lot of things, too many to mention as here. I’ll stick to the ones that are necessary to make this work. The options as configured will take the build output of any typescript file in the “react” folder of your project (the .js file and the .js.map files) and put them into a single javascript file and map file. This bundled file will have package imports bundled with it as needed. Most importantly this single file will be one that your browser can use! Directly referencing one of the typescript build output files will give you uncaught reference errors for things that are not defined. One that I remember finding amusing was an error that “define is not defined.”

The lambda given as the value for “entry” instructs webpack to pick up all the .js files; each typescript file will produce its own .js and .js.map file and we want to take all of those and smash them together into a single output file.

Development mode makes errors and debugging in the browser console easier to work with. Production default optimizations include things like minification. Setting the output to be inside the wwwroot folder somewhere allows index.html to incldue the script. Putting the output anywhere outside the wwwroot folder will give you a 404 when you try to view your site.

Finally the source map rules inside the module section and devtool: source-map instructs webpack to produce a sourcemap file and, in producing that file, to pick up and incorporate the already produced source maps instead of creating a brand new one that maps the bundle output file to the output files of the typescript build. This set up allows you to see and debug the original .tsx file in Visual Studio, Chrome, or any other source map supporting browser. With my particular setup I was able to set a breakpoint in my typescript file and see the debugger break in Visual Studio.

The scripts in package.json and the build targets attach webpack to the end of your build, so that you don’t have to invoke it outside the build for your build to actually be complete or to see changes made to your typescript files. If you want a hot reload while developing a page you can run npm run pack -- --watch to pass the watch parameter to webpack and set Compile on Save (TypeScriptCompileOnSaveEnabled) to true in the project properties. At this time Blazor does not support hot reloading, so only some of your changes can be made and seen without running a build this way (those in your typescript files). Don’t add the watch parameter to either the package.json or the build target! Your build will wait for the command to complete and it never will unless you tell it to stop.

Rendering a React Component From a Blazor Component

Attaching the render function to the global window object is for the JSInterop of Blazor. It invokes methods on window, so if you do any further development with Blazor using its javascript interop, remember that.

If you’ve started a react project without using create-react-app to get you started ReactDOM.render is something you’ve seen before or you may have come across it in other ways. If you haven’t seen this before this method takes your react code and puts it on the page in the DOM element specified.

Specifying a DOM element for ReactDOM to render in is what the @ref and ElementReference are for in Index.razor. Blazor will do the work of keeping track of the element it renders and sending the appropriate translation through IJsRuntime. The element is placed in Index.razor so that the react component is rendered somewhere that will be visible once Blazor has rendered its components. The interop call is in OnAfterRenderAsync so that the two different DOM manipulating frameworks won’t both be trying to change DOM elements at the same time; Blazor is done, now react can go. If you wanted something more like two SPAs on one page you could put the element in Index.html instead and render your react root component there, using document.getElementById or some other method to select it.

But Why?

Why did I spend a work day figuring all this out? My specific scenario was that I had been asked to temporarily join a team to work on some data transformation from a legacy system to the system that replaced it. The legacy system was written to use Silverlight (i.e. dotnet) and the new one was in React. The existing data conversion was a series of SQL stored procedures and they were only partially working to convert the data. Blazor WebAssembly had been released at Build mere days before and I had been waiting for it to be released since dotnet core 3.0 and my initial Blazor server side projects. Blazor WebAssembly and React in one application seemed like a great way to use existing code from both applications to show what the staged conversion would look like before commiting it to storage. I plan to use Google’s material design and component libraries to make it difficult to distinguish which parts of the page came from which technology (MatBlazor and Material-UI specifically). I plan on leveraging users to validate that the conversion was correct and gather feedback on what is and is not correct. At some point the incremental improvements would give us a high enough confidence level in the conversion based on our users, the ones that know their own data best, that we could migrate all remaining data, retire the code that converts the data, and retire the legacy system itself.

More generally I have had plenty of past experiences where some form of javascript build was mixed with a c# build. The result I wanted was for a complete build to result from executing dotnet build on the project, typescript to js bundle as well as c# to Blazor WebAssembly, while preserving a developer experience with quick IDE feedback for both languages in one place. That place, for me, is Visual Studio (specifically not Visual Studio + CLI). I also wanted both UI technologies to be a part of a single project. To me they represent a single piece of a system, the UI. I suspect there is a way to do something very similar using VS Code, perhaps even with better support for the typescript side of things!

What I wanted to avoid were things like my past experiences with projects mixing javascript build tools with .net build tools. The worst experience I had was working with an e-commerce monolith where the typescript build could take up to 20 minutes, gave no output until finished (the build appeared to hang) and ran whether the project needed to build or not!

Hopefully this set up and the time I spent figuring out how to get a project set up can help save you from experiencing things like I did, whether for the first time or from experiencing it again.

Senior Software Engineer looking to share tips and experience