From 72e6493873db32140bd07a54d315cbca769259df Mon Sep 17 00:00:00 2001 From: Marco Collovati Date: Tue, 3 Sep 2024 15:22:01 +0200 Subject: [PATCH] fix: use quarkus compatible classloader (CP: 2.0) (#166) * fix: use quarkus compatible classloader Use a classLoader that is compatible with quarkus for default i18N translation. part of vaadin/flow#18977 * fix theme pom --------- Co-authored-by: Mikael Grankvist --- .../flow/quarkus/it/i18n/TranslationView.java | 94 +++ .../vaadin-i18n/translations.properties | 1 + .../vaadin-i18n/translations_de.properties | 1 + .../vaadin-i18n/translations_fi_FI.properties | 1 + .../vaadin-i18n/translations_fr_FR.properties | 1 + .../vaadin-i18n/translations_ja_JP.properties | 1 + .../vaadin/flow/quarkus/it/TranslationIT.java | 74 ++ .../production/vite.generated.ts | 639 ------------------ integration-tests/reusable-theme/pom.xml | 2 +- pom.xml | 2 +- .../quarkus/QuarkusInstantiatorFactory.java | 9 +- 11 files changed, 182 insertions(+), 643 deletions(-) create mode 100644 integration-tests/common-test-code/src/main/java/com/vaadin/flow/quarkus/it/i18n/TranslationView.java create mode 100644 integration-tests/common-test-code/src/main/resources/vaadin-i18n/translations.properties create mode 100644 integration-tests/common-test-code/src/main/resources/vaadin-i18n/translations_de.properties create mode 100644 integration-tests/common-test-code/src/main/resources/vaadin-i18n/translations_fi_FI.properties create mode 100644 integration-tests/common-test-code/src/main/resources/vaadin-i18n/translations_fr_FR.properties create mode 100644 integration-tests/common-test-code/src/main/resources/vaadin-i18n/translations_ja_JP.properties create mode 100644 integration-tests/common-test-code/src/test/java/com/vaadin/flow/quarkus/it/TranslationIT.java delete mode 100644 integration-tests/production/vite.generated.ts diff --git a/integration-tests/common-test-code/src/main/java/com/vaadin/flow/quarkus/it/i18n/TranslationView.java b/integration-tests/common-test-code/src/main/java/com/vaadin/flow/quarkus/it/i18n/TranslationView.java new file mode 100644 index 0000000..45e73d3 --- /dev/null +++ b/integration-tests/common-test-code/src/main/java/com/vaadin/flow/quarkus/it/i18n/TranslationView.java @@ -0,0 +1,94 @@ +/* + * Copyright 2000-2024 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.flow.quarkus.it.i18n; + +import java.util.Locale; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +import com.vaadin.flow.component.AttachEvent; +import com.vaadin.flow.component.UI; +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.html.Span; +import com.vaadin.flow.i18n.I18NProvider; +import com.vaadin.flow.internal.LocaleUtil; +import com.vaadin.flow.router.Route; + +@Route(value = "translations") +public class TranslationView extends Div { + + public static final String TEST_VIEW_ID = "TranslationView"; + public static final String LOCALES_ID = "available-locales"; + + private Span dynamic; + + public TranslationView() { + setId(TEST_VIEW_ID); + + Span defaultLang = new Span(getTranslation("label", Locale.ENGLISH)); + defaultLang.setId("english"); + Span german = new Span(getTranslation("label", Locale.GERMAN)); + german.setId("german"); + Span germany = new Span(getTranslation("label", Locale.GERMANY)); + germany.setId("germany"); + Span finnish = new Span( + getTranslation("label", new Locale("fi", "FI"))); + finnish.setId("finnish"); + Span french = new Span(getTranslation("label", Locale.FRANCE)); + french.setId("french"); + Span japanese = new Span(getTranslation("label", Locale.JAPAN)); + japanese.setId("japanese"); + + Optional i18NProvider = LocaleUtil.getI18NProvider(); + if (i18NProvider.isPresent()) { + add(new Span("Available translation locales:")); + StringBuilder locales = new StringBuilder(); + for (Locale locale : i18NProvider.get().getProvidedLocales()) { + if (locales.length() > 0) { + locales.append(", "); + } + locales.append(locale.toString()); + } + Span localeSpan = new Span(locales.toString()); + localeSpan.setId(LOCALES_ID); + add(localeSpan, new Div()); + } + dynamic = new Span("waiting"); + dynamic.setId("dynamic"); + + add(defaultLang, new Div(), german, new Div(), germany, new Div(), + finnish, new Div(), french, new Div(), japanese, new Div(), + dynamic); + } + + @Override + protected void onAttach(AttachEvent event) { + UI ui = event.getUI(); + ui.setPollInterval(100); + CompletableFuture.runAsync(() -> { + try { + Thread.sleep(50); + } catch (Exception e) { + e.printStackTrace(); + } finally { + ui.access(() -> dynamic + .setText(getTranslation("label", Locale.FRANCE))); + ui.setPollInterval(-1); + } + }); + + } +} diff --git a/integration-tests/common-test-code/src/main/resources/vaadin-i18n/translations.properties b/integration-tests/common-test-code/src/main/resources/vaadin-i18n/translations.properties new file mode 100644 index 0000000..d8e29a9 --- /dev/null +++ b/integration-tests/common-test-code/src/main/resources/vaadin-i18n/translations.properties @@ -0,0 +1 @@ +label=Default diff --git a/integration-tests/common-test-code/src/main/resources/vaadin-i18n/translations_de.properties b/integration-tests/common-test-code/src/main/resources/vaadin-i18n/translations_de.properties new file mode 100644 index 0000000..5afe465 --- /dev/null +++ b/integration-tests/common-test-code/src/main/resources/vaadin-i18n/translations_de.properties @@ -0,0 +1 @@ +label=Deutsch diff --git a/integration-tests/common-test-code/src/main/resources/vaadin-i18n/translations_fi_FI.properties b/integration-tests/common-test-code/src/main/resources/vaadin-i18n/translations_fi_FI.properties new file mode 100644 index 0000000..38e68f8 --- /dev/null +++ b/integration-tests/common-test-code/src/main/resources/vaadin-i18n/translations_fi_FI.properties @@ -0,0 +1 @@ +label=Suomi diff --git a/integration-tests/common-test-code/src/main/resources/vaadin-i18n/translations_fr_FR.properties b/integration-tests/common-test-code/src/main/resources/vaadin-i18n/translations_fr_FR.properties new file mode 100644 index 0000000..ef14ff6 --- /dev/null +++ b/integration-tests/common-test-code/src/main/resources/vaadin-i18n/translations_fr_FR.properties @@ -0,0 +1 @@ +label=français diff --git a/integration-tests/common-test-code/src/main/resources/vaadin-i18n/translations_ja_JP.properties b/integration-tests/common-test-code/src/main/resources/vaadin-i18n/translations_ja_JP.properties new file mode 100644 index 0000000..16d1921 --- /dev/null +++ b/integration-tests/common-test-code/src/main/resources/vaadin-i18n/translations_ja_JP.properties @@ -0,0 +1 @@ +label=日本語 diff --git a/integration-tests/common-test-code/src/test/java/com/vaadin/flow/quarkus/it/TranslationIT.java b/integration-tests/common-test-code/src/test/java/com/vaadin/flow/quarkus/it/TranslationIT.java new file mode 100644 index 0000000..02b2744 --- /dev/null +++ b/integration-tests/common-test-code/src/test/java/com/vaadin/flow/quarkus/it/TranslationIT.java @@ -0,0 +1,74 @@ +/* + * Copyright 2000-2024 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.flow.quarkus.it; + +import io.quarkus.test.junit.QuarkusIntegrationTest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import com.vaadin.flow.component.html.testbench.SpanElement; +import com.vaadin.flow.quarkus.it.i18n.TranslationView; +import com.vaadin.flow.test.AbstractChromeIT; + +@QuarkusIntegrationTest +public class TranslationIT extends AbstractChromeIT { + @Override + protected String getTestPath() { + return "/translations"; + } + + @Test + public void translationFilesExist_defaultI18NInstantiated_languagesWork() { + open(); + + String locales = $(SpanElement.class).id(TranslationView.LOCALES_ID) + .getText(); + Assertions.assertTrue(locales.contains("de"), + "Couldn't verify German locale"); + Assertions.assertTrue(locales.contains("fi_FI"), + "Couldn't verify Finnish locale"); + Assertions.assertTrue(locales.contains("fr_FR"), + "Couldn't verify French locale"); + Assertions.assertTrue(locales.contains("ja_JP"), + "Couldn't verify Japanese locale"); + + Assertions.assertEquals("Default", + $(SpanElement.class).id("english").getText()); + Assertions.assertEquals("Deutsch", + $(SpanElement.class).id("german").getText()); + Assertions.assertEquals("Deutsch", + $(SpanElement.class).id("germany").getText()); + Assertions.assertEquals("Suomi", + $(SpanElement.class).id("finnish").getText()); + Assertions.assertEquals("français", + $(SpanElement.class).id("french").getText()); + Assertions.assertEquals("日本語", + $(SpanElement.class).id("japanese").getText()); + } + + @Test + public void translationFilesExist_defaultI18NInstantiated_updateFromExternalThreadWorks() { + open(); + + waitUntilNot(driver -> $(SpanElement.class).id("dynamic").getText() + .equals("waiting")); + + Assertions.assertEquals("français", + $(SpanElement.class).id("dynamic").getText(), + "Dynamic update from thread should have used correct bundle."); + } +} diff --git a/integration-tests/production/vite.generated.ts b/integration-tests/production/vite.generated.ts deleted file mode 100644 index 0c90ddf..0000000 --- a/integration-tests/production/vite.generated.ts +++ /dev/null @@ -1,639 +0,0 @@ -/** - * NOTICE: this is an auto-generated file - * - * This file has been generated by the `flow:prepare-frontend` maven goal. - * This file will be overwritten on every run. Any custom changes should be made to vite.config.ts - */ -import path from 'path'; -import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'fs'; -import * as net from 'net'; - -import { processThemeResources } from './target/plugins/application-theme-plugin/theme-handle.js'; -import { rewriteCssUrls } from './target/plugins/theme-loader/theme-loader-utils.js'; -import settings from './target/vaadin-dev-server-settings.json'; -import { defineConfig, mergeConfig, PluginOption, ResolvedConfig, UserConfigFn, OutputOptions, AssetInfo, ChunkInfo } from 'vite'; -import { getManifest } from 'workbox-build'; - -import * as rollup from 'rollup'; -import brotli from 'rollup-plugin-brotli'; -import replace from '@rollup/plugin-replace'; -import checker from 'vite-plugin-checker'; -import postcssLit from './target/plugins/rollup-plugin-postcss-lit-custom/rollup-plugin-postcss-lit.js'; - -const appShellUrl = '.'; - -const frontendFolder = path.resolve(__dirname, settings.frontendFolder); -const themeFolder = path.resolve(frontendFolder, settings.themeFolder); -const statsFolder = path.resolve(__dirname, settings.statsOutput); -const frontendBundleFolder = path.resolve(__dirname, settings.frontendBundleOutput); -const jarResourcesFolder = path.resolve(__dirname, settings.jarResourcesFolder); -const generatedFlowImportsFolder = path.resolve(__dirname, settings.generatedFlowImportsFolder); -const themeResourceFolder = path.resolve(__dirname, settings.themeResourceFolder); - -const statsFile = path.resolve(statsFolder, 'stats.json'); - -const projectStaticAssetsFolders = [ - path.resolve(__dirname, 'src', 'main', 'resources', 'META-INF', 'resources'), - path.resolve(__dirname, 'src', 'main', 'resources', 'static'), - frontendFolder -]; - -// Folders in the project which can contain application themes -const themeProjectFolders = projectStaticAssetsFolders.map((folder) => path.resolve(folder, settings.themeFolder)); - -const themeOptions = { - devMode: false, - // The following matches folder 'frontend/generated/themes/' - // (not 'frontend/themes') for theme in JAR that is copied there - themeResourceFolder: path.resolve(themeResourceFolder, settings.themeFolder), - themeProjectFolders: themeProjectFolders, - projectStaticAssetsOutputFolder: path.resolve(__dirname, settings.staticOutput), - frontendGeneratedFolder: path.resolve(frontendFolder, settings.generatedFolder) -}; - -const hasExportedWebComponents = existsSync(path.resolve(frontendFolder, 'web-component.html')); - -// Block debug and trace logs. -console.trace = () => {}; -console.debug = () => {}; - -function injectManifestToSWPlugin(): rollup.Plugin { - const rewriteManifestIndexHtmlUrl = (manifest) => { - const indexEntry = manifest.find((entry) => entry.url === 'index.html'); - if (indexEntry) { - indexEntry.url = appShellUrl; - } - - return { manifest, warnings: [] }; - }; - - return { - name: 'vaadin:inject-manifest-to-sw', - async transform(code, id) { - if (/sw\.(ts|js)$/.test(id)) { - const { manifestEntries } = await getManifest({ - globDirectory: frontendBundleFolder, - globPatterns: ['**/*'], - globIgnores: ['**/*.br'], - manifestTransforms: [rewriteManifestIndexHtmlUrl], - maximumFileSizeToCacheInBytes: 100 * 1024 * 1024, // 100mb, - }); - - return code.replace('self.__WB_MANIFEST', JSON.stringify(manifestEntries)); - } - } - } -} - -function buildSWPlugin(opts): PluginOption { - let config: ResolvedConfig; - const devMode = opts.devMode; - - const swObj = {} - - async function build(action: 'generate' | 'write', additionalPlugins: rollup.Plugin[] = []) { - const includedPluginNames = [ - 'alias', - 'vite:resolve', - 'vite:esbuild', - 'rollup-plugin-dynamic-import-variables', - 'vite:esbuild-transpile', - 'vite:terser', - ] - const plugins: rollup.Plugin[] = config.plugins.filter((p) => { - return includedPluginNames.includes(p.name) - }); - plugins.push( - replace({ - values: { - 'process.env.NODE_ENV': JSON.stringify(config.mode), - ...config.define, - }, - preventAssignment: true - }) - ); - if (additionalPlugins) { - plugins.push(...additionalPlugins); - } - const bundle = await rollup.rollup({ - input: path.resolve(settings.clientServiceWorkerSource), - plugins - }); - - try { - return await bundle[action]({ - file: path.resolve(frontendBundleFolder, 'sw.js'), - format: 'es', - exports: 'none', - sourcemap: config.command === 'serve' || config.build.sourcemap, - inlineDynamicImports: true, - }); - } finally { - await bundle.close(); - } - } - - return { - name: 'vaadin:build-sw', - enforce: 'post', - async configResolved(resolvedConfig) { - config = resolvedConfig; - }, - async buildStart() { - if (devMode) { - const { output } = await build('generate'); - swObj.code = output[0].code; - swObj.map = output[0].map; - } - }, - async load(id) { - if (id.endsWith('sw.js')) { - return ''; - } - }, - async transform(_code, id) { - if (id.endsWith('sw.js')) { - return swObj; - } - }, - async closeBundle() { - await build('write', [ - injectManifestToSWPlugin(), - brotli(), - ]); - } - } -} - -function statsExtracterPlugin(): PluginOption { - return { - name: 'vaadin:stats', - enforce: 'post', - async writeBundle(options: OutputOptions, bundle: { [fileName: string]: AssetInfo | ChunkInfo }) { - const modules = Object.values(bundle).flatMap((b) => (b.modules ? Object.keys(b.modules) : [])); - const nodeModulesFolders = modules.filter((id) => id.includes('node_modules')); - const npmModules = nodeModulesFolders - .map((id) => id.replace(/.*node_modules./, '')) - .map((id) => { - const parts = id.split('/'); - if (id.startsWith('@')) { - return parts[0] + '/' + parts[1]; - } else { - return parts[0]; - } - }) - .sort() - .filter((value, index, self) => self.indexOf(value) === index); - - mkdirSync(path.dirname(statsFile), { recursive: true }); - writeFileSync(statsFile, JSON.stringify({ npmModules }, null, 1)); - } - }; -} -function vaadinBundlesPlugin(): PluginOption { - type ExportInfo = - | string - | { - namespace?: string; - source: string; - }; - - type ExposeInfo = { - exports: ExportInfo[]; - }; - - type PackageInfo = { - version: string; - exposes: Record; - }; - - type BundleJson = { - packages: Record; - }; - - const disabledMessage = 'Vaadin component dependency bundles are disabled.'; - - const modulesDirectory = path.resolve(__dirname, 'node_modules').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(); - 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) - ] - } - }, - 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): PluginOption { - const fullThemeOptions = {...themeOptions, devMode: opts.devMode }; - return { - name: 'vaadin:theme', - config() { - processThemeResources(fullThemeOptions, console); - }, - configureServer(server) { - function handleThemeFileCreateDelete(themeFile, stats) { - if (themeFile.startsWith(themeFolder)) { - const changed = path.relative(themeFolder, themeFile) - console.debug('Theme file ' + (!!stats ? 'created' : 'deleted'), changed); - processThemeResources(fullThemeOptions, console); - } - } - server.watcher.on('add', handleThemeFileCreateDelete); - server.watcher.on('unlink', handleThemeFileCreateDelete); - }, - handleHotUpdate(context) { - const contextPath = path.resolve(context.file); - const themePath = path.resolve(themeFolder); - if (contextPath.startsWith(themePath)) { - const changed = path.relative(themePath, contextPath); - - console.debug('Theme file changed', changed); - - if (changed.startsWith(settings.themeName)) { - processThemeResources(fullThemeOptions, console); - } - } - }, - async resolveId(id, importer) { - // force theme generation if generated theme sources does not yet exist - // this may happen for example during Java hot reload when updating - // @Theme annotation value - if (path.resolve(themeOptions.frontendGeneratedFolder, "theme.js") === importer && - !existsSync(path.resolve(themeOptions.frontendGeneratedFolder, id))) { - console.debug('Generate theme file ' + id + ' not existing. Processing theme resource'); - processThemeResources(fullThemeOptions, console); - return; - } - if (!id.startsWith(settings.themeFolder)) { - return; - } - - for (const location of [themeResourceFolder, frontendFolder]) { - const result = await this.resolve(path.resolve(location, id)); - if (result) { - return result; - } - } - }, - async transform(raw, id, options) { - // rewrite urls for the application theme css files - const [bareId, query] = id.split('?'); - if (!bareId?.startsWith(themeFolder) || !bareId?.endsWith('.css')) { - return; - } - const [themeName] = bareId.substring(themeFolder.length + 1).split('/'); - return rewriteCssUrls(raw, path.dirname(bareId), path.resolve(themeFolder, themeName), console, opts); - } - }; -} -function lenientLitImportPlugin(): PluginOption { - return { - name: 'vaadin:lenient-lit-import', - async transform(code, id) { - const decoratorImports = [ - /import (.*?) from (['"])(lit\/decorators)(['"])/, - /import (.*?) from (['"])(lit-element\/decorators)(['"])/ - ]; - const directiveImports = [ - /import (.*?) from (['"])(lit\/directives\/)([^\\.]*?)(['"])/, - /import (.*?) from (['"])(lit-html\/directives\/)([^\\.]*?)(['"])/ - ]; - - decoratorImports.forEach((decoratorImport) => { - let decoratorMatch; - while ((decoratorMatch = code.match(decoratorImport))) { - console.warn( - `Warning: the file ${id} imports from '${decoratorMatch[3]}' when it should import from '${decoratorMatch[3]}.js'` - ); - code = code.replace(decoratorImport, 'import $1 from $2$3.js$4'); - } - }); - - directiveImports.forEach((directiveImport) => { - let directiveMatch; - while ((directiveMatch = code.match(directiveImport))) { - console.warn( - `Warning: the file ${id} imports from '${directiveMatch[3]}${directiveMatch[4]}' when it should import from '${directiveMatch[3]}${directiveMatch[4]}.js'` - ); - code = code.replace(directiveImport, 'import $1 from $2$3$4.js$5'); - } - }); - - return code; - } - }; -} - -function runWatchDog(watchDogPort, watchDogHost) { - const client = net.Socket(); - client.setEncoding('utf8'); - client.on('error', function (err) { - console.log('Watchdog connection error. Terminating vite process...', err); - client.destroy(); - process.exit(0); - }); - client.on('close', function () { - client.destroy(); - runWatchDog(watchDogPort, watchDogHost); - }); - - client.connect(watchDogPort, watchDogHost || 'localhost'); -} - -let spaMiddlewareForceRemoved = false; - -const allowedFrontendFolders = [ - frontendFolder, - path.resolve(generatedFlowImportsFolder), // Contains only generated-flow-imports - path.resolve(__dirname, 'node_modules') -]; - -function setHmrPortToServerPort(): PluginOption { - return { - name: 'set-hmr-port-to-server-port', - configResolved(config) { - if (config.server.strictPort && config.server.hmr !== false) { - if (config.server.hmr === true) config.server.hmr = {}; - config.server.hmr = config.server.hmr || {}; - config.server.hmr.clientPort = config.server.port; - } - } - }; -} -function showRecompileReason(): PluginOption { - return { - name: 'vaadin:why-you-compile', - handleHotUpdate(context) { - console.log('Recompiling because', context.file, 'changed'); - } - }; -} - -export const vaadinConfig: UserConfigFn = (env) => { - const devMode = env.mode === 'development'; - - if (devMode && process.env.watchDogPort) { - // Open a connection with the Java dev-mode handler in order to finish - // vite when it exits or crashes. - runWatchDog(process.env.watchDogPort, process.env.watchDogHost); - } - - return { - root: frontendFolder, - base: '', - resolve: { - alias: { - '@vaadin/flow-frontend': jarResourcesFolder, - Frontend: frontendFolder - }, - preserveSymlinks: true - }, - define: { - OFFLINE_PATH: settings.offlinePath, - VITE_ENABLED: 'true' - }, - server: { - host: '127.0.0.1', - strictPort: true, - fs: { - allow: allowedFrontendFolders - } - }, - build: { - outDir: frontendBundleFolder, - assetsDir: 'VAADIN/build', - rollupOptions: { - input: { - indexhtml: path.resolve(frontendFolder, 'index.html'), - - ...hasExportedWebComponents - ? { webcomponenthtml: path.resolve(frontendFolder, 'web-component.html') } - : {} - } - } - }, - optimizeDeps: { - entries: [ - // Pre-scan entrypoints in Vite to avoid reloading on first open - 'generated/vaadin.ts' - ], - exclude: [ - '@vaadin/router', - '@vaadin/vaadin-license-checker', - '@vaadin/vaadin-usage-statistics', - 'workbox-core', - 'workbox-precaching', - 'workbox-routing', - 'workbox-strategies' - ] - }, - plugins: [ - !devMode && brotli(), - devMode && vaadinBundlesPlugin(), - devMode && setHmrPortToServerPort(), - devMode && showRecompileReason(), - settings.offlineEnabled && buildSWPlugin({ devMode }), - !devMode && statsExtracterPlugin(), - themePlugin({devMode}), - lenientLitImportPlugin(), - postcssLit({ - include: ['**/*.css', '**/*.css\?*'], - exclude: [ - `${themeFolder}/**/*.css`, - `${themeFolder}/**/*.css\?*`, - `${themeResourceFolder}/**/*.css`, - `${themeResourceFolder}/**/*.css\?*`, - '**/*\?html-proxy*' - ] - }), - { - name: 'vaadin:force-remove-html-middleware', - transformIndexHtml: { - enforce: 'pre', - transform(_html, { server }) { - if (server && !spaMiddlewareForceRemoved) { - server.middlewares.stack = server.middlewares.stack.filter((mw) => { - const handleName = '' + mw.handle; - return !handleName.includes('viteHtmlFallbackMiddleware'); - }); - spaMiddlewareForceRemoved = true; - } - } - } - }, - hasExportedWebComponents && { - name: 'vaadin:inject-entrypoints-to-web-component-html', - transformIndexHtml: { - enforce: 'pre', - transform(_html, { path, server }) { - if (path !== '/web-component.html') { - return; - } - - return [ - { - tag: 'script', - attrs: { type: 'module', src: `/generated/vaadin-web-component.ts` }, - injectTo: 'head' - } - ] - } - } - }, - { - name: 'vaadin:inject-entrypoints-to-index-html', - transformIndexHtml: { - enforce: 'pre', - transform(_html, { path, server }) { - if (path !== '/index.html') { - return; - } - - const scripts = []; - - if (devMode) { - scripts.push({ - tag: 'script', - attrs: { type: 'module', src: `/generated/vite-devmode.ts` }, - injectTo: 'head' - }); - } - scripts.push({ - tag: 'script', - attrs: { type: 'module', src: '/generated/vaadin.ts' }, - injectTo: 'head' - }); - return scripts; - } - } - }, - checker({ - typescript: true - }) - ] - }; -}; - -export const overrideVaadinConfig = (customConfig: UserConfigFn) => { - return defineConfig((env) => mergeConfig(vaadinConfig(env), customConfig(env))); -}; diff --git a/integration-tests/reusable-theme/pom.xml b/integration-tests/reusable-theme/pom.xml index d5844d9..612a89a 100644 --- a/integration-tests/reusable-theme/pom.xml +++ b/integration-tests/reusable-theme/pom.xml @@ -16,7 +16,7 @@ true - 24.0-SNAPSHOT + 24.4-SNAPSHOT diff --git a/pom.xml b/pom.xml index f1d365e..8bccd17 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ ${surefire-plugin.version} true - 24.0-SNAPSHOT + 24.4-SNAPSHOT 3.0.1.Final 1.16.0-alpha 1.16.0 diff --git a/runtime/src/main/java/com/vaadin/quarkus/QuarkusInstantiatorFactory.java b/runtime/src/main/java/com/vaadin/quarkus/QuarkusInstantiatorFactory.java index 15b70ac..890384c 100644 --- a/runtime/src/main/java/com/vaadin/quarkus/QuarkusInstantiatorFactory.java +++ b/runtime/src/main/java/com/vaadin/quarkus/QuarkusInstantiatorFactory.java @@ -36,8 +36,13 @@ public Instantiator createInstantitor(VaadinService vaadinService) { if (!getServiceClass().isAssignableFrom(vaadinService.getClass())) { return null; } - return new QuarkusInstantiator(new DefaultInstantiator(vaadinService), - beanManager); + DefaultInstantiator delegate = new DefaultInstantiator(vaadinService) { + @Override + protected ClassLoader getClassLoader() { + return Thread.currentThread().getContextClassLoader(); + } + }; + return new QuarkusInstantiator(delegate, beanManager); } /**