Skip to content

Commit 918a0ee

Browse files
authored
feat: Lookup for any Hilla views for auto-detection (#18765)
* feat: Lookup for any Hilla views for auto-detection * moving method to file utils and add tests
1 parent 83c552a commit 918a0ee

File tree

3 files changed

+124
-6
lines changed

3 files changed

+124
-6
lines changed

flow-server/src/main/java/com/vaadin/flow/server/frontend/FileIOUtils.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,14 @@
2020
import java.net.URISyntaxException;
2121
import java.net.URL;
2222
import java.nio.charset.StandardCharsets;
23+
import java.nio.file.FileSystems;
24+
import java.nio.file.FileVisitResult;
25+
import java.nio.file.Files;
2326
import java.nio.file.Path;
27+
import java.nio.file.PathMatcher;
28+
import java.nio.file.SimpleFileVisitor;
29+
import java.nio.file.attribute.BasicFileAttributes;
30+
import java.util.ArrayList;
2431
import java.util.List;
2532
import java.util.stream.Collectors;
2633

@@ -135,4 +142,44 @@ public static boolean isProbablyTemporaryFile(File file) {
135142
return file.getName().endsWith("~");
136143
}
137144

145+
/**
146+
* Get a list of files in a given directory that match a given glob pattern.
147+
*
148+
* @param baseDir
149+
* a directory to walk in
150+
* @param pattern
151+
* glob pattern to filter files, e.g. "*.js".
152+
* @return a list of files matching a given pattern
153+
* @throws IOException
154+
* if an I/O error is thrown while walking through the tree in
155+
* base directory
156+
*/
157+
public static List<Path> getFilesByPattern(Path baseDir, String pattern)
158+
throws IOException {
159+
if (baseDir == null || !baseDir.toFile().exists()) {
160+
throw new IllegalArgumentException(
161+
"Base directory is empty or doesn't exist: " + baseDir);
162+
}
163+
164+
if (pattern == null || pattern.isBlank()) {
165+
pattern = "*";
166+
}
167+
168+
PathMatcher matcher = FileSystems.getDefault()
169+
.getPathMatcher("glob:" + pattern);
170+
171+
List<Path> matchingPaths = new ArrayList<>();
172+
Files.walkFileTree(baseDir, new SimpleFileVisitor<Path>() {
173+
@Override
174+
public FileVisitResult visitFile(Path file,
175+
BasicFileAttributes attrs) {
176+
if (matcher.matches(file)) {
177+
matchingPaths.add(file);
178+
}
179+
return FileVisitResult.CONTINUE;
180+
}
181+
});
182+
return matchingPaths;
183+
}
184+
138185
}

flow-server/src/main/java/com/vaadin/flow/server/frontend/FrontendUtils.java

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
*/
1616
package com.vaadin.flow.server.frontend;
1717

18-
import jakarta.servlet.ServletContext;
19-
2018
import java.io.File;
2119
import java.io.FileInputStream;
2220
import java.io.FileNotFoundException;
@@ -30,6 +28,7 @@
3028
import java.nio.file.Path;
3129
import java.nio.file.attribute.BasicFileAttributes;
3230
import java.util.Arrays;
31+
import java.util.Collection;
3332
import java.util.List;
3433
import java.util.Map;
3534
import java.util.Objects;
@@ -40,6 +39,7 @@
4039
import java.util.regex.Pattern;
4140
import java.util.stream.Stream;
4241

42+
import jakarta.servlet.ServletContext;
4343
import org.apache.commons.io.FileUtils;
4444
import org.apache.commons.io.IOUtils;
4545
import org.slf4j.Logger;
@@ -60,7 +60,6 @@
6060
import com.vaadin.flow.server.frontend.scanner.ClassFinder;
6161

6262
import elemental.json.JsonObject;
63-
6463
import static com.vaadin.flow.server.Constants.COMPATIBILITY_RESOURCES_FRONTEND_DEFAULT;
6564
import static com.vaadin.flow.server.Constants.RESOURCES_FRONTEND_DEFAULT;
6665
import static com.vaadin.flow.server.Constants.VAADIN_WEBAPP_RESOURCES;
@@ -258,6 +257,12 @@ public class FrontendUtils {
258257
public static final String FRONTEND_GENERATED_FLOW_IMPORT_PATH = FRONTEND_FOLDER_ALIAS
259258
+ "generated/flow/";
260259

260+
/**
261+
* The default directory in frontend directory, where Hilla views are
262+
* located.
263+
*/
264+
public static final String HILLA_VIEWS_PATH = "views";
265+
261266
/**
262267
* File used to enable npm mode.
263268
*/
@@ -1239,8 +1244,27 @@ public static boolean isReactRouterRequired(File frontendDirectory) {
12391244
*/
12401245
public static boolean isHillaViewsUsed(File frontendDirectory) {
12411246
Objects.requireNonNull(frontendDirectory);
1242-
var files = List.of(FrontendUtils.INDEX_TS, FrontendUtils.ROUTES_TS,
1243-
FrontendUtils.ROUTES_TSX);
1247+
File viewsDirectory = new File(frontendDirectory, HILLA_VIEWS_PATH);
1248+
if (viewsDirectory.exists()) {
1249+
try {
1250+
Collection<Path> views = FileIOUtils.getFilesByPattern(
1251+
viewsDirectory.toPath(), "**/*.{js,jsx,ts,tsx}");
1252+
for (Path view : views) {
1253+
String viewContent = IOUtils.toString(view.toUri(), UTF_8);
1254+
viewContent = StringUtil.removeComments(viewContent);
1255+
if (!viewContent.isBlank()) {
1256+
return true;
1257+
}
1258+
}
1259+
} catch (IOException e) {
1260+
getLogger().error(
1261+
"Couldn't scan Hilla views directory for hilla auto-detection",
1262+
e);
1263+
}
1264+
}
1265+
1266+
var files = List.of(FrontendUtils.INDEX_TS, FrontendUtils.INDEX_TSX,
1267+
FrontendUtils.ROUTES_TS, FrontendUtils.ROUTES_TSX);
12441268
for (String fileName : files) {
12451269
File routesFile = new File(frontendDirectory, fileName);
12461270
if (routesFile.exists()) {

flow-server/src/test/java/com/vaadin/flow/server/frontend/FrontendUtilsTest.java

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
import org.slf4j.Logger;
3838
import org.slf4j.LoggerFactory;
3939

40-
import com.vaadin.flow.di.Lookup;
4140
import com.vaadin.flow.internal.Pair;
4241
import com.vaadin.flow.server.ExecutionFailedException;
4342
import com.vaadin.flow.server.frontend.installer.NodeInstaller;
@@ -177,6 +176,17 @@ public class FrontendUtilsTest {
177176
];
178177
""";
179178

179+
private static final String HILLA_VIEW_TSX = """
180+
import { VerticalLayout } from "@vaadin/react-components/VerticalLayout.js";
181+
export default function AboutView() {
182+
return (
183+
<VerticalLayout theme="padding">
184+
<p>This is a Hilla view</p>
185+
</VerticalLayout>
186+
);
187+
}
188+
""";
189+
180190
@Test
181191
public void parseValidVersions() {
182192
FrontendVersion sixPointO = new FrontendVersion(6, 0);
@@ -604,6 +614,43 @@ public void isHillaViewsUsed_serverSideRoutesMainLayoutTsx_true()
604614
FrontendUtils.isHillaViewsUsed(frontend));
605615
}
606616

617+
@Test
618+
public void isHillaViewsUsed_nonEmptyHillaViewInViews_true()
619+
throws IOException {
620+
File frontend = null;
621+
for (String ext : Arrays.asList(".js", ".jsx", ".ts", ".tsx")) {
622+
try {
623+
frontend = prepareFrontendForRoutesFile(
624+
FrontendUtils.HILLA_VIEWS_PATH + "/HillaView" + ext,
625+
HILLA_VIEW_TSX);
626+
Assert.assertTrue(
627+
"hilla view is present, thus Hilla is expected",
628+
FrontendUtils.isHillaViewsUsed(frontend));
629+
} finally {
630+
if (frontend != null && frontend.exists()) {
631+
FileUtils.deleteQuietly(frontend);
632+
}
633+
}
634+
}
635+
}
636+
637+
@Test
638+
public void isHillaViewsUsed_emptyHillaViewContent_false()
639+
throws IOException {
640+
File frontend = prepareFrontendForRoutesFile(
641+
FrontendUtils.HILLA_VIEWS_PATH + "/HillaView.ts", "//comment");
642+
Assert.assertFalse("empty Hilla view, Hilla not expected",
643+
FrontendUtils.isHillaViewsUsed(frontend));
644+
}
645+
646+
@Test
647+
public void isHillaViewsUsed_noViews_false() throws IOException {
648+
File frontend = prepareFrontendForRoutesFile(
649+
FrontendUtils.HILLA_VIEWS_PATH + "/foo.css", "some css");
650+
Assert.assertFalse("no Hilla views, Hilla not expected",
651+
FrontendUtils.isHillaViewsUsed(frontend));
652+
}
653+
607654
private File prepareFrontendForRoutesFile(String fileName, String content)
608655
throws IOException {
609656
File frontend = tmpDir.newFolder("frontend");

0 commit comments

Comments
 (0)