Skip to content

Commit c06a9ac

Browse files
Add a WebJar Locator extension that duplicates webjar locator behavior from Spring Boot.
This reverts commit 7c9face.
1 parent f7c2cda commit c06a9ac

File tree

27 files changed

+810
-0
lines changed

27 files changed

+810
-0
lines changed

bom/deployment/pom.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -822,6 +822,19 @@
822822
<version>${project.version}</version>
823823
</dependency>
824824

825+
<dependency>
826+
<groupId>io.quarkus</groupId>
827+
<artifactId>quarkus-webjars-locator-deployment</artifactId>
828+
<version>${project.version}</version>
829+
</dependency>
830+
831+
<!-- Quarkus extension dependencies -->
832+
<dependency>
833+
<groupId>org.webjars</groupId>
834+
<artifactId>webjars-locator</artifactId>
835+
<version>0.39</version>
836+
</dependency>
837+
825838
<!-- Quarkus test dependencies -->
826839

827840
<dependency>

bom/runtime/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,6 +1075,11 @@
10751075
<artifactId>quarkus-picocli</artifactId>
10761076
<version>${project.version}</version>
10771077
</dependency>
1078+
<dependency>
1079+
<groupId>io.quarkus</groupId>
1080+
<artifactId>quarkus-webjars-locator</artifactId>
1081+
<version>${project.version}</version>
1082+
</dependency>
10781083

10791084
<!-- External dependencies -->
10801085

core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ public final class FeatureBuildItem extends MultiBuildItem {
111111
public static final String VERTX = "vertx";
112112
public static final String VERTX_WEB = "vertx-web";
113113
public static final String VERTX_GRAPHQL = "vertx-graphql";
114+
public static final String WEBJARS_LOCATOR = "webjars-locator";
114115

115116
private final String info;
116117

docs/src/main/asciidoc/http-reference.adoc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,29 @@ was chosen as it is the standard location for resources in `jar` files as define
2929
Quarkus can be used without Servlet following this convention allows existing code that places its resources in this
3030
location to function correctly.
3131

32+
=== WebJar Locator Support
33+
34+
If you are using webjars, like the following JQuery one
35+
[source, xml]
36+
----
37+
<dependency>
38+
<groupId>org.webjars</groupId>
39+
<artifactId>jquery</artifactId>
40+
<version>3.1.1</version>
41+
</dependency>
42+
----
43+
and rather write `/webjars/jquery/jquery.min.js` instead of `/webjars/jquery/3.1.1/jquery.min.js`
44+
in your HTML files, you can add the `quarkus-webjars-locator` extension to your project.
45+
To use it, add the following to your project's dependencies:
46+
47+
[source, xml]
48+
----
49+
<dependency>
50+
<groupId>io.quarkus</groupId>
51+
<artifactId>quarkus-webjars-locator</artifactId>
52+
</dependency>
53+
----
54+
3255
== Configuring the Context path
3356

3457
By default Quarkus will serve content from under the root context. If you want to change this you can use the

extensions/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
<module>vertx-http</module>
3434
<module>undertow</module>
3535
<module>undertow-websockets</module>
36+
<module>webjars-locator</module>
3637

3738
<!-- Monitoring -->
3839
<module>smallrye-health</module>
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<parent>
6+
<artifactId>quarkus-webjars-locator-parent</artifactId>
7+
<groupId>io.quarkus</groupId>
8+
<version>999-SNAPSHOT</version>
9+
<relativePath>../</relativePath>
10+
</parent>
11+
<modelVersion>4.0.0</modelVersion>
12+
13+
<artifactId>quarkus-webjars-locator-deployment</artifactId>
14+
<name>Quarkus - WebJar Locator - Deployment</name>
15+
16+
<dependencies>
17+
<dependency>
18+
<groupId>org.webjars</groupId>
19+
<artifactId>webjars-locator</artifactId>
20+
</dependency>
21+
<dependency>
22+
<groupId>io.quarkus</groupId>
23+
<artifactId>quarkus-core-deployment</artifactId>
24+
</dependency>
25+
<dependency>
26+
<groupId>io.quarkus</groupId>
27+
<artifactId>quarkus-vertx-http-deployment</artifactId>
28+
</dependency>
29+
<dependency>
30+
<groupId>io.quarkus</groupId>
31+
<artifactId>quarkus-webjars-locator</artifactId>
32+
</dependency>
33+
34+
<!-- Tests -->
35+
<dependency>
36+
<groupId>io.quarkus</groupId>
37+
<artifactId>quarkus-junit5-internal</artifactId>
38+
<scope>test</scope>
39+
</dependency>
40+
<dependency>
41+
<groupId>io.rest-assured</groupId>
42+
<artifactId>rest-assured</artifactId>
43+
<scope>test</scope>
44+
</dependency>
45+
46+
<!-- Using setForcedDependencies only works if the dependency is in the pom's test scope. -->
47+
<dependency>
48+
<groupId>org.webjars</groupId>
49+
<artifactId>jquery</artifactId>
50+
<version>3.4.1</version>
51+
<scope>test</scope>
52+
</dependency>
53+
<dependency>
54+
<groupId>org.webjars</groupId>
55+
<artifactId>momentjs</artifactId>
56+
<version>2.24.0</version>
57+
<scope>test</scope>
58+
</dependency>
59+
60+
<!-- DevMode has no setForcedDependencies, so need to add resteasy extension here -->
61+
<dependency>
62+
<groupId>io.quarkus</groupId>
63+
<artifactId>quarkus-resteasy</artifactId>
64+
</dependency>
65+
</dependencies>
66+
67+
<build>
68+
<plugins>
69+
<plugin>
70+
<artifactId>maven-compiler-plugin</artifactId>
71+
<configuration>
72+
<annotationProcessorPaths>
73+
<path>
74+
<groupId>io.quarkus</groupId>
75+
<artifactId>quarkus-extension-processor</artifactId>
76+
<version>${project.version}</version>
77+
</path>
78+
</annotationProcessorPaths>
79+
</configuration>
80+
</plugin>
81+
</plugins>
82+
</build>
83+
84+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package io.quarkus.webjar.locator.deployment;
2+
3+
import java.util.Map;
4+
5+
import org.webjars.WebJarAssetLocator;
6+
7+
import io.quarkus.deployment.annotations.BuildProducer;
8+
import io.quarkus.deployment.annotations.BuildStep;
9+
import io.quarkus.deployment.annotations.ExecutionTime;
10+
import io.quarkus.deployment.annotations.Record;
11+
import io.quarkus.deployment.builditem.FeatureBuildItem;
12+
import io.quarkus.vertx.http.deployment.RouteBuildItem;
13+
import io.quarkus.vertx.http.runtime.HttpBuildTimeConfig;
14+
import io.quarkus.webjar.locator.runtime.WebJarLocatorRecorder;
15+
16+
public class WebJarLocatorStandaloneBuildStep {
17+
18+
@BuildStep
19+
@Record(ExecutionTime.RUNTIME_INIT)
20+
public void findWebjarsAndCreateHandler(
21+
HttpBuildTimeConfig httpConfig,
22+
BuildProducer<FeatureBuildItem> feature,
23+
BuildProducer<RouteBuildItem> routes,
24+
WebJarLocatorRecorder recorder) throws Exception {
25+
26+
WebJarAssetLocator webJarLocator = new WebJarAssetLocator();
27+
Map<String, String> webjarNameToVersionMap = webJarLocator.getWebJars();
28+
29+
if (!webjarNameToVersionMap.isEmpty()) {
30+
// The context path + the resources path
31+
String rootPath = httpConfig.rootPath;
32+
String webjarRootPath = (rootPath.endsWith("/")) ? rootPath + "webjars/" : rootPath + "/webjars/";
33+
feature.produce(new FeatureBuildItem(FeatureBuildItem.WEBJARS_LOCATOR));
34+
routes.produce(
35+
new RouteBuildItem(webjarRootPath + "*",
36+
recorder.getHandler(webjarRootPath, webjarNameToVersionMap),
37+
false));
38+
}
39+
40+
}
41+
42+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package io.quarkus.webjar.locator.test;
2+
3+
import javax.annotation.PreDestroy;
4+
import javax.ws.rs.POST;
5+
import javax.ws.rs.Path;
6+
7+
@Path("/post")
8+
public class PostResource {
9+
10+
@POST
11+
public String modify(String data) {
12+
return "Hello: " + data;
13+
}
14+
15+
@PreDestroy
16+
void destroy() {
17+
throw new IllegalStateException("Something bad happened but dev mode should work fine");
18+
}
19+
20+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package io.quarkus.webjar.locator.test;
2+
3+
import static org.hamcrest.core.Is.is;
4+
5+
import org.hamcrest.Matchers;
6+
import org.jboss.shrinkwrap.api.ShrinkWrap;
7+
import org.jboss.shrinkwrap.api.asset.StringAsset;
8+
import org.jboss.shrinkwrap.api.spec.JavaArchive;
9+
import org.junit.jupiter.api.Test;
10+
import org.junit.jupiter.api.extension.RegisterExtension;
11+
12+
import io.quarkus.test.QuarkusDevModeTest;
13+
import io.restassured.RestAssured;
14+
15+
public class WebJarLocatorDevModeTest {
16+
private static final String META_INF_RESOURCES = "META-INF/resources/";
17+
18+
@RegisterExtension
19+
static QuarkusDevModeTest test = new QuarkusDevModeTest()
20+
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
21+
.addClass(PostResource.class)
22+
.addAsResource(new StringAsset("<html>Hello!<html>"), META_INF_RESOURCES + "/index.html")
23+
.addAsResource(new StringAsset("Test"), META_INF_RESOURCES + "/some/path/test.txt"));
24+
25+
@Test
26+
public void testDevMode() {
27+
// Test Endpoint
28+
RestAssured.given().body("Stuart")
29+
.when()
30+
.post("/post")
31+
.then()
32+
.body(Matchers.equalTo("Hello: Stuart"));
33+
// Test normal files
34+
RestAssured.get("/").then()
35+
.statusCode(200)
36+
.body(is("<html>Hello!<html>"));
37+
38+
RestAssured.get("/index.html").then()
39+
.statusCode(200)
40+
.body(is("<html>Hello!<html>"));
41+
42+
RestAssured.get("/some/path/test.txt").then()
43+
.statusCode(200)
44+
.body(is("Test"));
45+
46+
// Test Existing Web Jars
47+
RestAssured.get("/webjars/jquery/jquery.min.js").then()
48+
.statusCode(200);
49+
RestAssured.get("/webjars/momentjs/min/moment.min.js").then()
50+
.statusCode(200);
51+
52+
// Test using version in url of existing Web Jar
53+
RestAssured.get("/webjars/jquery/3.4.1/jquery.min.js").then()
54+
.statusCode(200);
55+
RestAssured.get("/webjars/momentjs/2.24.0/min/moment.min.js").then()
56+
.statusCode(200);
57+
58+
// Test non-existing Web Jar
59+
RestAssured.get("/webjars/bootstrap/js/bootstrap.min.js").then()
60+
.statusCode(404);
61+
RestAssured.get("/webjars/bootstrap/4.3.1/js/bootstrap.min.js").then()
62+
.statusCode(404);
63+
RestAssured.get("/webjars/momentjs/2.25.0/min/moment.min.js").then()
64+
.statusCode(404);
65+
66+
// Change a source file
67+
test.modifySourceFile(PostResource.class, s -> s.replace("Hello:", "Hi:"));
68+
69+
// Test modified endpoint
70+
RestAssured.given().body("Stuart")
71+
.when()
72+
.post("/post")
73+
.then()
74+
.body(Matchers.equalTo("Hi: Stuart"));
75+
76+
// Test normal files
77+
RestAssured.get("/").then()
78+
.statusCode(200)
79+
.body(is("<html>Hello!<html>"));
80+
81+
RestAssured.get("/index.html").then()
82+
.statusCode(200)
83+
.body(is("<html>Hello!<html>"));
84+
85+
RestAssured.get("/some/path/test.txt").then()
86+
.statusCode(200)
87+
.body(is("Test"));
88+
89+
// Test Existing Web Jars
90+
RestAssured.get("/webjars/jquery/jquery.min.js").then()
91+
.statusCode(200);
92+
RestAssured.get("/webjars/momentjs/min/moment.min.js").then()
93+
.statusCode(200);
94+
95+
// Test using version in url of existing Web Jar
96+
RestAssured.get("/webjars/jquery/3.4.1/jquery.min.js").then()
97+
.statusCode(200);
98+
RestAssured.get("/webjars/momentjs/2.24.0/min/moment.min.js").then()
99+
.statusCode(200);
100+
101+
// Test non-existing Web Jar
102+
RestAssured.get("/webjars/bootstrap/js/bootstrap.min.js").then()
103+
.statusCode(404);
104+
RestAssured.get("/webjars/bootstrap/4.3.1/js/bootstrap.min.js").then()
105+
.statusCode(404);
106+
RestAssured.get("/webjars/momentjs/2.25.0/min/moment.min.js").then()
107+
.statusCode(404);
108+
}
109+
}

0 commit comments

Comments
 (0)