diff --git a/.gitignore b/.gitignore index 2b9f69c0616..051ad88995d 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,6 @@ flow-tests/**/pnpmfile.js flow-tests/**/pnpm-lock.yaml flow-tests/**/tsconfig.json flow-tests/**/types.d.ts +flow-tests/test-themes/frontend/theme/app-theme/app-theme.js +package.json +package-lock.json 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 dfbc601e824..4dece2bbdc8 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 @@ -82,7 +82,7 @@ public static class TranslatedImports extends Component { } @Route - @Theme(value = Lumo.class, variant = Lumo.DARK) + @Theme(value =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/server/frontend/FrontendWebComponentGenerator.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/FrontendWebComponentGenerator.java index f5c60dbc58b..c49b674f895 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 @@ -67,7 +67,7 @@ public FrontendWebComponentGenerator(ClassFinder finder) { * outputDirectory}. * * @param outputDirectory - * target directory for the web component module files + * target directory for the web component module files * @return generated files * @throws java.lang.IllegalStateException * if {@code finder} cannot locate required classes 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 e984622c680..20b84dad9e9 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 @@ -497,6 +497,9 @@ private NodeTasks(Builder builder) { builder.frontendDirectory, builder.tokenFile, builder.tokenFileData, builder.enablePnpm, builder.additionalFrontendModules)); + + commands.add(new TaskUpdateThemeImport(builder.npmFolder, + frontendDependencies.getThemeDefinition())); } } 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 2bec101fc77..2fd6bbfa4e2 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 @@ -259,6 +259,7 @@ static Map getDefaultDevDependencies() { defaults.put("compression-webpack-plugin", "4.0.1"); defaults.put("webpack-merge", "4.2.2"); defaults.put("raw-loader", "3.1.0"); + defaults.put("css-loader", "4.2.1"); defaults.put("typescript", "4.0.3"); defaults.put("ts-loader", "8.0.12"); 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 cf9bf146087..d2a7927acef 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 @@ -47,7 +47,6 @@ import elemental.json.JsonArray; import elemental.json.JsonObject; import elemental.json.impl.JsonUtil; - import static com.vaadin.flow.server.frontend.FrontendUtils.IMPORTS_NAME; /** @@ -139,6 +138,13 @@ protected Collection getThemeLines() { Collection lines = new ArrayList<>(); AbstractTheme theme = getTheme(); ThemeDefinition themeDef = getThemeDefinition(); + + if (themeDef != null && !"".equals(themeDef.getName())) { + // If we define a theme name we need to import theme/theme-generated.js + lines.add("import {applyTheme} from 'theme/theme-generated.js';"); + lines.add("applyTheme(document);"); + } + if (theme != null) { if (!theme.getHeaderInlineContents().isEmpty()) { lines.add(THEME_PREPARE); @@ -147,9 +153,11 @@ protected Collection getThemeLines() { String.format(THEME_LINE_TPL, NEW_LINE_TRIM .matcher(html).replaceAll("")))); } - theme.getHtmlAttributes(themeDef.getVariant()) - .forEach((key, value) -> addLines(lines, - String.format(THEME_VARIANT_TPL, key, value))); + if (themeDef != null) { + theme.getHtmlAttributes(themeDef.getVariant()).forEach( + (key, value) -> addLines(lines, + String.format(THEME_VARIANT_TPL, key, value))); + } lines.add(""); } return lines; 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..b35de020581 --- /dev/null +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskUpdateThemeImport.java @@ -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. + */ +package com.vaadin.flow.server.frontend; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Collections; + +import org.apache.commons.io.FileUtils; +import org.slf4j.LoggerFactory; + +import com.vaadin.flow.server.ExecutionFailedException; +import com.vaadin.flow.theme.ThemeDefinition; + +/** + * Task for generating the theme-generated.js file for importing application + * theme. + * + * @since + */ +public class TaskUpdateThemeImport implements FallibleCommand { + + private File themeImportFile; + private ThemeDefinition theme; + + TaskUpdateThemeImport(File npmFolder, ThemeDefinition theme) { + File generatedDir = new File(npmFolder, FrontendUtils.DEFAULT_GENERATED_DIR); + this.themeImportFile = new File(new File(generatedDir, "theme"), + "theme-generated.js"); + this.theme = theme; + } + + @Override + public void execute() throws ExecutionFailedException { + if (theme == null || theme.getName().isEmpty()) { + return; + } + if (!themeImportFile.getParentFile().mkdirs()) { + LoggerFactory.getLogger(getClass()).debug( + "Didn't create folders as they probably already exist. " + + "If there is a problem check access rights for folder {}", + themeImportFile.getParentFile().getAbsolutePath()); + } + + 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) { + System.out.println("Throwing exception " + e.getMessage()); + throw new ExecutionFailedException( + "Unable to write theme import file", e); + } + } +} 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 17ffb67b326..13e47671e9b 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 @@ -27,6 +27,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static com.vaadin.flow.server.frontend.FrontendUtils.DEFAULT_GENERATED_DIR; import static com.vaadin.flow.server.frontend.FrontendUtils.WEBPACK_CONFIG; import static com.vaadin.flow.server.frontend.FrontendUtils.WEBPACK_GENERATED; @@ -46,6 +47,8 @@ public class TaskUpdateWebpack implements FallibleCommand { private final Path flowImportsFilePath; private final Path webpackConfigPath; private final Path frontendDirectory; + private final Path flowResourcesFolder; + private final Path resourceFolder; /** * Create an instance of the updater given all configurable parameters. @@ -74,6 +77,10 @@ public class TaskUpdateWebpack implements FallibleCommand { this.webpackOutputPath = webpackOutputDirectory.toPath(); this.flowImportsFilePath = generatedFlowImports.toPath(); this.webpackConfigPath = webpackConfigFolder.toPath(); + this.flowResourcesFolder = new File(webpackConfigFolder, + DEFAULT_GENERATED_DIR).toPath(); + this.resourceFolder = new File(webpackOutputDirectory.getParentFile(), + "resources").toPath(); } @Override @@ -126,18 +133,26 @@ private void createWebpackConfig() throws IOException { + getEscapedRelativeWebpackPath(webpackOutputPath) + "');"; String mainLine = "const fileNameOfTheFlowGeneratedMainEntryPoint = require('path').resolve(__dirname, '" + getEscapedRelativeWebpackPath(flowImportsFilePath) + "');"; - String devmodeGizmoJSLine = "const devmodeGizmoJS = '" + FrontendUtils.DEVMODE_GIZMO_MODULE + "'"; + + 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++) { String line = lines.get(i).trim(); if (lines.get(i).startsWith( - "const fileNameOfTheFlowGeneratedMainEntryPoint") - && !line.equals(mainLine)) { + "const fileNameOfTheFlowGeneratedMainEntryPoint") + && !line.equals(mainLine)) { lines.set(i, mainLine); } if (lines.get(i) - .startsWith("const mavenOutputFolderForFlowBundledFiles") - && !line.equals(outputLine)) { + .startsWith("const mavenOutputFolderForFlowBundledFiles") + && !line.equals(outputLine)) { lines.set(i, outputLine); } if (lines.get(i).startsWith("const frontendFolder")) { @@ -147,6 +162,12 @@ private void createWebpackConfig() throws IOException { if (lines.get(i).startsWith("const devmodeGizmoJS")) { lines.set(i, devmodeGizmoJSLine); } + if (lines.get(i).startsWith("const flowFrontendFolder")) { + lines.set(i, frontendFolder); + } + if (lines.get(i).startsWith("const projectStaticAssetsOutputFolder")) { + lines.set(i, assetsResourceFolder); + } } FileUtils.writeLines(generatedFile, lines); 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..8ec8833ec93 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_FOLDER = "themeFolder"; static final String VERSION = "version"; static final String ID = "id"; static final String INCLUDE = "include"; @@ -159,9 +160,11 @@ public void visit(String name, Object value) { themeRouteVisitor = new RepeatedAnnotationVisitor() { @Override public void visit(String name, Object value) { - if (VALUE.equals(name)) { - endPoint.theme.name = ((Type) value).getClassName(); - children.add(endPoint.theme.name); + if (THEME_FOLDER.equals(name)) { + endPoint.theme.themeName = (String)value; + } else if (VALUE.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 (THEME_FOLDER.equals(name)) { + themeRouteVisitor.visit(name, value); + } else if (VALUE.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 c0456b485ee..7b032cd788f 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().getThemeName().isEmpty()) || 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 da1918f5174..161ad0e97d9 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) { @@ -308,7 +309,8 @@ private ThemeData verifyTheme() { .map(theme -> new ThemeData( ((Class) invokeAnnotationMethod(theme, VALUE)) .getName(), - invokeAnnotationMethodAsString(theme, "variant"))) + invokeAnnotationMethodAsString(theme, "variant"), + invokeAnnotationMethodAsString(theme, "themeFolder"))) .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..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 @@ -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/theme/Theme.java b/flow-server/src/main/java/com/vaadin/flow/theme/Theme.java index 1fa018b5e89..2e2a7b33d04 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 @@ -29,8 +29,20 @@ * Defines that there is a theme to use and defines the theme handler * implementation. *

- * The theme allows to define a way to translate base component url to the - * themed component url (@see {@link AbstractTheme}), which specifies components + * The {@code themeFolder} property defines the name of the application theme. When + * the theme is present inside the project, it maps to the {@code + * frontend/theme/}/ folder or alternatively to a folder + * inside the static resources of a jar file, like {@code + * src/main/resources/META-INF/resources/theme//}. + * The application theme is always based on Lumo theme and this is the + * recommended way to theme applications starting from Flow 2.6 and Vaadin 14.6 + *

+ * Alternatively , if instead of Lumo theme the Material theme or another "old + * style custom theme" is to be used, that can be specified with the {@code + * value} property. + * This allows defining a way to translate base component url to the + * themed component url (@see {@link AbstractTheme}), which specifies + * components * styles. *

* By default {@code com.vaadin.flow.theme.lumo.Lumo} theme is used if it's in @@ -90,9 +102,11 @@ /** * The theme translation handler. * + * Defaults to Lumo, If not specified. + * * @return theme handler */ - Class value(); + Class value() default AbstractTheme.class; /** * The theme variant, if any. @@ -100,4 +114,11 @@ * @return the theme variant */ String variant() default ""; + + /** + * The name of the theme to use. + * + * If this is not specified will default to Lumo. + */ + String themeFolder() 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 75c810e2439..810491dfcd5 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 @@ -33,6 +33,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. @@ -41,15 +42,19 @@ public class ThemeDefinition implements Serializable { * the theme class, not null * @param variant * the variant of the theme, not null + * @param name + * name of the theme, not null */ public ThemeDefinition(Class theme, - String variant) { + String variant, String name) { Objects.requireNonNull(theme); Objects.requireNonNull(variant); + Objects.requireNonNull(name); this.theme = theme; this.variant = variant; + this.name = name; } /** @@ -60,7 +65,7 @@ public ThemeDefinition(Class theme, * the annotation to get the definition from */ public ThemeDefinition(Theme themeAnnotation) { - this(themeAnnotation.value(), themeAnnotation.variant()); + this(themeAnnotation.value(), themeAnnotation.variant(), themeAnnotation.themeFolder()); } /** @@ -81,4 +86,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/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..ac06fd2f931 --- /dev/null +++ b/flow-server/src/main/resources/plugins/application-theme-plugin/application-theme-plugin.js @@ -0,0 +1,114 @@ +/* + * 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; + +/** + * The application theme plugin is for generating, collecting and copying of theme files for the application theme. + * + * TODO: enable giving themes to handle #9383 + * + * The plugin should be supplied with the paths for + * + * themeJarFolder - theme folder inside a jar + * themeProjectFolders - array of possible locations for theme folders inside the project + * projectStaticAssetsOutputFolder - path to where static assets should be put + */ +class ApplicationThemePlugin { + constructor(options) { + this.options = options; + + if(!this.options.themeJarFolder) { + throw new Error("Missing themeJarFolder path"); + } + if(!this.options.projectStaticAssetsOutputFolder) { + throw new Error("Missing projectStaticAssetsOutputFolder path"); + } + if(!this.options.themeProjectFolders) { + throw new Error("Missing themeProjectFolders path array"); + } + } + + apply(compiler) { + logger = compiler.getInfrastructureLogger("ApplicationThemePlugin"); + + compiler.hooks.afterEnvironment.tap("ApplicationThemePlugin", () => { + if (fs.existsSync(this.options.themeJarFolder)) { + logger.debug("Found themeFolder in jar file ", this.options.themeJarFolder); + handleThemes(this.options.themeJarFolder, this.options.projectStaticAssetsOutputFolder); + } + + this.options.themeProjectFolders.forEach((themeProjectFolder) => { + if (fs.existsSync(themeProjectFolder)) { + logger.debug("Found themeFolder from ", themeProjectFolder); + handleThemes(themeProjectFolder, this.options.projectStaticAssetsOutputFolder); + } + }); + }); + } +} + +module.exports = ApplicationThemePlugin; + +/** + * Copies static resources for theme and generates/writes the [theme-name].js for webpack to handle. + * + * @param {path} themesFolder folder containing application theme folders + * @param {path} projectStaticAssetsOutputFolder folder to output files to + */ +function handleThemes(themesFolder, projectStaticAssetsOutputFolder) { + const dir = getThemeFoldersSync(themesFolder); + logger.debug("Found", dir.length, "theme directories"); + + for (let i = 0; i < dir.length; i++) { + const folder = dir[i]; + + const themeName = folder; + 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); + } +}; + +/** + * Collect all folders under the given theme project folder. + * The found sub folders are the actual theme 'implementations' + * + * @param {string | Buffer | URL} folder theme project folder to collect folders from + * @returns {string[]} array containing found folder names + */ +function getThemeFoldersSync(folder) { + const themeFolders = []; + fs.readdirSync(folder).forEach(file => { + if (fs.statSync(path.resolve(folder, file)).isDirectory()) { + themeFolders.push(file); + } + }); + return themeFolders; +} 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..31eaccbe1b7 --- /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": "0.1.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..5a642a4dde6 --- /dev/null +++ b/flow-server/src/main/resources/plugins/application-theme-plugin/theme-copy.js @@ -0,0 +1,68 @@ +/* + * 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 {string} themeFolder Folder with theme file + * @param {string} projectStaticAssetsOutputFolder resources output folder + */ +function copyThemeResources(themeName, themeFolder, projectStaticAssetsOutputFolder) { + if (!fs.existsSync(path.resolve(projectStaticAssetsOutputFolder))) { + require('mkdirp')(path.resolve(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 ignoredFileExtensions = [".css", ".js", ".json"]; + +/** + * Recursively copy files found in theme folder excluding any with a extension found in the `ignoredFileExtensions` array. + * + * Any folders met will be generated and the contents copied. + * + * @param {string} folderToCopy folder to copy files from + * @param {string} 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 (!ignoredFileExtensions.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..c7ba5deb782 --- /dev/null +++ b/flow-server/src/main/resources/plugins/application-theme-plugin/theme-generator.js @@ -0,0 +1,100 @@ +/* + * 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'); + +// 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 = ``; + +const injectGlobalCssMethod = ` +// target: Document | ShadowRoot +export const injectGlobalCss = (css, target) => { + const sheet = new CSSStyleSheet(); + sheet.replaceSync(css); + target.adoptedStyleSheets = [...target.adoptedStyleSheets, sheet]; +}; +`; + +/** + * Generate the [themeName].js file for themeFolder which collects all required information from the folder. + * + * @param {string} themeFolder folder of the theme + * @param {string} themeName name of the handled theme + * @returns {string} theme file content + */ +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; +}; + +/** + * Make given string into camelCase. + * + * @param {string} str string to make into cameCase + * @returns {string} camelCased version + */ +function camelCase(str) { + return str.replace(/(?:^\w|[A-Z]|\b\w)/g, function(word, index) { + return index === 0 ? word.toLowerCase() : word.toUpperCase(); + }).replace(/\s+/g, '').replace(/\./g, ''); +} + +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 9f9ef3326ba..f8cdbd1a3b1 100644 --- a/flow-server/src/main/resources/webpack.generated.js +++ b/flow-server/src/main/resources/webpack.generated.js @@ -11,6 +11,7 @@ const {BabelMultiTargetPlugin} = require('webpack-babel-multi-target-plugin'); // Flow plugins const StatsPlugin = require('@vaadin/stats-plugin'); +const ApplicationThemePlugin = require('@vaadin/application-theme-plugin'); const path = require('path'); const baseDir = path.resolve(__dirname); @@ -32,6 +33,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. +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'); @@ -89,6 +109,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 @@ -128,8 +154,21 @@ module.exports = { }] : []), { test: /\.css$/i, - use: ['raw-loader'] - } + use: [ + { + loader: 'css-loader', + options: { + url: (url, resourcePath) => { + // do not handle theme folder url files + if(url.startsWith("theme")) { + return false; + } + return true; + }, + }, + }, + ], + }, ] }, performance: { @@ -173,6 +212,12 @@ module.exports = { } })] : []), + 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/EmptyByteScannerDataTestComponents.java b/flow-server/src/test/java/com/vaadin/flow/server/frontend/EmptyByteScannerDataTestComponents.java index d726b15d5fa..749ccf8d1f7 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(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 62332a7226c..f625d257261 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 @@ -109,7 +109,7 @@ public static class TranslatedImports extends Component { } @JsModule("./common-js-file.js") - @Theme(value = LumoTest.class, variant = LumoTest.DARK) + @Theme(value =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/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'", 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 a3fb7e53c27..00f3b498bee 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 @@ -198,7 +198,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/frontend/UpdateThemedImportsTest.java b/flow-server/src/test/java/com/vaadin/flow/server/frontend/UpdateThemedImportsTest.java index 828a5ccd3ca..42152f01466 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 808dfeef426..1e7c30d2ef1 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(value =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..0219ffbb502 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(value =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(value =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(value =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(value =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(value =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..4290115c27f 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(value =CustomTheme.class) public class RouteLayoutComponent implements RouterLayout { @Override public Element getElement() { diff --git a/flow-test-generic/src/main/java/com/vaadin/flow/testutil/ClassesSerializableTest.java b/flow-test-generic/src/main/java/com/vaadin/flow/testutil/ClassesSerializableTest.java index e339f1b4f7f..c5dbdcdc223 100644 --- a/flow-test-generic/src/main/java/com/vaadin/flow/testutil/ClassesSerializableTest.java +++ b/flow-test-generic/src/main/java/com/vaadin/flow/testutil/ClassesSerializableTest.java @@ -185,6 +185,7 @@ protected Stream getExcludedPatterns() { "com\\.vaadin\\.flow\\.server\\.frontend\\.TaskRunNpmInstall", "com\\.vaadin\\.flow\\.server\\.frontend\\.TaskUpdateImports(\\$.*)?", "com\\.vaadin\\.flow\\.server\\.frontend\\.TaskUpdatePackages", + "com\\.vaadin\\.flow\\.server\\.frontend\\.TaskUpdateThemeImport", "com\\.vaadin\\.flow\\.server\\.frontend\\.TaskUpdateWebpack", // Node downloader classes diff --git a/flow-tests/pom.xml b/flow-tests/pom.xml index 18ae1217d66..2038a974f4b 100644 --- a/flow-tests/pom.xml +++ b/flow-tests/pom.xml @@ -276,8 +276,9 @@ test-scalability test-memory-leaks test-servlet - test-themes - test-themes/pom-npm.xml + test-themes + test-themes-legacy + test-themes-legacy/pom-npm.xml test-lumo-theme test-material-theme servlet-containers diff --git a/flow-tests/test-lumo-theme/pom.xml b/flow-tests/test-lumo-theme/pom.xml index 1290538a91a..757370fee65 100644 --- a/flow-tests/test-lumo-theme/pom.xml +++ b/flow-tests/test-lumo-theme/pom.xml @@ -12,6 +12,7 @@ war true + true @@ -76,6 +77,37 @@ + + legacy-theme + + false + + + + + + + org.eclipse.jetty + jetty-maven-plugin + + + com.vaadin + flow-maven-plugin + + + + prepare-frontend + build-frontend + + + + + true + + + + + local-run diff --git a/flow-tests/test-material-theme/pom.xml b/flow-tests/test-material-theme/pom.xml index b7dc8cd4071..3e1001e4d84 100644 --- a/flow-tests/test-material-theme/pom.xml +++ b/flow-tests/test-material-theme/pom.xml @@ -12,6 +12,7 @@ war true + true @@ -75,6 +76,37 @@ + + legacy-theme + + false + + + + + + + com.vaadin + flow-maven-plugin + + + + prepare-frontend + build-frontend + + + + + true + + + + org.eclipse.jetty + jetty-maven-plugin + + + + local-run diff --git a/flow-tests/test-misc/frontend/theme/my-theme/my-component-themed.js b/flow-tests/test-misc/frontend/legacyTheme/my-theme/my-component-themed.js similarity index 100% rename from flow-tests/test-misc/frontend/theme/my-theme/my-component-themed.js rename to flow-tests/test-misc/frontend/legacyTheme/my-theme/my-component-themed.js diff --git a/flow-tests/test-misc/pom.xml b/flow-tests/test-misc/pom.xml index b5f3cbb1436..115971aa31e 100644 --- a/flow-tests/test-misc/pom.xml +++ b/flow-tests/test-misc/pom.xml @@ -58,6 +58,17 @@ org.eclipse.jetty jetty-maven-plugin + + + + + + + vaadin.allow.appshell.annotations + false + + + 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 32d3a7f9a31..69236954843 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 @@ -45,7 +45,7 @@ public String getBaseUrl() { @Override public String getThemeUrl() { - return "theme/my-theme"; + return "legacyTheme/my-theme"; } } diff --git a/flow-tests/test-themes/frontend/theme/myTheme/client-side-component.js b/flow-tests/test-themes-legacy/frontend/legacyTheme/myTheme/client-side-component.js similarity index 100% rename from flow-tests/test-themes/frontend/theme/myTheme/client-side-component.js rename to flow-tests/test-themes-legacy/frontend/legacyTheme/myTheme/client-side-component.js diff --git a/flow-tests/test-themes/frontend/theme/myTheme/npm-themed-component.js b/flow-tests/test-themes-legacy/frontend/legacyTheme/myTheme/npm-themed-component.js similarity index 100% rename from flow-tests/test-themes/frontend/theme/myTheme/npm-themed-component.js rename to flow-tests/test-themes-legacy/frontend/legacyTheme/myTheme/npm-themed-component.js diff --git a/flow-tests/test-themes-legacy/frontend/src/client-side-component.js b/flow-tests/test-themes-legacy/frontend/src/client-side-component.js new file mode 100644 index 00000000000..0a15f228908 --- /dev/null +++ b/flow-tests/test-themes-legacy/frontend/src/client-side-component.js @@ -0,0 +1,17 @@ +import {PolymerElement} from '@polymer/polymer/polymer-element.js'; +import {html} from '@polymer/polymer/lib/utils/html-tag.js'; + +class ClientSideComponent extends PolymerElement { + static get template() { + return html` +

Non Themed Client Side Component
+`; + } + + static get is() { + return 'client-side-component' + } + +} + +customElements.define(ClientSideComponent.is, ClientSideComponent); diff --git a/flow-tests/test-themes-legacy/frontend/src/npm-themed-component.js b/flow-tests/test-themes-legacy/frontend/src/npm-themed-component.js new file mode 100644 index 00000000000..f73bc23f665 --- /dev/null +++ b/flow-tests/test-themes-legacy/frontend/src/npm-themed-component.js @@ -0,0 +1,19 @@ +import {PolymerElement} from '@polymer/polymer/polymer-element.js'; +import {html} from '@polymer/polymer/lib/utils/html-tag.js'; +import './client-side-component.js'; + +class NpmThemedComponent extends PolymerElement { + static get template() { + return html` +
Non Themed Component
+ +`; + } + + static get is() { + return 'npm-themed-component' + } + +} + +customElements.define(NpmThemedComponent.is, NpmThemedComponent); diff --git a/flow-tests/test-themes/pom-npm.xml b/flow-tests/test-themes-legacy/pom-npm.xml similarity index 98% rename from flow-tests/test-themes/pom-npm.xml rename to flow-tests/test-themes-legacy/pom-npm.xml index 45a7ddc5584..dd5748cb1b7 100644 --- a/flow-tests/test-themes/pom-npm.xml +++ b/flow-tests/test-themes-legacy/pom-npm.xml @@ -7,7 +7,7 @@ flow-tests 2.6-SNAPSHOT - flow-test-themes-npm + flow-test-themes-legacy-npm Flow themes tests in NPM mode war diff --git a/flow-tests/test-themes-legacy/pom.xml b/flow-tests/test-themes-legacy/pom.xml new file mode 100644 index 00000000000..3bac0aa9d6e --- /dev/null +++ b/flow-tests/test-themes-legacy/pom.xml @@ -0,0 +1,104 @@ + + + 4.0.0 + + com.vaadin + flow-tests + 2.6-SNAPSHOT + + flow-test-themes-legacy + Flow themes tests + war + + true + NpmThemedComponentIT + + + + + com.vaadin + flow-test-common + ${project.version} + + + com.vaadin + flow-html-components-testbench + ${project.version} + test + + + com.vaadin + flow-server-compatibility-mode + ${project.version} + + + org.webjars.bowergithub.polymer + polymer + + + + + + + + org.eclipse.jetty + jetty-maven-plugin + ${jetty.version} + + + + start-jetty + pre-integration-test + + start + + + + stop-jetty + post-integration-test + + stop + + + + + + + + + + local-run + + + !test.use.hub + + + + + + com.lazerycode.selenium + driver-binary-downloader-maven-plugin + ${driver.binary.downloader.maven.plugin.version} + + true + ${project.rootdir}/driver + ${project.rootdir}/driver_zips + ${project.rootdir}/drivers.xml + + + + pre-integration-test + + selenium + + + + + + + + + + diff --git a/flow-tests/test-themes-legacy/src/main/java/com/vaadin/flow/uitest/ui/servlets/WorkaroundServlet.java b/flow-tests/test-themes-legacy/src/main/java/com/vaadin/flow/uitest/ui/servlets/WorkaroundServlet.java new file mode 100644 index 00000000000..f7d11ba7a05 --- /dev/null +++ b/flow-tests/test-themes-legacy/src/main/java/com/vaadin/flow/uitest/ui/servlets/WorkaroundServlet.java @@ -0,0 +1,30 @@ +/* + * 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.uitest.ui.servlets; + +import javax.servlet.annotation.WebServlet; + +import com.vaadin.flow.server.VaadinServlet; + +/** + * This is a temporary workaround until #5740 is fixed. + * + * @since 2.0 + */ +@WebServlet("/*") +public class WorkaroundServlet extends VaadinServlet { + +} diff --git a/flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/template/ThemedTemplateView.java b/flow-tests/test-themes-legacy/src/main/java/com/vaadin/flow/uitest/ui/template/ThemedTemplateView.java similarity index 100% rename from flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/template/ThemedTemplateView.java rename to flow-tests/test-themes-legacy/src/main/java/com/vaadin/flow/uitest/ui/template/ThemedTemplateView.java diff --git a/flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/theme/CustomStylesView.java b/flow-tests/test-themes-legacy/src/main/java/com/vaadin/flow/uitest/ui/theme/CustomStylesView.java similarity index 100% rename from flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/theme/CustomStylesView.java rename to flow-tests/test-themes-legacy/src/main/java/com/vaadin/flow/uitest/ui/theme/CustomStylesView.java diff --git a/flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/theme/HtmlParserThemeTemplateView.java b/flow-tests/test-themes-legacy/src/main/java/com/vaadin/flow/uitest/ui/theme/HtmlParserThemeTemplateView.java similarity index 100% rename from flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/theme/HtmlParserThemeTemplateView.java rename to flow-tests/test-themes-legacy/src/main/java/com/vaadin/flow/uitest/ui/theme/HtmlParserThemeTemplateView.java diff --git a/flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/theme/MyComponent.java b/flow-tests/test-themes-legacy/src/main/java/com/vaadin/flow/uitest/ui/theme/MyComponent.java similarity index 100% rename from flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/theme/MyComponent.java rename to flow-tests/test-themes-legacy/src/main/java/com/vaadin/flow/uitest/ui/theme/MyComponent.java diff --git a/flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/theme/MyComponentView.java b/flow-tests/test-themes-legacy/src/main/java/com/vaadin/flow/uitest/ui/theme/MyComponentView.java similarity index 100% rename from flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/theme/MyComponentView.java rename to flow-tests/test-themes-legacy/src/main/java/com/vaadin/flow/uitest/ui/theme/MyComponentView.java diff --git a/flow-tests/test-themes-legacy/src/main/java/com/vaadin/flow/uitest/ui/theme/MyTheme.java b/flow-tests/test-themes-legacy/src/main/java/com/vaadin/flow/uitest/ui/theme/MyTheme.java new file mode 100644 index 00000000000..791ce50b2ca --- /dev/null +++ b/flow-tests/test-themes-legacy/src/main/java/com/vaadin/flow/uitest/ui/theme/MyTheme.java @@ -0,0 +1,39 @@ +/* + * 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.uitest.ui.theme; + +import java.util.Collections; +import java.util.List; + +import com.vaadin.flow.theme.AbstractTheme; + +public class MyTheme implements AbstractTheme { + @Override + public String getBaseUrl() { + return "src/"; + } + + @Override + public String getThemeUrl() { + return "legacyTheme/myTheme/"; + } + + @Override + public List getHeaderInlineContents() { + return Collections.singletonList("\n