Migrate to Spring Boot 4 (#868)

* Switched from TomCat to Jetty
* Hibernate migrations
* Removed dependency on Spring-Boot-Content-FS
* Migrate to Jackson 3
* Migrate LegacyExtensionFinder -> IndexedExtensionFinder
* Fix code inspection issues
* Exclude Config classes from Sonar coverage calcualtion
* Add FileStorageServiceTest
* Add tests for (De-)serializers
* Exclude H2 package from Sonar coverage reporting
* Add Sonar scan
* Update JVM in CI
* Update dependency versions
This commit is contained in:
grimsi
2026-02-01 10:48:26 +01:00
parent 111e164fab
commit ecd369cd30
103 changed files with 3562 additions and 2928 deletions
+22 -163
View File
@@ -29,16 +29,13 @@ import checker from 'vite-plugin-checker';
import postcssLit from './build/plugins/rollup-plugin-postcss-lit-custom/rollup-plugin-postcss-lit.js';
import vaadinI18n from './build/plugins/rollup-plugin-vaadin-i18n/rollup-plugin-vaadin-i18n.js';
import serviceWorkerPlugin from './build/plugins/vite-plugin-service-worker';
import { createRequire } from 'module';
import vaadinBundlesPlugin from './build/plugins/vite-plugin-vaadin-bundles';
import { visualizer } from 'rollup-plugin-visualizer';
import reactPlugin from '@vitejs/plugin-react';
import vitePluginFileSystemRouter from '@vaadin/hilla-file-router/vite-plugin.js';
// Make `require` compatible with ES modules
const require = createRequire(import.meta.url);
import vitePluginFileSystemRouter from '@vaadin/hilla-file-router/vite-plugin.js';
const frontendFolder = path.resolve(__dirname, settings.frontendFolder);
const themeFolder = path.resolve(frontendFolder, settings.themeFolder);
@@ -78,14 +75,16 @@ const themeOptions = {
projectStaticAssetsOutputFolder: devBundle
? path.resolve(devBundleFolder, '../assets')
: path.resolve(__dirname, settings.staticOutput),
frontendGeneratedFolder: path.resolve(frontendFolder, settings.generatedFolder)
frontendGeneratedFolder: path.resolve(frontendFolder, settings.generatedFolder),
projectStaticOutput: path.resolve(__dirname, settings.staticOutput),
javaResourceFolder: settings.javaResourceFolder ? path.resolve(__dirname, settings.javaResourceFolder) : ''
};
const hasExportedWebComponents = existsSync(path.resolve(frontendFolder, 'web-component.html'));
const commercialBannerComponent = path.resolve(frontendFolder, settings.generatedFolder, 'commercial-banner.js');
const hasCommercialBanner = existsSync(commercialBannerComponent);
const target = ['safari15', 'es2022'];
const target = ['es2023'];
// Block debug and trace logs.
console.trace = () => {};
@@ -183,6 +182,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> = {};
@@ -303,159 +306,6 @@ function statsExtracterPlugin(): PluginOption {
}
};
}
function vaadinBundlesPlugin(): PluginOption {
type ExportInfo =
| string
| {
namespace?: string;
source: string;
};
type ExposeInfo = {
exports: ExportInfo[];
};
type PackageInfo = {
version: string;
exposes: Record<string, ExposeInfo>;
};
type BundleJson = {
packages: Record<string, PackageInfo>;
};
const disabledMessage = 'Vaadin component dependency bundles are disabled.';
const modulesDirectory = nodeModulesFolder.replace(/\\/g, '/');
let vaadinBundleJson: BundleJson;
function parseModuleId(id: string): { packageName: string; modulePath: string } {
const [scope, scopedPackageName] = id.split('/', 3);
const packageName = scope.startsWith('@') ? `${scope}/${scopedPackageName}` : scope;
const modulePath = `.${id.substring(packageName.length)}`;
return {
packageName,
modulePath
};
}
function getExports(id: string): string[] | undefined {
const { packageName, modulePath } = parseModuleId(id);
const packageInfo = vaadinBundleJson.packages[packageName];
if (!packageInfo) return;
const exposeInfo: ExposeInfo = packageInfo.exposes[modulePath];
if (!exposeInfo) return;
const exportsSet = new Set<string>();
for (const e of exposeInfo.exports) {
if (typeof e === 'string') {
exportsSet.add(e);
} else {
const { namespace, source } = e;
if (namespace) {
exportsSet.add(namespace);
} else {
const sourceExports = getExports(source);
if (sourceExports) {
sourceExports.forEach((e) => exportsSet.add(e));
}
}
}
}
return Array.from(exportsSet);
}
function getExportBinding(binding: string) {
return binding === 'default' ? '_default as default' : binding;
}
function getImportAssigment(binding: string) {
return binding === 'default' ? 'default: _default' : binding;
}
return {
name: 'vaadin:bundles',
enforce: 'pre',
apply(config, { command }) {
if (command !== 'serve') return false;
try {
const vaadinBundleJsonPath = require.resolve('@vaadin/bundles/vaadin-bundle.json');
vaadinBundleJson = JSON.parse(readFileSync(vaadinBundleJsonPath, { encoding: 'utf8' }));
} catch (e: unknown) {
if (typeof e === 'object' && (e as { code: string }).code === 'MODULE_NOT_FOUND') {
vaadinBundleJson = { packages: {} };
console.info(`@vaadin/bundles npm package is not found, ${disabledMessage}`);
return false;
} else {
throw e;
}
}
const versionMismatches: Array<{ name: string; bundledVersion: string; installedVersion: string }> = [];
for (const [name, packageInfo] of Object.entries(vaadinBundleJson.packages)) {
let installedVersion: string | undefined = undefined;
try {
const { version: bundledVersion } = packageInfo;
const installedPackageJsonFile = path.resolve(modulesDirectory, name, 'package.json');
const packageJson = JSON.parse(readFileSync(installedPackageJsonFile, { encoding: 'utf8' }));
installedVersion = packageJson.version;
if (installedVersion && installedVersion !== bundledVersion) {
versionMismatches.push({
name,
bundledVersion,
installedVersion
});
}
} catch (_) {
// ignore package not found
}
}
if (versionMismatches.length) {
console.info(`@vaadin/bundles has version mismatches with installed packages, ${disabledMessage}`);
console.info(`Packages with version mismatches: ${JSON.stringify(versionMismatches, undefined, 2)}`);
vaadinBundleJson = { packages: {} };
return false;
}
return true;
},
async config(config) {
return mergeConfig(
{
optimizeDeps: {
exclude: [
// Vaadin bundle
'@vaadin/bundles',
...Object.keys(vaadinBundleJson.packages),
'@vaadin/vaadin-material-styles'
]
}
},
config
);
},
load(rawId) {
const [path, params] = rawId.split('?');
if (!path.startsWith(modulesDirectory)) return;
const id = path.substring(modulesDirectory.length + 1);
const bindings = getExports(id);
if (bindings === undefined) return;
const cacheSuffix = params ? `?${params}` : '';
const bundlePath = `@vaadin/bundles/vaadin.js${cacheSuffix}`;
return `import { init as VaadinBundleInit, get as VaadinBundleGet } from '${bundlePath}';
await VaadinBundleInit('default');
const { ${bindings.map(getImportAssigment).join(', ')} } = (await VaadinBundleGet('./node_modules/${id}'))();
export { ${bindings.map(getExportBinding).join(', ')} };`;
}
};
}
function themePlugin(opts: { devMode: boolean }): PluginOption {
const fullThemeOptions = { ...themeOptions, devMode: opts.devMode };
@@ -563,9 +413,12 @@ function preserveUsageStats() {
transform(src: string, id: string) {
if (id.includes('vaadin-usage-statistics')) {
if (src.includes('vaadin-dev-mode:start')) {
const newSrc = src.replace(DEV_MODE_START_REGEXP, '/*! vaadin-dev-mode:start');
const expectedComment = '/*! vaadin-dev-mode:start';
const newSrc = src.replace(DEV_MODE_START_REGEXP, expectedComment);
if (newSrc === src) {
console.error('Comment replacement failed to change anything');
if (!src.includes(expectedComment)) {
console.error('vaadin-dev-mode:start tag not found');
}
} else if (!newSrc.match(DEV_MODE_CODE_REGEXP)) {
console.error('New comment fails to match original regexp');
} else {
@@ -612,6 +465,9 @@ export const vaadinConfig: UserConfigFn = (env) => {
allow: allowedFrontendFolders
}
},
esbuild: {
legalComments: 'inline',
},
build: {
minify: productionMode,
outDir: buildOutputFolder,
@@ -668,7 +524,9 @@ export const vaadinConfig: UserConfigFn = (env) => {
},
plugins: [
productionMode && brotli(),
devMode && vaadinBundlesPlugin(),
devMode && vaadinBundlesPlugin({
nodeModulesFolder
}),
devMode && showRecompileReason(),
settings.offlineEnabled && serviceWorkerPlugin({
srcPath: settings.clientServiceWorkerSource,
@@ -714,6 +572,7 @@ export const vaadinConfig: UserConfigFn = (env) => {
].filter(Boolean)
}
}),
productionMode && vaadinI18n({
cwd: __dirname,
meta: {