Skip to content

Commit 667b358

Browse files
#2342 add TailwindStyleBuilder project (#2343)
* #2342 add TailwindStyleBuilder project * #2342 Remove console.log and remove using from global imports * #2342 fix style of setup page
1 parent ae3e8a4 commit 667b358

16 files changed

+297
-80
lines changed

src/FluentCMS.sln

+7
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentCMS.Repositories.EFCo
101101
EndProject
102102
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}"
103103
EndProject
104+
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}"
105+
EndProject
104106
Global
105107
GlobalSection(SolutionConfigurationPlatforms) = preSolution
106108
Debug|Any CPU = Debug|Any CPU
@@ -259,6 +261,10 @@ Global
259261
{1914FAAB-9B86-4B76-90CA-E16D3C8984FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
260262
{1914FAAB-9B86-4B76-90CA-E16D3C8984FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
261263
{1914FAAB-9B86-4B76-90CA-E16D3C8984FC}.Release|Any CPU.Build.0 = Release|Any CPU
264+
{9A614AAB-D386-4D5F-971B-33BA9E820AF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
265+
{9A614AAB-D386-4D5F-971B-33BA9E820AF8}.Debug|Any CPU.Build.0 = Debug|Any CPU
266+
{9A614AAB-D386-4D5F-971B-33BA9E820AF8}.Release|Any CPU.ActiveCfg = Release|Any CPU
267+
{9A614AAB-D386-4D5F-971B-33BA9E820AF8}.Release|Any CPU.Build.0 = Release|Any CPU
262268
EndGlobalSection
263269
GlobalSection(SolutionProperties) = preSolution
264270
HideSolutionNode = FALSE
@@ -308,6 +314,7 @@ Global
308314
{8E46C73B-D442-489F-B313-CD26F3C1F2DF} = {1CC73AD2-CFC9-4FE7-96C0-0E4FEFBB66F1}
309315
{E4350BBB-204F-4FD1-A187-7C8381860831} = {1CC73AD2-CFC9-4FE7-96C0-0E4FEFBB66F1}
310316
{1914FAAB-9B86-4B76-90CA-E16D3C8984FC} = {1CC73AD2-CFC9-4FE7-96C0-0E4FEFBB66F1}
317+
{9A614AAB-D386-4D5F-971B-33BA9E820AF8} = {5961A5E0-54A6-42F5-92A2-9A9E48DE2878}
311318
EndGlobalSection
312319
GlobalSection(ExtensibilityGlobals) = postSolution
313320
SolutionGuid = {2E9F4217-7A58-48A4-9850-84CD0CDA31DA}

src/FluentCMS.slnx

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
<Folder Name="/Frontend/">
2020
<Project Path="Frontend/FluentCMS.Web.ApiClients/FluentCMS.Web.ApiClients.csproj" />
2121
<Project Path="Frontend/FluentCMS.Web.UI.Components/FluentCMS.Web.UI.Components.csproj" />
22+
<Project Path="Frontend/FluentCMS.Web.UI.TailwindStyleBuilder/FluentCMS.Web.UI.TailwindStyleBuilder.csproj" />
2223
<Project Path="Frontend/FluentCMS.Web.UI/FluentCMS.Web.UI.csproj" />
2324
</Folder>
2425
<Folder Name="/Frontend/Plugins/">
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@namespace FluentCMS.Web.UI.TailwindStyleBuilder
2+
@rendermode RenderMode.InteractiveServer

src/Frontend/FluentCMS.Web.UI/Components/TailwindStyleBuilder.razor.cs renamed to src/Frontend/FluentCMS.Web.UI.TailwindStyleBuilder/Component/TailwindStyleBuilder.razor.cs

+10-22
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1+
using Microsoft.AspNetCore.Components;
12
using Microsoft.JSInterop;
23

3-
namespace FluentCMS.Web.UI;
4+
namespace FluentCMS.Web.UI.TailwindStyleBuilder;
45

56
public partial class TailwindStyleBuilder : IAsyncDisposable
67
{
78
[Inject]
8-
private ViewState ViewState { get; set; } = default!;
9+
public IJSRuntime JS { get; set; } = default!;
910

10-
[Inject]
11-
private ApiClientFactory ApiClient { get; set; } = default!;
11+
[Parameter]
12+
public EventCallback<string> OnCssGenerated { get; set; } = default!;
1213

13-
[Inject]
14-
public IJSRuntime JS { get; set; } = default!;
14+
[Parameter]
15+
public string Config { get; set; } = "{}";
1516

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

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

2526
DotNetRef = DotNetObjectReference.Create(this);
26-
Module = await JS.InvokeAsync<IJSObjectReference>("import", "/_content/FluentCMS.Web.UI/Components/TailwindStyleBuilder.razor.js");
27-
28-
var css = await Module.InvokeAsync<string>("initialize", DotNetRef);
27+
Module = await JS.InvokeAsync<IJSObjectReference>("import", "/_content/FluentCMS.Web.UI.TailwindStyleBuilder/TailwindStyleBuilder.js");
2928

30-
await OnCssGenerated(css);
31-
}
32-
33-
private async Task OnCssGenerated(string css)
34-
{
35-
var cssFilePath = Path.Combine("wwwroot", "tailwind", ViewState.Site.Id.ToString(), $"{ViewState.Page.Id}.css");
36-
37-
var directoryPath = Path.GetDirectoryName(cssFilePath);
38-
if (!Directory.Exists(directoryPath))
39-
{
40-
Directory.CreateDirectory(directoryPath);
41-
}
29+
var css = await Module.InvokeAsync<string>("initialize", DotNetRef, Config);
4230

43-
await File.WriteAllTextAsync(cssFilePath, css);
31+
await OnCssGenerated.InvokeAsync(css);
4432
}
4533

4634
public async ValueTask DisposeAsync()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Razor">
2+
<PropertyGroup>
3+
<TargetFramework>net9.0</TargetFramework>
4+
<Nullable>enable</Nullable>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
8+
<PackageId>FluentCMS.Web.UI.TailwindStyleBuilder</PackageId>
9+
<Version>0.0.1</Version>
10+
<Authors>Amir Pournasserian</Authors>
11+
<Company>FluentCMS</Company>
12+
<Description>TailwindStyleBuilder component for blazor.</Description>
13+
<PackageTags>fluentcms;cms;tailwind;style;core</PackageTags>
14+
<RepositoryUrl>https://github.com/fluentcms/FluentCMS</RepositoryUrl>
15+
<PackageProjectUrl>https://fluentcms.com</PackageProjectUrl>
16+
<PackageIcon>icon.png</PackageIcon>
17+
<PackageLicenseExpression>MIT</PackageLicenseExpression>
18+
<PackageReadmeFile>README.md</PackageReadmeFile>
19+
</PropertyGroup>
20+
21+
22+
<ItemGroup>
23+
<SupportedPlatform Include="browser" />
24+
</ItemGroup>
25+
26+
<ItemGroup>
27+
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="9.0.0" />
28+
</ItemGroup>
29+
30+
<ItemGroup>
31+
<None Include="Component\*.js">
32+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
33+
</None>
34+
</ItemGroup>
35+
36+
<ItemGroup>
37+
<None Include="..\..\icon.png" Pack="true" PackagePath="icon.png" />
38+
<None Include="README.md" Pack="true" PackagePath="README.md" />
39+
</ItemGroup>
40+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# Tailwind Style Builder Component for Blazor
2+
3+
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.
4+
5+
## Features
6+
7+
- **Dynamic Style Building**: Generate CSS dynamically by specifying Tailwind CSS classes.
8+
- **Tailwind CDN Integration**: Uses the Tailwind CSS CDN to ensure up-to-date styling.
9+
- **Blazor Compatibility**: Designed specifically for Blazor-based projects.
10+
- **Support for Dynamic Content**: Ideal for projects that render content dynamically in Blazor and need corresponding Tailwind CSS styling.
11+
12+
## Why TailwindStyleBuilder?
13+
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.
14+
15+
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.
16+
17+
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.
18+
19+
By adopting TailwindStyleBuilder, we improve both performance and resource usage, making it an ideal solution for FluentCMS’s dynamic page content.
20+
21+
## Use Cases
22+
23+
- Dynamically styled components in Blazor.
24+
- Applications generated base where CSS classes ared on runtime data.
25+
- Projects using Tailwind CSS with Blazor, needing lightweight integration without a full build pipeline.
26+
27+
## Installation
28+
29+
To add **TailwindStyleBuilder** to your Blazor project, follow these steps:
30+
31+
1. Install the package via NuGet:
32+
33+
```bash
34+
dotnet add package FluentCMS.Web.UI.TailwindStyleBuilder
35+
```
36+
37+
2. Import the namespace in your Blazor components or pages:
38+
39+
```csharp
40+
@using FluentCMS.Web.UI.TailwindStyleBuilder
41+
```
42+
43+
44+
## Usage
45+
46+
### Basic Example
47+
48+
Here’s a simple example of how to use TailwindStyleBuilder to generate styles dynamically:
49+
50+
51+
Use the component in Head section of your App.razor file:
52+
53+
```csharp
54+
<!DOCTYPE html>
55+
<html lang="en">
56+
57+
<head>
58+
...
59+
<TailwinStyleBuilder />
60+
</head>
61+
62+
<body>
63+
...
64+
<div class="bg-blue-200 text-blue-900 text-4xl">
65+
Hello World!
66+
</div>
67+
</body>
68+
69+
</html>
70+
71+
```
72+
73+
now you are able to use Tailwind in your components.
74+
75+
### Advanced Example
76+
77+
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.
78+
79+
80+
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: 
81+
82+
```csharp
83+
@rendermode RenderMode.InteractiveServer
84+
85+
@if (System.IO.File.Exists($"wwwroot/{Name}.css"))
86+
{
87+
<link rel="stylesheet" href=@($"/{Name}.css")>
88+
}
89+
else
90+
{
91+
@* You can pass tailwind config using Config property *@
92+
<TailwindStyleBuilder OnCssGenerated="OnCssGenerated" />
93+
}
94+
95+
@code {
96+
97+
[Inject] IWebHostEnvironment Environment { get; set; } = default!;
98+
99+
[Parameter]
100+
public string Name { get; set; } = "generated";
101+
102+
public void OnCssGenerated(string css)
103+
{
104+
var fileName = Name + ".css";
105+
var filePath = Path.Combine(Environment.WebRootPath, "css", fileName);
106+
Directory.CreateDirectory(Path.GetDirectoryName(filePath)!);
107+
File.WriteAllText(filePath, css);
108+
}
109+
}
110+
```
111+
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)
112+
113+
Now you can use this component in your page like this:
114+
115+
```csharp
116+
@page "/example"
117+
118+
119+
@* will use example.css if exists, otherwise it will build that file in first visit *@
120+
<TailwindStyles Name="example" />
121+
122+
<div class="p-4 bg-blue-100 text-blue-900">
123+
<h1>Hello Tailwind!</h1>
124+
</div>
125+
126+
```
127+
128+
## Update Styles
129+
130+
to update the css files, you need to remove previously generated css file.\
131+
in above example, when you update /example pageyou should remove /wwwroot/example.css file
132+
133+
## Benefits
134+
135+
- No need to pre-compile styles or configure a full Tailwind CSS build pipeline.
136+
- Tailwind CSS updates are automatically applied via the CDN.
137+
- Simplifies styling for Blazor components.
138+
139+
## Limitations
140+
141+
- CSS generation happens at runtime, which might impact performance for highly dynamic or complex styling requirements. (only first visit)
142+
143+
## Contributing
144+
145+
Contributions are welcome! Feel free to open issues or submit pull requests on the [GitHub repository](https://github.com/fluentcms/FluentCMS).
146+
147+
## License
148+
149+
This project is licensed under the [MIT License](LICENSE).
150+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@using Microsoft.AspNetCore.Components.Web
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import "/_content/FluentCMS.Web.UI.TailwindStyleBuilder/tailwind.cdn.js"
2+
3+
export async function initialize(dotnet, config)
4+
{
5+
tailwind.config = JSON.parse(config)
6+
await new Promise(resolve => setTimeout(resolve, 1000));
7+
8+
const styleTags = document.querySelectorAll('style')
9+
let result = ''
10+
11+
styleTags.forEach(style => {
12+
if(style.textContent.slice(0, 20).includes('tailwind')){
13+
result = style.textContent
14+
}
15+
})
16+
17+
return result
18+
}

src/Frontend/FluentCMS.Web.UI/Components/TailwindStyleBuilder.razor

-2
This file was deleted.

src/Frontend/FluentCMS.Web.UI/Components/TailwindStyleBuilder.razor.js

-54
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@namespace FluentCMS.Web.UI
2+
@rendermode RenderMode.InteractiveServer
3+
@using FluentCMS.Web.UI.TailwindStyleBuilder
4+
5+
<TailwindStyleBuilder OnCssGenerated="OnCssGenerated" Config="@Config" />

0 commit comments

Comments
 (0)