Skip to content

Commit

Permalink
feat: Enable adding assets from node_modules (#9495)
Browse files Browse the repository at this point in the history
* feat: Enable adding assets from node_modules

It is now possible to import resources from
installed node_modules by adding the asset to
theme/my-theme/theme.json

Fixes #9468

# Conflicts:
#	flow-tests/test-themes-legacy/src/main/java/com/vaadin/flow/uitest/ui/theme/MyComponent.java
  • Loading branch information
caalador authored and taefi committed Mar 2, 2021
1 parent b21c638 commit 1493a65
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
const fs = require('fs');
const path = require('path');
const generateThemeFile = require('./theme-generator');
const copyThemeResources = require('./theme-copy');
const { copyThemeResources, copyStaticAssets } = require('./theme-copy');

let logger;

Expand Down Expand Up @@ -110,7 +110,10 @@ function handleThemes(themeName, themesFolder, projectStaticAssetsOutputFolder)
if (fs.existsSync(themeFolder)) {
logger.debug("Found theme ", themeName, " in folder ", themeFolder);

const themeProperties = getThemeProperties(themeFolder);

copyThemeResources(themeFolder, projectStaticAssetsOutputFolder);
copyStaticAssets(themeProperties, projectStaticAssetsOutputFolder, logger);

const themeFile = generateThemeFile(themeFolder, themeName);

Expand All @@ -119,3 +122,11 @@ function handleThemes(themeName, themesFolder, projectStaticAssetsOutputFolder)
}
return false;
};

function getThemeProperties(themeFolder) {
const themePropertyFile = path.resolve(themeFolder, 'theme.json');
if (!fs.existsSync(themePropertyFile)) {
return {};
}
return JSON.parse(fs.readFileSync(themePropertyFile));
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
],
"repository": "vaadin/flow",
"name": "@vaadin/application-theme-plugin",
"version": "0.1.5",
"version": "0.1.6",
"main": "application-theme-plugin.js",
"author": "Vaadin Ltd",
"license": "Apache-2.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

const fs = require('fs');
const path = require('path');
const glob = require('glob');

/**
* Copy theme files to static assets folder. All files in the theme folder will be copied excluding
Expand Down Expand Up @@ -59,4 +60,62 @@ function copyThemeFiles(folderToCopy, targetFolder) {
});
}

module.exports = copyThemeResources;

/**
* Copy any static node_modules assets marked in theme.json to
* project static assets folder.
*
* The theme.json content for assets is set up as:
* {
* assets: {
* "node_module identifier": {
* "copy-rule": "target/folder",
* }
* }
* }
*
* This would mean that an asset would be built as:
* "@fortawesome/fontawesome-free": {
* "svgs/regular/**": "fortawesome/icons"
* }
* Where '@fortawesome/fontawesome-free' is the npm package, 'svgs/regular/**' is what should be copied
* and 'fortawesome/icons' is the target directory under projectStaticAssetsOutputFolder where things
* will get copied to.
*
* Note! there can be multiple copy-rules with target folders for one npm package asset.
*
* @param {json} themeProperties
* @param {string} projectStaticAssetsOutputFolder
* @param {logger} theme plugin logger
*/
function copyStaticAssets(themeProperties, projectStaticAssetsOutputFolder, logger) {

const assets = themeProperties['assets'];
if (!assets) {
logger.log("no assets to handle no static assets were copied");
return;
}

fs.mkdirSync(projectStaticAssetsOutputFolder, {
recursive: true
});
Object.keys(assets).forEach((module) => {

const copyRules = assets[module];
Object.keys(copyRules).forEach((copyRule) => {
const nodeSources = path.resolve('node_modules/', module, copyRule);
const files = glob.sync(nodeSources, { nodir: true });
const targetFolder = path.resolve(projectStaticAssetsOutputFolder, copyRules[copyRule]);

fs.mkdirSync(targetFolder, {
recursive: true
});
files.forEach((file) => {
logger.trace("Copying: ", file, '=>', targetFolder);
fs.copyFileSync(file, path.resolve(targetFolder, path.basename(file)));
});
});
});
};

module.exports = { copyThemeResources, copyStaticAssets };
7 changes: 7 additions & 0 deletions flow-tests/test-themes/frontend/theme/app-theme/theme.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"assets": {
"@fortawesome/fontawesome-free": {
"svgs/regular/**": "fortawesome/icons"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,21 @@

import com.vaadin.flow.component.dependency.NpmPackage;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.Image;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.theme.Theme;

@Route("com.vaadin.flow.uitest.ui.theme.ThemeView")
@Theme(themeFolder = "app-theme")
@NpmPackage(value = "@vaadin/vaadin-themable-mixin", version = "1.6.1")
@NpmPackage(value = "@fortawesome/fontawesome-free", version = "5.15.1")
public class ThemeView extends Div {

public static final String MY_POLYMER_ID = "field";
public static final String MY_LIT_ID = "button";
public static final String TEST_TEXT_ID = "test-text";
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 SUB_COMPONENT_ID = "sub-component";
Expand All @@ -47,7 +50,12 @@ public ThemeView() {
Span octopuss = new Span();
octopuss.setId(OCTOPUSS_ID);

add(textSpan, subCss, butterfly, octopuss);
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(new Div());
add(new MyPolymerField().withId(MY_POLYMER_ID));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;

import com.vaadin.flow.component.html.testbench.ImageElement;
import com.vaadin.flow.component.html.testbench.SpanElement;
import com.vaadin.flow.testutil.ChromeBrowserTest;
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.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;
import static com.vaadin.flow.uitest.ui.theme.ThemeView.OCTOPUSS_ID;
import static com.vaadin.flow.uitest.ui.theme.ThemeView.SUB_COMPONENT_ID;

Expand Down Expand Up @@ -87,18 +89,37 @@ public void subCssWithRelativePath_urlPathIsNotRelative() {
checkLogsForErrors();

Assert.assertEquals("Imported css file URLs should have been handled.",
"url(\"" + getRootURL() + "/path/VAADIN/static/icons/archive.png\")",
"url(\"" + getRootURL()
+ "/path/VAADIN/static/icons/archive.png\")",
$(SpanElement.class).id(SUB_COMPONENT_ID)
.getCssValue("background-image"));
}

@Test
public void staticModuleAsset_servedFromStatic() {
open();
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"));

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"));
}

@Test
public void nonThemeDependency_urlIsNotRewritten() {
open();
checkLogsForErrors();

Assert.assertEquals("Relative non theme url should not be touched",
"url(\"" + getRootURL() + "/path/test/path/monarch-butterfly.jpg\")",
"url(\"" + getRootURL()
+ "/path/test/path/monarch-butterfly.jpg\")",
$(SpanElement.class).id(BUTTERFLY_ID)
.getCssValue("background-image"));

Expand All @@ -107,12 +128,10 @@ public void nonThemeDependency_urlIsNotRewritten() {
$(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"));


getDriver().get(getRootURL() + "/octopuss.jpg");
Assert.assertFalse("root resource should be served",
driver.getPageSource().contains("HTTP ERROR 404 Not Found"));
Expand Down

0 comments on commit 1493a65

Please sign in to comment.