diff --git a/flow-build-tools/src/main/java/com/vaadin/flow/server/frontend/TaskCopyFrontendFiles.java b/flow-build-tools/src/main/java/com/vaadin/flow/server/frontend/TaskCopyFrontendFiles.java index 8e8a2fa857a..71b0b54b203 100644 --- a/flow-build-tools/src/main/java/com/vaadin/flow/server/frontend/TaskCopyFrontendFiles.java +++ b/flow-build-tools/src/main/java/com/vaadin/flow/server/frontend/TaskCopyFrontendFiles.java @@ -36,9 +36,17 @@ /** * Copies all frontend resources from JAR files into a given folder. *

- * The task considers "frontend resources" all files placed in - * {@literal META-INF/frontend}, {@literal META-INF/resources/frontend} and - * {@literal META-INF/resources/[**]/themes} folders. + * "Frontend resources" are bundle sources for {@code @JsModule} / + * {@code @CssImport} annotations. The recommended location for them in addon + * JARs is {@literal META-INF/frontend}. The legacy location + * {@literal META-INF/resources/frontend} is still scanned for backwards + * compatibility but is deprecated; a per-jar warning is emitted when it is + * used. Theme files under {@literal META-INF/resources/[**]/themes} are also + * copied. + *

+ * Public runtime resources for {@code @StyleSheet} / {@code @JavaScript} should + * be placed under {@literal META-INF/resources/} and are served directly by the + * servlet container — they are not handled by this task. *

* For internal use only. May be renamed or removed in a future release. * @@ -49,6 +57,7 @@ public class TaskCopyFrontendFiles private static final String WILDCARD_INCLUSION_APP_THEME_JAR = "**/themes/**/*"; private final Options options; private final Set resourceLocations; + private final Set warnedLegacyLocations = new HashSet<>(); /** * Scans the jar files given defined by {@code resourcesToScan}. @@ -85,10 +94,13 @@ public void execute() { .addAll(TaskCopyLocalFrontendFiles.copyLocalResources( new File(location, RESOURCES_FRONTEND_DEFAULT), targetDirectory)); + File legacyDir = new File(location, + COMPATIBILITY_RESOURCES_FRONTEND_DEFAULT); + if (legacyDir.isDirectory()) { + warnAboutDeprecatedFrontendLayout(location); + } handledFiles.addAll(TaskCopyLocalFrontendFiles - .copyLocalResources(new File(location, - COMPATIBILITY_RESOURCES_FRONTEND_DEFAULT), - targetDirectory)); + .copyLocalResources(legacyDir, targetDirectory)); // copies from resources, but excludes already copied from // resources/frontend handledFiles @@ -100,6 +112,10 @@ public void execute() { .copyIncludedFilesFromJarTrimmingBasePath(location, RESOURCES_FRONTEND_DEFAULT, targetDirectory, "**/*")); + if (jarContentsManager.containsPath(location, + COMPATIBILITY_RESOURCES_FRONTEND_DEFAULT + "/")) { + warnAboutDeprecatedFrontendLayout(location); + } handledFiles.addAll(jarContentsManager .copyIncludedFilesFromJarTrimmingBasePath(location, COMPATIBILITY_RESOURCES_FRONTEND_DEFAULT, @@ -136,4 +152,16 @@ private Logger log() { return LoggerFactory.getLogger(this.getClass()); } + private void warnAboutDeprecatedFrontendLayout(File location) { + if (warnedLegacyLocations.add(location)) { + log().warn("Addon '{}' contains frontend sources under {}/. " + + "This location is deprecated; migrate them to {}/ " + + "(bundle sources for @JsModule/@CssImport) or to " + + "META-INF/resources/ (runtime resources for " + + "@StyleSheet/@JavaScript).", location.getName(), + COMPATIBILITY_RESOURCES_FRONTEND_DEFAULT, + RESOURCES_FRONTEND_DEFAULT); + } + } + } diff --git a/flow-build-tools/src/main/java/com/vaadin/flow/server/frontend/scanner/ClassInfo.java b/flow-build-tools/src/main/java/com/vaadin/flow/server/frontend/scanner/ClassInfo.java index cd631858dbf..47847ea878e 100644 --- a/flow-build-tools/src/main/java/com/vaadin/flow/server/frontend/scanner/ClassInfo.java +++ b/flow-build-tools/src/main/java/com/vaadin/flow/server/frontend/scanner/ClassInfo.java @@ -31,6 +31,13 @@ public class ClassInfo { final LinkedHashSet modulesDevelopmentOnly = new LinkedHashSet<>(); final LinkedHashSet scripts = new LinkedHashSet<>(); final LinkedHashSet scriptsDevelopmentOnly = new LinkedHashSet<>(); + /** + * {@code @JsModule} values with a runtime URL prefix ({@code context://}, + * {@code base://}, {@code http(s)://}, {@code //}, {@code /}). These are + * filtered out of the bundle and emit a build-time deprecation warning + * recommending migration to {@code @JavaScript}. + */ + final LinkedHashSet deprecatedRuntimeModules = new LinkedHashSet<>(); final transient List css = new ArrayList<>(); String route = ""; String layout; diff --git a/flow-build-tools/src/main/java/com/vaadin/flow/server/frontend/scanner/FrontendClassVisitor.java b/flow-build-tools/src/main/java/com/vaadin/flow/server/frontend/scanner/FrontendClassVisitor.java index f1675e779b1..3b4069339ff 100644 --- a/flow-build-tools/src/main/java/com/vaadin/flow/server/frontend/scanner/FrontendClassVisitor.java +++ b/flow-build-tools/src/main/java/com/vaadin/flow/server/frontend/scanner/FrontendClassVisitor.java @@ -31,6 +31,7 @@ import com.vaadin.flow.component.dependency.JavaScript; import com.vaadin.flow.component.dependency.JsModule; import com.vaadin.flow.router.Route; +import com.vaadin.flow.server.FrontendDependencyUrlResolver; import com.vaadin.flow.theme.NoTheme; import com.vaadin.flow.theme.Theme; @@ -69,14 +70,21 @@ private static final class JSAnnotationVisitor boolean currentDevOnly = false; private String currentModule; + private boolean currentTypeIsModule = false; - private LinkedHashSet target; - private LinkedHashSet targetDevelopmentOnly; + private final LinkedHashSet target; + private final LinkedHashSet targetDevelopmentOnly; + private final LinkedHashSet deprecatedRuntimeTarget; + private final boolean isJavaScriptAnnotation; public JSAnnotationVisitor(LinkedHashSet target, - LinkedHashSet targetDevelopmentOnly) { + LinkedHashSet targetDevelopmentOnly, + boolean isJavaScriptAnnotation, + LinkedHashSet deprecatedRuntimeTarget) { this.target = target; this.targetDevelopmentOnly = targetDevelopmentOnly; + this.isJavaScriptAnnotation = isJavaScriptAnnotation; + this.deprecatedRuntimeTarget = deprecatedRuntimeTarget; } @Override @@ -91,12 +99,35 @@ public void visit(String name, Object value) { } } + @Override + public void visitEnum(String name, String descriptor, String value) { + // The "type" attribute only exists on @JavaScript; @JsModule has + // no such attribute, so this method is a no-op for it. + if ("type".equals(name) && "MODULE".equals(value)) { + currentTypeIsModule = true; + } + } + @Override public void visitEnd() { super.visitEnd(); - if (currentModule != null) { - // This visitor is called also for the $Container annotation - if (currentDevOnly) { + if (currentModule != null && !currentTypeIsModule) { + // type=MODULE @JavaScript values are loaded at runtime as + //