How do you add Tailwind CSS into a Blazor App?
Asked Answered
D

6

32

In particular, I'm using Blazor (server hosted) with ASP.NET Core Preview 8. I tried adding it using LibMan, but that seems to be more about downloading files from a CDN. I'd like to introduce Tailwind to my build process.

Is this a case where I should use something like Webpack? If so, how do I make Webpack part of my build process?

Dibromide answered 27/8, 2019 at 6:47 Comment(2)
As Blazor continues to grow this question's importance will increase. I have made an edit and hope it can be reopened because it needs an update.Checkbook
It seems to be a reasonable "how to" question.Manouch
D
62

UPDATE 2022

The original answer is still below...

Time has marched on since I originally asked this question. For example, I'm now targeting ES6 on modern browsers without Babel (the main reason why I was using Webpack).

So I thought I might document my current solution (Tailwind cli installed with npm without Webpack)

Assuming you have npm installed (part of node.js). In the root of your project:

npm init -y

This will create a package.json file. This is what my file looks like:

{
  "name": "holly",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

NB - I made the name lowercase (the folder/project name is "Holly") - I was looking at the file using VS Code and there was a squiggly line!

Next, I install Tailwind:

npm install -D tailwindcss cross-env

I've also added cross-env - this is so I can run the same command on my dev machine (Windows) and in my GitHub Action.

After that, generate the Tailwind config file:

npx tailwindcss init

Which should create a file similar to this:

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [],
  theme: {
    extend: {},
  },
  plugins: [],
}

I'm using ASP.NET Core/Blazor Server. So I set the content property to the following:

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./**/*.{razor,cshtml,html}"],
  theme: {
    extend: {},
  },
  plugins: [],
}

Now that Tailwind is configured, we need an input file for generating the css. In my project, I've created a Styles folder and a file called app.css:

@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";

The next step is to create some handy npm scripts. This is what my package.json file looks like now:

{
  "name": "holly",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {
    "cross-env": "^7.0.3",
    "tailwindcss": "^3.2.4"
  },
  "scripts": {
    "build": "cross-env NODE_ENV=development ./node_modules/tailwindcss/lib/cli.js -i ./Styles/app.css -o ./wwwroot/css/app.css",
    "watch": "cross-env NODE_ENV=development ./node_modules/tailwindcss/lib/cli.js -i ./Styles/app.css -o ./wwwroot/css/app.css --watch",
    "release": "cross-env NODE_ENV=production ./node_modules/tailwindcss/lib/cli.js -i ./Styles/app.css -o ./wwwroot/css/app.css --minify"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
  • build will be used by Visual Studio in DEBUG mode
  • watch is just handy for on-the-fly updates in dev mode.
  • release is for production

Finally, add the following to your project file (Holly.csproj in my case):

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>

    <NpmLastInstall>node_modules/.last-install</NpmLastInstall>
  </PropertyGroup>

  <!-- Items removed for brevity --> 

  <Target Name="CheckForNpm" BeforeTargets="NpmInstall">
    <Exec Command="npm -v" ContinueOnError="true">
      <Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
    </Exec>
    <Error Condition="'$(ErrorCode)' != '0'" Text="You must install NPM to build this project" />
  </Target>

  <Target Name="NpmInstall" BeforeTargets="BuildCSS" Inputs="package.json" Outputs="$(NpmLastInstall)">
    <Exec Command="npm install" />
    <Touch Files="$(NpmLastInstall)" AlwaysCreate="true" />
  </Target>

  <Target Name="BuildCSS" BeforeTargets="Compile">
    <Exec Command="npm run build" Condition=" '$(Configuration)' == 'Debug' " />
    <Exec Command="npm run release" Condition=" '$(Configuration)' == 'Release' " />
  </Target>
</Project>

This is based on a tutorial I found written by Chris Sainty (which I can't find at the moment). I did, however, find this rather excellent post by Chris on Adding Tailwind CSS v3 to a Blazor app. Though I think this version avoids the integration with Visual Studio.

Make sure you copy and paste the NpmLastInstall element and the Target elements to their appropriate locations in your .csproj file.

If you're in DEBUG mode, Visual Studio will execute npm run build. If you're in RELEASE mode, it'll execute npm run release. If you've just pulled the repo from the server, Visual Studio should be smart enough to automatically execute npm install. The only thing is, I don't think it'll run npm install when the package.json file has been updated - you'll need to remember to do that manually.

Targeting npm in the .csproj file means that Tailwind will build when the ASP.NET Core project builds. Things will stay consistent - if you hit F5 you know the css is up-to-date! The Tailwind JIT is on by default - so Tailwind build times are negligible and do not add much to the total build time.

One final thing. Because Tailwind css is updated by the Visual Studio project - it means the same thing happens in the cloud. Here's a cut down version of my GitHub Action:

name: production-deployment

on:
  push:
    branches: [ master ]

env:
  AZURE_WEBAPP_NAME: holly
  AZURE_WEBAPP_PACKAGE_PATH: './Holly'
  DOTNET_VERSION: '6.0.x'
  NODE_VERSION: '12.x'

jobs:
  build-and-deploy-holly:
    runs-on: ubuntu-latest
    steps:
      # Checkout the repo
      - uses: actions/checkout@master

      # Setup .NET Core 6 SDK
      - name: Setup .NET Core ${{ env.DOTNET_VERSION }}
        uses: actions/setup-dotnet@v1
        with:
          dotnet-version: ${{ env.DOTNET_VERSION }}

      # We need Node for npm!
      - name: Setup Node.js ${{ env.NODE_VERSION }}
        uses: actions/setup-node@v1
        with:
          node-version: ${{ env.NODE_VERSION }}

      # Run dotnet build and publish for holly
      - name: Dotnet build and publish for holly
        env:
          NUGET_USERNAME: ${{ secrets.NUGET_USERNAME }}
          NUGET_PASSWORD: ${{ secrets.NUGET_PASSWORD }}
        run: |
          cd '${{ env.AZURE_WEBAPP_PACKAGE_PATH }}'
          dotnet build --configuration Release /warnaserror
          dotnet publish -c Release -o 'app'

      # Deploy holly to Azure Web apps
      - name: 'Run Azure webapp deploy action for holly using publish profile credentials'
        uses: azure/webapps-deploy@v2
        with:
          app-name: ${{ env.AZURE_WEBAPP_NAME }} # Replace with your app name
          publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE  }} # Define secret variable in repository settings as per action documentation
          package: '${{ env.AZURE_WEBAPP_PACKAGE_PATH }}/app'

The Setup Node.js step ensures that npm is available before the dotnet build command is run. The dotnet build command will trigger the Tailwind build process. Thanks to cross-env the same command will work on a Windows, macOS or Linux machine.

I think that's everything!

Original Answer

After reviewing the information in this SO post. Here's a quick rundown of what I ended up implementing. It's not perfect and it needs some work. But it's a good starting point (without making things too complicated).

Created npm Package

I ran npm init in the root of the solution - this created a package.json file. Based on advice I read, this shouldn't be created underneath a project/sub-folder.

Installed/Configured Webpack

Based on the webpack installation guide, I did the following:

npm install webpack webpack-cli --save-dev

In preparation for my Tailwind setup, I also installed the following (see the webpack.config.js file below for more details):

npm install css-loader postcss-loader mini-css-extract-plugin --save-dev
npm install tailwindcss postcss-import

And here's my webpack.config.js file. Note that it's mainly geared towards processing css with Tailwind:

const path = require('path');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const bundleFileName = 'holly';
const dirName = 'Holly/wwwroot/dist';

module.exports = (env, argv) => {
    return {
        mode: argv.mode === "production" ? "production" : "development",
        entry: ['./Holly/wwwroot/js/app.js', './Holly/wwwroot/css/styles.css'],
        output: {
            filename: bundleFileName + '.js',
            path: path.resolve(__dirname, dirName)
        },
        module: {
            rules: [{
                test: /\.css$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                    'postcss-loader'
                ]
            }]
        },
        plugins: [
            new MiniCssExtractPlugin({
                filename: bundleFileName + '.css'
            })
        ]
    };
};

In the case of css, this will take a single entry point styles.css (which is underneath a sub-folder/project called "Holly") and process it with PostCSS/Tailwind CSS. CSS is broken into separate files, but handled by postcss-import (more on that below). All CSS is compiled into a single file called holly.css.

Managing Multiple CSS Files

I also have a postcss.config.js file in the root of my solution:

module.exports = {
  plugins: [
    require('postcss-import'),
    require('tailwindcss'),
    require('autoprefixer'),
  ]
}

This configures PostCSS for Tailwind, but also includes postcss-import. In the Webpack config styles.css is the entry point for processing:

@import "tailwindcss/base";
@import "./holly-base.css";

@import "tailwindcss/components";
@import "./holly-components.css";

@import "tailwindcss/utilities";

As per the Tailwind documentation postcss-import module pre-processes the @import statements before applying Tailwind CSS.

Making it Work

Once everything was configured, I added the following scripts to the npm package:

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --progress --profile",
    "watch": "webpack --progress --profile --watch",
    "production": "webpack --progress --profile --mode production"
  },

To apply Tailwind to the styles.css file, I ran the following command:

npm run build

It would be nice if I could get Visual Studio to run the above command anytime a file is altered (with a guarantee that it will wait for said compilation when debugging the app) and have Visual Studio show me the errors. But that's another kettle of fish/much more difficult. So I settled on the following workflow.

When I'm debugging on my machine, I run this command in an open terminal:

npm run watch

Whenever a .css file changes, a new holly.css file is generated. Which works fine while the app is running - I just have to refresh the page after I've made a change.

The production server runs inside a Docker container. So I ended up calling npm run production in the Dockerfile:

# Latest .NET Core from https://hub.docker.com/_/microsoft-dotnet-core-sdk/ (not the nightly one)
FROM mcr.microsoft.com/dotnet/core/sdk:3.0.100-preview9-disco AS build-env

# Setup npm!
RUN apt-get -y update && apt-get install npm -y && apt-get clean

WORKDIR /app
COPY . ./

# To run Tailwind via Webpack/Postcss
RUN npm install
RUN npm run production

RUN dotnet restore "./Holly/Holly.csproj"
RUN dotnet publish "./Holly/Holly.csproj" -c Release -o out

As you can see, the build process isn't as simple as hitting the "Start" button in Visual Studio. But the workflow is simple enough for others members of the team to learn. If the above workflow becomes problematic, I'll look at what my options are at that point.

The next thing I'll probably focus on is removing unused Tailwind CSS

If there's anything that doesn't make sense or could be done better, please let me know!

Dibromide answered 10/9, 2019 at 2:56 Comment(8)
How this has no upvotes is beyond me. This is probably the simplest answer I've found in about an hour of research.Trottier
In full VS, following code.visualstudio.com/api/working-with-extensions/… you can add the webpack and webpack-dev scripts to the package.json then in vs package manager console you can type npm run webpack-dev to start watching the css files for automatic rebuild on saveTrottier
I realised later that you actually did the same above in your walkthrough just with a different name. The only difference is I ran the watch from the PMC rather than from a standalone command window. Though that blocks the PMC from being used to install nuget packages so you have to stop the watch to do that.Trottier
@Dibromide Do you have something on Github i can look at. I kinda got lost and don't understand the project structureEphemeral
Very helpful. I can't get "watch" to detect changes even using your tailwind config, but I'll figure it out.Towill
@Towill did you set the content property?Dibromide
@Dibromide yes I copy/pasted your example. I'm on Windows and I found it is related to running watch in WSL ubuntu. If I run watch directly on the windows host, it does work.Towill
Oh, I see. I haven't tried running the code in WSL. Thanks for the heads up! I'll slot this away if I end up doing the same thingDibromide
E
14

I recently asked myself the same question. I decided that I didn't like a package.json or the node_modules directory in the project. For these reasons I created a NuGet package with a new build action.

With this build action you can simply give your stylesheet the build action "TailwindCSS" and during the build process the stylesheet will be converted via PostCSS.

For more details you can take a look on its GitHub repo.

Ejection answered 3/6, 2020 at 7:1 Comment(3)
I like this. But what are the prerequisites? I assume node & npm, plus some installed packages?Polypeptide
Yes, you are right. It depends on node & npm, the needed packages (tailwindcss, postcss, ..) will be installed by the MSBuild target.Ejection
I've installed the extension a blazor project,but I found no way to configure input css pathPeraza
D
1

Update to reflect the changes within Tailwind labs:

A new stand alone CLI was created that allows for the use of the CLI without having to install webpack or nodejs / npm.

Download the exe (macOS and linux available), and run it within the proj directory as you would the normal cli.

Details here: https://tailwindcss.com/blog/standalone-cli

Dangerfield answered 30/6, 2022 at 1:25 Comment(0)
S
0

At the time of the question the answers given here were probably the best way. As tailwind evolved there are alternative ways nowadays:

Recently I started using this approach which is much easier and doesn't require npm: https://github.com/tesar-tech/BlazorAndTailwind

Very easy setup and running the tailwind watch in the background works great.

Saturninasaturnine answered 9/9, 2022 at 7:13 Comment(1)
tried your solution and added Exec Command in myproject.csproj,css is created only on project first run,then is never rebuildPeraza
C
0

Installing Tailwind Version 3 - 2023 UPDATE:.

CDN

The first and easiest way to install Tailwind CSS into a Blazor app is to use a CDN. With TW 3 the CDN provides a JS library and no longer just a static CSS file.

To add the CDN you add the following script code to the head of the index.html file for a WASM app and to the _hosts.cshtml for a Blazor Server App.

<head>
...
<title>Tailwind via Play CDN</title>
<base href="/" />
<script src="https://cdn.tailwindcss.com"></script>

Although, this is fine for small projects and learning using CDN complicates matters for production environments.

The whole idea of Single Page Applications is to reduce the constant need for server calls. Now you are using a CDN that needs to call server every time the page loads. This approach is less than ideal for a production environment.

Installing Tailwind into the Project.

There are two basic ways of incorporating TW 3 into your blazor project.

  1. Using the CLI
  2. Post CSS with an existing tool (Like Webpack)

If you are already using some sort of JS implementation then using PostCSS would be best.

When it comes to the CLI there is now with TW 3 two options as to it's implementation.

  1. Using a Package manager like NPM
  2. Using the standalone CLI

Using NPM

This would be the better option if your application already uses NPM or you as a developer are already comfortable in using NPM

You can install TW3 globally on your machine with the following command.

npm install -g tailwindcss

Then from the root of your Blazor app you can install TW3 to your project with the following command.

npx tailwindcss init

This would now create a tailwind.config.js file, which looks like this...

module.exports = {
  content: [],
  theme: {
    extend: {},
  },
  plugins: [],
}

All we need to do to this file is to tell the CLI with file formats it should be looking out for. This can be achieved by adding the following.

Note the the cshtml files are for Blazor Server Apps, while the HTML and Razor files would be sufficient for WASM apps. This code snippet includes all three.

module.exports = {
  content: ["./src/**/*.{razor,html,cshtml}"],
  theme: {
    extend: {},
  },
  plugins: [],
}

Next step is to create a source CSS file

You can create a separate folder and css file for this purpose and add the following lines of code.

@tailwind base;
@tailwind components;
@tailwind utilities;

These are the TW3 directives and they will be replaced with whatever CSS the TW classes in the app may happen to use.

Now we can use the terminal to start the TW3 CLI.

npx tailwindcss -i path-to-file-we-created/file-name.css -o ./wwwroot/app.css --watch

In this case the manufactured CSS will be exported the the wwwroot folder. If you want it somewhere else change the path accordingly.

The final piece of the puzzle is to add a reference to this CSS file to the host page of the Blazor app, like so...

<head>
    ...
    <title>Tailwind via NPM</title>
    <base href="/" />
    <link href="app.css" rel="stylesheet" />
</head>
Using the Standalone CLI.A lot of these steps are the same. You first have to download the standalone CLI from the TW repo, found [here][1]. Do take a moment to download the correct CLI for your OS.

You then add the executable to the root folder of the app and use it in the same way.

Generate the configuration file.

./tailwindcss init

Update it with the file extensions to watch.

module.exports = {
  content: ["./src/**/*.{razor,html,cshtml}"],
  theme: {
    extend: {},
  },
  plugins: [],
}

Create the source CSS file and add the directives.

@tailwind base;
@tailwind components;
@tailwind utilities;

We can then run the stanalone CLI with a similar command.

npx tailwindcss -i path-to-file-we-created/file-name.css -o ./wwwroot/app.css --watch

Finally, we can add a reference to the CSS file to the root HTML file.

<head>
    ...
    <title>Tailwind via Standalone CLI</title>
    <base href="/" />
    <link href="app.css" rel="stylesheet" />
</head>
Checkbook answered 2/5, 2023 at 11:14 Comment(0)
B
0
Bluet answered 20/5 at 5:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.