Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
806a062
fix: move CSS imports from @Theme to bootstrap JS file
platosha Oct 15, 2025
a25d4c6
Merge branch 'main' into fix/theme-cssimport-bootstrap
platosha Oct 15, 2025
97ea2ca
chore: formatting
platosha Oct 16, 2025
00c7a57
test: unit test for app shell & theme css imports, fix existing cases
platosha Oct 17, 2025
9fb460a
Merge branch 'main' into fix/theme-cssimport-bootstrap
platosha Oct 17, 2025
83c5b06
fix: align byte-code and full scanner behavior for Theme CSS imports
platosha Oct 17, 2025
d3efd8d
test: update assertion for AppShell dependencies test
platosha Oct 17, 2025
e6f8dc0
test: update assertion in ApplicationThemeComponent test
platosha Oct 17, 2025
9a5f36c
test: restore embedded theme CSS include test assertion
platosha Oct 20, 2025
a3456d8
chore: remove loadCss flag
platosha Oct 21, 2025
8955283
chore: fix typo and formatting
platosha Oct 21, 2025
d51afa9
fix: take care of null pointer
platosha Oct 21, 2025
d8bfa48
Merge branch 'main' into fix/theme-cssimport-bootstrap
platosha Oct 21, 2025
6bfcaa7
Revert "chore: remove loadCss flag"
platosha Oct 22, 2025
5f75f8d
fix: rollback ignoring theme css in bundle analysis
platosha Oct 22, 2025
54b545b
fix: keep theme CSS import in generated web component imports
platosha Oct 22, 2025
1b38dba
Merge branch 'main' into fix/theme-cssimport-bootstrap
platosha Oct 22, 2025
9582762
fix: handle null map key
platosha Oct 22, 2025
d272f8b
fix: bug in generated web component imports implementation
platosha Oct 22, 2025
beff237
chore: formatting
platosha Oct 22, 2025
68503d5
fix: avoid duplicate lines in wc imports file
platosha Oct 23, 2025
e71afe8
Merge branch 'main' into fix/theme-cssimport-bootstrap
platosha Oct 23, 2025
9b23bc1
fix: write app-shell-imports.js info to stats.json for prod bundle
platosha Oct 23, 2025
44b1758
Merge branch 'main' into fix/theme-cssimport-bootstrap
platosha Oct 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ abstract class AbstractUpdateImports implements Runnable {
final File generatedFlowImports;
final File generatedFlowWebComponentImports;
private final File generatedFlowDefinitions;
final File appShellImports;
final File appShellDefinitions;
private File chunkFolder;

private final GeneratedFilesSupport generatedFilesSupport;
Expand All @@ -141,6 +143,12 @@ abstract class AbstractUpdateImports implements Runnable {
generatedFlowDefinitions = new File(
generatedFlowImports.getParentFile(),
FrontendUtils.IMPORTS_D_TS_NAME);
var generatedFolder = FrontendUtils
.getFrontendGeneratedFolder(options.getFrontendDirectory());
appShellImports = new File(generatedFolder,
FrontendUtils.APP_SHELL_IMPORTS_NAME);
appShellDefinitions = new File(generatedFolder,
FrontendUtils.APP_SHELL_IMPORTS_D_TS_NAME);

generatedFlowWebComponentImports = FrontendUtils
.getFlowGeneratedWebComponentsImports(
Expand All @@ -160,8 +168,8 @@ public void run() {

Map<File, List<String>> output = process(css, javascript);
writeOutput(output);
writeWebComponentImports(
filterWebComponentImports(output.get(generatedFlowImports)));
writeWebComponentImports(filterWebComponentImports(
mergeWebComponentOutputLines(output)));

getLogger().debug("Imports and chunks update took {} ms.",
TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start));
Expand Down Expand Up @@ -250,6 +258,17 @@ private void adaptCssInjectForWebComponent(ListIterator<String> iterator,
}
}

private List<String> mergeWebComponentOutputLines(
Map<File, List<String>> outputFiles) {
return Stream.concat(
outputFiles
.getOrDefault(appShellImports, Collections.emptyList())
.stream(),
outputFiles.getOrDefault(generatedFlowImports,
Collections.emptyList()).stream())
.distinct().toList();
}

private void writeWebComponentImports(List<String> lines) {
if (lines != null) {
try {
Expand All @@ -276,6 +295,7 @@ private void writeWebComponentImports(List<String> lines) {
private Map<File, List<String>> process(Map<ChunkInfo, List<CssData>> css,
Map<ChunkInfo, List<String>> javascript) {
getLogger().debug("Start sorting imports to lazy and eager.");
int cssLineOffset = 0;
long start = System.nanoTime();

Map<File, List<String>> files = new HashMap<>();
Expand All @@ -284,6 +304,7 @@ private Map<File, List<String>> process(Map<ChunkInfo, List<CssData>> css,
List<String> eagerJavascript = new ArrayList<>();
Map<ChunkInfo, List<String>> lazyCss = new LinkedHashMap<>();
List<CssData> eagerCssData = new ArrayList<>();
List<CssData> appShellCssData = new ArrayList<>();
for (Entry<ChunkInfo, List<String>> entry : javascript.entrySet()) {
if (isLazyRoute(entry.getKey())) {
lazyJavascript.put(entry.getKey(), entry.getValue());
Expand All @@ -296,12 +317,18 @@ private Map<File, List<String>> process(Map<ChunkInfo, List<CssData>> css,
boolean hasThemeFor = entry.getValue().stream()
.anyMatch(cssData -> cssData.getThemefor() != null);
if (isLazyRoute(entry.getKey()) && !hasThemeFor) {
List<String> cssLines = getCssLines(entry.getValue());
List<String> cssLines = getCssLines(entry.getValue(),
cssLineOffset);
cssLineOffset += cssLines.size();
if (!cssLines.isEmpty()) {
lazyCss.put(entry.getKey(), cssLines);
}
} else {
eagerCssData.addAll(entry.getValue());
if (entry.getKey().equals(ChunkInfo.APP_SHELL)) {
appShellCssData.addAll(entry.getValue());
} else {
eagerCssData.addAll(entry.getValue());
}
}
}

Expand Down Expand Up @@ -372,10 +399,22 @@ private Map<File, List<String>> process(Map<ChunkInfo, List<CssData>> css,
"const loadOnDemand = (key) => { return Promise.resolve(0); }");
}

List<String> appShellLines = new ArrayList<>();
List<String> appShellCssLines = getCssLines(appShellCssData,
cssLineOffset);
cssLineOffset += appShellCssLines.size();
if (!appShellCssLines.isEmpty()) {
appShellLines.add(IMPORT_INJECT);
appShellLines.addAll(appShellCssLines);
}
files.put(appShellImports, appShellLines);
files.put(appShellDefinitions, Collections.singletonList("export {}"));

List<String> mainLines = new ArrayList<>();

// Convert eager CSS data to JS and deduplicate it
List<String> mainCssLines = getCssLines(eagerCssData);
List<String> mainCssLines = getCssLines(eagerCssData, cssLineOffset);
cssLineOffset += mainCssLines.size();
if (!mainCssLines.isEmpty()) {
mainLines.add(IMPORT_INJECT);
mainLines.add(THEMABLE_MIXIN_IMPORT);
Expand Down Expand Up @@ -470,12 +509,12 @@ String resolveGeneratedModule(String module) {
* the CSS import data
* @return the JS statements needed to import and apply the CSS data
*/
protected List<String> getCssLines(List<CssData> css) {
private List<String> getCssLines(List<CssData> css, int startOffset) {
List<String> lines = new ArrayList<>();

Set<String> cssNotFound = new HashSet<>();
LinkedHashSet<CssData> allCss = new LinkedHashSet<>(css);
int i = 0;
int i = startOffset;
for (CssData cssData : allCss) {
if (!addCssLines(lines, cssData, i)) {
cssNotFound.add(cssData.getValue());
Expand Down Expand Up @@ -561,9 +600,9 @@ private Map<ChunkInfo, List<String>> mergeJavascript(

}

protected <T> List<String> merge(Map<T, List<String>> css) {
protected <T> List<String> merge(Map<T, List<String>> outputFiles) {
List<String> result = new ArrayList<>();
css.forEach((key, value) -> result.addAll(value));
outputFiles.forEach((key, value) -> result.addAll(value));
return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,18 @@ public class FrontendUtils {
public static final String THEME_IMPORTS_D_TS_NAME = "theme.d.ts";
public static final String THEME_IMPORTS_NAME = "theme.js";

/**
* The name of the file that contains application shell imports, such as
* style imports for the theme.
*/
public static final String APP_SHELL_IMPORTS_NAME = "app-shell-imports.js";

/**
* The TypeScript definitions for the
* {@link FrontendUtils#APP_SHELL_IMPORTS_NAME}
*/
public static final String APP_SHELL_IMPORTS_D_TS_NAME = "app-shell-imports.d.ts";

/**
* File name of the bootstrap file that is generated in frontend
* {@link #GENERATED} folder. The bootstrap file is always executed in a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ private String getIndexTsEntryPath() {

private Collection<String> getThemeLines() {
Collection<String> lines = new ArrayList<>();
lines.add("import './app-shell-imports.js';");
ThemeDefinition themeDef = frontDeps.getThemeDefinition();
if (themeDef != null && !"".equals(themeDef.getName())) {
lines.add("import './theme-" + themeDef.getName()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,20 @@
* loaded immediately when the JS bundle is loaded while chunks marked as not
* eager (i.e. lazy) are loaded on demand later.
* <p>
* There is one special, global chunk, defined as {@link #GLOBAL} in this class,
* There is a special application shell chunk, defined as {@link #APP_SHELL} in
* this class, which is used for gathering all data that relates to the
* application shell.
* <p>
* There is a special global chunk, defined as {@link #GLOBAL} in this class,
* which is used for gathering all data that relates to internal entry points.
* <p>
* For internal use only. May be renamed or removed in a future release.
**/
public class ChunkInfo {

public static final ChunkInfo APP_SHELL = new ChunkInfo(
EntryPointType.INTERNAL, null, null, true);

public static final ChunkInfo GLOBAL = new ChunkInfo(
EntryPointType.INTERNAL, null, null, false);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@
* For internal use only. May be renamed or removed in a future release.
*/
public enum EntryPointType {
ROUTE, WEB_COMPONENT, INTERNAL;
ROUTE, WEB_COMPONENT, INTERNAL, APP_SHELL;
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,8 @@ public FrontendDependencies(ClassFinder finder,
if (themeDefinition != null && themeDefinition.getTheme() != null) {
Class<? extends AbstractTheme> themeClass = themeDefinition
.getTheme();
if (!visitedClasses.containsKey(themeClass.getName())) {
addInternalEntryPoint(themeClass);
visitEntryPoint(entryPoints.get(themeClass.getName()));
visitedClasses.get(themeClass.getName()).loadCss = true;
}
addAppShellEntryPoint(themeClass);
visitEntryPoint(entryPoints.get(themeClass.getName()));
}
if (reactEnabled) {
computeReactClasses(finder);
Expand Down Expand Up @@ -186,14 +183,17 @@ private void computeReactClasses(ClassFinder finder) throws IOException {
}

private void aggregateEntryPointInformation() {
var activeTheme = themeDefinition != null
&& themeDefinition.getTheme() != null;
for (Entry<String, EntryPointData> entry : entryPoints.entrySet()) {
EntryPointData entryPoint = entry.getValue();
for (String className : entryPoint.reachableClasses) {
ClassInfo classInfo = visitedClasses.get(className);
entryPoint.getModules().addAll(classInfo.modules);
entryPoint.getModulesDevelopmentOnly()
.addAll(classInfo.modulesDevelopmentOnly);
if (classInfo.loadCss) {
if (classInfo.loadCss || activeTheme
&& entryPoint.getType() == EntryPointType.APP_SHELL) {
entryPoint.getCss().addAll(classInfo.css);
}
entryPoint.getScripts().addAll(classInfo.scripts);
Expand Down Expand Up @@ -322,6 +322,9 @@ public Map<ChunkInfo, List<String>> getModulesDevelopment() {
}

private ChunkInfo getChunkInfo(EntryPointData data) {
if (data.getType() == EntryPointType.APP_SHELL) {
return ChunkInfo.APP_SHELL;
}
if (data.getType() == EntryPointType.INTERNAL) {
return ChunkInfo.GLOBAL;
}
Expand Down Expand Up @@ -458,7 +461,7 @@ private void collectEntryPoints(boolean generateEmbeddableWebComponents)

for (Class<?> appShell : getFinder().getSubTypesOf(
getFinder().loadClass(AppShellConfigurator.class.getName()))) {
addInternalEntryPoint(appShell);
addAppShellEntryPoint(appShell);
}

try {
Expand Down Expand Up @@ -547,6 +550,10 @@ private List<String> getDependencyTriggers(Class<?> route,
return null;
}

private void addAppShellEntryPoint(Class<?> entryPointClass) {
addEntryPoint(entryPointClass, EntryPointType.APP_SHELL, null, true);
}

private void addInternalEntryPoint(Class<?> entryPointClass) {
addEntryPoint(entryPointClass, EntryPointType.INTERNAL, null, true);
}
Expand Down Expand Up @@ -631,9 +638,6 @@ private void computeApplicationTheme() throws ClassNotFoundException,
themeDefinition = new ThemeDefinition(theme, variant,
themeName);
themeInstance = new ThemeWrapper(theme);
classesWithTheme.get(themeData).children.stream()
.map(visitedClasses::get).filter(Objects::nonNull)
.forEach(classInfo -> classInfo.loadCss = true);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class FullDependenciesScanner extends AbstractDependenciesScanner {
private Map<String, String> devPackages;
private HashMap<String, List<String>> assets = new HashMap<>();
private HashMap<String, List<String>> devAssets = new HashMap<>();
private List<CssData> themeCssData;
private List<CssData> cssData;
private List<String> scripts;
private List<String> scriptsDevelopment;
Expand Down Expand Up @@ -150,7 +151,10 @@ class FullDependenciesScanner extends AbstractDependenciesScanner {

collectScripts(modulesSet, modulesSetDevelopment, JsModule.class);
collectScripts(scriptsSet, scriptsSetDevelopment, JavaScript.class);
cssData = discoverCss();

themeCssData = new ArrayList<>();
cssData = new ArrayList<>();
discoverCss();

if (!reactEnabled) {
modulesSet.removeIf(
Expand Down Expand Up @@ -214,8 +218,9 @@ public Map<ChunkInfo, List<String>> getScriptsDevelopment() {

@Override
public Map<ChunkInfo, List<CssData>> getCss() {
return Collections.singletonMap(ChunkInfo.GLOBAL,
new ArrayList<>(cssData));
// Map theme CSS to the APP_SHELL chunk
return Map.ofEntries(Map.entry(ChunkInfo.APP_SHELL, themeCssData),
Map.entry(ChunkInfo.GLOBAL, cssData));
}

@Override
Expand Down Expand Up @@ -307,27 +312,34 @@ private void discoverPackages(final Map<String, String> packages,
}
}

private List<CssData> discoverCss() {
private void discoverCss() {
try {
Class<? extends Annotation> loadedAnnotation = getFinder()
.loadClass(CssImport.class.getName());
Set<Class<?>> annotatedClasses = getFinder()
.getAnnotatedClasses(loadedAnnotation);
LinkedHashSet<CssData> result = new LinkedHashSet<>();
var themeCss = new LinkedHashSet<CssData>();
var globalCss = new LinkedHashSet<CssData>();
for (Class<?> clazz : annotatedClasses) {
classes.add(clazz.getName());
if (AbstractTheme.class.isAssignableFrom(clazz)
&& (themeDefinition == null
|| !clazz.equals(themeDefinition.getTheme()))) {
var isAppShellClass = AppShellConfigurator.class
.isAssignableFrom(clazz);
var isThemeClass = AbstractTheme.class.isAssignableFrom(clazz);
if (isThemeClass && (themeDefinition == null
|| !clazz.equals(themeDefinition.getTheme()))) {
// Do not add css from all found theme classes,
// only defined theme.
continue;
}
List<? extends Annotation> imports = annotationFinder
.apply(clazz, loadedAnnotation);
imports.stream().forEach(imp -> result.add(createCssData(imp)));
imports.stream()
.forEach(imp -> ((isAppShellClass || isThemeClass)
? themeCss
: globalCss).add(createCssData(imp)));
}
return new ArrayList<>(result);
themeCssData.addAll(themeCss);
cssData.addAll(globalCss);
} catch (ClassNotFoundException exception) {
throw new IllegalStateException(
COULD_NOT_LOAD_ERROR_MSG + CssData.class.getName(),
Expand Down
4 changes: 4 additions & 0 deletions flow-server/src/main/resources/vite.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,10 @@ function statsExtracterPlugin(): PluginOption {
path.resolve(themeOptions.frontendGeneratedFolder, 'flow', 'generated-flow-imports.js'),
generatedImportsSet
);
parseImports(
path.resolve(themeOptions.frontendGeneratedFolder, 'app-shell-imports.js'),
generatedImportsSet
);
const generatedImports = Array.from(generatedImportsSet).sort();

const frontendFiles: Record<string, string> = {};
Expand Down
Loading
Loading