diff --git a/flow-plugins/flow-gradle-plugin/src/main/kotlin/com/vaadin/gradle/VaadinFlowPluginExtension.kt b/flow-plugins/flow-gradle-plugin/src/main/kotlin/com/vaadin/gradle/VaadinFlowPluginExtension.kt index 75e6420d242..1fbab3f05af 100644 --- a/flow-plugins/flow-gradle-plugin/src/main/kotlin/com/vaadin/gradle/VaadinFlowPluginExtension.kt +++ b/flow-plugins/flow-gradle-plugin/src/main/kotlin/com/vaadin/gradle/VaadinFlowPluginExtension.kt @@ -510,7 +510,7 @@ public class PluginEffectiveConfiguration( "alwaysExecutePrepareFrontend=${alwaysExecutePrepareFrontend.get()}, " + "frontendHotdeploy=${frontendHotdeploy.get()}," + "reactEnable=${reactEnable.get()}," + - "cleanFrontendFiles=${cleanFrontendFiles.get()}" + + "cleanFrontendFiles=${cleanFrontendFiles.get()}," + "frontendExtraFileExtensions=${frontendExtraFileExtensions.get()}" + ")" public companion object { diff --git a/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/FlowModeAbstractMojo.java b/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/FlowModeAbstractMojo.java index b379805621f..8f806af91ec 100644 --- a/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/FlowModeAbstractMojo.java +++ b/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/FlowModeAbstractMojo.java @@ -238,14 +238,13 @@ public abstract class FlowModeAbstractMojo extends AbstractMojo /** * Parameter for adding file extensions to handle during frontend tasks. *

- * From the commandline use space separated list - * {@code -DfrontendExtraFileExtensions="svg ico"} + * From the commandline use comma separated list + * {@code -Ddevmode.frontendExtraFileExtensions="svg,ico"} *

- *

- * In plugin configuration + * In plugin configuration use comma separated values * - * svg - * ico + * + * svg,ico * * */ diff --git a/flow-plugins/flow-plugin-base/src/main/java/com/vaadin/flow/plugin/base/BuildFrontendUtil.java b/flow-plugins/flow-plugin-base/src/main/java/com/vaadin/flow/plugin/base/BuildFrontendUtil.java index 94f0b6b3236..c82860907c3 100644 --- a/flow-plugins/flow-plugin-base/src/main/java/com/vaadin/flow/plugin/base/BuildFrontendUtil.java +++ b/flow-plugins/flow-plugin-base/src/main/java/com/vaadin/flow/plugin/base/BuildFrontendUtil.java @@ -33,6 +33,7 @@ import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.Manifest; +import java.util.stream.Collectors; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; @@ -74,6 +75,7 @@ import static com.vaadin.flow.server.Constants.NPM_TOKEN; import static com.vaadin.flow.server.Constants.PROJECT_FRONTEND_GENERATED_DIR_TOKEN; import static com.vaadin.flow.server.InitParameters.APPLICATION_IDENTIFIER; +import static com.vaadin.flow.server.InitParameters.FRONTEND_EXTRA_EXTENSIONS; import static com.vaadin.flow.server.InitParameters.FRONTEND_HOTDEPLOY; import static com.vaadin.flow.server.InitParameters.NODE_DOWNLOAD_ROOT; import static com.vaadin.flow.server.InitParameters.NODE_VERSION; @@ -166,7 +168,7 @@ public static void prepareFrontend(PluginAdapterBase adapter) .withHomeNodeExecRequired(adapter.requireHomeNodeExec()) .setJavaResourceFolder(adapter.javaResourceFolder()) .withProductionMode(false).withReact(adapter.isReactEnabled()) - .withExtraFrontendFileExtensions( + .withFrontendExtraFileExtensions( adapter.frontendExtraFileExtensions()); // Copy jar artifact contents in TaskCopyFrontendFiles @@ -266,6 +268,12 @@ public static File propagateBuildInfo(PluginAdapterBase adapter) { buildInfo.put(REACT_ENABLE, adapter.isReactEnabled()); + if (!adapter.frontendExtraFileExtensions().isEmpty()) { + buildInfo.put(FRONTEND_EXTRA_EXTENSIONS, + adapter.frontendExtraFileExtensions().stream() + .collect(Collectors.joining(","))); + } + try { FileUtils.forceMkdir(token.getParentFile()); FileIOUtils.writeIfChanged(token, @@ -408,7 +416,7 @@ public static void runDevBuildNodeUpdater(PluginAdapterBuild adapter) .skipDevBundleBuild(adapter.skipDevBundleBuild()) .withCompressBundle(adapter.compressBundle()) .withReact(adapter.isReactEnabled()) - .withExtraFrontendFileExtensions( + .withFrontendExtraFileExtensions( adapter.frontendExtraFileExtensions()); new NodeTasks(options).execute(); } catch (ExecutionFailedException exception) { @@ -743,6 +751,7 @@ public static void updateBuildFile(PluginAdapterBuild adapter, buildInfo.remove(NODE_DOWNLOAD_ROOT); buildInfo.remove(FRONTEND_TOKEN); buildInfo.remove(FRONTEND_HOTDEPLOY); + buildInfo.remove(FRONTEND_EXTRA_EXTENSIONS); buildInfo.remove(InitParameters.SERVLET_PARAMETER_ENABLE_PNPM); buildInfo.remove(InitParameters.SERVLET_PARAMETER_ENABLE_BUN); buildInfo.remove(InitParameters.CI_BUILD); diff --git a/flow-server/src/main/java/com/vaadin/flow/server/InitParameters.java b/flow-server/src/main/java/com/vaadin/flow/server/InitParameters.java index be19d6435c2..391291bb7a0 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/InitParameters.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/InitParameters.java @@ -179,6 +179,12 @@ public class InitParameters implements Serializable { */ public static final String APPLICATION_PARAMETER_DEVMODE_ENABLE_COMPONENT_TRACKER = "devmode.componentTracker.enabled"; + /** + * Configuration parameter name for adding extra file extensions for stats + * bundle to generate hashes for. + */ + public static final String FRONTEND_EXTRA_EXTENSIONS = "devmode.frontendExtraFileExtensions"; + /** * I18N provider property. */ @@ -285,5 +291,4 @@ public class InitParameters implements Serializable { */ public static final String APPLICATION_IDENTIFIER = "applicationIdentifier"; - public static final String FRONTEND_EXTRA_EXTENSIONS = "frontendExtraFileExtensions"; } diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/Options.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/Options.java index c4b25be2761..8b55d72e62d 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/Options.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/Options.java @@ -4,6 +4,7 @@ import java.io.Serializable; import java.net.URI; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Set; @@ -977,7 +978,7 @@ public boolean isCleanOldGeneratedFiles() { * the file extensions to add for the project * @return this builder */ - public Options withExtraFrontendFileExtensions( + public Options withFrontendExtraFileExtensions( List frontendExtraFileExtensions) { this.frontendExtraFileExtensions = frontendExtraFileExtensions; return this; diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskUpdateVite.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskUpdateVite.java index 6f45170ac74..6703ca93681 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskUpdateVite.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskUpdateVite.java @@ -23,6 +23,7 @@ import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.Collections; +import java.util.List; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -121,6 +122,8 @@ private void createGeneratedConfig() throws IOException { .getResource(FrontendUtils.VITE_GENERATED_CONFIG); String template = IOUtils.toString(resource, StandardCharsets.UTF_8); + System.out.println( + "=== '" + options.getFrontendExtraFileExtensions() + "'"); template = template .replace("#settingsImport#", "./" + options.getBuildDirectoryName() + "/" @@ -131,12 +134,8 @@ private void createGeneratedConfig() throws IOException { webComponentTags == null || webComponentTags.isEmpty() ? "" : String.join(";", webComponentTags)) - .replace("#frontendExtraFileExtensions#", Optional - .ofNullable(options.getFrontendExtraFileExtensions()) - .orElse(Collections.emptyList()).stream() - .map(ext -> ext.replace("'", "\\'")) - .map(ext -> ext.startsWith(".") ? ext : "." + ext) - .collect(Collectors.joining("', '", ", '", "'"))); + .replace("#frontendExtraFileExtensions#", + getFrontendExtraFileExtensions()); template = updateFileSystemRouterVitePlugin(template); FileIOUtils.writeIfChanged(generatedConfigFile, template); @@ -144,6 +143,19 @@ private void createGeneratedConfig() throws IOException { generatedConfigFile); } + private String getFrontendExtraFileExtensions() { + Optional> frontendExtraFileExtensions = Optional + .ofNullable(options.getFrontendExtraFileExtensions()); + if (frontendExtraFileExtensions.isPresent() + && frontendExtraFileExtensions.get().size() > 0) { + return frontendExtraFileExtensions.get().stream() + .map(ext -> ext.replace("'", "\\'")) + .map(ext -> ext.startsWith(".") ? ext : "." + ext) + .collect(Collectors.joining("', '", ", '", "'")); + } + return ""; + } + private String updateFileSystemRouterVitePlugin(String template) { if (options.isReactEnabled() && FrontendUtils.isHillaUsed( options.getFrontendDirectory(), options.getClassFinder())) { diff --git a/flow-server/src/main/java/com/vaadin/flow/server/startup/AbstractConfigurationFactory.java b/flow-server/src/main/java/com/vaadin/flow/server/startup/AbstractConfigurationFactory.java index 5063bbbc1d9..cced90c38f7 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/startup/AbstractConfigurationFactory.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/startup/AbstractConfigurationFactory.java @@ -187,6 +187,11 @@ protected Map getConfigParametersUsingTokenData( String.valueOf(buildInfo.getBoolean(PREMIUM_FEATURES))); } + if (buildInfo.hasKey(InitParameters.FRONTEND_EXTRA_EXTENSIONS)) { + params.put(InitParameters.FRONTEND_EXTRA_EXTENSIONS, buildInfo + .getString(InitParameters.FRONTEND_EXTRA_EXTENSIONS)); + } + setDevModePropertiesUsingTokenData(params, buildInfo); return params; } diff --git a/flow-server/src/test/java/com/vaadin/flow/server/DeploymentConfigurationFactoryTest.java b/flow-server/src/test/java/com/vaadin/flow/server/DeploymentConfigurationFactoryTest.java index 62e1bdb1913..c9fd429591e 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/DeploymentConfigurationFactoryTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/DeploymentConfigurationFactoryTest.java @@ -350,7 +350,8 @@ public void createInitParameters_valuesAreTakenFromservletConfigAndTokenFile_val InitParameters.COMPILED_WEB_COMPONENTS_PATH, InitParameters.NODE_VERSION, InitParameters.NODE_DOWNLOAD_ROOT, InitParameters.BUILD_FOLDER, - InitParameters.APPLICATION_IDENTIFIER)); + InitParameters.APPLICATION_IDENTIFIER, + InitParameters.FRONTEND_EXTRA_EXTENSIONS)); Field[] initParamFields = InitParameters.class.getDeclaredFields(); String mockTokenJsonString = generateJsonStringFromFields( initParamFields, stringParams); diff --git a/flow-server/src/test/java/com/vaadin/flow/server/frontend/TaskUpdateViteTest.java b/flow-server/src/test/java/com/vaadin/flow/server/frontend/TaskUpdateViteTest.java index 38ba59203f0..f78241aff8e 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/frontend/TaskUpdateViteTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/frontend/TaskUpdateViteTest.java @@ -179,7 +179,7 @@ public void generatedTemplate_reactDisabled_correctFileRouterImport() @Test public void generatedTemplate_extraFrontendExtension_addedToViteConfiguration() throws IOException { - options.withExtraFrontendFileExtensions( + options.withFrontendExtraFileExtensions( Arrays.asList(".svg", ".ico", "png")); TaskUpdateVite task = new TaskUpdateVite(options, null); task.execute(); @@ -198,4 +198,25 @@ public void generatedTemplate_extraFrontendExtension_addedToViteConfiguration() "'.js', '.js.map', '.ts', '.ts.map', '.tsx', '.tsx.map', '.css', '.css.map', '.svg', '.ico', '.png'", matcher.group(1)); } + + @Test + public void generatedTemplate_noEraFrontendExtension_viteConfigurationWithoutExtraSelections() + throws IOException { + TaskUpdateVite task = new TaskUpdateVite(options, null); + task.execute(); + + File configFile = new File(temporaryFolder.getRoot(), + FrontendUtils.VITE_GENERATED_CONFIG); + + String template = IOUtils.toString(configFile.toURI(), + StandardCharsets.UTF_8); + Pattern matchSelection = Pattern + .compile("const projectFileExtensions = \\[(.*)];"); + Matcher matcher = matchSelection.matcher(template); + Assert.assertTrue("No projectFileExtensions found", matcher.find()); + Assert.assertEquals( + "Extra frontend extensions should be added to vite configuration, but was not.", + "'.js', '.js.map', '.ts', '.ts.map', '.tsx', '.tsx.map', '.css', '.css.map'", + matcher.group(1)); + } } diff --git a/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/pom.xml b/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/pom.xml index b5edddbdbed..4ef597ebb9d 100644 --- a/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/pom.xml +++ b/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/pom.xml @@ -111,6 +111,9 @@ + + scss + maven-clean-plugin diff --git a/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/frontend/styles/my-sass.scss b/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/frontend/styles/my-sass.scss new file mode 100644 index 00000000000..b07e52815e5 --- /dev/null +++ b/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/frontend/styles/my-sass.scss @@ -0,0 +1,3 @@ +p { + border: 3px solid orange; +} diff --git a/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/java/com/vaadin/flow/frontend/DevBundleCssImportView.java b/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/java/com/vaadin/flow/frontend/DevBundleCssImportView.java index 8b26a4018ab..fe4a4c96812 100644 --- a/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/java/com/vaadin/flow/frontend/DevBundleCssImportView.java +++ b/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/java/com/vaadin/flow/frontend/DevBundleCssImportView.java @@ -16,12 +16,15 @@ package com.vaadin.flow.frontend; import com.vaadin.flow.component.dependency.CssImport; +import com.vaadin.flow.component.dependency.NpmPackage; import com.vaadin.flow.component.html.Div; import com.vaadin.flow.component.html.Span; import com.vaadin.flow.router.Route; @Route("com.vaadin.flow.frontend.DevBundleCssImportView") @CssImport("./styles/my-styles.css") +@CssImport("./styles/my-sass.scss") +@NpmPackage(value = "sass-embedded", version = "1.80.6") public class DevBundleCssImportView extends Div { static final String MY_COMPONENT_ID = "test-css-import-meta-inf-resources-span"; diff --git a/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/test/java/com/vaadin/flow/frontend/DevBundleCssImportIT.java b/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/test/java/com/vaadin/flow/frontend/DevBundleCssImportIT.java index 831a6b9894d..769d0167791 100644 --- a/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/test/java/com/vaadin/flow/frontend/DevBundleCssImportIT.java +++ b/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/test/java/com/vaadin/flow/frontend/DevBundleCssImportIT.java @@ -59,7 +59,22 @@ public void cssImportedStyles_hashCalculatedWithNotQuestionMark() found = true; } } - Assert.assertTrue("My-styles.css import is expected", found); + + Assert.assertTrue("my-sass.scss content hash is expected", + frontendHashes.hasKey("styles/my-sass.scss")); + Assert.assertEquals("Unexpected my-sass.scss content hash", + "719cbd39e90caeecd2290124044e7cefb9e6150d3c338d4df71c21bcad825ab5", + frontendHashes.getString("styles/my-sass.scss")); + + Assert.assertTrue("my-sass.scss import is expected", found); + found = false; + for (int i = 0; i < bundleImports.length(); i++) { + if (bundleImports.get(i).asString() + .equals("Frontend/styles/my-sass.scss")) { + found = true; + } + } + Assert.assertTrue("my-sass.scss import is expected", found); } @Test diff --git a/vaadin-dev-server/src/main/java/com/vaadin/base/devserver/startup/DevModeInitializer.java b/vaadin-dev-server/src/main/java/com/vaadin/base/devserver/startup/DevModeInitializer.java index 67b2bc729b9..4477e32895f 100644 --- a/vaadin-dev-server/src/main/java/com/vaadin/base/devserver/startup/DevModeInitializer.java +++ b/vaadin-dev-server/src/main/java/com/vaadin/base/devserver/startup/DevModeInitializer.java @@ -265,11 +265,11 @@ public static DevModeHandler initDevModeHandler(Set> classes, File frontendGeneratedFolder = new File(frontendGeneratedFolderName); File jarFrontendResourcesFolder = new File(frontendGeneratedFolder, FrontendUtils.JAR_RESOURCES_FOLDER); - JsonObject tokenFileData = Json.createObject(); Mode mode = config.getMode(); boolean reactEnable = config.getBooleanProperty(REACT_ENABLE, FrontendUtils .isReactRouterRequired(options.getFrontendDirectory())); + options.enablePackagesUpdate(true) .useByteCodeScanner(useByteCodeScanner) .withFrontendGeneratedFolder(frontendGeneratedFolder) @@ -279,7 +279,6 @@ public static DevModeHandler initDevModeHandler(Set> classes, Constants.LOCAL_FRONTEND_RESOURCES_PATH)) .enableImportsUpdate(true) .withRunNpmInstall(mode == Mode.DEVELOPMENT_FRONTEND_LIVERELOAD) - .populateTokenFileData(tokenFileData) .withEmbeddableWebComponents(true).withEnablePnpm(enablePnpm) .withEnableBun(enableBun).useGlobalPnpm(useGlobalPnpm) .withHomeNodeExecRequired(useHomeNodeExec) @@ -289,12 +288,11 @@ public static DevModeHandler initDevModeHandler(Set> classes, .withFrontendHotdeploy( mode == Mode.DEVELOPMENT_FRONTEND_LIVERELOAD) .withBundleBuild(mode == Mode.DEVELOPMENT_BUNDLE) - .withReact(reactEnable); - - NodeTasks tasks = new NodeTasks(options); + .withReact(reactEnable).withFrontendExtraFileExtensions( + getFrontendExtraFileExtensions(config)); Runnable runnable = () -> { - runNodeTasks(context, tokenFileData, tasks); + runNodeTasks(new NodeTasks(options)); if (mode == Mode.DEVELOPMENT_FRONTEND_LIVERELOAD) { // For Vite, wait until a VaadinServlet is deployed so we know // which frontend servlet path to use @@ -329,6 +327,17 @@ public static DevModeHandler initDevModeHandler(Set> classes, } } + private static List getFrontendExtraFileExtensions( + ApplicationConfiguration config) { + List stringProperty = Arrays.asList(config + .getStringProperty(InitParameters.FRONTEND_EXTRA_EXTENSIONS, "") + .split(",")); + if (stringProperty.isEmpty()) { + return null; + } + return stringProperty; + } + private static Logger log() { return LoggerFactory.getLogger(DevModeStartupListener.class); } @@ -350,8 +359,7 @@ static Set getFrontendLocationsFromClassloader( return frontendFiles; } - private static void runNodeTasks(VaadinContext vaadinContext, - JsonObject tokenFileData, NodeTasks tasks) { + private static void runNodeTasks(NodeTasks tasks) { try { tasks.execute(); } catch (ExecutionFailedException exception) {