Skip to content

Commit f7fe6ac

Browse files
authored
fix: add package.json overrides for workbox dependencies (#24008) (CP: 24.10) (#24163)
- Add PwaConfiguration parameter to TaskUpdateVite constructor - Remove service worker plugin and its imports from vite.generated.ts when offline build is not enabled - Use FrontendDependenciesScanner in TaskGeneratePackageJson to generate correct dev dependencies - Enhance TaskUpdatePackages for proper override handling - Move all workbox dependencies to `workbox/package.json` and only add when offline sw build is needed - Add overrides for workbox dependencies to package.json - Add overrides support to NodeUpdater - Remove empty vaadin.overrides from package.json (cherry picked from commit 90eb196)
1 parent 51fa0f4 commit f7fe6ac

34 files changed

Lines changed: 2329 additions & 455 deletions

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

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,4 +632,103 @@ public static String toFileJson(JsonNode node)
632632
.withObjectFieldValueSpacing(Spacing.AFTER));
633633
return objectMapper.writer().with(filePrinter).writeValueAsString(node);
634634
}
635+
636+
/**
637+
* Retrieves a nested JSON key from an object node based on a provided path.
638+
*
639+
* @param objectNode
640+
* the root object node to search within
641+
* @param keyPath
642+
* the nested key path
643+
* @return the value of the last key found, or {@code null}
644+
*/
645+
public static JsonNode getNestedKey(ObjectNode objectNode,
646+
List<String> keyPath) {
647+
if (keyPath.isEmpty()) {
648+
return null;
649+
}
650+
final String firstKey = keyPath.get(0);
651+
final JsonNode value = objectNode.get(firstKey);
652+
if (keyPath.size() == 1) {
653+
return value;
654+
} else if (value instanceof ObjectNode nestedObjectNode) {
655+
return getNestedKey(nestedObjectNode,
656+
keyPath.subList(1, keyPath.size()));
657+
} else {
658+
return null;
659+
}
660+
}
661+
662+
/**
663+
* Sets a nested JSON key path in an {@code ObjectNode} with the provided
664+
* value.
665+
* <p>
666+
* This method handles both direct and nested key assignments. If the
667+
* current node at the specified top-level key does not exist not an
668+
* {@code ObjectNode}, it converts it to one using the provided converter
669+
* function, before proceeding with the remaining keys in the path.
670+
*
671+
* @param objectNode
672+
* the root object where the key path will be set
673+
* @param keyPath
674+
* the nested key path
675+
* @param valueNode
676+
* the value to be assigned
677+
* @param plainValueConverter
678+
* a function that converts intermediate {@code null} and
679+
* non-{@code ObjectNode} values into {@code ObjectNode}s when
680+
* necessary for recursive traversal
681+
*/
682+
public static void setNestedKey(ObjectNode objectNode, List<String> keyPath,
683+
JsonNode valueNode,
684+
Function<? super JsonNode, ? extends ObjectNode> plainValueConverter) {
685+
if (keyPath.isEmpty()) {
686+
return;
687+
}
688+
final String key = keyPath.get(0);
689+
if (keyPath.size() == 1) {
690+
objectNode.set(key, valueNode);
691+
return;
692+
}
693+
final JsonNode currentValueNode = objectNode.get(key);
694+
final ObjectNode nestedObjectNode;
695+
if (currentValueNode instanceof ObjectNode currentObjectNode) {
696+
nestedObjectNode = currentObjectNode;
697+
} else {
698+
nestedObjectNode = plainValueConverter.apply(currentValueNode);
699+
objectNode.set(key, nestedObjectNode);
700+
}
701+
setNestedKey(nestedObjectNode, keyPath.subList(1, keyPath.size()),
702+
valueNode, plainValueConverter);
703+
}
704+
705+
/**
706+
* Removes a nested key from an {@code ObjectNode} based on the provided
707+
* path. Also removes nested objects, if they become empty after key
708+
* removal.
709+
*
710+
* @param objectNode
711+
* the root object containing to process
712+
* @param keyPath
713+
* the nested key path
714+
*/
715+
public static void removeNestedKey(ObjectNode objectNode,
716+
List<String> keyPath) {
717+
if (keyPath.isEmpty()) {
718+
return;
719+
}
720+
final String key = keyPath.get(0);
721+
if (keyPath.size() == 1) {
722+
objectNode.remove(key);
723+
return;
724+
}
725+
final JsonNode currentValueNode = objectNode.get(key);
726+
if (currentValueNode instanceof ObjectNode nestedObjectNode) {
727+
removeNestedKey(nestedObjectNode,
728+
keyPath.subList(1, keyPath.size()));
729+
if (nestedObjectNode.isEmpty()) {
730+
objectNode.remove(key);
731+
}
732+
}
733+
}
635734
}

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
@@ -111,8 +111,6 @@ abstract class AbstractUpdateImports implements Runnable {
111111

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

114-
private FrontendDependenciesScanner scanner;
115-
116114
private ClassFinder classFinder;
117115

118116
final File generatedFlowImports;
@@ -122,19 +120,17 @@ abstract class AbstractUpdateImports implements Runnable {
122120

123121
private final GeneratedFilesSupport generatedFilesSupport;
124122

125-
AbstractUpdateImports(Options options,
126-
FrontendDependenciesScanner scanner) {
127-
this(options, scanner, new GeneratedFilesSupport());
123+
AbstractUpdateImports(Options options) {
124+
this(options, new GeneratedFilesSupport());
128125
}
129126

130-
AbstractUpdateImports(Options options, FrontendDependenciesScanner scanner,
127+
AbstractUpdateImports(Options options,
131128
GeneratedFilesSupport generatedFilesSupport) {
132129
this.generatedFilesSupport = generatedFilesSupport;
133130
this.options = options;
134-
this.scanner = scanner;
135131
this.classFinder = options.getClassFinder();
136132
this.themeToLocalPathConverter = createThemeToLocalPathConverter(
137-
scanner.getTheme());
133+
options.getFrontendDependenciesScanner().getTheme());
138134

139135
generatedFlowImports = FrontendUtils
140136
.getFlowGeneratedImports(options.getFrontendDirectory());
@@ -155,7 +151,8 @@ public void run() {
155151
getLogger().debug("Start updating imports file and chunk files.");
156152
long start = System.nanoTime();
157153

158-
Map<ChunkInfo, List<CssData>> css = scanner.getCss();
154+
Map<ChunkInfo, List<CssData>> css = options
155+
.getFrontendDependenciesScanner().getCss();
159156
Map<ChunkInfo, List<String>> javascript = getMergedJavascript();
160157

161158
Map<File, List<String>> output = process(css, javascript);
@@ -172,6 +169,8 @@ private Map<ChunkInfo, List<String>> getMergedJavascript() {
172169
long start = System.nanoTime();
173170

174171
Map<ChunkInfo, List<String>> javascript;
172+
final FrontendDependenciesScanner scanner = options
173+
.getFrontendDependenciesScanner();
175174
Map<ChunkInfo, List<String>> modules = scanner.getModules();
176175
Map<ChunkInfo, List<String>> scripts = scanner.getScripts();
177176

@@ -571,7 +570,8 @@ private Set<String> getUniqueEs6ImportPaths(Collection<String> modules) {
571570
Set<String> npmNotFound = new HashSet<>();
572571
Set<String> resourceNotFound = new HashSet<>();
573572
Set<String> es6ImportPaths = new LinkedHashSet<>();
574-
AbstractTheme theme = scanner.getTheme();
573+
AbstractTheme theme = options.getFrontendDependenciesScanner()
574+
.getTheme();
575575

576576
Set<String> visited = new HashSet<>();
577577

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
@@ -244,8 +244,7 @@ private static boolean needsBuildInternal(Options options,
244244
((ObjectNode) statsJson.get(FRONTEND_HASHES_STATS_KEY)).remove(
245245
FrontendUtils.GENERATED + FrontendUtils.COMMERCIAL_BANNER_JS);
246246

247-
if (!BundleValidationUtil.frontendImportsFound(statsJson, options,
248-
frontendDependencies)) {
247+
if (!BundleValidationUtil.frontendImportsFound(statsJson, options)) {
249248
UsageStatistics.markAsUsed(
250249
"flow/rebundle-reason-missing-frontend-import", null);
251250
return true;
@@ -324,8 +323,7 @@ public static JsonNode getPackageJson(Options options,
324323
public static JsonNode getDefaultPackageJson(Options options,
325324
FrontendDependenciesScanner frontendDependencies,
326325
ObjectNode packageJson) {
327-
NodeUpdater nodeUpdater = new NodeUpdater(frontendDependencies,
328-
options) {
326+
NodeUpdater nodeUpdater = new NodeUpdater(options) {
329327
@Override
330328
public void execute() {
331329
}
@@ -631,12 +629,11 @@ private static String getTag(
631629
}
632630

633631
public static boolean frontendImportsFound(JsonNode statsJson,
634-
Options options, FrontendDependenciesScanner frontendDependencies)
635-
throws IOException {
632+
Options options) throws IOException {
636633

637634
// Validate frontend requirements in flow-generated-imports.js
638635
final GenerateMainImports generateMainImports = new GenerateMainImports(
639-
frontendDependencies, options, statsJson);
636+
options, statsJson);
640637
generateMainImports.run();
641638
final List<String> imports = generateMainImports.getLines().stream()
642639
.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
@@ -197,8 +197,7 @@ public NodeTasks(Options options) {
197197
TaskUpdatePackages packageUpdater = null;
198198
if (options.isEnablePackagesUpdate()
199199
&& options.getJarFrontendResourcesFolder() != null) {
200-
packageUpdater = new TaskUpdatePackages(frontendDependencies,
201-
options);
200+
packageUpdater = new TaskUpdatePackages(options);
202201
commands.add(packageUpdater);
203202
}
204203

@@ -235,7 +234,7 @@ public NodeTasks(Options options) {
235234
// available)
236235
addEndpointServicesTasks(options);
237236

238-
commands.add(new TaskGenerateBootstrap(frontendDependencies, options));
237+
commands.add(new TaskGenerateBootstrap(options));
239238

240239
commands.add(new TaskGenerateFeatureFlags(options));
241240

@@ -273,7 +272,7 @@ public NodeTasks(Options options) {
273272
}
274273

275274
if (options.isEnableImportsUpdate()) {
276-
commands.add(new TaskUpdateImports(frontendDependencies, options));
275+
commands.add(new TaskUpdateImports(options));
277276

278277
commands.add(new TaskUpdateThemeImport(
279278
frontendDependencies.getThemeDefinition(), options));

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

Lines changed: 51 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,8 @@
4444
import com.vaadin.flow.internal.JacksonUtils;
4545
import com.vaadin.flow.internal.JsonDecodingException;
4646
import com.vaadin.flow.server.Constants;
47+
import com.vaadin.flow.server.PwaConfiguration;
4748
import com.vaadin.flow.server.frontend.scanner.ClassFinder;
48-
import com.vaadin.flow.server.frontend.scanner.FrontendDependencies;
49-
import com.vaadin.flow.server.frontend.scanner.FrontendDependenciesScanner;
5049

5150
import static com.vaadin.flow.server.Constants.PACKAGE_JSON;
5251
import static com.vaadin.flow.server.Constants.PACKAGE_LOCK_JSON;
@@ -93,12 +92,6 @@ public abstract class NodeUpdater implements FallibleCommand {
9392
static final String VAADIN_VERSION = "vaadinVersion";
9493
static final String PROJECT_FOLDER = "projectFolder";
9594

96-
/**
97-
* The {@link FrontendDependencies} object representing the application
98-
* dependencies.
99-
*/
100-
protected final FrontendDependenciesScanner frontDeps;
101-
10295
final ClassFinder finder;
10396

10497
boolean modified;
@@ -110,15 +103,11 @@ public abstract class NodeUpdater implements FallibleCommand {
110103
/**
111104
* Constructor.
112105
*
113-
* @param frontendDependencies
114-
* a reusable frontend dependencies
115106
* @param options
116107
* the task options
117108
*/
118-
protected NodeUpdater(FrontendDependenciesScanner frontendDependencies,
119-
Options options) {
109+
protected NodeUpdater(Options options) {
120110
this.finder = options.getClassFinder();
121-
this.frontDeps = frontendDependencies;
122111
this.options = options;
123112
}
124113

@@ -317,27 +306,38 @@ Map<String, String> getDefaultDependencies() {
317306
return dependencies;
318307
}
319308

320-
Map<String, String> readDependencies(String id, String packageJsonKey) {
309+
JsonNode readPackageJsonKey(String id, String packageJsonKey) {
310+
final JsonNode packageJson;
321311
try {
322-
Map<String, String> map = new HashMap<>();
323-
JsonNode dependencies = readPackageJson(id).get(packageJsonKey);
324-
if (dependencies == null) {
325-
log().error("Unable to find " + packageJsonKey + " from '" + id
326-
+ "'");
327-
return new HashMap<>();
328-
}
329-
for (String key : JacksonUtils.getKeys(dependencies)) {
330-
map.put(key, dependencies.get(key).textValue());
331-
}
332-
333-
return map;
312+
packageJson = readPackageJson(id);
334313
} catch (IOException e) {
335314
log().error(
336315
"Unable to read " + packageJsonKey + " from '" + id + "'",
337316
e);
317+
return null;
318+
}
319+
var value = packageJson.get(packageJsonKey);
320+
if (value == null) {
321+
log().error(
322+
"Unable to find " + packageJsonKey + " from '" + id + "'");
323+
}
324+
return value;
325+
}
326+
327+
Map<String, String> readDependencies(String id, String packageJsonKey) {
328+
Map<String, String> map = new HashMap<>();
329+
JsonNode dependencies = readPackageJsonKey(id, packageJsonKey);
330+
if (dependencies == null) {
331+
log().error(
332+
"Unable to find " + packageJsonKey + " from '" + id + "'");
338333
return new HashMap<>();
339334
}
340335

336+
for (String key : JacksonUtils.getKeys(dependencies)) {
337+
map.put(key, dependencies.get(key).textValue());
338+
}
339+
340+
return map;
341341
}
342342

343343
JsonNode readPackageJson(String id) throws IOException {
@@ -380,9 +380,34 @@ Map<String, String> getDefaultDevDependencies() {
380380
}
381381
}
382382

383+
// Add workbox dependencies only when PWA is enabled
384+
final PwaConfiguration pwaConfiguration = options
385+
.getFrontendDependenciesScanner().getPwaConfiguration();
386+
if (pwaConfiguration != null && pwaConfiguration.isOfflineEnabled()) {
387+
defaults.putAll(readDependencies("workbox", "devDependencies"));
388+
}
389+
383390
return defaults;
384391
}
385392

393+
ObjectNode getDefaultOverrides() {
394+
var overrides = JacksonUtils.createObjectNode();
395+
396+
final PwaConfiguration pwaConfiguration = options
397+
.getFrontendDependenciesScanner().getPwaConfiguration();
398+
// Currently, we only have overrides for workbox uses overrides, and
399+
// only when PWA is enabled
400+
final ObjectNode workboxOverrides = pwaConfiguration != null
401+
&& pwaConfiguration.isOfflineEnabled()
402+
? (ObjectNode) readPackageJsonKey("workbox",
403+
"overrides")
404+
: null;
405+
if (workboxOverrides != null) {
406+
overrides = overrides.setAll(workboxOverrides);
407+
}
408+
return overrides;
409+
}
410+
386411
/**
387412
* Updates default dependencies and development dependencies to
388413
* package.json.

0 commit comments

Comments
 (0)