blazortailwind-cssasp.net-core-css-isolation

Generating Tailwind CSS output on a per-component basis


I'm using Tailwind in a Blazor project. The build instructions as given work great if my website and component all live in the same project because it scoops up all the files matching the file globs and produces a single CSS output with the excess purged out.

However, in the name of easier reusability, I'd like to package up several of my components into a separate Razor class library and instead make use of CSS isolation on each of these components so that only the classes they use are applied on a per-component basis. It also means that in order to reference their CSS, I need only include the NuGet package and voila, CSS isolation is provided without any further configuration.

However, this means that instead of generating one site-wide CSS file, I instead need to generate these files on a file-by-file basis. In other words, for each file matching "**/*.razor", execute the Tailwind build process against it producing a CSS file specific to that file alone and write it to .razor.css alongside the file.

I found a similar question here that suggests trying to do the same thing, but the answer speaks only to producing a single isolated and project-wide file. Again, I'd like to produce a separate CSS file on a per-component basis.

Does anyone have any suggestions on how I might achieve this? Thank you!


Solution

  • There may certainly be a better way to do this, but I've at least got a solution in place in the meantime.

    This solution requires that there be an existing 'tailwind.config.js' file in the project root from which it will copy the configuration. The 'content' property in the configuration must match an empty list (e.g. content: [],) as it will be replaced as part of the build process. Mine is similar to the following:

    const colors = require('tailwindcss/colors');
    
    module.exports = {
        safelist: [],
        theme: {
            extend: {},
        },
        variants: {
            extend: {
                opacity: ['disabled']
            }
        },
        plugins: [
            require('@tailwindcss/forms'),
            require('@tailwindcss/aspect-ratio'),
            require('@tailwindcss/typography')
        ],
    }
    

    Note that the content value (previously "content: []" in module.exports) has been removed. If left in place, the CLI will favor the configuration value over the CLI argument, so it's removed here.

    Additionally, a CSS file should exist somewhere that contains the standard Tailwind imports (mine is at './Style/site.css') as follows:

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

    I've saved the following PowerShell script to my root project directory called "tailwindBuild.ps1". Now, in my project, the only .razor file I don't want included in this is "_Imports.razor", so it's saved to the $excludedFiles collection - add more there if you want to exclude other files.

    $baseTailwindConfigPath = "./tailwind.config.js"
    $baseTailwindStyleCss = "./Style/site.css"
    
    # Pull a list of paths of all .razor files in project directory
    
    $excludedFiles = @("_Imports.razor")
    $files = Get-ChildItem -Path $dir -Recurse -File -Exclude $excludedFiles -Filter "*.razor" | Select-Object FullName
    
    # Generate per-component tailwind config
    foreach ($file in $files) {
        Write-Output $file
    
        $dirName = Split-Path $file.FullName -Parent
        $fileName = Split-Path $file.FullName -leaf
        $outputCssFileName = $dirName + "\" + $fileName + ".css"
        $tailwindConfigName = "tailwind-config-" + $fileName + ".js"
        $tailwindConfigPath = $dirName + "\" + $tailwindConfigName
                
        # Update the "content: []" line
        $slashFilenameUpdate = $file.FullName.replace("\", "/")
        
        # Execute the Tailwind CLI to build the CSS file
        tailwindcss -c $baseTailwindConfigPath -i $baseTailwindStyleCss -o $outputCssFileName --content $slashFilenameUpdate
    }
    

    Finally, we need to add a couple of build events in our .csproj file as follows. I just put them at the bottom of the file as I'm pretty sure order doesn't matter.

        <Target Name="NodeCheck" BeforeTargets="Compile">
            <Exec Command="node --version" ContinueOnError="true">
                <Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
            </Exec>
            <Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build this projeect" />
            <Message Importance="high" Text="Restoring dependencies via 'npm'. This may take a few minutes." />
            <Exec Command="npm install" />
            <Exec Command="npm i -g tailwindcss" />
        </Target>
    
        <Target Name="PostBuild" AfterTargets="PostBuildEvent">
          <Exec Command="powershell.exe -ExecutionPolicy Bypass -File &quot;$(ProjectDir)tailwindBuild.ps1&quot;" />
        </Target>
    

    Note that you should change the name of the ps1 file there at the end if you're not calling it 'tailwindBuild.ps1' or if you're storing it somewhere else.

    Now, when you build your project, it'll create a tailwind configuration specific to each .razor file not excluded and build the minified Tailwind CSS file under a name matching that necessary for CSS isolation, then delete all these leftover Tailwind configuration files.

    Note that you might also want to update your .gitignore to exclude "*.razor.css" so you don't check in additional generated code.