Integrating Blazor Server and Tailwind CSS - Part 1

Integrating Blazor Server and Tailwind CSS - Part 2

Published on Friday, July 23, 2021 4:00:00 AM UTC in Programming & Tools

In part 2 of this mini-series, we're going to expand the watcher to also take isolated CSS into consideration. You can find the previous article here: Integrating Blazor Server and Tailwind CSS - Part 1

CSS isolation

Again I'm not going to explain what CSS isolation is in detail. The official documentation is quite good and nicely explains what to expect and how to set everything up. The important part for us to know is that the individual CSS files are taken during builds, processed and bundled for you. The result is then served by the name of your application assembly, which is why the project templates contain a styles link tag like:

<link href="{ASSEMBLY NAME}.styles.css" rel="stylesheet">

When you look around your build output folders, you'll find the intermediate files and final bundles in the "obj" folder of your project, like so:

image-1.png

For example, the MySample/Shared folder will contain a file NavMenu.razor.rz.scp.css that contains the scoped CSS for the template's navigation bar. The bundle folder holds the fully processed CSS bundle file.

Only for a real production builds are these files then physically moved to the wwwroot folder.

Integration with Tailwind

The offical documentation for scoped CSS files only very briefly mentions preprocessors and says that they can be integrated with a "Before Build" task in Visual Studio's Task Runner. That's a very rudimentary integration though, and I want to use a different approach that also enables integration with dotnet watch. To understand what's going on during a build, you can use a verbose output of dotnet build and see all the involved msbuild magic - to find a suitable target you can use to plug in your custom CSS preprocessing logic.

I've already done that and identified a target named _GenerateScopedCssFiles. What you can do now is, using standard msbuild features, plug in your custom additional build step after that target is completed. To this end, open your .csproj file and add the following:

<Target Name="ProcessScopedCssFilesWithTailwind" AfterTargets="_GenerateScopedCssFiles">
  <MSBuild Projects="$(MSBuildProjectFile)"
           Properties="CurrentScopedCssFile=%(_ScopedCssOutputs.Identity)"
           Targets="ProcessScopedCssFileWithTailwind">
  </MSBuild>
</Target>

<Target Name="ProcessScopedCssFileWithTailwind">
  <Message Importance="high"
           Text="Processing with Tailwind: $(CurrentScopedCssFile)" />
  <Exec Command="npx tailwindcss -i $(CurrentScopedCssFile) -o $(CurrentScopedCssFile)"
        WorkingDirectory="$(MSBuildProjectDirectory)"
        EnvironmentVariables="TAILWIND_MODE=build" />
</Target>

This is basically a for-each loop over all the generated scoped CSS files that call the Tailwind CLI for each one. Noteworthy:

  • We use the identical file name for output (-o) as for input (-i), effectively overwriting the source file. This is important because the following build steps pick up that exact file name.
  • The environment variable ensures that Tailwind creates a one-off build even in JIT mode and though the Node environment is not set to production. You can read about this override in the official docs here.

When you run dotnet watch run as before, you'll see that your scoped CSS styles work as expected even when they contain Tailwind-specific classes and directives, meaning they are properly transformed to browser-compatible CSS during the build. Try and change something in e.g. the navigation bar scoped CSS and hit save. You'll find that the dotnet watcher starts picking up the scoped CSS changes, kicks off the required msbuild targets, which in turn trigger our custom logic above. The automated browser reload then reflects your changes - sweet!

With this configuration in place, and the watcher setup of part one, you can now seamlessly build your Blazor application and apply Tailwind CSS changes on the fly, which are then reflected in the browser automatically. And it doesn't stop there. Try to publish a production build:

dotnet publish -c Release -o .\publish

You'll find that now the bundled scoped CSS is written to the wwwroot folder as [Your application assembly name].styles.css, properly preprocessed by Tailwind CSS once again, and ready to be used in production.

Tags: Blazor