From 9b25ea8b2b253319f85406ef98f277dbcbf725ba Mon Sep 17 00:00:00 2001 From: tanbui Date: Mon, 30 Nov 2020 18:12:22 +0200 Subject: [PATCH] feat: Enable importing CSS from node_modules (#9410) --- .../application-theme-plugin.js | 2 +- .../theme-generator.js | 11 ++- .../frontend/theme/app-theme/theme.json | 1 + .../flow/uitest/ui/theme/ThemeView.java | 7 +- .../vaadin/flow/uitest/ui/theme/ThemeIT.java | 71 +++++++++++-------- 5 files changed, 59 insertions(+), 33 deletions(-) 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 index 75ae11e582c..ea60202f537 100644 --- 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 @@ -115,7 +115,7 @@ function handleThemes(themeName, themesFolder, projectStaticAssetsOutputFolder) copyThemeResources(themeFolder, projectStaticAssetsOutputFolder); copyStaticAssets(themeProperties, projectStaticAssetsOutputFolder, logger); - const themeFile = generateThemeFile(themeFolder, themeName); + const themeFile = generateThemeFile(themeFolder, themeName, themeProperties); fs.writeFileSync(path.resolve(themeFolder, themeName + '.js'), themeFile); return true; 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 index f26356215e9..55f91686b53 100644 --- 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 @@ -46,7 +46,7 @@ export const injectGlobalCss = (css, target) => { * @param {string} themeName name of the handled theme * @returns {string} theme file content */ -function generateThemeFile(themeFolder, themeName) { +function generateThemeFile(themeFolder, themeName, themeProperties) { const globalFiles = glob.sync('*.css', { cwd: themeFolder, nodir: true, @@ -78,6 +78,15 @@ function generateThemeFile(themeFolder, themeName) { globalCssCode.push(`injectGlobalCss(${variable}.toString(), target);\n`); }); + let i = 0; + if (themeProperties.importCss) { + themeProperties.importCss.forEach((cssPath) => { + const variable = 'module' + i++; + imports.push(`import ${variable} from '${cssPath}';\n`); + globalCssCode.push(`injectGlobalCss(${variable}.toString(), target);\n`); + }); + } + componentsFiles.forEach((componentCss) => { const filename = path.basename(componentCss); const tag = filename.replace('.css', ''); diff --git a/flow-tests/test-themes/frontend/theme/app-theme/theme.json b/flow-tests/test-themes/frontend/theme/app-theme/theme.json index d719e3255c5..d56847bae71 100644 --- a/flow-tests/test-themes/frontend/theme/app-theme/theme.json +++ b/flow-tests/test-themes/frontend/theme/app-theme/theme.json @@ -1,4 +1,5 @@ { + "importCss": ["@fortawesome/fontawesome-free/css/all.css"], "assets": { "@fortawesome/fontawesome-free": { "svgs/regular/**": "fortawesome/icons" diff --git a/flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/theme/ThemeView.java b/flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/theme/ThemeView.java index 39049d46c66..fc86e89ae86 100644 --- a/flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/theme/ThemeView.java +++ b/flow-tests/test-themes/src/main/java/com/vaadin/flow/uitest/ui/theme/ThemeView.java @@ -30,6 +30,7 @@ public class ThemeView extends Div { public static final String SNOWFLAKE_ID = "fortawesome"; public static final String BUTTERFLY_ID = "butterfly"; public static final String OCTOPUSS_ID = "octopuss"; + public static final String FONTAWESOME_ID = "font-awesome"; public static final String SUB_COMPONENT_ID = "sub-component"; public ThemeView() { @@ -45,12 +46,16 @@ public ThemeView() { Span octopuss = new Span(); octopuss.setId(OCTOPUSS_ID); + Span faText = new Span("This test is FontAwesome."); + faText.setClassName("fab"); + faText.setId(FONTAWESOME_ID); + Image snowFlake = new Image( "VAADIN/static/fortawesome/icons/snowflake.svg", "snowflake"); snowFlake.setHeight("1em"); snowFlake.setId(SNOWFLAKE_ID); - add(textSpan, snowFlake, subCss, butterfly, octopuss); + add(textSpan, snowFlake, subCss, butterfly, octopuss, faText); add(new Div()); add(new MyPolymerField().withId(MY_POLYMER_ID)); diff --git a/flow-tests/test-themes/src/test/java/com/vaadin/flow/uitest/ui/theme/ThemeIT.java b/flow-tests/test-themes/src/test/java/com/vaadin/flow/uitest/ui/theme/ThemeIT.java index 41e66b94533..c51ba38e20d 100644 --- a/flow-tests/test-themes/src/test/java/com/vaadin/flow/uitest/ui/theme/ThemeIT.java +++ b/flow-tests/test-themes/src/test/java/com/vaadin/flow/uitest/ui/theme/ThemeIT.java @@ -26,6 +26,7 @@ import com.vaadin.testbench.TestBenchElement; import static com.vaadin.flow.uitest.ui.theme.ThemeView.BUTTERFLY_ID; +import static com.vaadin.flow.uitest.ui.theme.ThemeView.FONTAWESOME_ID; import static com.vaadin.flow.uitest.ui.theme.ThemeView.MY_LIT_ID; import static com.vaadin.flow.uitest.ui.theme.ThemeView.MY_POLYMER_ID; import static com.vaadin.flow.uitest.ui.theme.ThemeView.SNOWFLAKE_ID; @@ -38,11 +39,11 @@ public class ThemeIT extends ChromeBrowserTest { public void secondTheme_staticFilesNotCopied() { getDriver().get(getRootURL() + "/path/VAADIN/static/img/bg.jpg"); Assert.assertFalse("app-theme static files should be copied", - driver.getPageSource().contains("HTTP ERROR 404 Not Found")); + driver.getPageSource().contains("HTTP ERROR 404 Not Found")); getDriver().get(getRootURL() + "/path/VAADIN/static/no-copy.txt"); Assert.assertTrue("no-copy theme should not be handled", - driver.getPageSource().contains("HTTP ERROR 404 Not Found")); + driver.getPageSource().contains("HTTP ERROR 404 Not Found")); } @Test @@ -53,14 +54,24 @@ public void applicationTheme_GlobalCss_isUsed() { final WebElement body = findElement(By.tagName("body")); Assert.assertEquals( - "url(\"" + getRootURL() + "/path/VAADIN/static/img/bg.jpg\")", - body.getCssValue("background-image")); + "url(\"" + getRootURL() + "/path/VAADIN/static/img/bg.jpg\")", + body.getCssValue("background-image")); Assert.assertEquals("Ostrich", body.getCssValue("font-family")); getDriver().get(getRootURL() + "/path/VAADIN/static/img/bg.jpg"); Assert.assertFalse("app-theme background file should be served", - driver.getPageSource().contains("Could not navigate")); + driver.getPageSource().contains("Could not navigate")); + } + + @Test + public void applicationTheme_importCSS_isUsed() { + open(); + checkLogsForErrors(); + + Assert.assertEquals("Imported FontAwesome css file should be applied.", + "Font Awesome 5 Brands", $(SpanElement.class).id(FONTAWESOME_ID) + .getCssValue("font-family")); } @Test @@ -68,19 +79,19 @@ public void componentThemeIsApplied_forPolymerAndLit() { open(); TestBenchElement myField = $(TestBenchElement.class).id(MY_POLYMER_ID); TestBenchElement input = myField.$(TestBenchElement.class) - .id("vaadin-text-field-input-0"); + .id("vaadin-text-field-input-0"); Assert.assertEquals("Polymer text field should have red background", - "rgba(255, 0, 0, 1)", input.getCssValue("background-color")); + "rgba(255, 0, 0, 1)", input.getCssValue("background-color")); myField = $(TestBenchElement.class).id(MY_LIT_ID); final SpanElement radio = myField.$(SpanElement.class).all().stream() - .filter(element -> "radio".equals(element.getAttribute("part"))) - .findFirst().orElseGet(null); + .filter(element -> "radio".equals(element.getAttribute("part"))) + .findFirst().orElseGet(null); Assert.assertNotNull("Element with part='radio' was not found", radio); Assert.assertEquals("Lit radiobutton should have red background", - "rgba(255, 0, 0, 1)", radio.getCssValue("background-color")); + "rgba(255, 0, 0, 1)", radio.getCssValue("background-color")); } @Test @@ -89,10 +100,10 @@ public void subCssWithRelativePath_urlPathIsNotRelative() { checkLogsForErrors(); Assert.assertEquals("Imported css file URLs should have been handled.", - "url(\"" + getRootURL() - + "/path/VAADIN/static/icons/archive.png\")", - $(SpanElement.class).id(SUB_COMPONENT_ID) - .getCssValue("background-image")); + "url(\"" + getRootURL() + + "/path/VAADIN/static/icons/archive.png\")", + $(SpanElement.class).id(SUB_COMPONENT_ID) + .getCssValue("background-image")); } @Test @@ -101,15 +112,15 @@ public void staticModuleAsset_servedFromStatic() { checkLogsForErrors(); Assert.assertEquals( - "Node assets should have been copied to 'VAADIN/static'", - getRootURL() - + "/path/VAADIN/static/fortawesome/icons/snowflake.svg", - $(ImageElement.class).id(SNOWFLAKE_ID).getAttribute("src")); + "Node assets should have been copied to 'VAADIN/static'", + getRootURL() + + "/path/VAADIN/static/fortawesome/icons/snowflake.svg", + $(ImageElement.class).id(SNOWFLAKE_ID).getAttribute("src")); - open(getRootURL() + "/path/" + $(ImageElement.class).id(SNOWFLAKE_ID) - .getAttribute("src")); + open(getRootURL() + "/path/" + + $(ImageElement.class).id(SNOWFLAKE_ID).getAttribute("src")); Assert.assertFalse("Node static icon should be available", - driver.getPageSource().contains("HTTP ERROR 404 Not Found")); + driver.getPageSource().contains("HTTP ERROR 404 Not Found")); } @Test @@ -118,23 +129,23 @@ public void nonThemeDependency_urlIsNotRewritten() { checkLogsForErrors(); Assert.assertEquals("Relative non theme url should not be touched", - "url(\"" + getRootURL() - + "/path/test/path/monarch-butterfly.jpg\")", - $(SpanElement.class).id(BUTTERFLY_ID) - .getCssValue("background-image")); + "url(\"" + getRootURL() + + "/path/test/path/monarch-butterfly.jpg\")", + $(SpanElement.class).id(BUTTERFLY_ID) + .getCssValue("background-image")); Assert.assertEquals("Absolute non theme url should not be touched", - "url(\"" + getRootURL() + "/octopuss.jpg\")", - $(SpanElement.class).id(OCTOPUSS_ID) - .getCssValue("background-image")); + "url(\"" + getRootURL() + "/octopuss.jpg\")", + $(SpanElement.class).id(OCTOPUSS_ID) + .getCssValue("background-image")); getDriver().get(getRootURL() + "/path/test/path/monarch-butterfly.jpg"); Assert.assertFalse("webapp resource should be served", - driver.getPageSource().contains("HTTP ERROR 404 Not Found")); + driver.getPageSource().contains("HTTP ERROR 404 Not Found")); getDriver().get(getRootURL() + "/octopuss.jpg"); Assert.assertFalse("root resource should be served", - driver.getPageSource().contains("HTTP ERROR 404 Not Found")); + driver.getPageSource().contains("HTTP ERROR 404 Not Found")); } @Override