👉 See TailBlazorLite for just the default Blazor WASM project template, minus Bootstrap, plus Tailwind JIT & VS Code "F5" hooked up.
- Taking full advantage of Tailwind's great new JIT mode intermediate builds, by using background
npm
tasks rather than relying ondotnet watch
/ msbuild to do a full rebuild. - Coupling Tailwind CSS with Blazor CSS Isolation, by passing the Razor Class Library's intermediate CSS bundle to PostCSS, outputting the final vanilla CSS to
wwwroot
- Optimal "F5" debug/run experience, with the above (isolation/JIT) in mind - Everything should Just Work™ in VS Code. Not so much for Visual Studio or
dotnet watch
- Hot Reload won't trigger the msbuild Target that kicks off a Tailwind build; Usenpm run watch-wasm
(or-server
) manually and Hot Reload will pick it up, though. Also included arewatch-*.ps1
scripts that fire up both dotnet and npm in watch mode. - Implementing a Light/Dark/System mode switcher, using Tailwind's
class
dark Mode & Blazor's JS Isolation/Interop. - Integrating CSS Variables with Tailwind Config - configuring an
accent
color intailwind.config.js
equal tovar(--accent-color)
so that anywhereaccent
is used (ring-accent
andborder-accent
here, butbg-
,text-
, etc as well), changes to--accent-color
will be reflected. Click the page to see it in action. - UI in a separate project (
RazorClassLibrary
) so it can be used by existing and upcoming Blazor project types as well as MVC/Razor Pages. - Azure Functions API & Azure Static Web Apps Deployment.
- Basic Dependency Injection in each project.
I'll link a more detailed post in the future, but for now i'll run through the key steps. Here's the deployed Static Web App. The animation used to demonstrate DI, CSS Isolation, and a bunch of Tailwind features is inspired by one on TailwindCSS.com
- dotnet 6 SDK. This is installed with Visual Studio 2022.
- Latest Azure Functions Core Tools for the API, if using VSCode/CLI.
- Azurite for Azure Functions debugging, if using VSCode/CLI.
- node.js.
This project is currently geared more toward VS Code as i am a fan and it is easy to create an "F5" experience that takes full advantage of both Tailwind JIT incremental builds and dotnet Hot Reload. See .vscode/settings.json
for some settings that light up the same "code styles" (see this, this, and this) and analyzers/fixes/refactorings featured in Visual Studio, but provided by Roslyn. Also note .vscode/extensions.json
' suggestions, if you have the recommendations "muted". bradlc.vscode-tailwindcss
in particular - it provides intellisense, linting, and css previews for Tailwind.
- The first time launching the project in VS Code, it will complain about the
start tailwind jit/watch
Task having "no problem matcher". This isn't an error so much as an omission - the Tasks "just work" and cooking up whatever regex it's after isn't on my to-do list. (PR welcome, see issue #49. ) - When F5'ing one of the "WASM & API" configs, VS Code due to them building in parallel - one or the other build may fail. Doing a single full solution build followed by running the projects sans-build is not an option:
func start --no-build
fails for reasons i haven't figured out, anddotnet watch run --no-build
would renderwatch
useless. (--no-first-build
would be nice...) That's why the "FunctionsAPI: Watch/Debug" and "WASM ONLY: Watch/Debug" configs exist - normally you wouldn't run just one or the other. Kill the errored task terminals and use one of those. - Take a look at the note about "rude edits" prompts here
Templates used for the projects:
project | template used | notes |
---|---|---|
BlaorWasm | dotnet new blazorwasm |
Index.razor and MainLayout.razor are moved to RazorClassLibrary , lots of "fluff" removed from this and BlazorServer |
RazorClassLibrary | dotnet new razorclasslib |
Where Shared Razor & CSS goes |
BlazorServer | dotnet new blazorserver |
I'm only deploying BlazorWasm , but ensuring shared UI plays nicely with Blazor Server projects is a good idea. Blazor Server can make for more productive development/debugging as well. |
SharedClassLibrary | dotnet new classlib --framework netstandard2.1 |
Code common to all projects. Needs to be netstandard2.1 so it is compatible with FunctionsAPI and to keep Azure Static Web Apps oryx build system happy. Hoping to get it all on net5.0 + ASAP. |
FunctionsAPI | VS 2022's Azure Functions Wizard - .NET 6 isolated process option | When i upgraded this project to .NET 6, the Azure Functions Core Tools (func init ) was a bit behind, still outputting a template based on the Functions v3 Runtime. |
Followed by things like... stripping out Bootstrap, dotnet new sln
/ dotnet sln add ...
, adding AdditionalAssemblies="new[] { typeof(DarkSwitch).Assembly }"
to both BlazorWasm & BlazorServer's App.razor
's <Router>
after AppAssembly=
, and fixing up using
statements.
There are a couple special msbuild "Targets":
npm install
- Ensures prerequisitenode.js
/npm
and package installation. This will only run once, after clone, thanks toinstall-stamp
.tailwind build
Target does a one-off Tailwind CSS build. This is bypassed (for Hot Reload / JIT mode usage) by setting theTailwindBuild
msbuild property tofalse
. See awatch-*.ps1
script ortasks.json
for this in action. Visual StudioF5
will run this, as willdotnet watch run --no-hot-reload
, or a Hot Reload "rude edit" build - but a "normal" Hot Reload will not run this.
The <IntermediateOutputPath>
property is set to obj
so that Blazor's intermediate CSS bundles are always output to the same path, omitting Debug
/Release
. See Step 3 for more info. <AppendTargetFrameworkToOutputPath>
similarly omits net6.0
from the output path - there's no functional reason for this, i'm just shortening the path a bit.
npm init --yes
- initializes apackage.json
using defaults. This is where CSS build scripts and tool references will go.npm install -D postcss-import@latest tailwindcss@next
-The CSS tools we'll use.
PostCSS is like plumbing, feeding your input CSS through the plugins listed in postcss.config.js
before outputting the final CSS to disk. postcss-import
is used to aggregate any CSS files you @import
into one in-memory file for Tailwind CSS to process.
npx tailwind init
writes a tailwind.config.js
template to disk. The changes to note are:
Enabling JIT mode (line 7)- (no longer needed in tailwind 3.0)- Enabling CSS class-based dark mode. (line 8)
- Pointing it at our html, so JIT can keep an eye on what Tailwind features are being used and generate the appropriate CSS. (lines 4-6).
Now, the main CSS file will need a few key @import
s for Tailwind to do its magic. I've put that file in the root, site.css
. Note the reference to Shared.css
in there, which in turn points to the Blazor-generated RazorClassLibrary.styles.css
- i'll come back to that below.
This is where we tell the Tailwind CLI what to do, in package.json - transforming our five line CSS "master" file into kilobytes of generated CSS based on what Tailwind utilities are used in markup and any tailwind.config.js
tweaks.
They are mostly the same, build-***
being simplest:
npx tailwindcss --config tailwind.config.js --postcss postcss.config.js -i site.css -o ./***/wwwroot/site.min.css
Just pointing it at the tailwind.config.js
and postcss.config.js
config files, and the input & output files, nothing special. This does a full, one-off build. It is the "slow" option.
- The
watch
script, as you'd expect, adds--watch
to the command line. Edits to CSS files will trigger very fast incremental builds. This is integrated into the VS Code "F5" launch configurations, and what you'll want to run behind the scenes if using Visual Studio or vanilladotnet
CLI. - The
publish
script just adds minification to a one-off build, used for deployment.
- The "normal" way to use CSS Isolation is described here. But we can't do that and take full advantage of Tailwind CSS / PostCSS (
@apply
and other directives), so we need to@import
the "intermediate" bundle of the Scoped CSS located at/RazorClassLibrary/obj/scopedcss/bundle/RazorClassLibrary.styles.css
.
An optimal "F5" experience requires the following:
- Start
FunctionsAPI
(forBlazorWASM
only) - Start
tailwindcss --watch
to get Tailwind's fast incremental builds. - Start
dotnet watch run ***
to get .NET's Hot Reload. a) Do NOT runtailwindcss
whenRazorClassLibrary
builds. - Stop
tailwindcss --watch
whendotnet watch
exits.
I've got that all sorted for VS Code in tasks.json
/ launch.json
but haven't come up with an uncompromising solution for Visual Studio. I've put a build Target in the .csproj
to do a full (non-JIT) CSS build - but that task can't "watch" as it would stall the build. One could run watch
as a pre-build task in Visual Studio (Right Click on the project, go to Properties, then Build Events) - but this would result in multiple instances. Best to just manually start it in an external terminal and leave it run until you're done working ? Note if you're using VS Code, removing that build Target is needed to sastisfy "3.a".
(Ideally, we'd be able to watch a debug session, but that combo isn't compatible with Hot Reload.)
To start FunctionsAPI alongside WASM in Visual Studio, right-click on the Solution and go to "Set Startup Projects" and use "Multiple Startup Projects."
Steps have been added to the GitHub Workflow yml
file:
- Install .NET 6 & do an initial build, outputting Blazor's isolated CSS from our *.razor.css file(s).
- Install Node 16 & feed the isolated CSS into Tailwind CSS, outputting the final
site.min.css
- Run a
dotnet publish
, feeding that output path to the following "Build and Deploy" step.