From c9d31b7ab8333301c23bb6e220247770061f7248 Mon Sep 17 00:00:00 2001 From: Mikael Grankvist Date: Mon, 9 Nov 2020 07:08:46 +0200 Subject: [PATCH 01/17] feat: Theme can be defined as string or class Add a string definition for theme that matches the application theme in the theme folder. Change old version to use themeClass for theme selection. Fixes #9281 --- .../java/com/vaadin/flow/demo/DemoView.java | 2 +- .../flow/plugin/maven/TestComponents.java | 2 +- .../webcomponent/WebComponentUI.java | 2 +- .../FrontendWebComponentGenerator.java | 8 ++- .../flow/server/frontend/NodeTasks.java | 13 ++-- .../server/frontend/TaskUpdateImports.java | 7 +++ .../frontend/TaskUpdateThemeImport.java | 60 +++++++++++++++++++ .../scanner/FrontendClassVisitor.java | 11 +++- .../scanner/FrontendDependencies.java | 20 +++++-- .../scanner/FullDependenciesScanner.java | 25 ++++---- .../server/frontend/scanner/ThemeData.java | 32 +++++++--- .../webcomponent/WebComponentGenerator.java | 26 +++++--- .../WebComponentModulesWriter.java | 27 +++++---- .../java/com/vaadin/flow/theme/Theme.java | 12 +++- .../vaadin/flow/theme/ThemeDefinition.java | 15 ++++- .../webcomponent-script-template.js | 3 + .../EmptyByteScannerDataTestComponents.java | 2 +- .../server/frontend/NodeTestComponents.java | 2 +- .../frontend/UpdateThemedImportsTest.java | 2 +- .../scanner/FullDependenciesScannerTest.java | 2 +- .../scanner/ScannerTestComponents.java | 10 ++-- .../frontend/scanner/ScannerThemeTest.java | 2 +- .../scanner/samples/RouteLayoutComponent.java | 2 +- .../WebComponentGeneratorTest.java | 6 +- .../WebComponentModulesWriterTest.java | 18 +++--- .../webcomponent/ThemedComponentExporter.java | 2 +- .../ThemedVariantComponentExporter.java | 2 +- .../ui/lumo/ExplicitLumoTemplateView.java | 2 +- .../material/MaterialThemedTemplateView.java | 2 +- .../vaadin/flow/misc/ui/MiscelaneousView.java | 2 +- .../ui/theme/NpmThemedComponentView.java | 2 +- 31 files changed, 234 insertions(+), 89 deletions(-) create mode 100644 flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskUpdateThemeImport.java diff --git a/flow-component-demo-helpers/src/main/java/com/vaadin/flow/demo/DemoView.java b/flow-component-demo-helpers/src/main/java/com/vaadin/flow/demo/DemoView.java index cde19996b15..bb89da1cfac 100644 --- a/flow-component-demo-helpers/src/main/java/com/vaadin/flow/demo/DemoView.java +++ b/flow-component-demo-helpers/src/main/java/com/vaadin/flow/demo/DemoView.java @@ -47,7 +47,7 @@ * @since 1.0 */ @Tag(Tag.DIV) -@Theme(Lumo.class) +@Theme(themeClass = Lumo.class) @StyleSheet("frontend/src/css/demo.css") @StyleSheet("frontend/src/css/prism.css") public abstract class DemoView extends Component diff --git a/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/TestComponents.java b/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/TestComponents.java index f112740dd80..ccb71a59b88 100644 --- a/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/TestComponents.java +++ b/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/TestComponents.java @@ -76,7 +76,7 @@ public static class TranslatedImports extends Component { } @Route - @Theme(value = Lumo.class, variant = Lumo.DARK) + @Theme(themeClass = Lumo.class, variant = Lumo.DARK) public static class MainView extends Component { ButtonComponent buttonComponent; IconComponent iconComponent; diff --git a/flow-server/src/main/java/com/vaadin/flow/component/webcomponent/WebComponentUI.java b/flow-server/src/main/java/com/vaadin/flow/component/webcomponent/WebComponentUI.java index 9642a549f94..37d32a21436 100644 --- a/flow-server/src/main/java/com/vaadin/flow/component/webcomponent/WebComponentUI.java +++ b/flow-server/src/main/java/com/vaadin/flow/component/webcomponent/WebComponentUI.java @@ -290,7 +290,7 @@ private void assignThemeVariant() { return; } AbstractTheme themeInstance = Instantiator.get(this) - .getOrCreate(theme.get().value()); + .getOrCreate(theme.get().themeClass()); ThemeDefinition definition = new ThemeDefinition(theme.get()); Map attributes = themeInstance .getHtmlAttributes(definition.getVariant()); diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/FrontendWebComponentGenerator.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/FrontendWebComponentGenerator.java index f5c60dbc58b..d233560db16 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/FrontendWebComponentGenerator.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/FrontendWebComponentGenerator.java @@ -25,6 +25,8 @@ import com.vaadin.flow.component.WebComponentExporterFactory; import com.vaadin.flow.server.frontend.scanner.ClassFinder; import com.vaadin.flow.server.webcomponent.WebComponentModulesWriter; +import com.vaadin.flow.theme.Theme; +import com.vaadin.flow.theme.ThemeDefinition; /** * Generates embeddable web component files in npm mode, hiding the complexity @@ -72,7 +74,8 @@ public FrontendWebComponentGenerator(ClassFinder finder) { * @throws java.lang.IllegalStateException * if {@code finder} cannot locate required classes */ - public Set generateWebComponents(File outputDirectory) { + public Set generateWebComponents(File outputDirectory, + ThemeDefinition theme) { try { final Class writerClass = finder .loadClass(WebComponentModulesWriter.class.getName()); @@ -83,7 +86,8 @@ public Set generateWebComponents(File outputDirectory) { .forEach(exporterRelatedClasses::add); return WebComponentModulesWriter.DirectoryWriter .generateWebComponentsToDirectory(writerClass, - exporterRelatedClasses, outputDirectory, false); + exporterRelatedClasses, outputDirectory, false, + theme.getName()); } catch (ClassNotFoundException e) { throw new IllegalStateException( "Unable to locate a required class using custom class " diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/NodeTasks.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/NodeTasks.java index ac9f7ffe89a..4f49e9135df 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/NodeTasks.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/NodeTasks.java @@ -504,15 +504,15 @@ private NodeTasks(Builder builder) { FrontendDependenciesScanner frontendDependencies = null; if (builder.enablePackagesUpdate || builder.enableImportsUpdate) { + frontendDependencies = new FrontendDependenciesScanner.FrontendDependenciesScannerFactory() + .createScanner(!builder.useByteCodeScanner, classFinder, + builder.generateEmbeddableWebComponents); + if (builder.generateEmbeddableWebComponents) { FrontendWebComponentGenerator generator = new FrontendWebComponentGenerator( classFinder); - generator.generateWebComponents(builder.generatedFolder); + generator.generateWebComponents(builder.generatedFolder, frontendDependencies.getThemeDefinition()); } - - frontendDependencies = new FrontendDependenciesScanner.FrontendDependenciesScannerFactory() - .createScanner(!builder.useByteCodeScanner, classFinder, - builder.generateEmbeddableWebComponents); } if (builder.createMissingPackageJson) { @@ -578,6 +578,9 @@ private NodeTasks(Builder builder) { builder.npmFolder, builder.generatedFolder, builder.frontendDirectory, builder.tokenFile, builder.tokenFileData, builder.enablePnpm)); + + commands.add(new TaskUpdateThemeImport(builder.npmFolder, + frontendDependencies.getThemeDefinition())); } } diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskUpdateImports.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskUpdateImports.java index b471f052b19..0d94e147b0c 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskUpdateImports.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskUpdateImports.java @@ -132,6 +132,13 @@ protected Collection getThemeLines() { Collection lines = new ArrayList<>(); AbstractTheme theme = getTheme(); ThemeDefinition themeDef = getThemeDefinition(); + + if (!themeDef.getName().equals("")) { + // If we define a theme name we need to import theme/theme.js + lines.add("import {applyTheme} from 'theme/theme.js';"); + lines.add("applyTheme(document);"); + } + if (theme != null) { if (!theme.getHeaderInlineContents().isEmpty()) { lines.add(""); diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskUpdateThemeImport.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskUpdateThemeImport.java new file mode 100644 index 00000000000..57cfe20bf4d --- /dev/null +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskUpdateThemeImport.java @@ -0,0 +1,60 @@ +/* + * Copyright 2000-2020 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.flow.server.frontend; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import com.vaadin.flow.server.ExecutionFailedException; +import com.vaadin.flow.theme.ThemeDefinition; + +import org.apache.commons.io.FileUtils; + +/** + * Task for generating the theme.js file for importing application theme. + * + * @since + */ +public class TaskUpdateThemeImport implements FallibleCommand { + + private File themeImportFile; + private ThemeDefinition theme; + + TaskUpdateThemeImport(File npmFolder, ThemeDefinition theme) { + File nodeModules = new File(npmFolder, FrontendUtils.NODE_MODULES); + File flowFrontend = new File(nodeModules, FrontendUtils.FLOW_NPM_PACKAGE_NAME); + this.themeImportFile = new File(new File(flowFrontend, "theme"), "theme.js"); + this.theme = theme; + } + + @Override + public void execute() throws ExecutionFailedException { + if (theme.getName().isEmpty()) { + return; + } + themeImportFile.getParentFile().mkdirs(); + + try { + FileUtils.write( + themeImportFile, "import {applyTheme as _applyTheme} from 'theme/" + theme.getName() + "/" + + theme.getName() + ".js';\nexport const applyTheme = _applyTheme;\n", + StandardCharsets.UTF_8); + } catch (IOException e) { + throw new ExecutionFailedException("Unable to write theme import file", e); + } + } +} diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/scanner/FrontendClassVisitor.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/scanner/FrontendClassVisitor.java index eb81b86a2bc..9632b0d697b 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/scanner/FrontendClassVisitor.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/scanner/FrontendClassVisitor.java @@ -44,6 +44,7 @@ final class FrontendClassVisitor extends ClassVisitor { private static final String VARIANT = "variant"; private static final String LAYOUT = "layout"; static final String VALUE = "value"; + static final String THEME_CLASS = "themeClass"; static final String VERSION = "version"; static final String ID = "id"; static final String INCLUDE = "include"; @@ -160,8 +161,10 @@ public void visit(String name, Object value) { @Override public void visit(String name, Object value) { if (VALUE.equals(name)) { - endPoint.theme.name = ((Type) value).getClassName(); - children.add(endPoint.theme.name); + endPoint.theme.themeName = (String)value; + } else if (THEME_CLASS.equals(name)) { + endPoint.theme.themeClass = ((Type) value).getClassName(); + children.add(endPoint.theme.themeClass); } else if (VARIANT.equals(name)) { endPoint.theme.variant = value.toString(); } @@ -171,7 +174,9 @@ public void visit(String name, Object value) { themeLayoutVisitor = new RepeatedAnnotationVisitor() { @Override public void visit(String name, Object value) { - if (VALUE.equals(name) && endPoint.theme.name == null) { + if (VALUE.equals(name)) { + themeRouteVisitor.visit(name, value); + } else if (THEME_CLASS.equals(name) && endPoint.theme.themeClass == null) { themeRouteVisitor.visit(name, value); } else if (VARIANT.equals(name) && endPoint.theme.variant.isEmpty()) { diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/scanner/FrontendDependencies.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/scanner/FrontendDependencies.java index de0d3464204..bd4a3fd8e52 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/scanner/FrontendDependencies.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/scanner/FrontendDependencies.java @@ -267,13 +267,14 @@ private void computeApplicationTheme() throws ClassNotFoundException, visitClass(endPoint.getLayout(), endPoint, false); } if (endPoint.getTheme() != null) { - visitClass(endPoint.getTheme().getName(), endPoint, true); + visitClass(endPoint.getTheme().getThemeClass(), endPoint, true); } } Set themes = endPoints.values().stream() // consider only endPoints with theme information - .filter(data -> data.getTheme().getName() != null + .filter(data -> data.getTheme().getThemeClass() != null || + data.getTheme().getThemeName() != null || data.getTheme().isNotheme()) .map(EndPointData::getTheme) // Remove duplicates by returning a set @@ -281,12 +282,13 @@ private void computeApplicationTheme() throws ClassNotFoundException, if (themes.size() > 1) { String names = endPoints.values().stream() - .filter(data -> data.getTheme().getName() != null + .filter(data -> data.getTheme().getThemeClass() != null || + data.getTheme().getThemeName() != null || data.getTheme().isNotheme()) .map(data -> "found '" + (data.getTheme().isNotheme() ? NoTheme.class.getName() - : data.getTheme().getName()) + : data.getTheme().getThemeName()) + "' in '" + data.getName() + "'") .collect(Collectors.joining("\n ")); throw new IllegalStateException( @@ -296,6 +298,7 @@ private void computeApplicationTheme() throws ClassNotFoundException, Class theme = null; String variant = ""; + String themeName = ""; if (themes.isEmpty()) { theme = getDefaultTheme(); } else { @@ -303,14 +306,19 @@ private void computeApplicationTheme() throws ClassNotFoundException, ThemeData themeData = themes.iterator().next(); if (!themeData.isNotheme()) { variant = themeData.getVariant(); - theme = getFinder().loadClass(themeData.getName()); + String themeClass = themeData.getThemeClass(); + if (themeClass == null) { + themeClass = LUMO; + } + theme = getFinder().loadClass(themeClass); + themeName = themeData.getThemeName(); } } // theme could be null when lumo is not found or when a NoTheme found if (theme != null) { - themeDefinition = new ThemeDefinition(theme, variant); + themeDefinition = new ThemeDefinition(theme, variant, themeName); themeInstance = new ThemeWrapper(theme); } } diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/scanner/FullDependenciesScanner.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/scanner/FullDependenciesScanner.java index 3931886f9ef..7077701c03a 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/scanner/FullDependenciesScanner.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/scanner/FullDependenciesScanner.java @@ -264,7 +264,7 @@ private void discoverTheme() { ThemeData data = verifyTheme(); if (data == null) { - setupTheme(getLumoTheme(), ""); + setupTheme(getLumoTheme(), "", ""); return; } @@ -274,18 +274,19 @@ private void discoverTheme() { try { Class theme = getFinder() - .loadClass(data.name); - setupTheme(theme, data.variant); + .loadClass(data.getThemeClass()); + setupTheme(theme, data.getVariant(), data.getThemeName()); } catch (ClassNotFoundException exception) { throw new IllegalStateException( - "Could not load theme class " + data.name, exception); + "Could not load theme class " + data.getThemeClass(), + exception); } } private void setupTheme(Class theme, - String variant) { + String variant, String name) { if (theme != null) { - themeDefinition = new ThemeDefinition(theme, variant); + themeDefinition = new ThemeDefinition(theme, variant, name); try { themeInstance = new ThemeWrapper(theme); } catch (InstantiationException | IllegalAccessException e) { @@ -306,9 +307,10 @@ private ThemeData verifyTheme() { .flatMap(clazz -> annotationFinder .apply(clazz, loadedThemeAnnotation).stream()) .map(theme -> new ThemeData( - ((Class) invokeAnnotationMethod(theme, VALUE)) + ((Class) invokeAnnotationMethod(theme, "themeClass")) .getName(), - invokeAnnotationMethodAsString(theme, "variant"))) + invokeAnnotationMethodAsString(theme, "variant"), + invokeAnnotationMethodAsString(theme, "value"))) .collect(Collectors.toSet()); Class loadedNoThemeAnnotation = getFinder() @@ -342,9 +344,10 @@ private ThemeData verifyTheme() { } private String getThemesList(Collection themes) { - return themes - .stream().map(theme -> "name = '" + theme.getName() - + "' and variant = '" + theme.getVariant() + "'") + return themes.stream() + .map(theme -> "themeClass = '" + theme.getThemeClass() + + "' and variant = '" + theme.getVariant() + + "' and name = '" + theme.getThemeName() + "'") .collect(Collectors.joining(", ")); } diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/scanner/ThemeData.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/scanner/ThemeData.java index 88768c7d6b1..f2cc2b46378 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/scanner/ThemeData.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/scanner/ThemeData.java @@ -18,6 +18,8 @@ import java.io.Serializable; import java.util.Objects; +import com.vaadin.flow.theme.AbstractTheme; + /** * A container for Theme information when scanning the class path. It overrides * equals and hashCode in order to use HashSet to eliminate duplicates. @@ -25,26 +27,36 @@ * @since 2.0 */ final class ThemeData implements Serializable { - String name; + String themeClass; String variant = ""; + String themeName; boolean notheme; - ThemeData(String name, String variant) { - this.name = name; + ThemeData(String themeClass, String variant, String themeName) { + if (themeClass.equals(AbstractTheme.class.getName())) { + this.themeClass = FullDependenciesScanner.LUMO; + } else { + this.themeClass = themeClass; + } this.variant = variant; + this.themeName = themeName; } ThemeData() { } - String getName() { - return name; + String getThemeClass() { + return themeClass; } String getVariant() { return variant; } + public String getThemeName() { + return themeName; + } + boolean isNotheme() { return notheme; } @@ -61,7 +73,9 @@ public boolean equals(Object other) { return false; } ThemeData that = (ThemeData) other; - return notheme == that.notheme && Objects.equals(name, that.name); + return notheme == that.notheme && Objects + .equals(themeClass, that.themeClass) && Objects + .equals(themeName, that.themeName); } @Override @@ -69,12 +83,12 @@ public int hashCode() { // We might need to add variant when we wanted to fail in the // case of same theme class with different variant, which was // right in v13 - return Objects.hash(name, notheme); + return Objects.hash(themeClass, notheme, themeName); } @Override public String toString() { - return " notheme: " + notheme + "\n name:" + name + "\n variant: " - + variant; + return " notheme: " + notheme + "\n themeClass:" + themeClass + + "\n variant: " + variant + "\n themeName: " + themeName; } } diff --git a/flow-server/src/main/java/com/vaadin/flow/server/webcomponent/WebComponentGenerator.java b/flow-server/src/main/java/com/vaadin/flow/server/webcomponent/WebComponentGenerator.java index e03e58adbe5..99021d990e1 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/webcomponent/WebComponentGenerator.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/webcomponent/WebComponentGenerator.java @@ -30,6 +30,7 @@ import com.vaadin.flow.component.WebComponentExporterFactory; import com.vaadin.flow.component.webcomponent.WebComponentConfiguration; import com.vaadin.flow.shared.util.SharedUtil; +import com.vaadin.flow.theme.Theme; import elemental.json.JsonArray; import elemental.json.JsonValue; @@ -90,18 +91,20 @@ private static String getTemplate(boolean compatibilityMode) { * @param compatibilityMode * {@code true} to generate Polymer2 template, {@code false} to * generate Polymer3 template + * @param themeName + * the theme defined using {@link Theme} or {@code null} if not defined * @return generated web component html/JS to be served to the client */ public static String generateModule( WebComponentExporterFactory factory, - String frontendURI, boolean compatibilityMode) { + String frontendURI, boolean compatibilityMode, String themeName) { Objects.requireNonNull(factory); Objects.requireNonNull(frontendURI); WebComponentConfiguration config = new WebComponentExporter.WebComponentConfigurationFactory() .create(factory.create()); - return generateModule(config, frontendURI, false, compatibilityMode); + return generateModule(config, frontendURI, false, compatibilityMode, themeName); } /** @@ -114,22 +117,24 @@ public static String generateModule( * @param compatibilityMode * {@code true} to generate Polymer2 template, {@code false} to * generate Polymer3 template + * @param themeName + * the theme defined using {@link Theme} or {@code null} if not defined * @return generated web component html/JS to be served to the client */ public static String generateModule( WebComponentConfiguration webComponentConfiguration, - String frontendURI, boolean compatibilityMode) { + String frontendURI, boolean compatibilityMode, String themeName) { Objects.requireNonNull(webComponentConfiguration); Objects.requireNonNull(frontendURI); return generateModule(webComponentConfiguration, frontendURI, true, - compatibilityMode); + compatibilityMode, themeName); } private static String generateModule( WebComponentConfiguration webComponentConfiguration, String frontendURI, boolean generateUiImport, - boolean compatibilityMode) { + boolean compatibilityMode, String themeName) { Objects.requireNonNull(webComponentConfiguration); Objects.requireNonNull(frontendURI); @@ -138,7 +143,7 @@ private static String generateModule( Map replacements = getReplacementsMap( webComponentConfiguration.getTag(), propertyDataSet, - frontendURI, generateUiImport); + frontendURI, generateUiImport, themeName); String template = getTemplate(compatibilityMode); for (Map.Entry replacement : replacements.entrySet()) { @@ -150,9 +155,16 @@ private static String generateModule( static Map getReplacementsMap(String tag, Set> propertyDataSet, - String frontendURI, boolean generateUiImport) { + String frontendURI, boolean generateUiImport, String themeName) { Map replacements = new HashMap<>(); + if (themeName != null) { + replacements.put("ThemeImport","import {applyTheme} from 'theme/theme.js';"); + replacements.put("ApplyTheme","applyTheme(shadow);"); + } else { + replacements.put("ThemeImport",""); + replacements.put("ApplyTheme",""); + } replacements.put("TagDash", tag); replacements.put("TagCamel", SharedUtil .capitalize(SharedUtil.dashSeparatedToCamelCase(tag))); diff --git a/flow-server/src/main/java/com/vaadin/flow/server/webcomponent/WebComponentModulesWriter.java b/flow-server/src/main/java/com/vaadin/flow/server/webcomponent/WebComponentModulesWriter.java index ea705ae1935..bc52219310c 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/webcomponent/WebComponentModulesWriter.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/webcomponent/WebComponentModulesWriter.java @@ -37,6 +37,7 @@ import com.vaadin.flow.component.WebComponentExporter; import com.vaadin.flow.component.WebComponentExporterFactory; import com.vaadin.flow.internal.ReflectTools; +import com.vaadin.flow.theme.Theme; /** * Writes web components generated from @@ -66,6 +67,8 @@ private WebComponentModulesWriter() { * @param compatibilityMode * {@code true} to generated html modules, {@code false} to * generate JavaScript modules + * @param themeName + * the theme defined using {@link Theme} or {@code null} if not defined * @return generated files * @throws java.lang.NullPointerException * if {@code exportedClasses} or {@code outputDirectory} is null @@ -74,7 +77,7 @@ private WebComponentModulesWriter() { */ private static Set writeWebComponentsToDirectory( // NOSONAR Set> exporterClasses, File outputDirectory, - boolean compatibilityMode) { + boolean compatibilityMode, String themeName) { // this method is used via reflection by DirectoryWriter Objects.requireNonNull(exporterClasses, "Parameter 'exporterClasses' must not be null"); @@ -89,7 +92,7 @@ private static Set writeWebComponentsToDirectory( // NOSONAR return WebComponentExporterUtils.getFactories(exporterClasses).stream() .map(factory -> writeWebComponentToDirectory(factory, - outputDirectory, compatibilityMode)) + outputDirectory, compatibilityMode, themeName)) .collect(Collectors.toSet()); } @@ -101,11 +104,13 @@ private static Set writeWebComponentsToDirectory( // NOSONAR * web component exporter factory * @param outputDirectory * folder into which the generate file is written + * @param themeName + * the theme defined using {@link Theme} or {@code null} if not defined * @return the generated module content */ private static File writeWebComponentToDirectory( WebComponentExporterFactory factory, File outputDirectory, - boolean compatibilityMode) { + boolean compatibilityMode, String themeName) { String tag = getTag(factory); String fileName = compatibilityMode ? tag + ".html" : tag + ".js"; @@ -114,7 +119,7 @@ private static File writeWebComponentToDirectory( FileUtils.forceMkdir(generatedFile.getParent().toFile()); Files.write(generatedFile, Collections.singletonList( - generateModule(factory, compatibilityMode)), + generateModule(factory, compatibilityMode, themeName)), StandardCharsets.UTF_8); } catch (IOException e) { throw new UncheckedIOException(String.format( @@ -126,9 +131,9 @@ private static File writeWebComponentToDirectory( private static String generateModule( WebComponentExporterFactory factory, - boolean compatibilityMode) { + boolean compatibilityMode, String themeName) { return WebComponentGenerator.generateModule(factory, "../", - compatibilityMode); + compatibilityMode, themeName); } private static String getTag( @@ -149,7 +154,7 @@ public static final class DirectoryWriter implements Serializable { /** * Calls - * {@link #writeWebComponentsToDirectory(java.util.Set, java.io.File, boolean)} + * {@link #writeWebComponentsToDirectory(java.util.Set, java.io.File, boolean, java.lang.String)} * via reflection on the supplied {@code writer}. The {@code writer} and * {@code exporterClasses} must be loaded with the same class loader. * @@ -179,16 +184,16 @@ public static final class DirectoryWriter implements Serializable { * share a class loader * @throws java.lang.IllegalStateException * if the received {@code writer} does not have method - * {@link #writeWebComponentsToDirectory(java.util.Set, java.io.File, boolean)} + * {@link #writeWebComponentsToDirectory(java.util.Set, java.io.File, boolean, java.lang.String)} * @throws java.lang.RuntimeException * if reflective method invocation fails * @see #writeWebComponentsToDirectory(java.util.Set, java.io.File, - * boolean) + * boolean, java.lang.String) */ @SuppressWarnings("unchecked") public static Set generateWebComponentsToDirectory( Class writerClass, Set> exporterClasses, - File outputDirectory, boolean compatibilityMode) { + File outputDirectory, boolean compatibilityMode, String themeName) { Objects.requireNonNull(writerClass, "Parameter 'writerClassSupplier' must not null"); Objects.requireNonNull(exporterClasses, @@ -235,7 +240,7 @@ public static Set generateWebComponentsToDirectory( final boolean accessible = writeMethod.isAccessible(); writeMethod.setAccessible(true); Set files = ((Set) writeMethod.invoke(null, - exporterClasses, outputDirectory, compatibilityMode)); + exporterClasses, outputDirectory, compatibilityMode, themeName)); writeMethod.setAccessible(accessible); return files; } catch (IllegalAccessException | InvocationTargetException diff --git a/flow-server/src/main/java/com/vaadin/flow/theme/Theme.java b/flow-server/src/main/java/com/vaadin/flow/theme/Theme.java index 1fa018b5e89..b028bd153b9 100644 --- a/flow-server/src/main/java/com/vaadin/flow/theme/Theme.java +++ b/flow-server/src/main/java/com/vaadin/flow/theme/Theme.java @@ -90,9 +90,11 @@ /** * The theme translation handler. * + * Defaults to Lumo, If not specified. + * * @return theme handler */ - Class value(); + Class themeClass() default AbstractTheme.class; /** * The theme variant, if any. @@ -100,4 +102,12 @@ * @return the theme variant */ String variant() default ""; + + /** + * The name of the application theme to use + * + * If this is not specified, no application theme is used. + * + */ + String value() default ""; } diff --git a/flow-server/src/main/java/com/vaadin/flow/theme/ThemeDefinition.java b/flow-server/src/main/java/com/vaadin/flow/theme/ThemeDefinition.java index 92a25bd34b8..70023d055d3 100644 --- a/flow-server/src/main/java/com/vaadin/flow/theme/ThemeDefinition.java +++ b/flow-server/src/main/java/com/vaadin/flow/theme/ThemeDefinition.java @@ -30,6 +30,7 @@ public class ThemeDefinition implements Serializable { private final Class theme; private final String variant; + private final String name; /** * Creates a definition with the given them class and variant. @@ -40,13 +41,14 @@ public class ThemeDefinition implements Serializable { * the variant of the theme, not null */ public ThemeDefinition(Class theme, - String variant) { + String variant, String name) { Objects.requireNonNull(theme); Objects.requireNonNull(variant); this.theme = theme; this.variant = variant; + this.name = name; } /** @@ -57,7 +59,7 @@ public ThemeDefinition(Class theme, * the annotation to get the definition from */ public ThemeDefinition(Theme themeAnnotation) { - this(themeAnnotation.value(), themeAnnotation.variant()); + this(themeAnnotation.themeClass(), themeAnnotation.variant(), themeAnnotation.value()); } /** @@ -78,4 +80,13 @@ public String getVariant() { return variant; } + /** + * Gets the name of the theme. + * + * @return name of the theme + */ + public String getName() { + return name; + } + } diff --git a/flow-server/src/main/resources/com/vaadin/flow/server/webcomponent/webcomponent-script-template.js b/flow-server/src/main/resources/com/vaadin/flow/server/webcomponent/webcomponent-script-template.js index f27f1baf1e2..fc792a659a1 100644 --- a/flow-server/src/main/resources/com/vaadin/flow/server/webcomponent/webcomponent-script-template.js +++ b/flow-server/src/main/resources/com/vaadin/flow/server/webcomponent/webcomponent-script-template.js @@ -1,3 +1,5 @@ +_ThemeImport_ + class _TagCamel_ extends HTMLElement { constructor() { super(); @@ -12,6 +14,7 @@ class _TagCamel_ extends HTMLElement { display: inline-block; } `; + _ApplyTheme_ shadow.appendChild(style); shadow.appendChild(document.createElement("slot")); diff --git a/flow-server/src/test/java/com/vaadin/flow/server/frontend/EmptyByteScannerDataTestComponents.java b/flow-server/src/test/java/com/vaadin/flow/server/frontend/EmptyByteScannerDataTestComponents.java index d726b15d5fa..be01433bc0d 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/frontend/EmptyByteScannerDataTestComponents.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/frontend/EmptyByteScannerDataTestComponents.java @@ -34,7 +34,7 @@ public class EmptyByteScannerDataTestComponents { @JsModule("./common-js-file.js") @JavaScript("ExampleConnector.js") @CssImport(value = "./foo.css", id = "baz", include = "bar") - @Theme(value = HiddenTheme.class) + @Theme(themeClass = HiddenTheme.class) public static class MainLayout extends Component { } diff --git a/flow-server/src/test/java/com/vaadin/flow/server/frontend/NodeTestComponents.java b/flow-server/src/test/java/com/vaadin/flow/server/frontend/NodeTestComponents.java index b6635769bb1..4fc9a1feb6c 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/frontend/NodeTestComponents.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/frontend/NodeTestComponents.java @@ -107,7 +107,7 @@ public static class TranslatedImports extends Component { } @JsModule("./common-js-file.js") - @Theme(value = LumoTest.class, variant = LumoTest.DARK) + @Theme(themeClass = LumoTest.class, variant = LumoTest.DARK) @Route public static class MainLayout implements RouterLayout { @Override diff --git a/flow-server/src/test/java/com/vaadin/flow/server/frontend/UpdateThemedImportsTest.java b/flow-server/src/test/java/com/vaadin/flow/server/frontend/UpdateThemedImportsTest.java index 69036280a7e..fa73f679226 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/frontend/UpdateThemedImportsTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/frontend/UpdateThemedImportsTest.java @@ -138,7 +138,7 @@ public AbstractTheme getTheme() { @Override public ThemeDefinition getThemeDefinition() { - return new ThemeDefinition(MyTheme.class, ""); + return new ThemeDefinition(MyTheme.class, "", ""); } }; updater = new TaskUpdateImports(finder, deps, cf -> null, tmpRoot, diff --git a/flow-server/src/test/java/com/vaadin/flow/server/frontend/scanner/FullDependenciesScannerTest.java b/flow-server/src/test/java/com/vaadin/flow/server/frontend/scanner/FullDependenciesScannerTest.java index e118ea57522..7339f7f6edb 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/frontend/scanner/FullDependenciesScannerTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/frontend/scanner/FullDependenciesScannerTest.java @@ -73,7 +73,7 @@ public String getThemeUrl() { } - @Theme(FakeLumoTheme.class) + @Theme(themeClass = FakeLumoTheme.class) public static class ThemedComponent extends Component { } diff --git a/flow-server/src/test/java/com/vaadin/flow/server/frontend/scanner/ScannerTestComponents.java b/flow-server/src/test/java/com/vaadin/flow/server/frontend/scanner/ScannerTestComponents.java index 63952acf50e..dd4ec164ff4 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/frontend/scanner/ScannerTestComponents.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/frontend/scanner/ScannerTestComponents.java @@ -108,7 +108,7 @@ static abstract class GeneratedComponent extends Component { } - @Theme(value = Theme1.class, variant = Theme0.DARK) + @Theme(themeClass = Theme1.class, variant = Theme0.DARK) @JsModule("./router-layout-1.js") public class RouterLayout1 implements RouterLayout { @Override @@ -117,7 +117,7 @@ public Element getElement() { } } - @Theme(value = Theme1.class, variant = Theme0.DARK) + @Theme(themeClass = Theme1.class, variant = Theme0.DARK) @JsModule("./router-layout-2.js") public class RouterLayout2 extends RouterLayout1 { } @@ -127,7 +127,7 @@ public static abstract class View0 extends Component { } @Route(value = "") - @Theme(Theme4.class) + @Theme(themeClass = Theme4.class) public static class RootViewWithTheme extends Component { } @@ -151,7 +151,7 @@ public static class RootView2WithLayoutTheme { } @Route(value = "", layout = RouterLayout2.class) - @Theme(value = Theme2.class, variant = Theme2.FOO) + @Theme(themeClass = Theme2.class, variant = Theme2.FOO) @JsModule("./view-2.js") public static class RootViewWithMultipleTheme extends Component { @@ -233,7 +233,7 @@ public void configureInstance(WebComponent webComponent, Root } } - @Theme(Theme2.class) + @Theme(themeClass = Theme2.class) public static class ThemeExporter extends WebComponentExporter { public ThemeExporter() { super("root-view"); diff --git a/flow-server/src/test/java/com/vaadin/flow/server/frontend/scanner/ScannerThemeTest.java b/flow-server/src/test/java/com/vaadin/flow/server/frontend/scanner/ScannerThemeTest.java index 57effdf42a2..a6f7efaa54a 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/frontend/scanner/ScannerThemeTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/frontend/scanner/ScannerThemeTest.java @@ -177,7 +177,7 @@ public void should_takeThemeFromLayout_ifLayoutAlreadyVisited() throws Exception FrontendDependencies deps = getFrontendDependencies(RootViewWithLayoutTheme.class, RootView2WithLayoutTheme.class); assertEquals(Theme1.class, deps.getThemeDefinition().getTheme()); deps.getEndPoints().forEach(endPoint -> { - assertEquals(Theme1.class.getName(), endPoint.getTheme().getName()); + assertEquals(Theme1.class.getName(), endPoint.getTheme().getThemeClass()); }); } } diff --git a/flow-server/src/test/java/com/vaadin/flow/server/frontend/scanner/samples/RouteLayoutComponent.java b/flow-server/src/test/java/com/vaadin/flow/server/frontend/scanner/samples/RouteLayoutComponent.java index 5c83a97a727..4f5e005f070 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/frontend/scanner/samples/RouteLayoutComponent.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/frontend/scanner/samples/RouteLayoutComponent.java @@ -22,7 +22,7 @@ import com.vaadin.flow.theme.Theme; @JsModule("foo.js") -@Theme(CustomTheme.class) +@Theme(themeClass = CustomTheme.class) public class RouteLayoutComponent implements RouterLayout { @Override public Element getElement() { diff --git a/flow-server/src/test/java/com/vaadin/flow/server/webcomponent/WebComponentGeneratorTest.java b/flow-server/src/test/java/com/vaadin/flow/server/webcomponent/WebComponentGeneratorTest.java index 2fa99155db1..5cca7b75337 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/webcomponent/WebComponentGeneratorTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/webcomponent/WebComponentGeneratorTest.java @@ -49,7 +49,7 @@ public void assertGeneratedReplacementMapContainsExpectedEntries( .getReplacementsMap("my-component", new WebComponentExporter.WebComponentConfigurationFactory() .create(exporter).getPropertyDataSet(), - "/foo", generateUi); + "/foo", generateUi, null); Assert.assertTrue("Missing dashed tag name", replacementsMap.containsKey("TagDash")); @@ -137,7 +137,7 @@ public void providesHTMLModuleInBowerMode() { String module = WebComponentGenerator.generateModule( new DefaultWebComponentExporterFactory( MyComponentExporter.class), - "", true); + "", true, null); // make sure that the test works on windows machines: module = module.replace("\r", ""); Assert.assertThat(module, startsWith( @@ -154,7 +154,7 @@ public void providesJSModulesInNpmMode() { String module = WebComponentGenerator.generateModule( new DefaultWebComponentExporterFactory( MyComponentExporter.class), - "", false); + "", false, null); // make sure that the test works on windows machines: module = module.replace("\r", ""); Assert.assertThat(module, diff --git a/flow-server/src/test/java/com/vaadin/flow/server/webcomponent/WebComponentModulesWriterTest.java b/flow-server/src/test/java/com/vaadin/flow/server/webcomponent/WebComponentModulesWriterTest.java index f20491f0d6a..3861b61db23 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/webcomponent/WebComponentModulesWriterTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/webcomponent/WebComponentModulesWriterTest.java @@ -52,7 +52,7 @@ public void directoryWriter_generateWebComponentsToDirectory_canCallMethodReflec .generateWebComponentsToDirectory( WebComponentModulesWriter.class, Collections.singleton(MyExporter.class), - outputDirectory, false); + outputDirectory, false, null); Assert.assertEquals("One file was created", 1, files.size()); Assert.assertEquals("File is js module with correct name", @@ -65,7 +65,7 @@ public void directoryWriter_generateWebComponentsToDirectoryUsingFactory_canCall .generateWebComponentsToDirectory( WebComponentModulesWriter.class, Collections.singleton(ExporterFactory.class), - outputDirectory, false); + outputDirectory, false, null); Assert.assertEquals("One file was created", 1, files.size()); Assert.assertEquals("File is js module with correct name", "foo-bar.js", @@ -78,7 +78,7 @@ public void directoryWriter_generateWebComponentsToDirectory_canCallMethodReflec .generateWebComponentsToDirectory( WebComponentModulesWriter.class, Collections.singleton(MyExporter.class), - outputDirectory, true); + outputDirectory, true, null); Assert.assertEquals("One file was created", 1, files.size()); Assert.assertEquals("File is js module with correct name", @@ -91,7 +91,7 @@ public void directoryWriter_generateWebComponentsToDirectoryUsingFactory_canCall .generateWebComponentsToDirectory( WebComponentModulesWriter.class, Collections.singleton(ExporterFactory.class), - outputDirectory, true); + outputDirectory, true, null); Assert.assertEquals("One file was created", 1, files.size()); Assert.assertEquals("File is js module with correct name", @@ -103,7 +103,7 @@ public void directoryWriter_generateWebComponentsToDirectory_zeroExportersCreate Set files = WebComponentModulesWriter.DirectoryWriter .generateWebComponentsToDirectory( WebComponentModulesWriter.class, new HashSet<>(), - outputDirectory, false); + outputDirectory, false, null); Assert.assertEquals("No files were created", 0, files.size()); } @@ -115,14 +115,14 @@ public void directoryWriter_generateWebComponentsToDirectory_nonWriterClassThrow "but it is '" + MyComponent.class.getName() + "'"); Set files = WebComponentModulesWriter.DirectoryWriter .generateWebComponentsToDirectory(MyComponent.class, - new HashSet<>(), outputDirectory, false); + new HashSet<>(), outputDirectory, false, null); } @Test(expected = NullPointerException.class) public void directoryWriter_generateWebComponentsToDirectory_nullWriterThrows() { Set files = WebComponentModulesWriter.DirectoryWriter .generateWebComponentsToDirectory(null, new HashSet<>(), - outputDirectory, false); + outputDirectory, false, null); } @Test(expected = NullPointerException.class) @@ -130,7 +130,7 @@ public void directoryWriter_generateWebComponentsToDirectory_nullExporterSetThro Set files = WebComponentModulesWriter.DirectoryWriter .generateWebComponentsToDirectory( WebComponentModulesWriter.class, null, outputDirectory, - false); + false, null); } @Test(expected = NullPointerException.class) @@ -138,7 +138,7 @@ public void directoryWriter_generateWebComponentsToDirectory_nullOutputDirectory Set files = WebComponentModulesWriter.DirectoryWriter .generateWebComponentsToDirectory( WebComponentModulesWriter.class, new HashSet<>(), null, - false); + false, null); } /* diff --git a/flow-tests/test-embedding/embedding-test-assets/src/main/java/com/vaadin/flow/webcomponent/ThemedComponentExporter.java b/flow-tests/test-embedding/embedding-test-assets/src/main/java/com/vaadin/flow/webcomponent/ThemedComponentExporter.java index 9acd028dc39..2b88ea09d6c 100644 --- a/flow-tests/test-embedding/embedding-test-assets/src/main/java/com/vaadin/flow/webcomponent/ThemedComponentExporter.java +++ b/flow-tests/test-embedding/embedding-test-assets/src/main/java/com/vaadin/flow/webcomponent/ThemedComponentExporter.java @@ -19,7 +19,7 @@ import com.vaadin.flow.component.webcomponent.WebComponent; import com.vaadin.flow.theme.Theme; -@Theme(MyTheme.class) +@Theme(themeClass = MyTheme.class) public class ThemedComponentExporter extends WebComponentExporter { public ThemedComponentExporter() { diff --git a/flow-tests/test-embedding/test-embedding-theme-variant/src/main/java/com/vaadin/flow/webcomponent/ThemedVariantComponentExporter.java b/flow-tests/test-embedding/test-embedding-theme-variant/src/main/java/com/vaadin/flow/webcomponent/ThemedVariantComponentExporter.java index 4fbc3732d07..dc8145a49d0 100644 --- a/flow-tests/test-embedding/test-embedding-theme-variant/src/main/java/com/vaadin/flow/webcomponent/ThemedVariantComponentExporter.java +++ b/flow-tests/test-embedding/test-embedding-theme-variant/src/main/java/com/vaadin/flow/webcomponent/ThemedVariantComponentExporter.java @@ -20,7 +20,7 @@ import com.vaadin.flow.theme.Theme; import com.vaadin.flow.theme.lumo.Lumo; -@Theme(value = Lumo.class, variant = Lumo.DARK) +@Theme(themeClass = Lumo.class, variant = Lumo.DARK) public class ThemedVariantComponentExporter extends WebComponentExporter { public ThemedVariantComponentExporter() { diff --git a/flow-tests/test-lumo-theme/src/main/java/com/vaadin/flow/uitest/ui/lumo/ExplicitLumoTemplateView.java b/flow-tests/test-lumo-theme/src/main/java/com/vaadin/flow/uitest/ui/lumo/ExplicitLumoTemplateView.java index aca7de22299..737d288da47 100644 --- a/flow-tests/test-lumo-theme/src/main/java/com/vaadin/flow/uitest/ui/lumo/ExplicitLumoTemplateView.java +++ b/flow-tests/test-lumo-theme/src/main/java/com/vaadin/flow/uitest/ui/lumo/ExplicitLumoTemplateView.java @@ -25,7 +25,7 @@ @Tag("explicit-lumo-themed-template") @JsModule("./src/LumoThemedTemplate.js") @Route(value = "com.vaadin.flow.uitest.ui.lumo.ExplicitLumoTemplateView") -@Theme(value = Lumo.class, variant = Lumo.DARK) +@Theme(themeClass = Lumo.class, variant = Lumo.DARK) /* * Note that this is using component instead of polymer template, because * otherwise the themed module would have to import the original /src module, diff --git a/flow-tests/test-material-theme/src/main/java/com/vaadin/flow/uitest/ui/material/MaterialThemedTemplateView.java b/flow-tests/test-material-theme/src/main/java/com/vaadin/flow/uitest/ui/material/MaterialThemedTemplateView.java index d82b66eb17f..300ae84058d 100644 --- a/flow-tests/test-material-theme/src/main/java/com/vaadin/flow/uitest/ui/material/MaterialThemedTemplateView.java +++ b/flow-tests/test-material-theme/src/main/java/com/vaadin/flow/uitest/ui/material/MaterialThemedTemplateView.java @@ -25,7 +25,7 @@ @Tag("material-themed-template") @JsModule("./src/MaterialThemedTemplate.js") @Route(value = "com.vaadin.flow.uitest.ui.material.MaterialThemedTemplateView") -@Theme(Material.class) +@Theme(themeClass = Material.class) /* * Note that this is using component instead of polymer template, because * otherwise the themed module would have to import the original /src module, and diff --git a/flow-tests/test-misc/src/main/java/com/vaadin/flow/misc/ui/MiscelaneousView.java b/flow-tests/test-misc/src/main/java/com/vaadin/flow/misc/ui/MiscelaneousView.java index b2582588bad..1ad4fe700c6 100644 --- a/flow-tests/test-misc/src/main/java/com/vaadin/flow/misc/ui/MiscelaneousView.java +++ b/flow-tests/test-misc/src/main/java/com/vaadin/flow/misc/ui/MiscelaneousView.java @@ -29,7 +29,7 @@ @JsModule("./src/my-component-themed.js") // `src/` in component above should be replaced by `theme/my-theme` -@Theme(MyTheme.class) +@Theme(themeClass = MyTheme.class) @PWA(name = "Project Base for Vaadin", shortName = "Project Base") public class MiscelaneousView extends Div { diff --git a/flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/theme/NpmThemedComponentView.java b/flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/theme/NpmThemedComponentView.java index bf782a1c4b8..c2acaf797fa 100644 --- a/flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/theme/NpmThemedComponentView.java +++ b/flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/theme/NpmThemedComponentView.java @@ -22,7 +22,7 @@ import com.vaadin.flow.theme.Theme; @Route(value = "com.vaadin.flow.uitest.ui.theme.NpmThemedComponentView") -@Theme(MyTheme.class) +@Theme(themeClass = MyTheme.class) @Tag("npm-themed-component") @JsModule("./src/npm-themed-component.js") /* From d1dfba9b17cf9df76ea20a84d473b77702c1379a Mon Sep 17 00:00:00 2001 From: Mikael Grankvist Date: Mon, 9 Nov 2020 10:26:18 +0200 Subject: [PATCH 02/17] Add theme plugin for theme generation --- .../flow/server/frontend/NodeUpdater.java | 1 + .../server/frontend/TaskUpdateWebpack.java | 42 +++++---- .../application-theme-plugin.js | 71 ++++++++++++++++ .../application-theme-plugin/package.json | 20 +++++ .../application-theme-plugin/theme-copy.js | 65 ++++++++++++++ .../theme-generator.js | 85 +++++++++++++++++++ .../resources/plugins/webpack-plugins.json | 3 +- .../src/main/resources/webpack.generated.js | 62 +++++++++++++- .../TaskInstallWebpackPluginsTest.java | 2 +- 9 files changed, 332 insertions(+), 19 deletions(-) create mode 100644 flow-server/src/main/resources/plugins/application-theme-plugin/application-theme-plugin.js create mode 100644 flow-server/src/main/resources/plugins/application-theme-plugin/package.json create mode 100644 flow-server/src/main/resources/plugins/application-theme-plugin/theme-copy.js create mode 100644 flow-server/src/main/resources/plugins/application-theme-plugin/theme-generator.js diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/NodeUpdater.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/NodeUpdater.java index 044108f3d0f..0e5f0403eb0 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/NodeUpdater.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/NodeUpdater.java @@ -309,6 +309,7 @@ static Map getDefaultDevDependencies() { defaults.put("lit-html", "1.2.1"); defaults.put("@types/validator", "10.11.3"); defaults.put("validator", "12.0.0"); + defaults.put("construct-style-sheets-polyfill", "2.4.2"); // Forcing chokidar version for now until new babel version is available // check out https://github.com/babel/babel/issues/11488 diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskUpdateWebpack.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskUpdateWebpack.java index 70d505a175d..7d7cb9a04e3 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskUpdateWebpack.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskUpdateWebpack.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; @@ -53,6 +54,7 @@ public class TaskUpdateWebpack implements FallibleCommand { private final Path frontendDirectory; private final boolean useV14Bootstrapping; private final Path flowResourcesFolder; + private final Path resourceFolder; /** * Create an instance of the updater given all configurable parameters. @@ -88,6 +90,7 @@ public class TaskUpdateWebpack implements FallibleCommand { this.webpackConfigPath = webpackConfigFolder.toPath(); this.useV14Bootstrapping = useV14Bootstrapping; this.flowResourcesFolder = flowResourcesFolder.toPath(); + this.resourceFolder = new File(webpackOutputDirectory.getParentFile(), "resources").toPath(); } @Override @@ -138,7 +141,7 @@ private void createWebpackConfig() throws IOException { private List modifyWebpackConfig(File generatedFile) throws IOException { - List lines = FileUtils.readLines(generatedFile, "UTF-8"); + List lines = FileUtils.readLines(generatedFile, StandardCharsets.UTF_8); String frontendLine = "const frontendFolder = require('path').resolve(__dirname, '" + getEscapedRelativeWebpackPath(frontendDirectory) + "');"; @@ -153,31 +156,38 @@ private List modifyWebpackConfig(File generatedFile) + getEscapedRelativeWebpackPath( flowResourcesFolder.resolve("VaadinDevmodeGizmo.js")) + "');"; + + String frontendFolder = + "const flowFrontendFolder = require('path').resolve(__dirname, '" + getEscapedRelativeWebpackPath( + flowResourcesFolder) + "');"; + String assetsResourceFolder = + "const projectStaticAssetsOutputFolder = require('path').resolve(__dirname, '" + + getEscapedRelativeWebpackPath(resourceFolder) + "');"; + for (int i = 0; i < lines.size(); i++) { if (lines.get(i).startsWith( "const fileNameOfTheFlowGeneratedMainEntryPoint")) { lines.set(i, mainLine); - } - if (lines.get(i) - .startsWith("const mavenOutputFolderForFlowBundledFiles")) { + } else if (lines.get(i) + .startsWith("const mavenOutputFolderForFlowBundledFiles")) { lines.set(i, outputLine); - } - if (lines.get(i).startsWith("const frontendFolder")) { + } else if (lines.get(i).startsWith("const frontendFolder")) { lines.set(i, frontendLine); - } - if (lines.get(i).startsWith("const useClientSideIndexFileForBootstrapping")) { + } else if (lines.get(i) + .startsWith("const useClientSideIndexFileForBootstrapping")) { lines.set(i, isClientSideBootstrapModeLine); - } - if (lines.get(i).startsWith("const clientSideIndexHTML")) { + } else if (lines.get(i).startsWith("const clientSideIndexHTML")) { lines.set(i, getIndexHtmlPath()); - } - - if (lines.get(i).startsWith("const clientSideIndexEntryPoint")) { + } else if (lines.get(i) + .startsWith("const clientSideIndexEntryPoint")) { lines.set(i, getClientEntryPoint()); - } - - if (lines.get(i).startsWith("const devmodeGizmoJS")) { + } else if (lines.get(i).startsWith("const devmodeGizmoJS")) { lines.set(i, devModeGizmoJSLine); + } else if (lines.get(i).startsWith("const flowFrontendFolder")) { + lines.set(i, frontendFolder); + } else if (lines.get(i) + .startsWith("const projectStaticAssetsOutputFolder")) { + lines.set(i, assetsResourceFolder); } } return lines; diff --git a/flow-server/src/main/resources/plugins/application-theme-plugin/application-theme-plugin.js b/flow-server/src/main/resources/plugins/application-theme-plugin/application-theme-plugin.js new file mode 100644 index 00000000000..5555a93edbc --- /dev/null +++ b/flow-server/src/main/resources/plugins/application-theme-plugin/application-theme-plugin.js @@ -0,0 +1,71 @@ +/* + * Copyright 2000-2020 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +const fs = require('fs'); +const path = require('path'); +const generateThemeFile = require('./theme-generator'); +const copyThemeResources = require('./theme-copy'); + +let logger; + +class ApplicationThemePlugin { + constructor(options) { + this.options = options; + } + + apply(compiler) { + logger = compiler.getInfrastructureLogger("application-theme-plugin"); + + compiler.hooks.afterEnvironment.tap("FlowApplicationThemePlugin", () => { + if (fs.existsSync(this.options.themeJarFolder)) { + logger.debug("Found themeFolder to handle ", this.options.themeJarFolder); + handleThemes(this.options.themeJarFolder, this.options.projectStaticAssetsOutputFolder); + } else { + logger.warn('Theme JAR folder not found from ', this.options.themeJarFolder); + } + + this.options.themeProjectFolders.forEach((themeProjectFolder) => { + if (fs.existsSync(themeProjectFolder)) { + logger.debug("Found themeFolder to handle ", themeProjectFolder); + handleThemes(themeProjectFolder, this.options.projectStaticAssetsOutputFolder); + } + }); + }); + } +} + +module.exports = ApplicationThemePlugin; + +function handleThemes(themesFolder, projectStaticAssetsOutputFolder) { + const dir = fs.opendirSync(themesFolder); + while ((dirent = dir.readSync())) { + if (!dirent.isDirectory()) { + continue; + } + const themeName = dirent.name; + const themeFolder = path.resolve(themesFolder, themeName); + logger.debug("Found theme ", themeName, " in folder ", themeFolder); + + copyThemeResources(themeName, themeFolder, projectStaticAssetsOutputFolder); + + const themeFile = generateThemeFile( + themeFolder, + themeName + ); + + fs.writeFileSync(path.resolve(themeFolder, themeName + '.js'), themeFile); + } +}; diff --git a/flow-server/src/main/resources/plugins/application-theme-plugin/package.json b/flow-server/src/main/resources/plugins/application-theme-plugin/package.json new file mode 100644 index 00000000000..a462c01a07a --- /dev/null +++ b/flow-server/src/main/resources/plugins/application-theme-plugin/package.json @@ -0,0 +1,20 @@ +{ + "description": "application-theme-plugin", + "keywords": [ + "plugin" + ], + "repository": "vaadin/flow", + "name": "@vaadin/application-theme-plugin", + "version": "1.0.0", + "main": "application-theme-plugin.js", + "author": "Vaadin Ltd", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/vaadin/flow/issues" + }, + "files": [ + "application-theme-plugin.js", + "theme-generator.js", + "theme-copy.js" + ] +} diff --git a/flow-server/src/main/resources/plugins/application-theme-plugin/theme-copy.js b/flow-server/src/main/resources/plugins/application-theme-plugin/theme-copy.js new file mode 100644 index 00000000000..be9bf895c22 --- /dev/null +++ b/flow-server/src/main/resources/plugins/application-theme-plugin/theme-copy.js @@ -0,0 +1,65 @@ +/* + * Copyright 2000-2020 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +/** + * This file handles copying of theme files to + * [staticResourcesFolder]/theme/[theme-name] + */ + +const fs = require('fs'); +const path = require('path'); + +/** + * create theme/themeName folders and copy theme files there. + * + * @param {String} themeName name of theme we are handling + * @param {Path} themeFolder Folder with theme file + * @param {Path} projectStaticAssetsOutputFolder resources output folder + */ +function copyThemeResources(themeName, themeFolder, projectStaticAssetsOutputFolder) { + if (!fs.existsSync(path.resolve(projectStaticAssetsOutputFolder, "theme"))) { + fs.mkdirSync(path.resolve(projectStaticAssetsOutputFolder, "theme")); + } + if (!fs.existsSync(path.resolve(projectStaticAssetsOutputFolder, "theme", themeName))) { + fs.mkdirSync(path.resolve(projectStaticAssetsOutputFolder, "theme", themeName)); + } + copyThemeFiles(themeFolder, path.resolve(projectStaticAssetsOutputFolder, "theme", themeName)); +} + +const notToCopy = ["css", "js", "json"]; + +/** + * Recursively copy files found in theme folder excluding any with a extension found in the `notToCopy` array. + * + * If a directory is met create directory and copy files from that folder to new target folder. + * + * @param {path} folderToCopy folder to copy files from + * @param {path} targetFolder folder to copy files to + */ +function copyThemeFiles(folderToCopy, targetFolder) { + fs.readdirSync(folderToCopy).forEach(file => { + if (fs.statSync(path.resolve(folderToCopy, file)).isDirectory()) { + if (!fs.existsSync(path.resolve(targetFolder, file))) { + fs.mkdirSync(path.resolve(targetFolder, file)); + } + copyThemeFiles(path.resolve(folderToCopy, file), path.resolve(targetFolder, file)); + } else if (!notToCopy.includes(path.extname(file))) { + fs.copyFileSync(path.resolve(folderToCopy, file), path.resolve(targetFolder, file)); + } + }); +} + +module.exports = copyThemeResources; diff --git a/flow-server/src/main/resources/plugins/application-theme-plugin/theme-generator.js b/flow-server/src/main/resources/plugins/application-theme-plugin/theme-generator.js new file mode 100644 index 00000000000..d4cb5534f65 --- /dev/null +++ b/flow-server/src/main/resources/plugins/application-theme-plugin/theme-generator.js @@ -0,0 +1,85 @@ +/* + * Copyright 2000-2020 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +/** + * This file handles the generation of the '[theme-name].js' to + * the theme/[theme-name] folder according to properties from 'theme.json'. + */ +const glob = require('glob'); +const path = require('path'); +const camelCase = require('camelCase'); + +// The contents of a global CSS file with this name in a theme is always added to +// the document. E.g. @font-face must be in this +const themeFileAlwaysAddToDocument = 'document.css'; + +const headerImport = ` +import { css, unsafeCSS, registerStyles } from '@vaadin/vaadin-themable-mixin/register-styles'; +import 'construct-style-sheets-polyfill'; +`; + +const injectGlobalCssMethod = ` +// target: Document | ShadowRoot +export const injectGlobalCss = (css, target) => { + const sheet = new CSSStyleSheet(); + sheet.replaceSync(css); + target.adoptedStyleSheets = [...target.adoptedStyleSheets, sheet]; +}; +`; + +function generateThemeFile(themeFolder, themeName) { + const globalFiles = glob.sync('*.css', { + cwd: themeFolder, + nodir: true, + }); + + let themeFile = headerImport; + + themeFile += injectGlobalCssMethod; + + const imports = []; + const globalCssCode = []; + + globalFiles.forEach((global) => { + const filename = path.basename(global); + const variable = camelCase(filename); + imports.push(`import ${variable} from './${filename}';\n`); + if (filename == themeFileAlwaysAddToDocument) { + globalCssCode.push(`injectGlobalCss(${variable}.toString(), document);\n`); + } + globalCssCode.push(`injectGlobalCss(${variable}.toString(), target);\n`); + }); + + const themeIdentifier = '_vaadinds_' + themeName + '_'; + const globalCssFlag = themeIdentifier + 'globalCss'; + + themeFile += imports.join(''); + +// Don't format as the generated file formatting will get wonky! + const themeFileApply = `export const applyTheme = (target) => { + if (!target['${globalCssFlag}']) { + ${globalCssCode.join('')} + target['${globalCssFlag}'] = true; + } +} +`; + + themeFile += themeFileApply; + + return themeFile; +}; + +module.exports = generateThemeFile; diff --git a/flow-server/src/main/resources/plugins/webpack-plugins.json b/flow-server/src/main/resources/plugins/webpack-plugins.json index ee2c9be8926..fb85a4a36c1 100644 --- a/flow-server/src/main/resources/plugins/webpack-plugins.json +++ b/flow-server/src/main/resources/plugins/webpack-plugins.json @@ -1,5 +1,6 @@ { "plugins": [ - "stats-plugin" + "stats-plugin", + "application-theme-plugin" ] } diff --git a/flow-server/src/main/resources/webpack.generated.js b/flow-server/src/main/resources/webpack.generated.js index eed1a658d52..0632d4f08d0 100644 --- a/flow-server/src/main/resources/webpack.generated.js +++ b/flow-server/src/main/resources/webpack.generated.js @@ -11,6 +11,7 @@ const CompressionPlugin = require('compression-webpack-plugin'); // Flow plugins const StatsPlugin = require('@vaadin/stats-plugin'); +const ApplicationThemePlugin = require('@vaadin/application-theme-plugin'); const path = require('path'); @@ -34,6 +35,25 @@ const buildFolder = `${mavenOutputFolderForFlowBundledFiles}/${build}`; const confFolder = `${mavenOutputFolderForFlowBundledFiles}/${config}`; // file which is used by flow to read templates for server `@Id` binding const statsFile = `${confFolder}/stats.json`; +// Folders in the project which can contain static assets. +// FIXME This is missing some entries +const projectStaticAssetsFolders = [ + path.resolve(__dirname, 'src', 'main', 'resources', 'META-INF', 'resources'), + path.resolve(__dirname, 'src', 'main', 'resources', 'static'), + frontendFolder +]; + +const projectStaticAssetsOutputFolder = [to-be-generated-by-flow]; + +// Folders in the project which can contain application themes +const themeProjectFolders = projectStaticAssetsFolders.map((folder) => + path.resolve(folder, 'theme') +); + + +// Target flow-fronted auto generated to be the actual target folder +const flowFrontendFolder = '[to-be-generated-by-flow]'; + // make sure that build folder exists before outputting anything const mkdirp = require('mkdirp'); @@ -104,6 +124,12 @@ module.exports = { }, resolve: { + // Search for import 'x/y' inside these folders, used at least for importing an application theme + modules: [ + 'node_modules', + flowFrontendFolder, + ...projectStaticAssetsFolders, + ], extensions: ['.ts', '.js'], alias: { Frontend: frontendFolder @@ -141,7 +167,35 @@ module.exports = { }, { test: /\.css$/i, - use: ['lit-css-loader', 'extract-loader', 'css-loader'] + use: [ + { + loader: 'css-loader', + options: { + url: (url, resourcePath) => { + // resourcePath - path to css file + // If the import happens from within a node_modules file, we must resolve and inline. Otherwise resources will not be found at runtime as node_modules is not deployed + const resolveUrl = resourcePath.includes('/node_modules/'); + if (resolveUrl) { + console.debug('Inlining ', url); + } + return resolveUrl; + }, + import: (url, media, resourcePath) => { + // resourcePath - path to css file + // If the import happens from within a node_modules file, we must resolve and inline. Otherwise resources will not be found at runtime as node_modules is not deployed + const resolveUrl = resourcePath.includes('/node_modules/'); + if (resolveUrl) { + console.debug('Inlining ', url); + } + return resolveUrl; + }, + }, + }, + ], + // }, + // { + // test: /\.css$/i, + // use: ['lit-css-loader', 'extract-loader'] } ] }, @@ -153,6 +207,12 @@ module.exports = { // Generate compressed bundles when not devMode !devMode && new CompressionPlugin(), + new ApplicationThemePlugin({ + themeJarFolder: path.resolve(flowFrontendFolder, 'theme'), + themeProjectFolders: themeProjectFolders, + projectStaticAssetsOutputFolder: projectStaticAssetsOutputFolder, + }), + new StatsPlugin({ devMode: devMode, statsFile: statsFile, diff --git a/flow-server/src/test/java/com/vaadin/flow/server/frontend/TaskInstallWebpackPluginsTest.java b/flow-server/src/test/java/com/vaadin/flow/server/frontend/TaskInstallWebpackPluginsTest.java index 522f3cc2d99..c06aaaca20d 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/frontend/TaskInstallWebpackPluginsTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/frontend/TaskInstallWebpackPluginsTest.java @@ -53,7 +53,7 @@ public void init() throws IOException { @Test public void getPluginsReturnsExpectedList() { - String[] expectedPlugins = new String[] { "stats-plugin" }; + String[] expectedPlugins = new String[] { "stats-plugin", "application-theme-plugin" }; final List plugins = task.getPlugins(); Assert.assertEquals( "Unexpected amount of plugins in 'webpack-plugins.json'", From 2163d3ecece7d22bebc27f55fb2b025e50cdf6c7 Mon Sep 17 00:00:00 2001 From: Mikael Grankvist Date: Mon, 9 Nov 2020 14:30:21 +0200 Subject: [PATCH 03/17] Fix NPE problems and tests --- .../FrontendWebComponentGenerator.java | 3 ++- .../server/frontend/TaskUpdateImports.java | 2 +- .../frontend/TaskUpdateThemeImport.java | 2 +- .../scanner/FrontendDependencies.java | 2 +- .../server/frontend/scanner/ThemeData.java | 2 +- .../vaadin/flow/theme/ThemeDefinition.java | 1 + .../frontend/TaskUpdateWebpackTest.java | 2 +- .../WebComponentGeneratorTest.java | 25 ++++++++++--------- .../testutil/ClassesSerializableTest.java | 1 + 9 files changed, 22 insertions(+), 18 deletions(-) diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/FrontendWebComponentGenerator.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/FrontendWebComponentGenerator.java index d233560db16..8518ae28fd3 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/FrontendWebComponentGenerator.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/FrontendWebComponentGenerator.java @@ -84,10 +84,11 @@ public Set generateWebComponents(File outputDirectory, .forEach(exporterRelatedClasses::add); finder.getSubTypesOf(WebComponentExporterFactory.class.getName()) .forEach(exporterRelatedClasses::add); + final String themeName = theme == null ? "" : theme.getName(); return WebComponentModulesWriter.DirectoryWriter .generateWebComponentsToDirectory(writerClass, exporterRelatedClasses, outputDirectory, false, - theme.getName()); + themeName); } catch (ClassNotFoundException e) { throw new IllegalStateException( "Unable to locate a required class using custom class " diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskUpdateImports.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskUpdateImports.java index 0d94e147b0c..9ee3c3af30d 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskUpdateImports.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskUpdateImports.java @@ -133,7 +133,7 @@ protected Collection getThemeLines() { AbstractTheme theme = getTheme(); ThemeDefinition themeDef = getThemeDefinition(); - if (!themeDef.getName().equals("")) { + if (themeDef!= null && !themeDef.getName().equals("")) { // If we define a theme name we need to import theme/theme.js lines.add("import {applyTheme} from 'theme/theme.js';"); lines.add("applyTheme(document);"); diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskUpdateThemeImport.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskUpdateThemeImport.java index 57cfe20bf4d..37d2c9b5cbb 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskUpdateThemeImport.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskUpdateThemeImport.java @@ -43,7 +43,7 @@ public class TaskUpdateThemeImport implements FallibleCommand { @Override public void execute() throws ExecutionFailedException { - if (theme.getName().isEmpty()) { + if (theme == null || theme.getName().isEmpty()) { return; } themeImportFile.getParentFile().mkdirs(); diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/scanner/FrontendDependencies.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/scanner/FrontendDependencies.java index bd4a3fd8e52..8d49401c7ba 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/scanner/FrontendDependencies.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/scanner/FrontendDependencies.java @@ -274,7 +274,7 @@ private void computeApplicationTheme() throws ClassNotFoundException, Set themes = endPoints.values().stream() // consider only endPoints with theme information .filter(data -> data.getTheme().getThemeClass() != null || - data.getTheme().getThemeName() != null + (data.getTheme().getThemeName() != null && !data.getTheme().getThemeName().isEmpty()) || data.getTheme().isNotheme()) .map(EndPointData::getTheme) // Remove duplicates by returning a set diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/scanner/ThemeData.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/scanner/ThemeData.java index f2cc2b46378..9b2b19285cc 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/scanner/ThemeData.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/scanner/ThemeData.java @@ -29,7 +29,7 @@ final class ThemeData implements Serializable { String themeClass; String variant = ""; - String themeName; + String themeName = ""; boolean notheme; ThemeData(String themeClass, String variant, String themeName) { diff --git a/flow-server/src/main/java/com/vaadin/flow/theme/ThemeDefinition.java b/flow-server/src/main/java/com/vaadin/flow/theme/ThemeDefinition.java index 70023d055d3..b4ccc156831 100644 --- a/flow-server/src/main/java/com/vaadin/flow/theme/ThemeDefinition.java +++ b/flow-server/src/main/java/com/vaadin/flow/theme/ThemeDefinition.java @@ -45,6 +45,7 @@ public ThemeDefinition(Class theme, Objects.requireNonNull(theme); Objects.requireNonNull(variant); + Objects.requireNonNull(name); this.theme = theme; this.variant = variant; diff --git a/flow-server/src/test/java/com/vaadin/flow/server/frontend/TaskUpdateWebpackTest.java b/flow-server/src/test/java/com/vaadin/flow/server/frontend/TaskUpdateWebpackTest.java index c421a1eafbc..7ffcf202807 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/frontend/TaskUpdateWebpackTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/frontend/TaskUpdateWebpackTest.java @@ -237,7 +237,7 @@ private void verifyUpdate(List webpackContents, String entryPoint, + entryPoint + "');")); Assert.assertTrue( - "webpack config should update fileNameOfTheFlowGeneratedMainEntryPoint", + "webpack config should update mavenOutputFolderForFlowBundledFiles", webpackContents.contains( "const mavenOutputFolderForFlowBundledFiles = require('path').resolve(__dirname, '" + outputFolder + "');")); diff --git a/flow-server/src/test/java/com/vaadin/flow/server/webcomponent/WebComponentGeneratorTest.java b/flow-server/src/test/java/com/vaadin/flow/server/webcomponent/WebComponentGeneratorTest.java index 5cca7b75337..2c25dd58d48 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/webcomponent/WebComponentGeneratorTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/webcomponent/WebComponentGeneratorTest.java @@ -139,14 +139,15 @@ public void providesHTMLModuleInBowerMode() { MyComponentExporter.class), "", true, null); // make sure that the test works on windows machines: - module = module.replace("\r", ""); - Assert.assertThat(module, startsWith( - "\n" + "