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
+ //