Skip to content

Commit 46f0451

Browse files
ZheSun88claude
andauthored
fix: add package.json overrides for workbox dependencies (#24008) (CP:25.0) (#24131)
Cherry-pick of 90eb196 from main, adapted for 25.0: - Files in flow-build-tools/ on main mapped to flow-server/ on 25.0 - JUnit 5 assertions/annotations converted to JUnit 4 - temporaryFolder.getRoot() preserved (JUnit 4 TemporaryFolder) - Removed checkJacksonCompatibility test (method not on 25.0) - Kept vaadinBundlesPlugin import (25.0-specific) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 474afa4 commit 46f0451

31 files changed

+2170
-474
lines changed

flow-server/src/main/java/com/vaadin/flow/internal/JacksonUtils.java

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.util.stream.DoubleStream;
3232
import java.util.stream.Stream;
3333

34+
import org.jspecify.annotations.Nullable;
3435
import tools.jackson.core.JacksonException;
3536
import tools.jackson.core.JsonGenerator;
3637
import tools.jackson.core.json.JsonReadFeature;
@@ -539,6 +540,105 @@ public static String toFileJson(JsonNode node) throws JacksonException {
539540
return objectMapper.writer().with(filePrinter).writeValueAsString(node);
540541
}
541542

543+
/**
544+
* Retrieves a nested JSON key from an object node based on a provided path.
545+
*
546+
* @param objectNode
547+
* the root object node to search within
548+
* @param keyPath
549+
* the nested key path
550+
* @return the value of the last key found, or {@code null}
551+
*/
552+
public static @Nullable JsonNode getNestedKey(ObjectNode objectNode,
553+
List<String> keyPath) {
554+
if (keyPath.isEmpty()) {
555+
return null;
556+
}
557+
final String firstKey = keyPath.getFirst();
558+
final JsonNode value = objectNode.get(firstKey);
559+
if (keyPath.size() == 1) {
560+
return value;
561+
} else if (value instanceof ObjectNode nestedObjectNode) {
562+
return getNestedKey(nestedObjectNode,
563+
keyPath.subList(1, keyPath.size()));
564+
} else {
565+
return null;
566+
}
567+
}
568+
569+
/**
570+
* Sets a nested JSON key path in an {@code ObjectNode} with the provided
571+
* value.
572+
* <p>
573+
* This method handles both direct and nested key assignments. If the
574+
* current node at the specified top-level key does not exist not an
575+
* {@code ObjectNode}, it converts it to one using the provided converter
576+
* function, before proceeding with the remaining keys in the path.
577+
*
578+
* @param objectNode
579+
* the root object where the key path will be set
580+
* @param keyPath
581+
* the nested key path
582+
* @param valueNode
583+
* the value to be assigned
584+
* @param plainValueConverter
585+
* a function that converts intermediate {@code null} and
586+
* non-{@code ObjectNode} values into {@code ObjectNode}s when
587+
* necessary for recursive traversal
588+
*/
589+
public static void setNestedKey(ObjectNode objectNode, List<String> keyPath,
590+
JsonNode valueNode,
591+
Function<? super JsonNode, ? extends ObjectNode> plainValueConverter) {
592+
if (keyPath.isEmpty()) {
593+
return;
594+
}
595+
final String key = keyPath.getFirst();
596+
if (keyPath.size() == 1) {
597+
objectNode.set(key, valueNode);
598+
return;
599+
}
600+
final JsonNode currentValueNode = objectNode.get(key);
601+
final ObjectNode nestedObjectNode;
602+
if (currentValueNode instanceof ObjectNode currentObjectNode) {
603+
nestedObjectNode = currentObjectNode;
604+
} else {
605+
nestedObjectNode = plainValueConverter.apply(currentValueNode);
606+
objectNode.set(key, nestedObjectNode);
607+
}
608+
setNestedKey(nestedObjectNode, keyPath.subList(1, keyPath.size()),
609+
valueNode, plainValueConverter);
610+
}
611+
612+
/**
613+
* Removes a nested key from an {@code ObjectNode} based on the provided
614+
* path. Also removes nested objects, if they become empty after key
615+
* removal.
616+
*
617+
* @param objectNode
618+
* the root object containing to process
619+
* @param keyPath
620+
* the nested key path
621+
*/
622+
public static void removeNestedKey(ObjectNode objectNode,
623+
List<String> keyPath) {
624+
if (keyPath.isEmpty()) {
625+
return;
626+
}
627+
final String key = keyPath.getFirst();
628+
if (keyPath.size() == 1) {
629+
objectNode.remove(key);
630+
return;
631+
}
632+
final JsonNode currentValueNode = objectNode.get(key);
633+
if (currentValueNode instanceof ObjectNode nestedObjectNode) {
634+
removeNestedKey(nestedObjectNode,
635+
keyPath.subList(1, keyPath.size()));
636+
if (nestedObjectNode.isEmpty()) {
637+
objectNode.remove(key);
638+
}
639+
}
640+
}
641+
542642
/**
543643
* Custom Jackson serializer for Component that delegates to NodeSerializer.
544644
*/

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

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,6 @@ abstract class AbstractUpdateImports implements Runnable {
114114

115115
private final Map<Path, List<String>> resolvedImportPathsCache = new HashMap<>();
116116

117-
private FrontendDependenciesScanner scanner;
118-
119117
private ClassFinder classFinder;
120118

121119
final File generatedFlowImports;
@@ -127,19 +125,17 @@ abstract class AbstractUpdateImports implements Runnable {
127125

128126
private final GeneratedFilesSupport generatedFilesSupport;
129127

130-
AbstractUpdateImports(Options options,
131-
FrontendDependenciesScanner scanner) {
132-
this(options, scanner, new GeneratedFilesSupport());
128+
AbstractUpdateImports(Options options) {
129+
this(options, new GeneratedFilesSupport());
133130
}
134131

135-
AbstractUpdateImports(Options options, FrontendDependenciesScanner scanner,
132+
AbstractUpdateImports(Options options,
136133
GeneratedFilesSupport generatedFilesSupport) {
137134
this.generatedFilesSupport = generatedFilesSupport;
138135
this.options = options;
139-
this.scanner = scanner;
140136
this.classFinder = options.getClassFinder();
141137
this.themeToLocalPathConverter = createThemeToLocalPathConverter(
142-
scanner.getTheme());
138+
options.getFrontendDependenciesScanner().getTheme());
143139

144140
generatedFlowImports = FrontendUtils
145141
.getFlowGeneratedImports(options.getFrontendDirectory());
@@ -166,7 +162,8 @@ public void run() {
166162
getLogger().debug("Start updating imports file and chunk files.");
167163
long start = System.nanoTime();
168164

169-
Map<ChunkInfo, List<CssData>> css = scanner.getCss();
165+
Map<ChunkInfo, List<CssData>> css = options
166+
.getFrontendDependenciesScanner().getCss();
170167
Map<ChunkInfo, List<String>> javascript = getMergedJavascript();
171168

172169
Map<File, List<String>> output = process(css, javascript);
@@ -183,6 +180,8 @@ private Map<ChunkInfo, List<String>> getMergedJavascript() {
183180
long start = System.nanoTime();
184181

185182
Map<ChunkInfo, List<String>> javascript;
183+
final FrontendDependenciesScanner scanner = options
184+
.getFrontendDependenciesScanner();
186185
Map<ChunkInfo, List<String>> modules = scanner.getModules();
187186
Map<ChunkInfo, List<String>> scripts = scanner.getScripts();
188187

@@ -711,7 +710,8 @@ private Set<String> getUniqueEs6ImportPaths(Collection<String> modules) {
711710
Set<String> npmNotFound = new HashSet<>();
712711
Set<String> resourceNotFound = new HashSet<>();
713712
Set<String> es6ImportPaths = new LinkedHashSet<>();
714-
AbstractTheme theme = scanner.getTheme();
713+
AbstractTheme theme = options.getFrontendDependenciesScanner()
714+
.getTheme();
715715

716716
Set<String> visited = new HashSet<>();
717717

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

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -260,8 +260,7 @@ private static boolean needsBuildInternal(Options options,
260260
((ObjectNode) statsJson.get(FRONTEND_HASHES_STATS_KEY)).remove(
261261
FrontendUtils.GENERATED + FrontendUtils.COMMERCIAL_BANNER_JS);
262262

263-
if (!BundleValidationUtil.frontendImportsFound(statsJson, options,
264-
frontendDependencies)) {
263+
if (!BundleValidationUtil.frontendImportsFound(statsJson, options)) {
265264
UsageStatistics.markAsUsed(
266265
"flow/rebundle-reason-missing-frontend-import", null);
267266
return true;
@@ -341,8 +340,7 @@ public static JsonNode getPackageJson(Options options,
341340
public static JsonNode getDefaultPackageJson(Options options,
342341
FrontendDependenciesScanner frontendDependencies,
343342
ObjectNode packageJson) {
344-
NodeUpdater nodeUpdater = new NodeUpdater(frontendDependencies,
345-
options) {
343+
NodeUpdater nodeUpdater = new NodeUpdater(options) {
346344
@Override
347345
public void execute() {
348346
}
@@ -648,12 +646,11 @@ private static String getTag(
648646
}
649647

650648
public static boolean frontendImportsFound(JsonNode statsJson,
651-
Options options, FrontendDependenciesScanner frontendDependencies)
652-
throws IOException {
649+
Options options) throws IOException {
653650

654651
// Validate frontend requirements in flow-generated-imports.js
655652
final GenerateMainImports generateMainImports = new GenerateMainImports(
656-
frontendDependencies, options, statsJson);
653+
options, statsJson);
657654
generateMainImports.run();
658655
final List<String> imports = generateMainImports.getLines().stream()
659656
.filter(line -> line.startsWith("import"))

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828

2929
import com.vaadin.flow.internal.JacksonUtils;
3030
import com.vaadin.flow.server.frontend.scanner.CssData;
31-
import com.vaadin.flow.server.frontend.scanner.FrontendDependenciesScanner;
3231

3332
/**
3433
* Collect generated-flow-imports content for project to use to determine if
@@ -43,9 +42,8 @@ public class GenerateMainImports extends AbstractUpdateImports {
4342
private JsonNode statsJson;
4443
private Map<File, List<String>> output;
4544

46-
public GenerateMainImports(FrontendDependenciesScanner frontendDepScanner,
47-
Options options, JsonNode statsJson) {
48-
super(options, frontendDepScanner);
45+
public GenerateMainImports(Options options, JsonNode statsJson) {
46+
super(options);
4947
this.statsJson = statsJson;
5048
}
5149

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,7 @@ public NodeTasks(Options options) {
201201
TaskUpdatePackages packageUpdater = null;
202202
if (options.isEnablePackagesUpdate()
203203
&& options.getJarFrontendResourcesFolder() != null) {
204-
packageUpdater = new TaskUpdatePackages(frontendDependencies,
205-
options);
204+
packageUpdater = new TaskUpdatePackages(options);
206205
commands.add(packageUpdater);
207206
}
208207

@@ -239,7 +238,7 @@ public NodeTasks(Options options) {
239238
// available)
240239
addEndpointServicesTasks(options);
241240

242-
commands.add(new TaskGenerateBootstrap(frontendDependencies, options));
241+
commands.add(new TaskGenerateBootstrap(options));
243242

244243
commands.add(new TaskGenerateFeatureFlags(options));
245244

@@ -277,7 +276,7 @@ public NodeTasks(Options options) {
277276
}
278277

279278
if (options.isEnableImportsUpdate()) {
280-
commands.add(new TaskUpdateImports(frontendDependencies, options));
279+
commands.add(new TaskUpdateImports(options));
281280

282281
commands.add(new TaskUpdateThemeImport(
283282
frontendDependencies.getThemeDefinition(), options));

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

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.util.function.Supplier;
3333
import java.util.stream.Collectors;
3434

35+
import org.jspecify.annotations.Nullable;
3536
import org.slf4j.Logger;
3637
import org.slf4j.LoggerFactory;
3738
import tools.jackson.databind.JsonNode;
@@ -43,9 +44,8 @@
4344
import com.vaadin.flow.internal.StringUtil;
4445
import com.vaadin.flow.internal.hilla.EndpointRequestUtil;
4546
import com.vaadin.flow.server.Constants;
47+
import com.vaadin.flow.server.PwaConfiguration;
4648
import com.vaadin.flow.server.frontend.scanner.ClassFinder;
47-
import com.vaadin.flow.server.frontend.scanner.FrontendDependencies;
48-
import com.vaadin.flow.server.frontend.scanner.FrontendDependenciesScanner;
4949

5050
import static com.vaadin.flow.server.Constants.PACKAGE_JSON;
5151
import static com.vaadin.flow.server.Constants.PACKAGE_LOCK_JSON;
@@ -91,12 +91,6 @@ public abstract class NodeUpdater implements FallibleCommand {
9191
static final String VAADIN_VERSION = "vaadinVersion";
9292
static final String PROJECT_FOLDER = "projectFolder";
9393

94-
/**
95-
* The {@link FrontendDependencies} object representing the application
96-
* dependencies.
97-
*/
98-
protected final FrontendDependenciesScanner frontDeps;
99-
10094
final ClassFinder finder;
10195

10296
boolean modified;
@@ -108,15 +102,11 @@ public abstract class NodeUpdater implements FallibleCommand {
108102
/**
109103
* Constructor.
110104
*
111-
* @param frontendDependencies
112-
* a reusable frontend dependencies
113105
* @param options
114106
* the task options
115107
*/
116-
protected NodeUpdater(FrontendDependenciesScanner frontendDependencies,
117-
Options options) {
108+
protected NodeUpdater(Options options) {
118109
this.finder = options.getClassFinder();
119-
this.frontDeps = frontendDependencies;
120110
this.options = options;
121111
}
122112

@@ -322,27 +312,37 @@ Map<String, String> getDefaultDependencies() {
322312
return dependencies;
323313
}
324314

325-
Map<String, String> readDependencies(String id, String packageJsonKey) {
315+
@Nullable
316+
JsonNode readPackageJsonKey(String id, String packageJsonKey) {
317+
final JsonNode packageJson;
326318
try {
327-
Map<String, String> map = new HashMap<>();
328-
JsonNode dependencies = readPackageJson(id).get(packageJsonKey);
329-
if (dependencies == null) {
330-
log().error("Unable to find " + packageJsonKey + " from '" + id
331-
+ "'");
332-
return new HashMap<>();
333-
}
334-
for (String key : JacksonUtils.getKeys(dependencies)) {
335-
map.put(key, dependencies.get(key).asString());
336-
}
337-
338-
return map;
319+
packageJson = readPackageJson(id);
339320
} catch (IOException e) {
340321
log().error(
341322
"Unable to read " + packageJsonKey + " from '" + id + "'",
342323
e);
343-
return new HashMap<>();
324+
return null;
325+
}
326+
var value = packageJson.get(packageJsonKey);
327+
if (value == null) {
328+
log().error(
329+
"Unable to find " + packageJsonKey + " from '" + id + "'");
330+
}
331+
return value;
332+
}
333+
334+
Map<String, String> readDependencies(String id, String packageJsonKey) {
335+
Map<String, String> map = new HashMap<>();
336+
JsonNode dependencies = readPackageJsonKey(id, packageJsonKey);
337+
if (dependencies == null) {
338+
return map;
344339
}
345340

341+
for (String key : JacksonUtils.getKeys(dependencies)) {
342+
map.put(key, dependencies.get(key).asString());
343+
}
344+
345+
return map;
346346
}
347347

348348
JsonNode readPackageJson(String id) throws IOException {
@@ -393,14 +393,33 @@ Map<String, String> getDefaultDevDependencies() {
393393
}
394394

395395
// Add workbox dependencies only when PWA is enabled
396-
if (frontDeps != null && frontDeps.getPwaConfiguration() != null
397-
&& frontDeps.getPwaConfiguration().isEnabled()) {
396+
final PwaConfiguration pwaConfiguration = options
397+
.getFrontendDependenciesScanner().getPwaConfiguration();
398+
if (pwaConfiguration != null && pwaConfiguration.isOfflineEnabled()) {
398399
defaults.putAll(readDependencies("workbox", "devDependencies"));
399400
}
400401

401402
return defaults;
402403
}
403404

405+
ObjectNode getDefaultOverrides() {
406+
var overrides = JacksonUtils.createObjectNode();
407+
408+
final PwaConfiguration pwaConfiguration = options
409+
.getFrontendDependenciesScanner().getPwaConfiguration();
410+
// Currently, we only have overrides for workbox uses overrides, and
411+
// only when PWA is enabled
412+
final ObjectNode workboxOverrides = pwaConfiguration != null
413+
&& pwaConfiguration.isOfflineEnabled()
414+
? (ObjectNode) readPackageJsonKey("workbox",
415+
"overrides")
416+
: null;
417+
if (workboxOverrides != null) {
418+
overrides = overrides.setAll(workboxOverrides);
419+
}
420+
return overrides;
421+
}
422+
404423
/**
405424
* Updates default dependencies and development dependencies to
406425
* package.json.

0 commit comments

Comments
 (0)