From 049a2f2117b6036b5be1e193cc7bfedc133e362b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Carrasco=20Mo=C3=B1ino?= Date: Thu, 16 May 2019 11:57:32 +0200 Subject: [PATCH] Improve version resolution (#5681) * Improve version resolution - Support any syntax in NpmPackage version field. - Split package.json in main one and app one - User can pin versions by editing main package.json or with annotation - Remove packages dependency when not used anymore - Allow multiple version, but warn about it - Add name and version fields to generate files to avoid warnings * Fix checks for DevModeInitializer to point to correct folders * We only need a parameter to disable updaters * Always write application package file * update main package file when exists * Suggestions and other fixes * Disable webpack liveReloading --- .../maven/NodeBuildFrontendMojoTest.java | 97 ++++++--------- .../plugin/maven/NodeValidateMojoTest.java | 41 +++--- .../com/vaadin/flow/server/Constants.java | 14 +-- .../vaadin/flow/server/DevModeHandler.java | 92 +++++++------- .../PropertyDeploymentConfiguration.java | 31 +++-- .../server/frontend/FrontendDependencies.java | 33 ++--- .../flow/server/frontend/NodeTasks.java | 24 ++-- .../flow/server/frontend/NodeUpdater.java | 90 +++++++++----- .../frontend/TaskCreatePackageJson.java | 18 ++- .../server/frontend/TaskRunNpmInstall.java | 4 +- .../server/frontend/TaskUpdateImports.java | 10 +- .../server/frontend/TaskUpdatePackages.java | 62 +++++++--- .../server/frontend/TaskUpdateWebpack.java | 20 +-- .../server/startup/DevModeInitializer.java | 62 +++++----- .../flow/server/DevModeHandlerTest.java | 46 +++---- .../frontend/FrontendDependenciesTest.java | 31 ++--- .../FrontendDependenciesTestComponents.java | 4 +- .../flow/server/frontend/NodeTasksTest.java | 4 +- .../frontend/NodeUpdatePackagesTest.java | 40 +++--- .../startup/DevModeInitializerTest.java | 117 +++++++++++++----- 20 files changed, 475 insertions(+), 365 deletions(-) diff --git a/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/NodeBuildFrontendMojoTest.java b/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/NodeBuildFrontendMojoTest.java index 5a789ecc150..6594895b1c4 100644 --- a/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/NodeBuildFrontendMojoTest.java +++ b/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/NodeBuildFrontendMojoTest.java @@ -16,17 +16,6 @@ */ package com.vaadin.flow.plugin.maven; -import static com.vaadin.flow.server.Constants.PACKAGE_JSON; -import static com.vaadin.flow.server.frontend.FrontendUtils.DEFAULT_FRONTEND_DIR; -import static com.vaadin.flow.server.frontend.FrontendUtils.FLOW_NPM_PACKAGE_NAME; -import static com.vaadin.flow.server.frontend.FrontendUtils.IMPORTS_NAME; -import static com.vaadin.flow.server.frontend.FrontendUtils.NODE_MODULES; -import static com.vaadin.flow.server.frontend.FrontendUtils.TARGET; -import static com.vaadin.flow.server.frontend.FrontendUtils.WEBPACK_CONFIG; -import static com.vaadin.flow.server.frontend.FrontendUtils.WEBPACK_PREFIX_ALIAS; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - import java.io.File; import java.io.IOException; import java.net.URL; @@ -60,6 +49,17 @@ import elemental.json.Json; import elemental.json.JsonObject; +import static com.vaadin.flow.server.Constants.PACKAGE_JSON; +import static com.vaadin.flow.server.frontend.FrontendUtils.DEFAULT_FRONTEND_DIR; +import static com.vaadin.flow.server.frontend.FrontendUtils.DEFAULT_GENERATED_DIR; +import static com.vaadin.flow.server.frontend.FrontendUtils.FLOW_NPM_PACKAGE_NAME; +import static com.vaadin.flow.server.frontend.FrontendUtils.IMPORTS_NAME; +import static com.vaadin.flow.server.frontend.FrontendUtils.NODE_MODULES; +import static com.vaadin.flow.server.frontend.FrontendUtils.WEBPACK_CONFIG; +import static com.vaadin.flow.server.frontend.FrontendUtils.WEBPACK_PREFIX_ALIAS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + public class NodeBuildFrontendMojoTest { @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); @@ -68,7 +68,8 @@ public class NodeBuildFrontendMojoTest { private File generatedFolder; private File nodeModulesPath; private File flowPackagPath; - private String packageJson; + private String mainPackage; + private String appPackage; private String webpackConfig; private final NodeBuildFrontendMojo mojo = new NodeBuildFrontendMojo(); @@ -79,15 +80,16 @@ public void setup() throws Exception { Mockito.when(project.getRuntimeClasspathElements()) .thenReturn(getClassPath()); - File tmpRoot = temporaryFolder.getRoot(); - generatedFolder = new File(tmpRoot, TARGET); + File npmFolder = temporaryFolder.getRoot(); + generatedFolder = new File(npmFolder, DEFAULT_GENERATED_DIR); importsFile = new File(generatedFolder, IMPORTS_NAME); - nodeModulesPath = new File(tmpRoot, NODE_MODULES); + nodeModulesPath = new File(npmFolder, NODE_MODULES); flowPackagPath = new File(nodeModulesPath, FLOW_NPM_PACKAGE_NAME); - File frontendDirectory = new File(tmpRoot, DEFAULT_FRONTEND_DIR); + File frontendDirectory = new File(npmFolder, DEFAULT_FRONTEND_DIR); - packageJson = new File(tmpRoot, PACKAGE_JSON).getAbsolutePath(); - webpackConfig = new File(tmpRoot, WEBPACK_CONFIG).getAbsolutePath(); + mainPackage = new File(npmFolder, PACKAGE_JSON).getAbsolutePath(); + appPackage = new File(generatedFolder, PACKAGE_JSON).getAbsolutePath(); + webpackConfig = new File(npmFolder, WEBPACK_CONFIG).getAbsolutePath(); ReflectionUtils.setVariableValueInObject(mojo, "project", project); ReflectionUtils.setVariableValueInObject(mojo, "generatedFolder", @@ -97,23 +99,26 @@ public void setup() throws Exception { ReflectionUtils.setVariableValueInObject(mojo, "generateEmbeddableWebComponents", false); ReflectionUtils.setVariableValueInObject(mojo, "convertHtml", true); - ReflectionUtils.setVariableValueInObject(mojo, "npmFolder", tmpRoot); + ReflectionUtils.setVariableValueInObject(mojo, "npmFolder", npmFolder); ReflectionUtils.setVariableValueInObject(mojo, "generateBundle", false); ReflectionUtils.setVariableValueInObject(mojo, "runNpmInstall", false); - Assert.assertTrue(flowPackagPath.mkdirs()); + flowPackagPath.mkdirs(); + generatedFolder.mkdirs(); setProject(mojo, "war", "war_output"); // Install all imports used in the tests on node_modules so as we don't // need to run `npm install` createExpectedImports(frontendDirectory, nodeModulesPath); - FileUtils.fileWrite(packageJson, "UTF-8", "{}"); + FileUtils.fileWrite(mainPackage, "UTF-8", "{}"); + FileUtils.fileWrite(appPackage, "UTF-8", "{}"); } @After public void teardown() { - FileUtils.fileDelete(packageJson); + FileUtils.fileDelete(mainPackage); + FileUtils.fileDelete(appPackage); FileUtils.fileDelete(webpackConfig); } @@ -221,49 +226,23 @@ public void should_AddRemove_Imports() throws Exception { @Test public void mavenGoal_when_packageJsonExists() throws Exception { + FileUtils.fileWrite(appPackage, "{\"dependencies\":{\"foo\":\"bar\"}}"); - FileUtils.fileWrite(packageJson, "{}"); - long tsPackage1 = FileUtils.getFile(packageJson).lastModified(); - - // need to sleep because timestamp is in seconds - sleep(1000); - mojo.execute(); - long tsPackage2 = FileUtils.getFile(packageJson).lastModified(); - - sleep(1000); mojo.execute(); - long tsPackage3 = FileUtils.getFile(packageJson).lastModified(); - - Assert.assertTrue(tsPackage1 < tsPackage2); - Assert.assertEquals(tsPackage2, tsPackage3); - - assertPackageJsonContent(); - } - - private void assertPackageJsonContent() throws IOException { - JsonObject packageJsonObject = getPackageJson(packageJson); - + JsonObject packageJsonObject = getPackageJson(appPackage); JsonObject dependencies = packageJsonObject.getObject("dependencies"); - Assert.assertTrue("Missing @vaadin/vaadin-button package", - dependencies.hasKey("@vaadin/vaadin-button")); - Assert.assertTrue("Missing @webcomponents/webcomponentsjs package", - dependencies.hasKey("@webcomponents/webcomponentsjs")); + assertContainsPackage(dependencies, + "@vaadin/vaadin-button", + "@vaadin/vaadin-element-mixin", + "@vaadin/vaadin-core-shrinkwrap"); - JsonObject devDependencies = packageJsonObject - .getObject("devDependencies"); + Assert.assertFalse("Has foo", dependencies.hasKey("foo")); + } - Assert.assertTrue("Missing webpack dev package", - devDependencies.hasKey("webpack")); - Assert.assertTrue("Missing webpack-cli dev package", - devDependencies.hasKey("webpack-cli")); - Assert.assertTrue("Missing webpack-dev-server dev package", - devDependencies.hasKey("webpack-dev-server")); - Assert.assertTrue( - "Missing webpack-babel-multi-target-plugin dev package", - devDependencies.hasKey("webpack-babel-multi-target-plugin")); - Assert.assertTrue("Missing copy-webpack-plugin dev package", - devDependencies.hasKey("copy-webpack-plugin")); + static void assertContainsPackage(JsonObject dependencies, String... packages) { + Arrays.asList(packages) + .forEach(dep -> Assert.assertTrue("Missing " + dep, dependencies.hasKey(dep))); } private void assertContainsImports(boolean contains, String... imports) diff --git a/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/NodeValidateMojoTest.java b/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/NodeValidateMojoTest.java index fa7eb67c2f5..d5445be814e 100644 --- a/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/NodeValidateMojoTest.java +++ b/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/NodeValidateMojoTest.java @@ -24,6 +24,7 @@ import elemental.json.JsonObject; +import static com.vaadin.flow.plugin.maven.NodeBuildFrontendMojoTest.assertContainsPackage; import static com.vaadin.flow.plugin.maven.NodeBuildFrontendMojoTest.getPackageJson; import static com.vaadin.flow.plugin.maven.NodeBuildFrontendMojoTest.setProject; import static com.vaadin.flow.server.Constants.PACKAGE_JSON; @@ -152,27 +153,29 @@ public void assertWebpackContent_NotWarNotJar() throws Exception { mojo.execute(); } + @Test + public void should_keepDependencies_when_packageJsonExists() throws Exception { + FileUtils.fileWrite(packageJson, "{\"dependencies\":{\"foo\":\"bar\"}}"); + mojo.execute(); + assertPackageJsonContent(); + + JsonObject packageJsonObject = getPackageJson(packageJson); + assertContainsPackage(packageJsonObject.getObject("dependencies"), "foo"); + } + private void assertPackageJsonContent() throws IOException { JsonObject packageJsonObject = getPackageJson(packageJson); - JsonObject dependencies = packageJsonObject.getObject("dependencies"); - - Assert.assertTrue("Missing @webcomponents/webcomponentsjs package", - dependencies.hasKey("@webcomponents/webcomponentsjs")); - Assert.assertTrue("Missing @polymer/polymer package", - dependencies.hasKey("@polymer/polymer")); - - JsonObject devDependencies = packageJsonObject.getObject("devDependencies"); - - Assert.assertTrue("Missing webpack dev package", - devDependencies.hasKey("webpack")); - Assert.assertTrue("Missing webpack-cli dev package", - devDependencies.hasKey("webpack-cli")); - Assert.assertTrue("Missing webpack-dev-server dev package", - devDependencies.hasKey("webpack-dev-server")); - Assert.assertTrue("Missing webpack-babel-multi-target-plugin dev package", - devDependencies.hasKey("webpack-babel-multi-target-plugin")); - Assert.assertTrue("Missing copy-webpack-plugin dev package", - devDependencies.hasKey("copy-webpack-plugin")); + + assertContainsPackage(packageJsonObject.getObject("dependencies"), + "@webcomponents/webcomponentsjs", + "@polymer/polymer"); + + assertContainsPackage(packageJsonObject.getObject("devDependencies"), + "webpack", + "webpack-cli", + "webpack-dev-server", + "webpack-babel-multi-target-plugin", + "copy-webpack-plugin"); } diff --git a/flow-server/src/main/java/com/vaadin/flow/server/Constants.java b/flow-server/src/main/java/com/vaadin/flow/server/Constants.java index 1062128b2fe..fd06726a63a 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/Constants.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/Constants.java @@ -154,8 +154,8 @@ public final class Constants implements Serializable { * Configuration name for the parameter that indicates the tcp port of a webpack-dev-server * already running. This property is automatically defined when * {@link DevModeHandler} starts the webpack server. If you have your own - * server already running, define this property and {@link DevModeHandler} - * will re-use that server. + * server already running, define this property, then {@link DevModeHandler} + * will re-use that server and will disable updaters. */ public static final String SERVLET_PARAMETER_DEVMODE_WEBPACK_RUNNING_PORT = "devmode.webpack.running-port"; @@ -187,16 +187,6 @@ public final class Constants implements Serializable { */ public static final String SERVLET_PARAMETER_DEVMODE_WEBPACK_OPTIONS = "devmode.webpack.options"; - /** - * Configuration name which disables the node imports updater. - */ - public static final String SERVLET_PARAMETER_DEVMODE_SKIP_UPDATE_IMPORTS = "devmode.skip.update-imports"; - - /** - * Configuration name which disables the node dependencies updater. - */ - public static final String SERVLET_PARAMETER_DEVMODE_SKIP_UPDATE_NPM = "devmode.skip.update-npm-dependencies"; - private Constants() { // prevent instantiation constants class only } diff --git a/flow-server/src/main/java/com/vaadin/flow/server/DevModeHandler.java b/flow-server/src/main/java/com/vaadin/flow/server/DevModeHandler.java index a7307e28048..accc48e34ff 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/DevModeHandler.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/DevModeHandler.java @@ -48,7 +48,6 @@ import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_DEVMODE_WEBPACK_RUNNING_PORT; import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_DEVMODE_WEBPACK_SUCCESS_PATTERN; import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_DEVMODE_WEBPACK_TIMEOUT; -import static com.vaadin.flow.server.frontend.FrontendUtils.getBaseDir; import static java.net.HttpURLConnection.HTTP_NOT_FOUND; import static java.net.HttpURLConnection.HTTP_OK; /** @@ -95,31 +94,32 @@ public class DevModeHandler implements Serializable { public static final String WEBPACK_SERVER = "node_modules/webpack-dev-server/bin/webpack-dev-server.js"; private int port; - private final DeploymentConfiguration config; // For testing purposes - DevModeHandler(DeploymentConfiguration configuration, int port) { - this.config = configuration; + DevModeHandler(int port) { this.port = port; } - private DevModeHandler(DeploymentConfiguration configuration, - File directory, File webpack, File webpackConfig) { - this.config = configuration; + private DevModeHandler(DeploymentConfiguration config, int runningPort, + File npmFolder, File webpack, File webpackConfig) { + port = runningPort; // If port is defined, means that webpack is already running - port = Integer.parseInt(config.getStringProperty( - SERVLET_PARAMETER_DEVMODE_WEBPACK_RUNNING_PORT, "0")); - if (port > 0 && checkWebpackConnection()) { - getLogger().info("Webpack is running at {}:{}", WEBPACK_HOST, port); - return; + if (port > 0) { + if (checkWebpackConnection()) { + getLogger().info("Webpack is running at {}:{}", WEBPACK_HOST, port); + return; + } + throw new IllegalStateException(String.format( + "webpack server port '%s=%d' is defined but it's not working properly", + SERVLET_PARAMETER_DEVMODE_WEBPACK_RUNNING_PORT, port)); } // We always compute a free port. port = getFreePort(); ProcessBuilder processBuilder = new ProcessBuilder() - .directory(directory); + .directory(npmFolder); List command = new ArrayList<>(); command.add(FrontendUtils.getNodeExecutable()); @@ -129,13 +129,12 @@ private DevModeHandler(DeploymentConfiguration configuration, command.add("--port"); command.add(String.valueOf(port)); command.addAll(Arrays.asList(config - .getStringProperty(SERVLET_PARAMETER_DEVMODE_WEBPACK_OPTIONS, - "-d --hot false") + .getStringProperty(SERVLET_PARAMETER_DEVMODE_WEBPACK_OPTIONS, "-d --inline=false") .split(" +"))); if (getLogger().isInfoEnabled()) { - getLogger().info("Starting Webpack in dev mode\n {}", - String.join(" ", command)); + getLogger().info("Starting Webpack in dev mode, port: {} dir: {}\n {}", + port, npmFolder, String.join(" ", command)); } processBuilder.command(command); @@ -179,12 +178,14 @@ private DevModeHandler(DeploymentConfiguration configuration, * * @param configuration * deployment configuration + * @param npmFolder + * folder with npm configuration files * * @return the instance in case everything is alright, null otherwise */ - public static DevModeHandler start(DeploymentConfiguration configuration) { + public static DevModeHandler start(DeploymentConfiguration configuration, File npmFolder) { atomicHandler.compareAndSet(null, - DevModeHandler.createInstance(configuration)); + DevModeHandler.createInstance(configuration, npmFolder)); return getDevModeHandler(); } @@ -197,35 +198,38 @@ public static DevModeHandler getDevModeHandler() { return atomicHandler.get(); } - private static DevModeHandler createInstance(DeploymentConfiguration configuration) { - if (configuration.isBowerMode() || configuration.isProductionMode()) { - getLogger().trace("Instance not created because not in npm-dev mode"); - return null; - } - - File directory = new File(getBaseDir(), FrontendUtils.DEFAULT_NODE_DIR).getAbsoluteFile(); - if (!directory.exists()) { - getLogger().warn("Instance not created because cannot change to '{}'", directory); + private static DevModeHandler createInstance(DeploymentConfiguration configuration, File npmFolder) { + if (configuration.isProductionMode() || configuration.isBowerMode()) { return null; } - File webpack = new File(getBaseDir(), WEBPACK_SERVER); - if (!webpack.canExecute()) { - getLogger().warn("Instance not created because cannot execute '{}'. Did you run `npm install`", webpack); - return null; - } else if(!webpack.exists()) { - getLogger().warn("Instance not created because file '{}' doesn't exist. Did you run `npm install`", - webpack); - return null; - } + File webpack = null; + File webpackConfig = null; + int runningPort = Integer.parseInt(configuration.getStringProperty( + SERVLET_PARAMETER_DEVMODE_WEBPACK_RUNNING_PORT, "0")); - File webpackConfig = new File(getBaseDir(), FrontendUtils.WEBPACK_CONFIG); - if (!webpackConfig.canRead()) { - getLogger().warn("Instance not created because there is not webpack configuration '{}'", webpackConfig); - return null; + // Skip checks if we have a webpack-dev-server already running + if (runningPort == 0) { + webpack = new File(npmFolder, WEBPACK_SERVER); + webpackConfig = new File(npmFolder, FrontendUtils.WEBPACK_CONFIG); + if (!npmFolder.exists()) { + getLogger().warn("Instance not created because cannot change to '{}'", npmFolder); + return null; + } + if (!webpack.canExecute()) { + getLogger().warn("Instance not created because cannot execute '{}'. Did you run `npm install`", webpack); + return null; + } else if (!webpack.exists()) { + getLogger().warn("Instance not created because file '{}' doesn't exist. Did you run `npm install`", + webpack); + return null; + } + if (!webpackConfig.canRead()) { + getLogger().warn("Instance not created because there is not webpack configuration '{}'", webpackConfig); + return null; + } } - - return new DevModeHandler(configuration, directory, webpack, webpackConfig); + return new DevModeHandler(configuration, runningPort, npmFolder, webpack, webpackConfig); } /** @@ -265,6 +269,7 @@ public boolean serveDevModeRequest(HttpServletRequest request, HttpServletRespon } // Send the request + getLogger().debug("Requesting resource to webpack {}", connection.getURL()); int responseCode = connection.getResponseCode(); if (responseCode == HTTP_NOT_FOUND) { getLogger().debug("Resource not served by webpack {}", requestFilename); @@ -272,7 +277,6 @@ public boolean serveDevModeRequest(HttpServletRequest request, HttpServletRespon // handle it return false; } - getLogger().debug("Served resource by webpack: {} {}", responseCode, requestFilename); // Copies response headers diff --git a/flow-server/src/main/java/com/vaadin/flow/server/PropertyDeploymentConfiguration.java b/flow-server/src/main/java/com/vaadin/flow/server/PropertyDeploymentConfiguration.java index 7b0c2fc251f..4dc53a78161 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/PropertyDeploymentConfiguration.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/PropertyDeploymentConfiguration.java @@ -22,6 +22,15 @@ import com.vaadin.flow.function.DeploymentConfiguration; import com.vaadin.flow.shared.communication.PushMode; +import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_BOWER_MODE; +import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_CLOSE_IDLE_SESSIONS; +import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_DEVMODE_WEBPACK_RUNNING_PORT; +import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION; +import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_PRODUCTION_MODE; +import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_REQUEST_TIMING; +import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_SEND_URLS_AS_PARAMETERS; +import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_SYNC_ID_CHECK; + /** * The property handling implementation of {@link DeploymentConfiguration} based * on a base @@ -138,19 +147,23 @@ public String getApplicationProperty(String parameterName) { @Override public boolean isProductionMode() { - return getBooleanProperty(Constants.SERVLET_PARAMETER_PRODUCTION_MODE, + return getBooleanProperty(SERVLET_PARAMETER_PRODUCTION_MODE, false); } @Override public boolean isBowerMode() { - return getBooleanProperty(Constants.SERVLET_PARAMETER_BOWER_MODE, false) + return getBooleanProperty(SERVLET_PARAMETER_BOWER_MODE, false) || isBowerLegacyMode(); } protected boolean isBowerLegacyMode() { - // User can force mode by setting bowerMode to some string value - if (getStringProperty(Constants.SERVLET_PARAMETER_BOWER_MODE, null) != null) { + // User can force npm mode by setting bowerMode to some string value + if (getStringProperty(SERVLET_PARAMETER_BOWER_MODE, null) != null) { + return false; + } + // User can force npm mode by starting a webpack server manually + if (getStringProperty(SERVLET_PARAMETER_DEVMODE_WEBPACK_RUNNING_PORT, null) != null) { return false; } return isProductionMode() && getBooleanProperty(IS_BOWER_PROD, false) @@ -159,19 +172,19 @@ protected boolean isBowerLegacyMode() { @Override public boolean isRequestTiming() { - return getBooleanProperty(Constants.SERVLET_PARAMETER_REQUEST_TIMING, + return getBooleanProperty(SERVLET_PARAMETER_REQUEST_TIMING, !isProductionMode()); } @Override public boolean isXsrfProtectionEnabled() { return !getBooleanProperty( - Constants.SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION, false); + SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION, false); } @Override public boolean isSyncIdCheckEnabled() { - return getBooleanProperty(Constants.SERVLET_PARAMETER_SYNC_ID_CHECK, + return getBooleanProperty(SERVLET_PARAMETER_SYNC_ID_CHECK, true); } @@ -188,13 +201,13 @@ public int getWebComponentDisconnect() { @Override public boolean isSendUrlsAsParameters() { return getBooleanProperty( - Constants.SERVLET_PARAMETER_SEND_URLS_AS_PARAMETERS, true); + SERVLET_PARAMETER_SEND_URLS_AS_PARAMETERS, true); } @Override public boolean isCloseIdleSessions() { return getBooleanProperty( - Constants.SERVLET_PARAMETER_CLOSE_IDLE_SESSIONS, false); + SERVLET_PARAMETER_CLOSE_IDLE_SESSIONS, false); } @Override diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/FrontendDependencies.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/FrontendDependencies.java index 547d3905e05..299352cc358 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/FrontendDependencies.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/FrontendDependencies.java @@ -31,6 +31,8 @@ import java.util.stream.Collectors; import net.bytebuddy.jar.asm.ClassReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.vaadin.flow.component.Component; import com.vaadin.flow.component.WebComponentExporter; @@ -52,20 +54,6 @@ public class FrontendDependencies implements Serializable { public static final String LUMO = "com.vaadin.flow.theme.lumo.Lumo"; - private static final String MULTIPLE_VERSIONS = - "%n%n======================================================================================================" - + "%nFailed to determine the version for the '%s' npm package." - + "%nFlow found multiple versions: %s" - + "%nPlease visit check your Java dependencies and @NpmModule annotations so as all of them" - + "%nmeet the same version." - + "%n======================================================================================================%n"; - - private static final String BAD_VERSIOM = - "%n%n======================================================================================================" - + "%nFailed to determine the version for the '%s' npm package." - + "%nVersion '%s' has an invalid format, it should follow pattern 'd.d.d' or 'd.d.d-suffix'" - + "%n======================================================================================================%n"; - /** * A wrapper for the Theme instance that use reflection for executing its * methods. This is needed because updaters can be executed from maven @@ -326,19 +314,22 @@ private void computePackages() throws ClassNotFoundException, IOException { Set dependencies = npmPackageVisitor.getValues(VALUE); for (String dependency : dependencies) { Set versions = npmPackageVisitor.getValuesForKey(VALUE, dependency, VERSION); - if (versions.size() > 1) { - throw new IllegalStateException(String.format(MULTIPLE_VERSIONS, dependency, versions.toString())); - } - String version = versions.iterator().next(); - if (!version.matches("^\\d+\\.\\d+\\.\\d+(-[A-z][\\w]*\\d+)?$")) { - throw new IllegalStateException(String.format(BAD_VERSIOM, dependency, version)); + if (versions.size() > 1) { + String foundVersions = versions.toString(); + log().warn("Multiple npm versions for {} found: {}. First version found '{}' will be considered.", + dependency, foundVersions, version); } - packages.put(dependency, version); } } + + private static Logger log() { + // Using short prefix so as npm output is more readable + return LoggerFactory.getLogger("dev-updater"); + } + /** * Visits all classes extending {@link com.vaadin.flow.component.WebComponentExporter} * and update an {@link EndPointData} object with the info found. 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 6fc0c70d99d..bfb635648c9 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 @@ -44,10 +44,6 @@ public static class Builder implements Serializable { private final ClassFinder classFinder; - private final File npmFolder; - - private final File generatedPath; - private final File frontendDirectory; private final boolean convertHtml; @@ -68,6 +64,16 @@ public static class Builder implements Serializable { private Set visitedClasses; + /** + * Directory for for npm and folders and files. + */ + public final File npmFolder; + + /** + * Directory where generated files are written. + */ + public final File generatedFolder; + /** * Create a builder instance, with everything set as default. * @@ -131,7 +137,7 @@ public Builder(ClassFinder classFinder, File npmFolder, this.npmFolder = npmFolder; this.convertHtml = convertHtml; this.generateEmbeddableWebComponents = true; - this.generatedPath = generatedPath.isAbsolute() ? generatedPath + this.generatedFolder = generatedPath.isAbsolute() ? generatedPath : new File(npmFolder, generatedPath.getPath()); this.frontendDirectory = frontendDirectory.isAbsolute() ? frontendDirectory @@ -263,14 +269,14 @@ private NodeTasks(Builder builder) { if (builder.createMissingPackageJson) { TaskCreatePackageJson packageCreator = new TaskCreatePackageJson( - builder.npmFolder, builder.generatedPath); + builder.npmFolder, builder.generatedFolder); commands.add(packageCreator); } if (builder.enablePackagesUpdate) { TaskUpdatePackages packageUpdater = new TaskUpdatePackages( classFinder, frontendDependencies, builder.npmFolder, - builder.generatedPath); + builder.generatedFolder); commands.add(packageUpdater); if (builder.runNpmInstall) { @@ -282,13 +288,13 @@ private NodeTasks(Builder builder) { && !builder.webpackTemplate.isEmpty()) { commands.add(new TaskUpdateWebpack(builder.npmFolder, builder.webpackOutputDirectory, builder.webpackTemplate, - new File(builder.generatedPath, IMPORTS_NAME))); + new File(builder.generatedFolder, IMPORTS_NAME))); } if (builder.enableImportsUpdate) { commands.add( new TaskUpdateImports(classFinder, frontendDependencies, - builder.npmFolder, builder.generatedPath, + builder.npmFolder, builder.generatedFolder, builder.frontendDirectory, builder.convertHtml)); if (builder.visitedClasses != null) { 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 1c80f41b34f..6044a50b199 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 @@ -58,6 +58,12 @@ public abstract class NodeUpdater implements Command { static final String DEPENDENCIES = "dependencies"; private static final String DEV_DEPENDENCIES = "devDependencies"; + private static final String DEP_LICENSE_KEY = "license"; + private static final String DEP_LICENSE_DEFAULT = "UNLICENSED"; + private static final String DEP_NAME_KEY = "name"; + private static final String DEP_NAME_DEFAULT = "no-name"; + private static final String DEP_NAME_FLOW_DEPS = "@vaadin/flow-deps"; + /** * Base directory for {@link Constants#PACKAGE_JSON}, * {@link FrontendUtils#WEBPACK_CONFIG}, {@link FrontendUtils#NODE_MODULES}. @@ -67,13 +73,13 @@ public abstract class NodeUpdater implements Command { /** * The path to the {@link FrontendUtils#NODE_MODULES} directory. */ - protected final File nodeModulesPath; - - + protected final File nodeModulesFolder; + + /** - * Base directory for flow generated files. + * Base directory for flow generated files. */ - protected final File generatedPath; + protected final File generatedFolder; /** * Enable or disable legacy components annotated only with @@ -114,8 +120,8 @@ protected NodeUpdater(ClassFinder finder, FrontendDependencies frontendDependenc : frontendDependencies; this.finder = finder; this.npmFolder = npmFolder; - this.nodeModulesPath = new File(npmFolder, NODE_MODULES); - this.generatedPath = generatedPath; + this.nodeModulesFolder = new File(npmFolder, NODE_MODULES); + this.generatedFolder = generatedPath; this.convertHtml = convertHtml; } @@ -193,16 +199,16 @@ private String htmlImportToNpmPackage(String htmlImport) { return Objects.equals(module, htmlImport) ? null : module; } - JsonObject createDefaultJson() { - log().info("Creating a default {} file ...", PACKAGE_JSON); - JsonObject packageJson = Json.createObject(); - updateDefaultDependencies(packageJson); - return packageJson; + JsonObject getMainPackageJson() throws IOException { + return getPackageJson(new File(npmFolder, PACKAGE_JSON)); + } + + JsonObject getAppPackageJson() throws IOException { + return getPackageJson(new File(generatedFolder, PACKAGE_JSON)); } - JsonObject getPackageJson() throws IOException { + JsonObject getPackageJson(File packageFile) throws IOException { JsonObject packageJson = null; - File packageFile = new File(npmFolder, PACKAGE_JSON); if (packageFile.exists()) { String fileContent = FileUtils.readFileToString(packageFile, UTF_8.name()); packageJson = Json.parse(fileContent); @@ -210,35 +216,59 @@ JsonObject getPackageJson() throws IOException { return packageJson; } - boolean updateDefaultDependencies(JsonObject packageJson) { + boolean updateMainDefaultDependencies(JsonObject packageJson) { boolean added = false; - added = addDependency(packageJson, DEV_DEPENDENCIES, "webpack", "4.30.0") || added; - added = addDependency(packageJson, DEV_DEPENDENCIES, "webpack-cli", "3.3.0") || added; - added = addDependency(packageJson, DEV_DEPENDENCIES, "webpack-dev-server", "3.3.0") || added; - added = addDependency(packageJson, DEV_DEPENDENCIES, "webpack-babel-multi-target-plugin", "2.1.0") || added; - added = addDependency(packageJson, DEV_DEPENDENCIES, "copy-webpack-plugin", "5.0.3") || added; - added = addDependency(packageJson, DEPENDENCIES, "@polymer/polymer", "3.1.0") || added; - added = addDependency(packageJson, DEPENDENCIES, "@webcomponents/webcomponentsjs", "2.2.10") || added; + added = addDependency(packageJson, null, DEP_NAME_KEY, DEP_NAME_DEFAULT) || added; + added = addDependency(packageJson, null, DEP_LICENSE_KEY, DEP_LICENSE_DEFAULT) || added; + + + added = addDependency(packageJson, DEPENDENCIES, "@polymer/polymer", "^3.1.0") || added; + added = addDependency(packageJson, DEPENDENCIES, "@webcomponents/webcomponentsjs", "^2.2.10") || added; + // dependency for the custom package.json placed in the generated folder. + String customPkg = "./" + npmFolder.getAbsoluteFile().toPath() + .relativize(generatedFolder.toPath()).toString(); + added = addDependency(packageJson, DEPENDENCIES, DEP_NAME_FLOW_DEPS, customPkg) || added; + + added = addDependency(packageJson, DEV_DEPENDENCIES, "webpack", "^4.30.0") || added; + added = addDependency(packageJson, DEV_DEPENDENCIES, "webpack-cli", "^3.3.0") || added; + added = addDependency(packageJson, DEV_DEPENDENCIES, "webpack-dev-server", "^3.3.0") || added; + added = addDependency(packageJson, DEV_DEPENDENCIES, "webpack-babel-multi-target-plugin", "^2.1.0") || added; + added = addDependency(packageJson, DEV_DEPENDENCIES, "copy-webpack-plugin", "^5.0.3") || added; return added; } + void updateAppDefaultDependencies(JsonObject packageJson) { + addDependency(packageJson, null, DEP_NAME_KEY, DEP_NAME_FLOW_DEPS); + addDependency(packageJson, null, DEP_LICENSE_KEY, DEP_LICENSE_DEFAULT); + } + boolean addDependency(JsonObject json, String key, String pkg, String vers) { - if (!json.hasKey(key)) { - json.put(key, Json.createObject()); + if (key != null) { + if (!json.hasKey(key)) { + json.put(key, Json.createObject()); + } + json = json.get(key); } - json = json.get(key); - vers = vers.startsWith("=") ? vers : ("=" + vers); if (!json.hasKey(pkg) || !json.getString(pkg).equals(vers)) { json.put(pkg, vers); - log().info("Added {}@{} dependency.", pkg, vers); + log().info("Added \"{}\": \"{}\" line.", pkg, vers); return true; } return false; } - void writePackageFile(JsonObject packageJson) throws IOException { - File packageFile = new File(npmFolder, PACKAGE_JSON); - FileUtils.writeStringToFile(packageFile, stringify(packageJson, 2), UTF_8.name()); + void writeMainPackageFile(JsonObject packageJson) throws IOException { + writePackageFile(packageJson, new File(npmFolder, PACKAGE_JSON)); + } + + void writeAppPackageFile(JsonObject packageJson) throws IOException { + writePackageFile(packageJson, new File(generatedFolder, PACKAGE_JSON)); + } + + void writePackageFile(JsonObject json, File packageFile) throws IOException { + log().info("Updated npm {}.", packageFile.getAbsolutePath()); + FileUtils.forceMkdirParent(packageFile); + FileUtils.writeStringToFile(packageFile, stringify(json, 2) + "\n", UTF_8.name()); } static Logger log() { diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskCreatePackageJson.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskCreatePackageJson.java index 3f047228234..f23c832f372 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskCreatePackageJson.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskCreatePackageJson.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.io.UncheckedIOException; +import elemental.json.Json; import elemental.json.JsonObject; /** @@ -42,10 +43,19 @@ public class TaskCreatePackageJson extends NodeUpdater { public void execute() { try { modified = false; - JsonObject packageJson = getPackageJson(); - if (packageJson == null) { - packageJson = createDefaultJson(); - writePackageFile(packageJson); + JsonObject mainContent = getMainPackageJson(); + if (mainContent == null) { + mainContent = Json.createObject(); + } + modified = updateMainDefaultDependencies(mainContent); + if (modified) { + writeMainPackageFile(mainContent); + } + JsonObject customContent = getAppPackageJson(); + if (customContent == null) { + customContent = Json.createObject(); + updateAppDefaultDependencies(customContent); + writeAppPackageFile(customContent); modified = true; } } catch (IOException e) { diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskRunNpmInstall.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskRunNpmInstall.java index be1c946260d..edf3d7ec9a5 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskRunNpmInstall.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskRunNpmInstall.java @@ -54,8 +54,8 @@ public void execute() { } private boolean shouldRunNpmInstall() { - if (packageUpdater.nodeModulesPath.isDirectory()) { - File[] installedPackages = packageUpdater.nodeModulesPath.listFiles(); + if (packageUpdater.nodeModulesFolder.isDirectory()) { + File[] installedPackages = packageUpdater.nodeModulesFolder.listFiles(); return installedPackages == null || (installedPackages.length == 1 && FLOW_NPM_PACKAGE_NAME .startsWith(installedPackages[0].getName())); 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 907595f91f1..6ce3ace617c 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 @@ -85,7 +85,7 @@ public void execute() { } modules.addAll(getJavascriptJsModules(frontDeps.getScripts())); - modules.addAll(getGeneratedModules(generatedPath, + modules.addAll(getGeneratedModules(generatedFolder, Collections.singleton(generatedFlowImports.getName()))); modules = sortModules(modules); @@ -146,7 +146,7 @@ private List modulesToImports(Set modules, AbstractTheme theme) StringBuilder errorMessage = new StringBuilder(String.format( "Failed to resolve the following module imports neither in the node_modules directory '%s' " + "nor in project files in '%s': ", - nodeModulesPath, frontendDirectory)).append("\n"); + nodeModulesFolder, frontendDirectory)).append("\n"); unresolvedImports .forEach((originalModulePath, translatedModulePath) -> { @@ -171,9 +171,9 @@ private List modulesToImports(Set modules, AbstractTheme theme) private boolean importedFileExists(String jsImport) { return new File(frontendDirectory, jsImport).isFile() - || new File(nodeModulesPath, jsImport).isFile() - || new File(new File(nodeModulesPath, FLOW_NPM_PACKAGE_NAME), jsImport).isFile() - || new File(generatedPath, + || new File(nodeModulesFolder, jsImport).isFile() + || new File(new File(nodeModulesFolder, FLOW_NPM_PACKAGE_NAME), jsImport).isFile() + || new File(generatedFolder, generatedResourcePathIntoRelativePath(jsImport)).isFile(); } diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskUpdatePackages.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskUpdatePackages.java index 83506ccc141..81f16c967ec 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskUpdatePackages.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskUpdatePackages.java @@ -15,16 +15,17 @@ */ package com.vaadin.flow.server.frontend; -import static com.vaadin.flow.server.Constants.PACKAGE_JSON; - import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; +import java.util.Arrays; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import com.vaadin.flow.component.dependency.NpmPackage; +import elemental.json.Json; import elemental.json.JsonObject; /** @@ -34,6 +35,15 @@ */ public class TaskUpdatePackages extends NodeUpdater { + private static final List PRO_PACKAGES = Arrays.asList( + "@vaadin/vaadin-board", + "@vaadin/vaadin-charts", + "@vaadin/vaadin-confirm-dialog", + "@vaadin/vaadin-cookie-consent", + "@vaadin/vaadin-crud", + "@vaadin/vaadin-grid-pro", + "@vaadin/vaadin-rich-text-editor"); + /** * Create an instance of the updater given all configurable parameters. * @@ -55,20 +65,14 @@ public class TaskUpdatePackages extends NodeUpdater { @Override public void execute() { try { - JsonObject packageJson = getPackageJson(); + Map deps = frontDeps.getPackages(); + JsonObject packageJson = getAppPackageJson(); if (packageJson == null) { - throw new IllegalStateException("Unable to read '" - + PACKAGE_JSON + "' file in: " + npmFolder); + packageJson = Json.createObject(); } - - Map deps = frontDeps.getPackages(); modified = updatePackageJsonDependencies(packageJson, deps); - modified = updateDefaultDependencies(packageJson) || modified; - if (modified) { - writePackageFile(packageJson); - } else { - log().info("No packages to update"); + writeAppPackageFile(packageJson); } } catch (IOException e) { throw new UncheckedIOException(e); @@ -78,11 +82,39 @@ public void execute() { private boolean updatePackageJsonDependencies(JsonObject packageJson, Map deps) { boolean added = false; - for (Entry e : deps.entrySet()) { - added = addDependency(packageJson, DEPENDENCIES, e.getKey(), - e.getValue()) || added; + + // Add the appropriate shrink-dependency to the package table + addShrinkDependency(deps); + + // Add application dependencies + for(Entry dep : deps.entrySet()) { + added = addDependency(packageJson, DEPENDENCIES, dep.getKey(), dep.getValue()) || added; + } + + // Remove obsolete dependencies + JsonObject dependencies = packageJson.getObject(DEPENDENCIES); + if (dependencies != null) { + for (String key : dependencies.keys()) { + if (!deps.containsKey(key)) { + dependencies.remove(key); + } + } } return added; } + private void addShrinkDependency(Map deps) { + boolean hasVaadin = false; + boolean hasPro = false; + for(Entry e : deps.entrySet()) { + String pkg = e.getKey(); + hasPro = hasPro || PRO_PACKAGES.contains(pkg); + hasVaadin = hasPro || hasVaadin || pkg.startsWith("@vaadin"); + } + if (hasVaadin) { + String dep = hasPro ? "@vaadin/vaadin-shrinkwrap" : "@vaadin/vaadin-core-shrinkwrap"; + deps.put(dep, "v14.0.0-alpha3"); + } + } + } 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 bd5abc3896f..0c2318482b0 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 @@ -38,9 +38,9 @@ public class TaskUpdateWebpack implements Command { * The name of the webpack config file. */ private final String webpackTemplate; - private final transient Path webpackOutputDirectory; - private final transient Path generatedFlowImports; - private final transient Path webpackConfigFolder; + private final transient Path webpackOutputPath; + private final transient Path flowImportsFilePath; + private final transient Path webpackConfigPath; /** * Create an instance of the updater given all configurable parameters. @@ -58,9 +58,9 @@ public class TaskUpdateWebpack implements Command { TaskUpdateWebpack(File webpackConfigFolder, File webpackOutputDirectory, String webpackTemplate, File generatedFlowImports) { this.webpackTemplate = webpackTemplate; - this.webpackOutputDirectory = webpackOutputDirectory.toPath(); - this.generatedFlowImports = generatedFlowImports.toPath(); - this.webpackConfigFolder = webpackConfigFolder.toPath(); + this.webpackOutputPath = webpackOutputDirectory.toPath(); + this.flowImportsFilePath = generatedFlowImports.toPath(); + this.webpackConfigPath = webpackConfigFolder.toPath(); } @Override @@ -77,7 +77,7 @@ private void createWebpackConfig() throws IOException { return; } - File configFile = new File(webpackConfigFolder.toFile(), + File configFile = new File(webpackConfigPath.toFile(), WEBPACK_CONFIG); if (!configFile.exists()) { @@ -92,9 +92,9 @@ private void createWebpackConfig() throws IOException { boolean modified = false; String outputLine = "mavenOutputFolderForFlowBundledFiles = require('path').resolve(__dirname, '" - + getEscapedRelativeWebpackPath(webpackOutputDirectory) + "');"; + + getEscapedRelativeWebpackPath(webpackOutputPath) + "');"; String mainLine = "fileNameOfTheFlowGeneratedMainEntryPoint = require('path').resolve(__dirname, '" - + getEscapedRelativeWebpackPath(generatedFlowImports) + "');"; + + getEscapedRelativeWebpackPath(flowImportsFilePath) + "');"; for (int i = 0; i < lines.size(); i++) { String line = lines.get(i).trim(); @@ -117,7 +117,7 @@ private void createWebpackConfig() throws IOException { private String getEscapedRelativeWebpackPath(Path path) { Path relativePath = path.isAbsolute() - ? webpackConfigFolder.relativize(path) + ? webpackConfigPath.relativize(path) : path; return relativePath.toString().replaceAll("\\\\", "/"); } diff --git a/flow-server/src/main/java/com/vaadin/flow/server/startup/DevModeInitializer.java b/flow-server/src/main/java/com/vaadin/flow/server/startup/DevModeInitializer.java index daa13e4eba3..dbf08c27594 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/startup/DevModeInitializer.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/startup/DevModeInitializer.java @@ -20,8 +20,10 @@ import javax.servlet.ServletException; import javax.servlet.ServletRegistration; import javax.servlet.annotation.HandlesTypes; + import java.io.File; import java.io.Serializable; +import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Set; @@ -38,14 +40,12 @@ import com.vaadin.flow.server.VaadinServlet; import com.vaadin.flow.server.frontend.ClassFinder.DefaultClassFinder; import com.vaadin.flow.server.frontend.NodeTasks; +import com.vaadin.flow.server.frontend.NodeTasks.Builder; import com.vaadin.flow.server.startup.ServletDeployer.StubServletConfig; import static com.vaadin.flow.server.Constants.PACKAGE_JSON; -import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_DEVMODE_SKIP_UPDATE_IMPORTS; -import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_DEVMODE_SKIP_UPDATE_NPM; -import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_DEVMODE_WEBPACK_RUNNING_PORT; +import static com.vaadin.flow.server.Constants.*; import static com.vaadin.flow.server.frontend.FrontendUtils.WEBPACK_CONFIG; -import static com.vaadin.flow.server.frontend.FrontendUtils.getBaseDir; /** * Servlet initializer starting node updaters as well as the webpack-dev-mode @@ -119,33 +119,39 @@ public void onStartup(Set> classes, ServletContext context) .createDeploymentConfiguration(context, registrations.iterator().next(), VaadinServlet.class); - if (config.isProductionMode() || config.isBowerMode()) { + if (config.isProductionMode()) { + log().debug("Skiping DEV MODE because PRODUCTION MODE is set."); return; } - if ( - // User have a webpack-dev-server in the background - config.getStringProperty(SERVLET_PARAMETER_DEVMODE_WEBPACK_RUNNING_PORT, - null) == null && - // There isn't any of the common dev files in the current folder - (!new File(getBaseDir(), PACKAGE_JSON).canRead() - || !new File(getBaseDir(), WEBPACK_CONFIG).canRead())) { - - log().warn( - "Skiping DEV MODE because cannot find '{}' or '{}' in '{}' folder", - PACKAGE_JSON, WEBPACK_CONFIG, getBaseDir()); + // Not using config.isBowerMode because it checks for npm files, and we + // want to do below in order to give the appropriate message to user + if (config.getBooleanProperty(SERVLET_PARAMETER_BOWER_MODE, false)) { + log().info("Skiping DEV MODE because BOWER MODE is set."); return; } - try { - Set visitedClassNames = new HashSet<>(); + Builder builder = new NodeTasks.Builder(new DefaultClassFinder(classes)); + + int runningPort = Integer.parseInt(config.getStringProperty( + SERVLET_PARAMETER_DEVMODE_WEBPACK_RUNNING_PORT, "0")); + // User can run its own webpack server and provide port + if (runningPort == 0) { + log().info("Starting dev-mode updaters in {} folder.", builder.npmFolder); + for (File file : Arrays.asList( + new File(builder.npmFolder, PACKAGE_JSON), + new File(builder.generatedFolder, PACKAGE_JSON), + new File(builder.npmFolder, WEBPACK_CONFIG) + )) { + if (!file.canRead()) { + log().warn("Skiping DEV MODE because cannot read '{}' file.", file.getPath()); + return; + } + } - new NodeTasks.Builder(new DefaultClassFinder(classes)) - .enablePackagesUpdate(!config.getBooleanProperty( - SERVLET_PARAMETER_DEVMODE_SKIP_UPDATE_NPM, false)) - .enableImportsUpdate(!config.getBooleanProperty( - SERVLET_PARAMETER_DEVMODE_SKIP_UPDATE_IMPORTS, - false)) + Set visitedClassNames = new HashSet<>(); + builder.enablePackagesUpdate(true) + .enableImportsUpdate(true) .runNpmInstall(true) .withEmbeddableWebComponents(true) .collectVisitedClasses(visitedClassNames) @@ -153,13 +159,9 @@ public void onStartup(Set> classes, ServletContext context) context.setAttribute(VisitedClasses.class.getName(), new VisitedClasses(visitedClassNames)); - - DevModeHandler.start(config); - } catch (Exception e) { - log().warn( - "Failed to start a dev mode, hot reload is disabled. Continuing to start the application.", - e); } + + DevModeHandler.start(config, builder.npmFolder); } private Logger log() { diff --git a/flow-server/src/test/java/com/vaadin/flow/server/DevModeHandlerTest.java b/flow-server/src/test/java/com/vaadin/flow/server/DevModeHandlerTest.java index a66e1fafa79..6d9dd751ec5 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/DevModeHandlerTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/DevModeHandlerTest.java @@ -45,8 +45,6 @@ import com.vaadin.flow.server.frontend.FrontendUtils; import com.vaadin.tests.util.MockDeploymentConfiguration; -import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_DEVMODE_SKIP_UPDATE_IMPORTS; -import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_DEVMODE_SKIP_UPDATE_NPM; import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_DEVMODE_WEBPACK_RUNNING_PORT; import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_DEVMODE_WEBPACK_TIMEOUT; import static com.vaadin.flow.server.DevModeHandler.WEBPACK_SERVER; @@ -72,6 +70,7 @@ public class DevModeHandlerTest { private HttpServer httpServer; private int responseStatus; + private File npmFolder; @Rule public ExpectedException exception = ExpectedException.none(); @@ -81,15 +80,15 @@ public class DevModeHandlerTest { @Before public void setup() throws Exception { - System.setProperty("user.dir", temporaryFolder.getRoot().getAbsolutePath()); + npmFolder = temporaryFolder.getRoot(); + System.setProperty("user.dir", npmFolder.getAbsolutePath()); configuration = new MockDeploymentConfiguration(); configuration.setProductionMode(false); - configuration.setApplicationOrSystemProperty(SERVLET_PARAMETER_DEVMODE_SKIP_UPDATE_NPM, "true"); - configuration.setApplicationOrSystemProperty(SERVLET_PARAMETER_DEVMODE_SKIP_UPDATE_IMPORTS, "true"); new File(getBaseDir(), FrontendUtils.WEBPACK_CONFIG).createNewFile(); createStubWebpackServer("Compiled", 100); + System.clearProperty("vaadin." + SERVLET_PARAMETER_DEVMODE_WEBPACK_RUNNING_PORT); } @After @@ -98,6 +97,7 @@ public void teardown() throws Exception { httpServer.stop(0); } + // Reset unique instance in DevModeHandler Field atomicHandler = DevModeHandler.class.getDeclaredField("atomicHandler"); atomicHandler.setAccessible(true); @@ -106,7 +106,7 @@ public void teardown() throws Exception { @Test public void should_CreateInstanceAndRunWebPack_When_DevModeAndNpmInstalled() throws Exception { - assertNotNull(DevModeHandler.start(configuration)); + assertNotNull(DevModeHandler.start(configuration, npmFolder)); assertTrue(new File(getBaseDir(), FrontendUtils.DEFAULT_NODE_DIR + WEBPACK_TEST_OUT_FILE).canRead()); assertNull(DevModeHandler.getDevModeHandler().getFailedOutput()); Thread.sleep(150); //NOSONAR @@ -119,14 +119,14 @@ public void should_Fail_When_WebpackPrematurelyExit() throws Exception { exception.expectMessage("Webpack exited prematurely"); createStubWebpackServer("Foo", 0); - DevModeHandler.start(configuration); + DevModeHandler.start(configuration, npmFolder); } @Test public void should_CreateInstance_After_TimeoutWaitingForPattern() throws Exception { configuration.setApplicationOrSystemProperty(SERVLET_PARAMETER_DEVMODE_WEBPACK_TIMEOUT, "100"); createStubWebpackServer("Foo", 300); - assertNotNull(DevModeHandler.start(configuration)); + assertNotNull(DevModeHandler.start(configuration, npmFolder)); assertTrue(Integer.getInteger("vaadin." + SERVLET_PARAMETER_DEVMODE_WEBPACK_RUNNING_PORT, 0) > 0); Thread.sleep(350); //NOSONAR } @@ -135,7 +135,7 @@ public void should_CreateInstance_After_TimeoutWaitingForPattern() throws Except public void should_CaptureWebpackOutput_When_Failed() throws Exception { configuration.setApplicationOrSystemProperty(SERVLET_PARAMETER_DEVMODE_WEBPACK_TIMEOUT, "100"); createStubWebpackServer("Failed to compile", 300); - assertNotNull(DevModeHandler.start(configuration)); + assertNotNull(DevModeHandler.start(configuration, npmFolder)); assertNotNull(DevModeHandler.getDevModeHandler().getFailedOutput()); Thread.sleep(350); //NOSONAR } @@ -143,19 +143,19 @@ public void should_CaptureWebpackOutput_When_Failed() throws Exception { @Test public void shouldNot_CreateInstance_When_ProductionMode() throws Exception { configuration.setProductionMode(true); - assertNull(DevModeHandler.start(configuration)); + assertNull(DevModeHandler.start(configuration, npmFolder)); } @Test public void shouldNot_CreateInstance_When_BowerMode() throws Exception { configuration.setProductionMode(true); - assertNull(DevModeHandler.start(configuration)); + assertNull(DevModeHandler.start(configuration, npmFolder)); Thread.sleep(150); //NOSONAR } @Test public void should_RunWebpack_When_WebpackNotListening() throws Exception { - DevModeHandler.start(configuration); + DevModeHandler.start(configuration, npmFolder); assertTrue(new File(getBaseDir(), FrontendUtils.DEFAULT_NODE_DIR + WEBPACK_TEST_OUT_FILE).canRead()); Thread.sleep(150); //NOSONAR } @@ -163,14 +163,14 @@ public void should_RunWebpack_When_WebpackNotListening() throws Exception { @Test public void shouldNot_RunWebpack_When_WebpackRunning() throws Exception { prepareHttpServer(HTTP_OK, "bar"); - DevModeHandler.start(configuration); + DevModeHandler.start(configuration, npmFolder); assertFalse(new File(getBaseDir(), FrontendUtils.DEFAULT_NODE_DIR + WEBPACK_TEST_OUT_FILE).canRead()); } @Test public void shouldNot_CreateInstance_When_WebpackNotInstalled() throws Exception { new File(getBaseDir(), WEBPACK_SERVER).delete(); - assertNull(DevModeHandler.start(configuration)); + assertNull(DevModeHandler.start(configuration, npmFolder)); } @Test @@ -178,32 +178,32 @@ public void shouldNot_CreateInstance_When_WebpackIsNotExecutable() { // The set executable doesn't work in Windows and will always return false boolean systemImplementsExecutable = new File(getBaseDir(), WEBPACK_SERVER).setExecutable(false); if(systemImplementsExecutable) { - assertNull(DevModeHandler.start(configuration)); + assertNull(DevModeHandler.start(configuration, npmFolder)); } } @Test public void shouldNot_CreateInstance_When_WebpackNotConfigured() { new File(getBaseDir(), FrontendUtils.WEBPACK_CONFIG).delete(); - assertNull(DevModeHandler.start(configuration)); + assertNull(DevModeHandler.start(configuration, npmFolder)); } @Test public void should_HandleJavaScriptRequests() { HttpServletRequest request = prepareRequest("/foo.js"); - assertTrue(new DevModeHandler(configuration, 0).isDevModeRequest(request)); + assertTrue(new DevModeHandler(0).isDevModeRequest(request)); } @Test public void shouldNot_HandleOtherRequests() { HttpServletRequest request = prepareRequest("/foo.bar"); - assertFalse(new DevModeHandler(configuration, 0).isDevModeRequest(request)); + assertFalse(new DevModeHandler(0).isDevModeRequest(request)); } @Test(expected = ConnectException.class) public void should_ThrowAnException_When_WebpackNotListening() throws IOException { HttpServletRequest request = prepareRequest("/foo.js"); - new DevModeHandler(configuration, 0).serveDevModeRequest(request, null); + new DevModeHandler(0).serveDevModeRequest(request, null); } @Test @@ -212,7 +212,7 @@ public void should_ReturnTrue_When_WebpackResponseOK() throws Exception { HttpServletResponse response = prepareResponse(); int port = prepareHttpServer(HTTP_OK, "bar"); - assertTrue(new DevModeHandler(configuration, port).serveDevModeRequest(request, response)); + assertTrue(new DevModeHandler(port).serveDevModeRequest(request, response)); assertEquals(HTTP_OK, responseStatus); } @@ -222,7 +222,7 @@ public void should_ReturnFalse_When_WebpackResponseNotFound() throws Exception { HttpServletResponse response = prepareResponse(); int port = prepareHttpServer(HTTP_NOT_FOUND, ""); - assertFalse(new DevModeHandler(configuration, port).serveDevModeRequest(request, response)); + assertFalse(new DevModeHandler(port).serveDevModeRequest(request, response)); assertEquals(0, responseStatus); } @@ -232,7 +232,7 @@ public void should_ReturnTrue_When_OtherResponseCodes() throws Exception { HttpServletResponse response = prepareResponse(); int port = prepareHttpServer(HTTP_UNAUTHORIZED, ""); - assertTrue(new DevModeHandler(configuration, port).serveDevModeRequest(request, response)); + assertTrue(new DevModeHandler(port).serveDevModeRequest(request, response)); assertEquals(HTTP_UNAUTHORIZED, responseStatus); } @@ -256,7 +256,7 @@ public void servlet_should_GetValidResponse_When_WebpackListening() throws Excep } private VaadinServlet prepareServlet() throws ServletException { - DevModeHandler.start(configuration); + DevModeHandler.start(configuration, npmFolder); VaadinServlet servlet = new VaadinServlet(); ServletConfig cfg = mock(ServletConfig.class); ServletContext ctx = mock(ServletContext.class); diff --git a/flow-server/src/test/java/com/vaadin/flow/server/frontend/FrontendDependenciesTest.java b/flow-server/src/test/java/com/vaadin/flow/server/frontend/FrontendDependenciesTest.java index 5a5d49b8b81..1fe379af81c 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/frontend/FrontendDependenciesTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/frontend/FrontendDependenciesTest.java @@ -6,11 +6,16 @@ import java.util.HashSet; import java.util.Set; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + import com.vaadin.flow.server.frontend.ClassFinder.DefaultClassFinder; import com.vaadin.flow.server.frontend.FrontendDependenciesTestComponents.Component0; import com.vaadin.flow.server.frontend.FrontendDependenciesTestComponents.Component1; import com.vaadin.flow.server.frontend.FrontendDependenciesTestComponents.Component2; -import com.vaadin.flow.server.frontend.FrontendDependenciesTestComponents.Component3; import com.vaadin.flow.server.frontend.FrontendDependenciesTestComponents.FirstView; import com.vaadin.flow.server.frontend.FrontendDependenciesTestComponents.RootViewWithLayoutTheme; import com.vaadin.flow.server.frontend.FrontendDependenciesTestComponents.RootViewWithMultipleTheme; @@ -21,12 +26,6 @@ import com.vaadin.flow.server.frontend.FrontendDependenciesTestComponents.Theme2; import com.vaadin.flow.server.frontend.FrontendDependenciesTestComponents.Theme4; import com.vaadin.flow.server.frontend.FrontendDependenciesTestComponents.ThirdView; -import org.apache.commons.lang3.reflect.FieldUtils; -import org.hamcrest.CoreMatchers; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -82,14 +81,14 @@ public void should_extractClassesFromSignatures() { "(Lcom/vaadin/flow/component/orderedlayout/FlexComponent$Alignment;[Lcom/vaadin/flow/component/Component;)"); assertEquals(13, classes.size()); assertTrue(classes.contains("com.vaadin.flow.component.orderedlayout.FlexComponent$Alignment")); - + // Apart from proper signature representation, it should handle class names, and class paths visitor.addSignatureToClasses(classes, this.getClass().getName()); assertTrue(classes.contains(this.getClass().getName())); visitor.addSignatureToClasses(classes, "com/vaadin/flow/server/frontend/FrontendDependenciesTestComponents$AnotherComponent"); assertTrue(classes.contains("com.vaadin.flow.server.frontend.FrontendDependenciesTestComponents$AnotherComponent")); - + } @Test @@ -103,17 +102,9 @@ public void should_visitNpmPakageAnnotations() throws Exception { } @Test - public void should_Fail_when_MultipleVersions() throws Exception { - exception.expect(IllegalStateException.class); - exception.expectMessage(CoreMatchers.containsString("multiple versions")); - create(Component0.class); - } - - @Test - public void should_Fail_when_BadVersion() throws Exception { - exception.expect(IllegalStateException.class); - exception.expectMessage(CoreMatchers.containsString("invalid format")); - create(Component3.class); + public void when_MultipleVersions_should_returnFirstVisitedOne() throws Exception { + FrontendDependencies deps = create(Component0.class); + assertEquals("=2.1.0", deps.getPackages().get("@vaadin/component-0")); } @Test diff --git a/flow-server/src/test/java/com/vaadin/flow/server/frontend/FrontendDependenciesTestComponents.java b/flow-server/src/test/java/com/vaadin/flow/server/frontend/FrontendDependenciesTestComponents.java index 70552267baa..294963ffd03 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/frontend/FrontendDependenciesTestComponents.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/frontend/FrontendDependenciesTestComponents.java @@ -64,12 +64,12 @@ static class Theme4 extends Theme0 { static class ThemeDefault extends Theme0 { } - @NpmPackage(value = "@vaadin/component-0", version = "2.1.0") + @NpmPackage(value = "@vaadin/component-0", version = "=2.1.0") @JsModule("./component-0.js") @HtmlImport("frontend://component-0.html") @JavaScript("frontend://component-0.js") @Tag("component-0") - @NpmPackage(value = "@vaadin/component-0", version = "1.1.0") + @NpmPackage(value = "@vaadin/component-0", version = "^1.1.0") @NpmPackage(value="@vaadin/vaadin-foo", version="1.23.114-alpha1") static class Component0 extends Component { } diff --git a/flow-server/src/test/java/com/vaadin/flow/server/frontend/NodeTasksTest.java b/flow-server/src/test/java/com/vaadin/flow/server/frontend/NodeTasksTest.java index 8ca80060c62..1d48f3b811f 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/frontend/NodeTasksTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/frontend/NodeTasksTest.java @@ -45,7 +45,7 @@ public void should_UseDefaultFolders()throws Exception { Assert.assertEquals(new File(userDir, DEFAULT_FRONTEND_DIR).getAbsolutePath(), ((File)getFieldValue(builder, "frontendDirectory")).getAbsolutePath()); Assert.assertEquals(new File(userDir, DEFAULT_GENERATED_DIR).getAbsolutePath(), - ((File)getFieldValue(builder, "generatedPath")).getAbsolutePath()); + ((File)getFieldValue(builder, "generatedFolder")).getAbsolutePath()); builder.build().execute(); Assert.assertTrue(new File(userDir, DEFAULT_GENERATED_DIR + IMPORTS_NAME).exists()); @@ -65,7 +65,7 @@ public void should_BeAbleToCustomizeFolders() throws Exception { Assert.assertEquals(new File(userDir, "my_custom_sources_folder").getAbsolutePath(), ((File)getFieldValue(builder, "frontendDirectory")).getAbsolutePath()); Assert.assertEquals(new File(userDir, "my/custom/generated/folder").getAbsolutePath(), - ((File)getFieldValue(builder, "generatedPath")).getAbsolutePath()); + ((File)getFieldValue(builder, "generatedFolder")).getAbsolutePath()); builder.build().execute(); Assert.assertTrue(new File(userDir, "my/custom/generated/folder/" + IMPORTS_NAME).exists()); diff --git a/flow-server/src/test/java/com/vaadin/flow/server/frontend/NodeUpdatePackagesTest.java b/flow-server/src/test/java/com/vaadin/flow/server/frontend/NodeUpdatePackagesTest.java index 52622783ca1..75c8f45cc94 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/frontend/NodeUpdatePackagesTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/frontend/NodeUpdatePackagesTest.java @@ -39,7 +39,8 @@ public class NodeUpdatePackagesTest extends NodeUpdateTestUtil { private TaskUpdatePackages packageUpdater; private TaskCreatePackageJson packageCreator; - private File packageJson; + private File mainPackageJson; + private File appPackageJson; @Before public void setup() throws Exception { @@ -54,15 +55,16 @@ public void setup() throws Exception { packageUpdater = new TaskUpdatePackages(getClassFinder(), null, baseDir, generatedDir); - - packageJson = new File(baseDir, PACKAGE_JSON); + mainPackageJson = new File(baseDir, PACKAGE_JSON); + appPackageJson = new File(generatedDir, PACKAGE_JSON); } @Test public void should_CreatePackageJson() throws Exception { - Assert.assertFalse(packageJson.exists()); + Assert.assertFalse(mainPackageJson.exists()); packageCreator.execute(); - Assert.assertTrue(packageJson.exists()); + Assert.assertTrue(mainPackageJson.exists()); + Assert.assertTrue(appPackageJson.exists()); } @Test @@ -81,22 +83,20 @@ public void should_AddNewDependencies() throws Exception { packageUpdater.execute(); Assert.assertTrue(packageCreator.modified); Assert.assertTrue(packageUpdater.modified); - assertPackageJsonContent(); + assertMainPackageJsonContent(); + assertAppPackageJsonContent(); } - private void assertPackageJsonContent() throws IOException { - JsonObject packageJsonObject = packageUpdater.getPackageJson(); - - JsonObject dependencies = packageJsonObject.getObject("dependencies"); + private void assertMainPackageJsonContent() throws IOException { + JsonObject json = packageUpdater.getMainPackageJson(); + Assert.assertTrue(json.hasKey("name")); + Assert.assertTrue(json.hasKey("license")); - Assert.assertTrue("Missing @vaadin/vaadin-button package", - dependencies.hasKey("@vaadin/vaadin-button")); + JsonObject dependencies = json.getObject("dependencies"); Assert.assertTrue("Missing @webcomponents/webcomponentsjs package", dependencies.hasKey("@webcomponents/webcomponentsjs")); - JsonObject devDependencies = packageJsonObject - .getObject("devDependencies"); - + JsonObject devDependencies = json.getObject("devDependencies"); Assert.assertTrue("Missing webpack dev package", devDependencies.hasKey("webpack")); Assert.assertTrue("Missing webpack-cli dev package", @@ -109,5 +109,15 @@ private void assertPackageJsonContent() throws IOException { Assert.assertTrue("Missing copy-webpack-plugin dev package", devDependencies.hasKey("copy-webpack-plugin")); } + private void assertAppPackageJsonContent() throws IOException { + JsonObject json = packageUpdater.getAppPackageJson(); + Assert.assertTrue(json.hasKey("name")); + Assert.assertTrue(json.hasKey("license")); + + JsonObject dependencies = json.getObject("dependencies"); + + Assert.assertTrue("Missing @vaadin/vaadin-button package", + dependencies.hasKey("@vaadin/vaadin-button")); + } } diff --git a/flow-server/src/test/java/com/vaadin/flow/server/startup/DevModeInitializerTest.java b/flow-server/src/test/java/com/vaadin/flow/server/startup/DevModeInitializerTest.java index b6048726478..48bc6f0d579 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/startup/DevModeInitializerTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/startup/DevModeInitializerTest.java @@ -2,6 +2,7 @@ import javax.servlet.ServletContext; import javax.servlet.ServletRegistration; + import java.io.File; import java.lang.reflect.Field; import java.util.Arrays; @@ -12,21 +13,25 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicReference; -import com.vaadin.flow.component.dependency.JsModule; -import com.vaadin.flow.server.DevModeHandler; -import com.vaadin.flow.server.startup.DevModeInitializer.VisitedClasses; import net.jcip.annotations.NotThreadSafe; import org.apache.commons.io.FileUtils; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; import org.mockito.Mockito; +import com.vaadin.flow.component.dependency.JsModule; +import com.vaadin.flow.server.DevModeHandler; +import com.vaadin.flow.server.startup.DevModeInitializer.VisitedClasses; + import static com.vaadin.flow.server.Constants.PACKAGE_JSON; -import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_DEVMODE_SKIP_UPDATE_IMPORTS; -import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_DEVMODE_SKIP_UPDATE_NPM; +import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_BOWER_MODE; +import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_DEVMODE_WEBPACK_RUNNING_PORT; +import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_PRODUCTION_MODE; +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.getBaseDir; import static com.vaadin.flow.server.frontend.NodeUpdateTestUtil.createStubNode; @@ -39,34 +44,36 @@ @NotThreadSafe public class DevModeInitializerTest { + + @Rule + public ExpectedException exception = ExpectedException.none(); + private ServletContext servletContext; private DevModeInitializer devModeInitializer; private Set> classes; + private File mainPackageFile; + private File appPackageFile; + private File webpackFile; + @JsModule("foo") public static class Visited { - } public static class NotVisitedWithoutDeps { - } @JsModule("foo") public static class NotVisitedWithDeps { - } public static class WithoutDepsSubclass extends NotVisitedWithoutDeps { - } public static class WithDepsSubclass extends NotVisitedWithDeps { - } public static class VisitedSubclass extends Visited { - } @Rule @@ -77,14 +84,12 @@ public static class VisitedSubclass extends Visited { public void setup() throws Exception { System.setProperty("user.dir", temporaryFolder.getRoot().getPath()); - new File(getBaseDir(), "src").mkdir(); createStubNode(false, true); - createStubWebpackServer("Compiled", 1500); + createStubWebpackServer("Compiled", 0); servletContext = Mockito.mock(ServletContext.class); - ServletRegistration registration = Mockito - .mock(ServletRegistration.class); + ServletRegistration registration = Mockito.mock(ServletRegistration.class); classes = new HashSet<>(); Map registry = new HashMap(); @@ -94,17 +99,26 @@ public void setup() throws Exception { Mockito.when(servletContext.getInitParameterNames()) .thenReturn(Collections.emptyEnumeration()); + mainPackageFile = new File(getBaseDir(), PACKAGE_JSON); + appPackageFile = new File(getBaseDir(), DEFAULT_GENERATED_DIR + PACKAGE_JSON); + webpackFile = new File(getBaseDir(), WEBPACK_CONFIG); + appPackageFile.getParentFile().mkdirs(); + + FileUtils.write(mainPackageFile, "{}", "UTF-8"); + FileUtils.write(appPackageFile, "{}", "UTF-8"); + webpackFile.createNewFile(); devModeInitializer = new DevModeInitializer(); } @After public void teardown() throws Exception, SecurityException { - System.clearProperty( - "vaadin." + SERVLET_PARAMETER_DEVMODE_SKIP_UPDATE_NPM); - System.clearProperty( - "vaadin." + SERVLET_PARAMETER_DEVMODE_SKIP_UPDATE_IMPORTS); - new File(getBaseDir(), PACKAGE_JSON).delete(); - new File(getBaseDir(), WEBPACK_CONFIG).delete(); + System.clearProperty("vaadin." + SERVLET_PARAMETER_DEVMODE_WEBPACK_RUNNING_PORT); + System.clearProperty("vaadin." + SERVLET_PARAMETER_PRODUCTION_MODE); + System.clearProperty("vaadin." + SERVLET_PARAMETER_BOWER_MODE); + + webpackFile.delete(); + mainPackageFile.delete(); + appPackageFile.delete(); // Reset unique instance in DevModeHandler Field atomicHandler = DevModeHandler.class @@ -114,31 +128,66 @@ public void teardown() throws Exception, SecurityException { } @Test - public void should_Not_Run_Updaters_when_Disabled() throws Exception { - System.setProperty( - "vaadin." + SERVLET_PARAMETER_DEVMODE_SKIP_UPDATE_NPM, "true"); - System.setProperty( - "vaadin." + SERVLET_PARAMETER_DEVMODE_SKIP_UPDATE_IMPORTS, - "true"); + public void should_Run_Updaters() throws Exception { devModeInitializer.onStartup(classes, servletContext); - assertFalse(new File(getBaseDir(), PACKAGE_JSON).canRead()); - assertFalse(new File(getBaseDir(), WEBPACK_CONFIG).canRead()); - assertNull(DevModeHandler.getDevModeHandler()); + assertNotNull(DevModeHandler.getDevModeHandler()); } @Test public void should_Not_Run_Updaters_when_NoNodeConfFiles() throws Exception { + webpackFile.delete(); + mainPackageFile.delete(); + appPackageFile.delete(); + devModeInitializer.onStartup(classes, servletContext); + assertNull(DevModeHandler.getDevModeHandler()); + } + + @Test + public void should_Not_Run_Updaters_when_NoMainPackageFile() throws Exception { + mainPackageFile.delete(); + assertNull(DevModeHandler.getDevModeHandler()); + } + + @Test + public void should_Not_Run_Updaters_when_NoAppPackageFile() throws Exception { + appPackageFile.delete(); + devModeInitializer.onStartup(classes, servletContext); + assertNull(DevModeHandler.getDevModeHandler()); + } + + @Test + public void should_Not_Run_Updaters_when_NoWebpackFile() throws Exception { + webpackFile.delete(); devModeInitializer.onStartup(classes, servletContext); assertNull(DevModeHandler.getDevModeHandler()); } @Test - public void should_Run_Updaters_when_NodeConfFiles() throws Exception { - FileUtils.write(new File(getBaseDir(), PACKAGE_JSON), "{}", "UTF-8"); - new File(getBaseDir(), WEBPACK_CONFIG).createNewFile(); + public void should_Not_Run_Updaters_inBowerMode() throws Exception { + System.setProperty("vaadin." + SERVLET_PARAMETER_BOWER_MODE, "true"); + devModeInitializer = new DevModeInitializer(); + devModeInitializer.onStartup(classes, servletContext); + assertNull(DevModeHandler.getDevModeHandler()); + } + @Test + public void should_Not_Run_Updaters_inProductionMode() throws Exception { + System.setProperty("vaadin." + SERVLET_PARAMETER_PRODUCTION_MODE, "true"); + devModeInitializer = new DevModeInitializer(); devModeInitializer.onStartup(classes, servletContext); - assertNotNull(DevModeHandler.getDevModeHandler()); + assertNull(DevModeHandler.getDevModeHandler()); + } + + + @Test + public void should_Fail_when_skipUpdaters_and_portNotListening() throws Exception { + webpackFile.delete(); + mainPackageFile.delete(); + appPackageFile.delete(); + System.setProperty("vaadin." + SERVLET_PARAMETER_DEVMODE_WEBPACK_RUNNING_PORT, "1234"); + + exception.expect(IllegalStateException.class); + new DevModeInitializer().onStartup(classes, servletContext); } @Test