Skip to content

Commit

Permalink
#2342 add TailwindStyleBuilder project (#2343)
Browse files Browse the repository at this point in the history
* #2342 add TailwindStyleBuilder project

* #2342 Remove console.log and remove using from global imports

* #2342 fix style of setup page
  • Loading branch information
TheHadiAhmadi authored Dec 16, 2024
1 parent ae3e8a4 commit 667b358
Show file tree
Hide file tree
Showing 16 changed files with 297 additions and 80 deletions.
7 changes: 7 additions & 0 deletions src/FluentCMS.sln
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentCMS.Repositories.EFCo
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentCMS.Repositories.EFCore.Sqlite", "Backend\Repositories\FluentCMS.Repositories.EFCore.Sqlite\FluentCMS.Repositories.EFCore.Sqlite.csproj", "{1914FAAB-9B86-4B76-90CA-E16D3C8984FC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentCMS.Web.UI.TailwindStyleBuilder", "Frontend\FluentCMS.Web.UI.TailwindStyleBuilder\FluentCMS.Web.UI.TailwindStyleBuilder.csproj", "{9A614AAB-D386-4D5F-971B-33BA9E820AF8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -259,6 +261,10 @@ Global
{1914FAAB-9B86-4B76-90CA-E16D3C8984FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1914FAAB-9B86-4B76-90CA-E16D3C8984FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1914FAAB-9B86-4B76-90CA-E16D3C8984FC}.Release|Any CPU.Build.0 = Release|Any CPU
{9A614AAB-D386-4D5F-971B-33BA9E820AF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9A614AAB-D386-4D5F-971B-33BA9E820AF8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9A614AAB-D386-4D5F-971B-33BA9E820AF8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9A614AAB-D386-4D5F-971B-33BA9E820AF8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -308,6 +314,7 @@ Global
{8E46C73B-D442-489F-B313-CD26F3C1F2DF} = {1CC73AD2-CFC9-4FE7-96C0-0E4FEFBB66F1}
{E4350BBB-204F-4FD1-A187-7C8381860831} = {1CC73AD2-CFC9-4FE7-96C0-0E4FEFBB66F1}
{1914FAAB-9B86-4B76-90CA-E16D3C8984FC} = {1CC73AD2-CFC9-4FE7-96C0-0E4FEFBB66F1}
{9A614AAB-D386-4D5F-971B-33BA9E820AF8} = {5961A5E0-54A6-42F5-92A2-9A9E48DE2878}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2E9F4217-7A58-48A4-9850-84CD0CDA31DA}
Expand Down
1 change: 1 addition & 0 deletions src/FluentCMS.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<Folder Name="/Frontend/">
<Project Path="Frontend/FluentCMS.Web.ApiClients/FluentCMS.Web.ApiClients.csproj" />
<Project Path="Frontend/FluentCMS.Web.UI.Components/FluentCMS.Web.UI.Components.csproj" />
<Project Path="Frontend/FluentCMS.Web.UI.TailwindStyleBuilder/FluentCMS.Web.UI.TailwindStyleBuilder.csproj" />
<Project Path="Frontend/FluentCMS.Web.UI/FluentCMS.Web.UI.csproj" />
</Folder>
<Folder Name="/Frontend/Plugins/">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@namespace FluentCMS.Web.UI.TailwindStyleBuilder
@rendermode RenderMode.InteractiveServer
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;

namespace FluentCMS.Web.UI;
namespace FluentCMS.Web.UI.TailwindStyleBuilder;

public partial class TailwindStyleBuilder : IAsyncDisposable
{
[Inject]
private ViewState ViewState { get; set; } = default!;
public IJSRuntime JS { get; set; } = default!;

[Inject]
private ApiClientFactory ApiClient { get; set; } = default!;
[Parameter]
public EventCallback<string> OnCssGenerated { get; set; } = default!;

[Inject]
public IJSRuntime JS { get; set; } = default!;
[Parameter]
public string Config { get; set; } = "{}";

private IJSObjectReference Module { get; set; } = default!;

Expand All @@ -23,24 +24,11 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
return;

DotNetRef = DotNetObjectReference.Create(this);
Module = await JS.InvokeAsync<IJSObjectReference>("import", "/_content/FluentCMS.Web.UI/Components/TailwindStyleBuilder.razor.js");

var css = await Module.InvokeAsync<string>("initialize", DotNetRef);
Module = await JS.InvokeAsync<IJSObjectReference>("import", "/_content/FluentCMS.Web.UI.TailwindStyleBuilder/TailwindStyleBuilder.js");

await OnCssGenerated(css);
}

private async Task OnCssGenerated(string css)
{
var cssFilePath = Path.Combine("wwwroot", "tailwind", ViewState.Site.Id.ToString(), $"{ViewState.Page.Id}.css");

var directoryPath = Path.GetDirectoryName(cssFilePath);
if (!Directory.Exists(directoryPath))
{
Directory.CreateDirectory(directoryPath);
}
var css = await Module.InvokeAsync<string>("initialize", DotNetRef, Config);

await File.WriteAllTextAsync(cssFilePath, css);
await OnCssGenerated.InvokeAsync(css);
}

public async ValueTask DisposeAsync()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageId>FluentCMS.Web.UI.TailwindStyleBuilder</PackageId>
<Version>0.0.1</Version>
<Authors>Amir Pournasserian</Authors>
<Company>FluentCMS</Company>
<Description>TailwindStyleBuilder component for blazor.</Description>
<PackageTags>fluentcms;cms;tailwind;style;core</PackageTags>
<RepositoryUrl>https://github.com/fluentcms/FluentCMS</RepositoryUrl>
<PackageProjectUrl>https://fluentcms.com</PackageProjectUrl>
<PackageIcon>icon.png</PackageIcon>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>


<ItemGroup>
<SupportedPlatform Include="browser" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="9.0.0" />
</ItemGroup>

<ItemGroup>
<None Include="Component\*.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

<ItemGroup>
<None Include="..\..\icon.png" Pack="true" PackagePath="icon.png" />
<None Include="README.md" Pack="true" PackagePath="README.md" />
</ItemGroup>
</Project>
150 changes: 150 additions & 0 deletions src/Frontend/FluentCMS.Web.UI.TailwindStyleBuilder/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# Tailwind Style Builder Component for Blazor

TailwindStyleBuilder is a utility for Blazor applications that integrates with Tailwind CSS via CDN to generate dynamic CSS classes. It allows developers to dynamically construct styles for their Blazor components, making it ideal for projects with rich, interactive, and dynamic content. The component provides a seamless way to apply Tailwind CSS styling without requiring a full build pipeline. By eliminating the need for a Node.js-based build system, it streamlines the integration of Tailwind CSS into Blazor projects, ensuring efficient and up-to-date design implementation.

## Features

- **Dynamic Style Building**: Generate CSS dynamically by specifying Tailwind CSS classes.
- **Tailwind CDN Integration**: Uses the Tailwind CSS CDN to ensure up-to-date styling.
- **Blazor Compatibility**: Designed specifically for Blazor-based projects.
- **Support for Dynamic Content**: Ideal for projects that render content dynamically in Blazor and need corresponding Tailwind CSS styling.

## Why TailwindStyleBuilder?
At [FluentCMS](https://github.com/FluentCMS/FluentCMS), we leverage Tailwind CSS to build our UIs. Since page content is dynamic and fetched from the database, it requires efficient handling of styles. In our initial approach, we considered building styles on the server-side using Node.js. However, this method proved to be resource-intensive and did not perform well.

We also explored using the Tailwind CDN for runtime styling. While this can work in some cases, generating styles on the fly during each request negatively impacts page load times and overall performance.

This is where TailwindStyleBuilder comes in. With TailwindStyleBuilder, we optimize style generation by building the CSS only once when the page content is first updated or visited by an admin. On subsequent visits, we serve the pre-generated CSS file, ensuring fast and efficient page rendering without the overhead of runtime styling.

By adopting TailwindStyleBuilder, we improve both performance and resource usage, making it an ideal solution for FluentCMS’s dynamic page content.

## Use Cases

- Dynamically styled components in Blazor.
- Applications generated base where CSS classes ared on runtime data.
- Projects using Tailwind CSS with Blazor, needing lightweight integration without a full build pipeline.

## Installation

To add **TailwindStyleBuilder** to your Blazor project, follow these steps:

1. Install the package via NuGet:

```bash
dotnet add package FluentCMS.Web.UI.TailwindStyleBuilder
```

2. Import the namespace in your Blazor components or pages:

```csharp
@using FluentCMS.Web.UI.TailwindStyleBuilder
```


## Usage

### Basic Example

Here’s a simple example of how to use TailwindStyleBuilder to generate styles dynamically:


Use the component in Head section of your App.razor file:

```csharp
<!DOCTYPE html>
<html lang="en">

<head>
...
<TailwinStyleBuilder />
</head>

<body>
...
<div class="bg-blue-200 text-blue-900 text-4xl">
Hello World!
</div>
</body>

</html>

```

now you are able to use Tailwind in your components.

### Advanced Example

While it functions similarly to using pure Tailwind CDN, our component offers an additional feature: the ability to generate CSS code dynamically and write it to the `wwwroot` directory. This allows you to use the generated CSS file directly, reducing runtime dependencies on the CDN and improving performance.


To utilize the dynamic CSS generation feature, you can build an interactive component that acts as a wrapper and writes the generated CSS to a file: 

```csharp
@rendermode RenderMode.InteractiveServer

@if (System.IO.File.Exists($"wwwroot/{Name}.css"))
{
<link rel="stylesheet" href=@($"/{Name}.css")>
}
else
{
@* You can pass tailwind config using Config property *@
<TailwindStyleBuilder OnCssGenerated="OnCssGenerated" />
}

@code {

[Inject] IWebHostEnvironment Environment { get; set; } = default!;

[Parameter]
public string Name { get; set; } = "generated";

public void OnCssGenerated(string css)
{
var fileName = Name + ".css";
var filePath = Path.Combine(Environment.WebRootPath, "css", fileName);
Directory.CreateDirectory(Path.GetDirectoryName(filePath)!);
File.WriteAllText(filePath, css);
}
}
```
Here is full implementation of Wrapper component used in fluentCMS [TailwindStyleBuilderWrapper.razor](https://github.com/fluentcms/FluentCMS/blob/dev/src/Frontend/FluentCMS.Web.UI/Components/TailwindStyleBuilderWrapper.razor) and [TailwindStyleBuilderWrapper.razor.cs](https://github.com/fluentcms/FluentCMS/blob/dev/src/Frontend/FluentCMS.Web.UI/Components/TailwindStyleBuilderWrapper.razor.cs)

Now you can use this component in your page like this:

```csharp
@page "/example"


@* will use example.css if exists, otherwise it will build that file in first visit *@
<TailwindStyles Name="example" />

<div class="p-4 bg-blue-100 text-blue-900">
<h1>Hello Tailwind!</h1>
</div>

```

## Update Styles

to update the css files, you need to remove previously generated css file.\
in above example, when you update /example pageyou should remove /wwwroot/example.css file

## Benefits

- No need to pre-compile styles or configure a full Tailwind CSS build pipeline.
- Tailwind CSS updates are automatically applied via the CDN.
- Simplifies styling for Blazor components.

## Limitations

- CSS generation happens at runtime, which might impact performance for highly dynamic or complex styling requirements. (only first visit)

## Contributing

Contributions are welcome! Feel free to open issues or submit pull requests on the [GitHub repository](https://github.com/fluentcms/FluentCMS).
## License

This project is licensed under the [MIT License](LICENSE).

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@using Microsoft.AspNetCore.Components.Web
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import "/_content/FluentCMS.Web.UI.TailwindStyleBuilder/tailwind.cdn.js"

export async function initialize(dotnet, config)
{
tailwind.config = JSON.parse(config)
await new Promise(resolve => setTimeout(resolve, 1000));

const styleTags = document.querySelectorAll('style')
let result = ''

styleTags.forEach(style => {
if(style.textContent.slice(0, 20).includes('tailwind')){
result = style.textContent
}
})

return result
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@namespace FluentCMS.Web.UI
@rendermode RenderMode.InteractiveServer
@using FluentCMS.Web.UI.TailwindStyleBuilder

<TailwindStyleBuilder OnCssGenerated="OnCssGenerated" Config="@Config" />
Loading

0 comments on commit 667b358

Please sign in to comment.