From fd1168aaa72a189dba43aab2f12fd26e2d313e77 Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Wed, 10 Apr 2024 18:00:35 +1000 Subject: [PATCH] Add mvnpm support in webjar-locator Signed-off-by: Phillip Kruger --- extensions/webjars-locator/deployment/pom.xml | 13 ++ .../deployment/ImportMapBuildItem.java | 15 +++ .../deployment/WebJarLocatorConfig.java | 26 ++++ .../WebJarLocatorStandaloneBuildStep.java | 127 ++++++++++++++---- .../devui/WebJarLibrariesBuildItem.java | 15 ++- .../WebJarLocatorDevModeApiProcessor.java | 31 +++-- .../devui/WebJarLocatorDevUIProcessor.java | 27 +++- .../dev-ui/qwc-webjar-locator-importmap.js | 48 +++++++ .../webjar/locator/test/ImportMapTest.java | 34 +++++ .../test/WebJarLocatorDevModeTest.java | 12 ++ .../test/WebJarLocatorRootPathTest.java | 12 +- .../locator/test/WebJarLocatorTest.java | 9 +- .../test/WebJarLocatorTestSupport.java | 1 + .../runtime/WebJarLocatorRecorder.java | 25 ++++ 14 files changed, 347 insertions(+), 48 deletions(-) create mode 100644 extensions/webjars-locator/deployment/src/main/java/io/quarkus/webjar/locator/deployment/ImportMapBuildItem.java create mode 100644 extensions/webjars-locator/deployment/src/main/java/io/quarkus/webjar/locator/deployment/WebJarLocatorConfig.java create mode 100644 extensions/webjars-locator/deployment/src/main/resources/dev-ui/qwc-webjar-locator-importmap.js create mode 100644 extensions/webjars-locator/deployment/src/test/java/io/quarkus/webjar/locator/test/ImportMapTest.java diff --git a/extensions/webjars-locator/deployment/pom.xml b/extensions/webjars-locator/deployment/pom.xml index d2e2c75ca3f28..c7e61954ff64b 100644 --- a/extensions/webjars-locator/deployment/pom.xml +++ b/extensions/webjars-locator/deployment/pom.xml @@ -16,6 +16,7 @@ 2.24.0 1.13.0 + 4.5.2 @@ -31,6 +32,10 @@ io.quarkus quarkus-webjars-locator + + io.mvnpm + importmap + @@ -63,6 +68,13 @@ 3.0.6 test + + org.mvnpm + bootstrap + ${mvnpm.bootstrap.version} + test + + io.quarkus quarkus-resteasy-deployment @@ -90,6 +102,7 @@ ${webjar.jquery-ui.version} ${webjar.momentjs.version} + ${mvnpm.bootstrap.version} diff --git a/extensions/webjars-locator/deployment/src/main/java/io/quarkus/webjar/locator/deployment/ImportMapBuildItem.java b/extensions/webjars-locator/deployment/src/main/java/io/quarkus/webjar/locator/deployment/ImportMapBuildItem.java new file mode 100644 index 0000000000000..9b951366ab031 --- /dev/null +++ b/extensions/webjars-locator/deployment/src/main/java/io/quarkus/webjar/locator/deployment/ImportMapBuildItem.java @@ -0,0 +1,15 @@ +package io.quarkus.webjar.locator.deployment; + +import io.quarkus.builder.item.SimpleBuildItem; + +public final class ImportMapBuildItem extends SimpleBuildItem { + private final String importmap; + + public ImportMapBuildItem(String importmap) { + this.importmap = importmap; + } + + public String getImportMap() { + return this.importmap; + } +} diff --git a/extensions/webjars-locator/deployment/src/main/java/io/quarkus/webjar/locator/deployment/WebJarLocatorConfig.java b/extensions/webjars-locator/deployment/src/main/java/io/quarkus/webjar/locator/deployment/WebJarLocatorConfig.java new file mode 100644 index 0000000000000..2676e483b2c79 --- /dev/null +++ b/extensions/webjars-locator/deployment/src/main/java/io/quarkus/webjar/locator/deployment/WebJarLocatorConfig.java @@ -0,0 +1,26 @@ +package io.quarkus.webjar.locator.deployment; + +import java.util.Map; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigRoot; + +/** + * Build time configuration for WebJar Locator. + */ +@ConfigRoot +public class WebJarLocatorConfig { + + /** + * If the version reroute is enabled. + */ + @ConfigItem(defaultValue = "true") + public boolean versionReroute; + + /** + * User defined import mappings + */ + @ConfigItem + public Map importMappings; + +} diff --git a/extensions/webjars-locator/deployment/src/main/java/io/quarkus/webjar/locator/deployment/WebJarLocatorStandaloneBuildStep.java b/extensions/webjars-locator/deployment/src/main/java/io/quarkus/webjar/locator/deployment/WebJarLocatorStandaloneBuildStep.java index 95dd06ed48fb7..bf3af18e035a1 100644 --- a/extensions/webjars-locator/deployment/src/main/java/io/quarkus/webjar/locator/deployment/WebJarLocatorStandaloneBuildStep.java +++ b/extensions/webjars-locator/deployment/src/main/java/io/quarkus/webjar/locator/deployment/WebJarLocatorStandaloneBuildStep.java @@ -2,16 +2,20 @@ import java.io.IOException; import java.io.UncheckedIOException; +import java.net.MalformedURLException; +import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Stream; import org.jboss.logging.Logger; +import io.mvnpm.importmap.Aggregator; import io.quarkus.bootstrap.classloading.ClassPathElement; import io.quarkus.bootstrap.classloading.QuarkusClassLoader; import io.quarkus.deployment.Feature; @@ -26,75 +30,142 @@ import io.quarkus.vertx.http.deployment.RouteBuildItem; import io.quarkus.vertx.http.runtime.HttpBuildTimeConfig; import io.quarkus.webjar.locator.runtime.WebJarLocatorRecorder; +import io.vertx.core.Handler; +import io.vertx.ext.web.RoutingContext; public class WebJarLocatorStandaloneBuildStep { private static final String WEBJARS_PREFIX = "META-INF/resources/webjars"; + private static final String WEBJARS_NAME = "webjars"; + + private static final String MVNPM_PREFIX = "META-INF/resources/_static"; + private static final String MVNPM_NAME = "mvnpm"; + private static final Logger log = Logger.getLogger(WebJarLocatorStandaloneBuildStep.class.getName()); @BuildStep @Record(ExecutionTime.RUNTIME_INIT) public void findWebjarsAndCreateHandler( + WebJarLocatorConfig config, HttpBuildTimeConfig httpConfig, BuildProducer feature, BuildProducer routes, + BuildProducer im, CurateOutcomeBuildItem curateOutcome, WebJarLocatorRecorder recorder) throws Exception { - final List providers = QuarkusClassLoader.getElements(WEBJARS_PREFIX, false); - Map webjarNameToVersionMap = Collections.emptyMap(); + LibInfo webjarsLibInfo = getLibInfo(curateOutcome, WEBJARS_PREFIX, WEBJARS_NAME); + LibInfo mvnpmNameLibInfo = getLibInfo(curateOutcome, MVNPM_PREFIX, MVNPM_NAME); + + if (webjarsLibInfo != null || mvnpmNameLibInfo != null) { + feature.produce(new FeatureBuildItem(Feature.WEBJARS_LOCATOR)); + + if (webjarsLibInfo != null) { + if (config.versionReroute) { + Handler handler = recorder.getHandler(getRootPath(httpConfig, "webjars"), + webjarsLibInfo.nameVersionMap); + routes.produce(RouteBuildItem.builder().route("/webjars/*").handler(handler).build()); + } + } else { + log.warn( + "No WebJars were found in the project. Requests to the /webjars/ path will always return 404 (Not Found)"); + } + if (mvnpmNameLibInfo != null) { + if (config.versionReroute) { + Handler handler = recorder.getHandler(getRootPath(httpConfig, "_static"), + mvnpmNameLibInfo.nameVersionMap); + routes.produce(RouteBuildItem.builder().route("/_static/*").handler(handler).build()); + } + // Also create a importmap endpoint + Aggregator aggregator = new Aggregator(mvnpmNameLibInfo.jars); + if (!config.importMappings.isEmpty()) { + aggregator.addMappings(config.importMappings); + } + + String importMap = aggregator.aggregateAsJson(false); + im.produce(new ImportMapBuildItem(importMap)); + String path = getRootPath(httpConfig, IMPORTMAP_ROOT) + IMPORTMAP_FILENAME; + Handler importMapHandler = recorder.getImportMapHandler(path, + importMap); + routes.produce( + RouteBuildItem.builder().route("/" + IMPORTMAP_ROOT + "/" + IMPORTMAP_FILENAME) + .handler(importMapHandler).build()); + } else { + log.warn( + "No Mvnpm jars were found in the project. Requests to the /_static/ path will always return 404 (Not Found)"); + } + } + } + + private LibInfo getLibInfo(CurateOutcomeBuildItem curateOutcome, String prefix, String name) { + + final List providers = QuarkusClassLoader.getElements(prefix, false); if (!providers.isEmpty()) { - final Map webJarKeys = new HashMap<>(providers.size()); + final Map keys = new HashMap<>(providers.size()); for (ClassPathElement provider : providers) { - if (provider.getDependencyKey() == null || !provider.isRuntime()) { - log.warn("webjars content found in " + provider.getRoot() - + " won't be available. Please, report this issue."); - } else { - webJarKeys.put(provider.getDependencyKey(), provider); + if (provider.getDependencyKey() != null && provider.isRuntime()) { + keys.put(provider.getDependencyKey(), provider); } } - if (!webJarKeys.isEmpty()) { - final Map webjarMap = new HashMap<>(webJarKeys.size()); + if (!keys.isEmpty()) { + final Map map = new HashMap<>(keys.size()); + final Set jars = new HashSet<>(); for (ResolvedDependency dep : curateOutcome.getApplicationModel().getDependencies()) { if (!dep.isRuntimeCp()) { continue; } - final ClassPathElement provider = webJarKeys.get(dep.getKey()); + + final ClassPathElement provider = keys.get(dep.getKey()); if (provider == null) { continue; } provider.apply(tree -> { - final Path webjarsDir = tree.getPath(WEBJARS_PREFIX); + final Path dir = tree.getPath(prefix); final Path nameDir; - try (Stream webjarsDirPaths = Files.list(webjarsDir)) { - nameDir = webjarsDirPaths.filter(Files::isDirectory).findFirst().get(); + try (Stream dirPaths = Files.list(dir)) { + nameDir = dirPaths.filter(Files::isDirectory).findFirst().get(); } catch (IOException e) { throw new UncheckedIOException(e); } if (nameDir == null) { - log.warn("Failed to determine the name for webjars included in " + log.warn("Failed to determine the name for " + name + " included in " + tree.getOriginalTree().getRoots()); return null; } final String version = Files.isDirectory(nameDir.resolve(dep.getVersion())) ? dep.getVersion() : null; - webjarMap.put(nameDir.getFileName().toString(), version); + map.put(nameDir.getFileName().toString(), version); + try { + jars.add(dep.getResolvedPaths().getSinglePath().toUri().toURL()); + } catch (MalformedURLException ex) { + throw new RuntimeException(ex); + } return null; }); } - webjarNameToVersionMap = webjarMap; + + return new LibInfo(map, jars); } } + return null; + } - if (!webjarNameToVersionMap.isEmpty()) { - // The context path + the resources path - String rootPath = httpConfig.rootPath; - String webjarRootPath = (rootPath.endsWith("/")) ? rootPath + "webjars/" : rootPath + "/webjars/"; - feature.produce(new FeatureBuildItem(Feature.WEBJARS_LOCATOR)); - routes.produce( - RouteBuildItem.builder().route("/webjars/*") - .handler(recorder.getHandler(webjarRootPath, webjarNameToVersionMap)).build()); - } else { - log.warn("No WebJars were found in the project. Requests to the /webjars/ path will always return 404 (Not Found)"); + private String getRootPath(HttpBuildTimeConfig httpConfig, String path) { + // The context path + the resources path + String rootPath = httpConfig.rootPath; + return (rootPath.endsWith("/")) ? rootPath + path + "/" : rootPath + "/" + path + "/"; + } + + static class LibInfo { + Map nameVersionMap; + Set jars; + + LibInfo(Map nameVersionMap, Set jars) { + this.nameVersionMap = nameVersionMap; + this.jars = jars; } + } + + private static final String IMPORTMAP_ROOT = "_importmap"; + private static final String IMPORTMAP_FILENAME = "generated_importmap.js"; } diff --git a/extensions/webjars-locator/deployment/src/main/java/io/quarkus/webjar/locator/deployment/devui/WebJarLibrariesBuildItem.java b/extensions/webjars-locator/deployment/src/main/java/io/quarkus/webjar/locator/deployment/devui/WebJarLibrariesBuildItem.java index 87003d19c4908..709d0d21f88c0 100644 --- a/extensions/webjars-locator/deployment/src/main/java/io/quarkus/webjar/locator/deployment/devui/WebJarLibrariesBuildItem.java +++ b/extensions/webjars-locator/deployment/src/main/java/io/quarkus/webjar/locator/deployment/devui/WebJarLibrariesBuildItem.java @@ -2,17 +2,22 @@ import java.util.List; -import io.quarkus.builder.item.SimpleBuildItem; - -public final class WebJarLibrariesBuildItem extends SimpleBuildItem { +import io.quarkus.builder.item.MultiBuildItem; +public final class WebJarLibrariesBuildItem extends MultiBuildItem { + private final String provider; private final List webJarLibraries; - public WebJarLibrariesBuildItem(List webJarLibraries) { + public WebJarLibrariesBuildItem(String provider, List webJarLibraries) { + this.provider = provider; this.webJarLibraries = webJarLibraries; } public List getWebJarLibraries() { - return webJarLibraries; + return this.webJarLibraries; + } + + public String getProvider() { + return this.provider; } } diff --git a/extensions/webjars-locator/deployment/src/main/java/io/quarkus/webjar/locator/deployment/devui/WebJarLocatorDevModeApiProcessor.java b/extensions/webjars-locator/deployment/src/main/java/io/quarkus/webjar/locator/deployment/devui/WebJarLocatorDevModeApiProcessor.java index ee17abebef60c..cc2c688e56ea3 100644 --- a/extensions/webjars-locator/deployment/src/main/java/io/quarkus/webjar/locator/deployment/devui/WebJarLocatorDevModeApiProcessor.java +++ b/extensions/webjars-locator/deployment/src/main/java/io/quarkus/webjar/locator/deployment/devui/WebJarLocatorDevModeApiProcessor.java @@ -30,7 +30,10 @@ public class WebJarLocatorDevModeApiProcessor { - private static final String WEBJARS_PREFIX = "META-INF/resources/webjars"; + private static final String PREFIX = "META-INF/resources/"; + private static final String WEBJARS_PATH = "webjars"; + private static final String MVNPM_PATH = "_static"; + private static final Logger log = Logger.getLogger(WebJarLocatorDevModeApiProcessor.class.getName()); @BuildStep(onlyIf = IsDevelopment.class) @@ -39,8 +42,18 @@ public void findWebjarsAssets( CurateOutcomeBuildItem curateOutcome, BuildProducer webJarLibrariesProducer) { + final List webJarLibraries = getLibraries(httpConfig, curateOutcome, WEBJARS_PATH); + webJarLibrariesProducer.produce(new WebJarLibrariesBuildItem("webjars", webJarLibraries)); + + final List mvnpmLibraries = getLibraries(httpConfig, curateOutcome, MVNPM_PATH); + webJarLibrariesProducer.produce(new WebJarLibrariesBuildItem("mvnpm", mvnpmLibraries)); + + } + + private List getLibraries(HttpBuildTimeConfig httpConfig, + CurateOutcomeBuildItem curateOutcome, String path) { final List webJarLibraries = new ArrayList<>(); - final List providers = QuarkusClassLoader.getElements(WEBJARS_PREFIX, false); + final List providers = QuarkusClassLoader.getElements(PREFIX + path, false); if (!providers.isEmpty()) { // Map of webjar artifact keys to class path elements final Map webJarKeys = providers.stream() @@ -51,19 +64,21 @@ public void findWebjarsAssets( // The root path of the application final String rootPath = httpConfig.rootPath; // The root path of the webjars - final String webjarRootPath = (rootPath.endsWith("/")) ? rootPath + "webjars/" : rootPath + "/webjars/"; + final String webjarRootPath = (rootPath.endsWith("/")) ? rootPath + path + "/" : rootPath + "/" + path + "/"; // For each packaged webjar dependency, create a WebJarLibrary object curateOutcome.getApplicationModel().getDependencies().stream() - .map(dep -> createWebJarLibrary(dep, webjarRootPath, webJarKeys)) + .map(dep -> createWebJarLibrary(dep, webjarRootPath, webJarKeys, path)) .filter(Objects::nonNull).forEach(webJarLibraries::add); } } - webJarLibrariesProducer.produce(new WebJarLibrariesBuildItem(webJarLibraries)); + return webJarLibraries; } - private WebJarLibrary createWebJarLibrary(ResolvedDependency dep, String webjarRootPath, - Map webJarKeys) { + private WebJarLibrary createWebJarLibrary(ResolvedDependency dep, + String webjarRootPath, + Map webJarKeys, + String path) { // If the dependency is not a runtime class path dependency, return null if (!dep.isRuntimeCp()) { return null; @@ -74,7 +89,7 @@ private WebJarLibrary createWebJarLibrary(ResolvedDependency dep, String webjarR } final WebJarLibrary webJarLibrary = new WebJarLibrary(provider.getDependencyKey().getArtifactId()); provider.apply(tree -> { - final Path webjarsDir = tree.getPath(WEBJARS_PREFIX); + final Path webjarsDir = tree.getPath(PREFIX + path); final Path nameDir; try (Stream webjarsDirPaths = Files.list(webjarsDir)) { nameDir = webjarsDirPaths.filter(Files::isDirectory).findFirst().orElseThrow(() -> new IOException( diff --git a/extensions/webjars-locator/deployment/src/main/java/io/quarkus/webjar/locator/deployment/devui/WebJarLocatorDevUIProcessor.java b/extensions/webjars-locator/deployment/src/main/java/io/quarkus/webjar/locator/deployment/devui/WebJarLocatorDevUIProcessor.java index 9fb2440b5bdaa..c13cf38105351 100644 --- a/extensions/webjars-locator/deployment/src/main/java/io/quarkus/webjar/locator/deployment/devui/WebJarLocatorDevUIProcessor.java +++ b/extensions/webjars-locator/deployment/src/main/java/io/quarkus/webjar/locator/deployment/devui/WebJarLocatorDevUIProcessor.java @@ -1,22 +1,29 @@ package io.quarkus.webjar.locator.deployment.devui; +import java.util.ArrayList; import java.util.List; +import java.util.Optional; import io.quarkus.deployment.IsDevelopment; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.devui.spi.page.CardPageBuildItem; import io.quarkus.devui.spi.page.Page; +import io.quarkus.webjar.locator.deployment.ImportMapBuildItem; public class WebJarLocatorDevUIProcessor { @BuildStep(onlyIf = IsDevelopment.class) public void createPages(BuildProducer cardPageProducer, - WebJarLibrariesBuildItem webJarLibrariesBuildItem) { + List webJarLibrariesBuildItems, + Optional importMapBuildItem) { - CardPageBuildItem cardPageBuildItem = new CardPageBuildItem(); - List webJarLibraries = webJarLibrariesBuildItem.getWebJarLibraries(); + List webJarLibraries = new ArrayList<>(); + for (WebJarLibrariesBuildItem webJarLibrariesBuildItem : webJarLibrariesBuildItems) { + webJarLibraries.addAll(webJarLibrariesBuildItem.getWebJarLibraries()); + } + CardPageBuildItem cardPageBuildItem = new CardPageBuildItem(); if (!webJarLibraries.isEmpty()) { // WebJar Libraries cardPageBuildItem.addBuildTimeData("webJarLibraries", webJarLibraries); @@ -24,9 +31,21 @@ public void createPages(BuildProducer cardPageProducer, // WebJar Asset List cardPageBuildItem.addPage(Page.webComponentPageBuilder() .componentLink("qwc-webjar-locator-webjar-libraries.js") - .title("WebJar Libraries") + .title("Web libraries") .icon("font-awesome-solid:folder-tree") .staticLabel(String.valueOf(webJarLibraries.size()))); + + if (importMapBuildItem.isPresent()) { + cardPageBuildItem.addBuildTimeData("importMap", importMapBuildItem.get().getImportMap()); + + // ImportMap + cardPageBuildItem.addPage(Page.webComponentPageBuilder() + .componentLink("qwc-webjar-locator-importmap.js") + .title("Import Map") + .icon("font-awesome-solid:diagram-project")); + + } + } cardPageProducer.produce(cardPageBuildItem); diff --git a/extensions/webjars-locator/deployment/src/main/resources/dev-ui/qwc-webjar-locator-importmap.js b/extensions/webjars-locator/deployment/src/main/resources/dev-ui/qwc-webjar-locator-importmap.js new file mode 100644 index 0000000000000..fa732bb3ff901 --- /dev/null +++ b/extensions/webjars-locator/deployment/src/main/resources/dev-ui/qwc-webjar-locator-importmap.js @@ -0,0 +1,48 @@ +import {LitElement, html, css} from 'lit'; +import {importMap} from 'build-time-data'; + +import '@quarkus-webcomponents/codeblock'; + +export class QwcWebjarLocatorImportmap extends LitElement { + + static styles = css` + :host{ + display: flex; + flex-direction: column; + gap: 15px; + padding: 10px; + height: 100%; + } + `; + + static properties = { + _importMap: {type: String} + }; + + constructor() { + super(); + this._importMap = importMap; + } + + render() { + return html` + To use this in your app, add this to the head of your main html: +
+ + +
+ + Here is the generated import map: +
+ + +
+ `; + } +} + +customElements.define('qwc-webjar-locator-importmap', QwcWebjarLocatorImportmap) \ No newline at end of file diff --git a/extensions/webjars-locator/deployment/src/test/java/io/quarkus/webjar/locator/test/ImportMapTest.java b/extensions/webjars-locator/deployment/src/test/java/io/quarkus/webjar/locator/test/ImportMapTest.java new file mode 100644 index 0000000000000..02f01cb64b614 --- /dev/null +++ b/extensions/webjars-locator/deployment/src/test/java/io/quarkus/webjar/locator/test/ImportMapTest.java @@ -0,0 +1,34 @@ +package io.quarkus.webjar.locator.test; + +import static org.hamcrest.Matchers.containsString; + +import java.util.List; + +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.maven.dependency.Dependency; +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class ImportMapTest extends WebJarLocatorTestSupport { + private static final String META_INF_RESOURCES = "META-INF/resources/"; + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addAsResource(new StringAsset("Hello!"), META_INF_RESOURCES + "/index.html") + .addAsResource(new StringAsset("Test"), META_INF_RESOURCES + "/some/path/test.txt")) + .setForcedDependencies(List.of( + Dependency.of("org.mvnpm", "bootstrap", BOOTSTRAP_VERSION))); + + @Test + public void test() { + // Test normal files + RestAssured.get("/_importmap/generated_importmap.js").then() + .statusCode(200) + .body(containsString("\"bootstrap/\" : \"/_static/bootstrap/" + BOOTSTRAP_VERSION + "/dist/\"")); + + } +} diff --git a/extensions/webjars-locator/deployment/src/test/java/io/quarkus/webjar/locator/test/WebJarLocatorDevModeTest.java b/extensions/webjars-locator/deployment/src/test/java/io/quarkus/webjar/locator/test/WebJarLocatorDevModeTest.java index 7949be6326818..fad3ee7262948 100644 --- a/extensions/webjars-locator/deployment/src/test/java/io/quarkus/webjar/locator/test/WebJarLocatorDevModeTest.java +++ b/extensions/webjars-locator/deployment/src/test/java/io/quarkus/webjar/locator/test/WebJarLocatorDevModeTest.java @@ -46,12 +46,16 @@ public void testDevMode() { .statusCode(200); RestAssured.get("/webjars/momentjs/min/moment.min.js").then() .statusCode(200); + RestAssured.get("/_static/bootstrap/dist/js/bootstrap.min.js").then() + .statusCode(200); // Test using version in url of existing Web Jar RestAssured.get("/webjars/jquery-ui/" + JQUERY_UI_VERSION + "/jquery-ui.min.js").then() .statusCode(200); RestAssured.get("/webjars/momentjs/" + MOMENTJS_VERSION + "/min/moment.min.js").then() .statusCode(200); + RestAssured.get("/_static/bootstrap/" + BOOTSTRAP_VERSION + "/dist/js/bootstrap.min.js").then() + .statusCode(200); // Test non-existing Web Jar RestAssured.get("/webjars/bootstrap/js/bootstrap.min.js").then() @@ -60,6 +64,8 @@ public void testDevMode() { .statusCode(404); RestAssured.get("/webjars/momentjs/2.25.0/min/moment.min.js").then() .statusCode(404); + RestAssured.get("/_static/foundation-sites/6.8.1/dist/js/foundation.esm.js").then() + .statusCode(404); // Test webjar that does not have a version in the jar path RestAssured.get("/webjars/dcjs/dc.min.js").then() @@ -93,12 +99,16 @@ public void testDevMode() { .statusCode(200); RestAssured.get("/webjars/momentjs/min/moment.min.js").then() .statusCode(200); + RestAssured.get("/_static/bootstrap/dist/js/bootstrap.min.js").then() + .statusCode(200); // Test using version in url of existing Web Jar RestAssured.get("/webjars/jquery-ui/" + JQUERY_UI_VERSION + "/jquery-ui.min.js").then() .statusCode(200); RestAssured.get("/webjars/momentjs/" + MOMENTJS_VERSION + "/min/moment.min.js").then() .statusCode(200); + RestAssured.get("/_static/bootstrap/" + BOOTSTRAP_VERSION + "/dist/js/bootstrap.min.js").then() + .statusCode(200); // Test non-existing Web Jar RestAssured.get("/webjars/bootstrap/js/bootstrap.min.js").then() @@ -107,5 +117,7 @@ public void testDevMode() { .statusCode(404); RestAssured.get("/webjars/momentjs/2.25.0/min/moment.min.js").then() .statusCode(404); + RestAssured.get("/_static/foundation-sites/6.8.1/dist/js/foundation.esm.js").then() + .statusCode(404); } } diff --git a/extensions/webjars-locator/deployment/src/test/java/io/quarkus/webjar/locator/test/WebJarLocatorRootPathTest.java b/extensions/webjars-locator/deployment/src/test/java/io/quarkus/webjar/locator/test/WebJarLocatorRootPathTest.java index 26db27c282959..6efa49606eb43 100644 --- a/extensions/webjars-locator/deployment/src/test/java/io/quarkus/webjar/locator/test/WebJarLocatorRootPathTest.java +++ b/extensions/webjars-locator/deployment/src/test/java/io/quarkus/webjar/locator/test/WebJarLocatorRootPathTest.java @@ -21,8 +21,10 @@ public class WebJarLocatorRootPathTest extends WebJarLocatorTestSupport { .addAsResource(new StringAsset("Hello!"), META_INF_RESOURCES + "index.html") .addAsResource(new StringAsset("Test"), META_INF_RESOURCES + "some/path/test.txt")) .overrideConfigKey("quarkus.http.root-path", "/app") - .setForcedDependencies(List.of(Dependency.of("org.webjars", "jquery-ui", JQUERY_UI_VERSION), - Dependency.of("org.webjars", "momentjs", MOMENTJS_VERSION))); + .setForcedDependencies(List.of( + Dependency.of("org.webjars", "jquery-ui", JQUERY_UI_VERSION), + Dependency.of("org.webjars", "momentjs", MOMENTJS_VERSION), + Dependency.of("org.mvnpm", "bootstrap", BOOTSTRAP_VERSION))); @Test public void test() { @@ -43,12 +45,16 @@ public void test() { .statusCode(200); RestAssured.get("/webjars/momentjs/min/moment.min.js").then() .statusCode(200); + RestAssured.get("/_static/bootstrap/dist/js/bootstrap.min.js").then() + .statusCode(200); // Test using version in url of existing Web Jar RestAssured.get("/webjars/jquery-ui/" + JQUERY_UI_VERSION + "/jquery-ui.min.js").then() .statusCode(200); RestAssured.get("/webjars/momentjs/" + MOMENTJS_VERSION + "/min/moment.min.js").then() .statusCode(200); + RestAssured.get("/_static/bootstrap/" + BOOTSTRAP_VERSION + "/dist/js/bootstrap.min.js").then() + .statusCode(200); // Test non-existing Web Jar RestAssured.get("/webjars/bootstrap/js/bootstrap.min.js").then() @@ -57,6 +63,8 @@ public void test() { .statusCode(404); RestAssured.get("/webjars/momentjs/2.25.0/min/moment.min.js").then() .statusCode(404); + RestAssured.get("/_static/foundation-sites/6.8.1/dist/js/foundation.esm.js").then() + .statusCode(404); // Test webjar that does not have a version in the jar path RestAssured.get("/webjars/dcjs/dc.min.js").then() diff --git a/extensions/webjars-locator/deployment/src/test/java/io/quarkus/webjar/locator/test/WebJarLocatorTest.java b/extensions/webjars-locator/deployment/src/test/java/io/quarkus/webjar/locator/test/WebJarLocatorTest.java index 7453c71b3b306..715a2d042f187 100644 --- a/extensions/webjars-locator/deployment/src/test/java/io/quarkus/webjar/locator/test/WebJarLocatorTest.java +++ b/extensions/webjars-locator/deployment/src/test/java/io/quarkus/webjar/locator/test/WebJarLocatorTest.java @@ -22,7 +22,8 @@ public class WebJarLocatorTest extends WebJarLocatorTestSupport { .addAsResource(new StringAsset("Test"), META_INF_RESOURCES + "/some/path/test.txt")) .setForcedDependencies(List.of( Dependency.of("org.webjars", "jquery-ui", JQUERY_UI_VERSION), - Dependency.of("org.webjars", "momentjs", MOMENTJS_VERSION))); + Dependency.of("org.webjars", "momentjs", MOMENTJS_VERSION), + Dependency.of("org.mvnpm", "bootstrap", BOOTSTRAP_VERSION))); @Test public void test() { @@ -44,12 +45,16 @@ public void test() { .statusCode(200); RestAssured.get("/webjars/momentjs/min/moment.min.js").then() .statusCode(200); + RestAssured.get("/_static/bootstrap/dist/js/bootstrap.min.js").then() + .statusCode(200); // Test using version in url of existing Web Jar RestAssured.get("/webjars/jquery-ui/" + JQUERY_UI_VERSION + "/jquery-ui.min.js").then() .statusCode(200); RestAssured.get("/webjars/momentjs/" + MOMENTJS_VERSION + "/min/moment.min.js").then() .statusCode(200); + RestAssured.get("/_static/bootstrap/" + BOOTSTRAP_VERSION + "/dist/js/bootstrap.min.js").then() + .statusCode(200); // Test non-existing Web Jar RestAssured.get("/webjars/bootstrap/js/bootstrap.min.js").then() @@ -58,6 +63,8 @@ public void test() { .statusCode(404); RestAssured.get("/webjars/momentjs/2.25.0/min/moment.min.js").then() .statusCode(404); + RestAssured.get("/_static/foundation-sites/6.8.1/dist/js/foundation.esm.js").then() + .statusCode(404); // Test webjar that does not have a version in the jar path RestAssured.get("/webjars/dcjs/dc.min.js").then() diff --git a/extensions/webjars-locator/deployment/src/test/java/io/quarkus/webjar/locator/test/WebJarLocatorTestSupport.java b/extensions/webjars-locator/deployment/src/test/java/io/quarkus/webjar/locator/test/WebJarLocatorTestSupport.java index 83e84572f0b8e..0afe0cfae437c 100644 --- a/extensions/webjars-locator/deployment/src/test/java/io/quarkus/webjar/locator/test/WebJarLocatorTestSupport.java +++ b/extensions/webjars-locator/deployment/src/test/java/io/quarkus/webjar/locator/test/WebJarLocatorTestSupport.java @@ -4,4 +4,5 @@ class WebJarLocatorTestSupport { static final String JQUERY_UI_VERSION = System.getProperty("webjar.jquery-ui.version"); static final String MOMENTJS_VERSION = System.getProperty("webjar.momentjs.version"); + static final String BOOTSTRAP_VERSION = System.getProperty("mvnpm.bootstrap.version"); } diff --git a/extensions/webjars-locator/runtime/src/main/java/io/quarkus/webjar/locator/runtime/WebJarLocatorRecorder.java b/extensions/webjars-locator/runtime/src/main/java/io/quarkus/webjar/locator/runtime/WebJarLocatorRecorder.java index 813f4e920f830..55f22be5cf44a 100644 --- a/extensions/webjars-locator/runtime/src/main/java/io/quarkus/webjar/locator/runtime/WebJarLocatorRecorder.java +++ b/extensions/webjars-locator/runtime/src/main/java/io/quarkus/webjar/locator/runtime/WebJarLocatorRecorder.java @@ -4,6 +4,8 @@ import io.quarkus.runtime.annotations.Recorder; import io.vertx.core.Handler; +import io.vertx.core.http.HttpHeaders; +import io.vertx.core.http.HttpServerResponse; import io.vertx.ext.web.RoutingContext; @Recorder @@ -42,4 +44,27 @@ public Handler getHandler(String webjarsRootUrl, Map getImportMapHandler(String expectedPath, String importmap) { + return new Handler() { + @Override + public void handle(RoutingContext event) { + String path = event.normalizedPath(); + if (path.equals(expectedPath)) { + HttpServerResponse response = event.response(); + response.headers().set(HttpHeaders.CONTENT_TYPE, "text/javascript"); + response.end(JAVASCRIPT_CODE.formatted(importmap)); + } else { + // should not happen if route is set up correctly + event.next(); + } + } + }; + } + + private static final String JAVASCRIPT_CODE = """ + const im = document.createElement('script'); + im.type = 'importmap'; + im.textContent = JSON.stringify(%s); + document.currentScript.after(im); + """; }