Skip to content

Commit 6f6cd57

Browse files
authored
feat: add Tailwind CSS support behind the feature flag (#22611)
* feat: add Tailwind CSS support behind the feature flag Fixes #21643 * fix: add Tailwind CSS file required by its tooling * test: add IT test module for Tailwind CSS * chore: formatting * chore: fix javadoc * test: add TaskGenerateTailwindCssTest
1 parent 1c777e1 commit 6f6cd57

File tree

16 files changed

+403
-1
lines changed

16 files changed

+403
-1
lines changed

flow-server/src/main/java/com/vaadin/experimental/CoreFeatureFlagProvider.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,14 @@ public class CoreFeatureFlagProvider implements FeatureFlagProvider {
4545
"Copilot experimental features", "copilotExperimentalFeatures",
4646
"https://vaadin.com/docs/latest/tools/copilot", false, null);
4747

48+
public static final Feature TAILWIND_CSS = new Feature(
49+
"Tailwind CSS framework", "tailwindCss",
50+
"https://github.com/vaadin/flow/issues/21643", true, null);
51+
4852
@Override
4953
public List<Feature> getFeatures() {
5054
return List.of(COLLABORATION_ENGINE_BACKEND, FLOW_FULLSTACK_SIGNALS,
5155
ACCESSIBLE_DISABLED_BUTTONS, COMPONENT_STYLE_INJECTION,
52-
COPILOT_EXPERIMENTAL);
56+
COPILOT_EXPERIMENTAL, TAILWIND_CSS);
5357
}
5458
}

flow-server/src/main/java/com/vaadin/flow/server/frontend/AbstractUpdateImports.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
import static com.vaadin.flow.server.Constants.PACKAGE_JSON;
6363
import static com.vaadin.flow.server.Constants.RESOURCES_FRONTEND_DEFAULT;
6464
import static com.vaadin.flow.server.frontend.FrontendUtils.FRONTEND_FOLDER_ALIAS;
65+
import static com.vaadin.flow.server.frontend.FrontendUtils.TAILWIND_CSS;
6566

6667
/**
6768
* Common logic for generate import file JS content.
@@ -94,6 +95,8 @@ abstract class AbstractUpdateImports implements Runnable {
9495
.compile("^\\s*injectGlobalCss\\(([^,]+),.*$");
9596
private static final String INJECT_WC_CSS = "injectGlobalWebcomponentCss(%s);";
9697

98+
private static final String TAILWIND_IMPORT = "./" + TAILWIND_CSS;
99+
97100
private static final String THEMABLE_MIXIN_IMPORT = "import { css, unsafeCSS, registerStyles } from '@vaadin/vaadin-themable-mixin';";
98101
private static final String REGISTER_STYLES_FOR_TEMPLATE = CSS_IMPORT_AND_MAKE_LIT_CSS
99102
+ "%n" + "registerStyles('%s', $css_%1$d%s);";
@@ -305,6 +308,9 @@ private Map<File, List<String>> process(Map<ChunkInfo, List<CssData>> css,
305308
Map<ChunkInfo, List<String>> lazyCss = new LinkedHashMap<>();
306309
List<CssData> eagerCssData = new ArrayList<>();
307310
List<CssData> appShellCssData = new ArrayList<>();
311+
if (FrontendUtils.isTailwindCssEnabled(options)) {
312+
appShellCssData.add(new CssData(TAILWIND_IMPORT, null, null, null));
313+
}
308314
for (Entry<ChunkInfo, List<String>> entry : javascript.entrySet()) {
309315
if (isLazyRoute(entry.getKey())) {
310316
lazyJavascript.put(entry.getKey(), entry.getValue());

flow-server/src/main/java/com/vaadin/flow/server/frontend/FrontendUtils.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import org.slf4j.LoggerFactory;
4949
import tools.jackson.databind.JsonNode;
5050

51+
import com.vaadin.experimental.CoreFeatureFlagProvider;
5152
import com.vaadin.flow.di.Lookup;
5253
import com.vaadin.flow.di.ResourceProvider;
5354
import com.vaadin.flow.function.DeploymentConfiguration;
@@ -268,6 +269,11 @@ public class FrontendUtils {
268269

269270
public static final String ROUTES_JS = "routes.js";
270271

272+
/**
273+
* File name of the Tailwind CSS framework integration entrypoint.
274+
*/
275+
public static final String TAILWIND_CSS = "tailwind.css";
276+
271277
/**
272278
* Default generated path for generated frontend files.
273279
*/
@@ -1548,4 +1554,16 @@ public static List<String> getClientRoutes() {
15481554
VaadinService.getCurrent().getDeploymentConfiguration());
15491555
}
15501556

1557+
/**
1558+
* Checks if integration with Tailwind CSS framework is enabled.
1559+
*
1560+
* @param options
1561+
* the build options
1562+
* @return true if Tailwind CSS integration is enabled, false otherwise
1563+
*/
1564+
public static boolean isTailwindCssEnabled(Options options) {
1565+
return options.getFeatureFlags()
1566+
.isEnabled(CoreFeatureFlagProvider.TAILWIND_CSS);
1567+
}
1568+
15511569
}

flow-server/src/main/java/com/vaadin/flow/server/frontend/NodeTasks.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public class NodeTasks implements FallibleCommand {
6363
TaskGenerateIndexHtml.class,
6464
TaskGenerateIndexTs.class,
6565
TaskGenerateReactFiles.class,
66+
TaskGenerateTailwindCss.class,
6667
TaskUpdateOldIndexTs.class,
6768
TaskGenerateViteDevMode.class,
6869
TaskGenerateCommercialBanner.class,
@@ -294,6 +295,9 @@ private void addBootstrapTasks(Options options) {
294295
|| options.isBundleBuild()) {
295296
commands.add(new TaskGenerateIndexTs(options));
296297
commands.add(new TaskGenerateReactFiles(options));
298+
if (FrontendUtils.isTailwindCssEnabled(options)) {
299+
commands.add(new TaskGenerateTailwindCss(options));
300+
}
297301
if (!options.isProductionMode()) {
298302
commands.add(new TaskGenerateViteDevMode(options));
299303
}

flow-server/src/main/java/com/vaadin/flow/server/frontend/NodeUpdater.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,10 @@ Map<String, String> getDefaultDependencies() {
310310
dependencies
311311
.putAll(readDependencies("vaadin-router", "dependencies"));
312312
}
313+
if (FrontendUtils.isTailwindCssEnabled(options)) {
314+
dependencies
315+
.putAll(readDependencies("tailwindcss", "dependencies"));
316+
}
313317
putHillaComponentsDependencies(dependencies, "dependencies");
314318
return dependencies;
315319
}
@@ -373,6 +377,9 @@ Map<String, String> getDefaultDevDependencies() {
373377
defaults.putAll(
374378
readDependencies("react-router", "devDependencies"));
375379
}
380+
if (FrontendUtils.isTailwindCssEnabled(options)) {
381+
defaults.putAll(readDependencies("tailwindcss", "devDependencies"));
382+
}
376383

377384
// Add workbox dependencies only when PWA is enabled
378385
if (frontDeps != null && frontDeps.getPwaConfiguration() != null
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright 2000-2025 Vaadin Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package com.vaadin.flow.server.frontend;
17+
18+
import java.io.File;
19+
import java.io.IOException;
20+
import java.io.InputStream;
21+
22+
import org.apache.commons.io.IOUtils;
23+
24+
import static com.vaadin.flow.server.frontend.FrontendUtils.TAILWIND_CSS;
25+
import static java.nio.charset.StandardCharsets.UTF_8;
26+
27+
/**
28+
* Generate <code>tailwind.css</code> if it is missing in the generated frontend
29+
* folder.
30+
* <p>
31+
* For internal use only. May be renamed or removed in a future release.
32+
*
33+
* @since 25.0
34+
*/
35+
public class TaskGenerateTailwindCss extends AbstractTaskClientGenerator {
36+
37+
private static final String RELATIVE_SOURCE_PATH_MARKER = "#relativeSourcePath#";
38+
39+
private String relativeSourcePath;
40+
41+
private File tailwindCss;
42+
43+
/**
44+
* Create a task to generate <code>tailwind.css</code> integration file.
45+
*
46+
* @param options
47+
* the task options
48+
*/
49+
TaskGenerateTailwindCss(Options options) {
50+
tailwindCss = new File(options.getFrontendGeneratedFolder(),
51+
TAILWIND_CSS);
52+
relativeSourcePath = options.getFrontendGeneratedFolder().toPath()
53+
.relativize(options.getNpmFolder().toPath().resolve("src"))
54+
.toString();
55+
// Use forward slash as a separator
56+
relativeSourcePath = relativeSourcePath.replace(File.separator, "/");
57+
}
58+
59+
@Override
60+
protected String getFileContent() throws IOException {
61+
try (InputStream indexStream = getClass()
62+
.getResourceAsStream(TAILWIND_CSS)) {
63+
var template = IOUtils.toString(indexStream, UTF_8);
64+
template = template.replace(RELATIVE_SOURCE_PATH_MARKER,
65+
relativeSourcePath);
66+
return template;
67+
}
68+
}
69+
70+
@Override
71+
protected File getGeneratedFile() {
72+
return tailwindCss;
73+
}
74+
75+
@Override
76+
protected boolean shouldGenerate() {
77+
return true;
78+
}
79+
}

flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskUpdateVite.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ private void createGeneratedConfig() throws IOException {
133133
.replace("#frontendExtraFileExtensions#",
134134
getFrontendExtraFileExtensions());
135135
template = updateFileSystemRouterVitePlugin(template);
136+
template = updateTailwindCssVitePlugin(template);
136137

137138
FileIOUtils.writeIfChanged(generatedConfigFile, template);
138139
log().debug("Created vite generated configuration file: '{}'",
@@ -166,6 +167,18 @@ private String updateFileSystemRouterVitePlugin(String template) {
166167
.replace("//#vitePluginFileSystemRouter#", "");
167168
}
168169

170+
private String updateTailwindCssVitePlugin(String template) {
171+
if (FrontendUtils.isTailwindCssEnabled(options)) {
172+
return template
173+
.replace("//#tailwindcssVitePluginImport#",
174+
"import tailwindcss from '@tailwindcss/vite';")
175+
.replace("//#tailwindcssVitePlugin#", "tailwindcss(),");
176+
} else {
177+
return template.replace("//#tailwindcssVitePluginImport#", "")
178+
.replace("//#tailwindcssVitePlugin#", "");
179+
}
180+
}
181+
169182
private Logger log() {
170183
return LoggerFactory.getLogger(this.getClass());
171184
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"private": true,
3+
"description": "Tailwind CSS dependencies",
4+
"dependencies": {
5+
"tailwindcss": "4.1.16"
6+
},
7+
"devDependencies": {
8+
"@tailwindcss/vite": "4.1.16"
9+
}
10+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@import 'tailwindcss/theme.css';
2+
@import 'tailwindcss/utilities.css';
3+
4+
@source "#relativeSourcePath#";

flow-server/src/main/resources/vite.generated.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import vaadinBundlesPlugin from '#buildFolder#/plugins/vite-plugin-vaadin-bundle
3333

3434
import { visualizer } from 'rollup-plugin-visualizer';
3535
import reactPlugin from '@vitejs/plugin-react';
36+
//#tailwindcssVitePluginImport#
3637

3738
//#vitePluginFileSystemRouterImport#
3839

@@ -565,6 +566,7 @@ export const vaadinConfig: UserConfigFn = (env) => {
565566
].filter(Boolean)
566567
}
567568
}),
569+
//#tailwindcssVitePlugin#
568570
productionMode && vaadinI18n({
569571
cwd: __dirname,
570572
meta: {

0 commit comments

Comments
 (0)