diff --git a/generators/base-core/generator.ts b/generators/base-core/generator.ts index 609616392a9f..813ed5978177 100644 --- a/generators/base-core/generator.ts +++ b/generators/base-core/generator.ts @@ -259,7 +259,7 @@ export default class CoreGenerator extends YeomanGenerator generator.microfrontend && (generator.clientFrameworkVue || generator.clientFrameworkReact), + condition: generator => generator.microfrontend && generator.clientFrameworkReact, templates: ['webpack/webpack.microfrontend.js.jhi'], }), { diff --git a/generators/client/generator.ts b/generators/client/generator.ts index e42da8e2b3d9..8e2e48d5d959 100644 --- a/generators/client/generator.ts +++ b/generators/client/generator.ts @@ -252,10 +252,8 @@ export default class JHipsterClientGenerator extends BaseApplicationGenerator { source.addWebpackConfig({ config: `${conditional}require('./webpack.microfrontend')(config, options, targetOptions)`, }); - } else if (application.clientFrameworkVue || application.clientFrameworkReact) { + } else if (application.clientFrameworkReact) { source.addWebpackConfig({ config: "require('./webpack.microfrontend')({ serve: options.env.WEBPACK_SERVE })" }); - } else { - throw new Error(`Client framework ${application.clientFramework} doesn't support microfrontends`); } }, }); diff --git a/generators/gradle/generators/node-gradle/templates/buildSrc/src/main/groovy/jhipster.node-gradle-conventions.gradle.ejs b/generators/gradle/generators/node-gradle/templates/buildSrc/src/main/groovy/jhipster.node-gradle-conventions.gradle.ejs index 76180541fd09..d771a525540b 100644 --- a/generators/gradle/generators/node-gradle/templates/buildSrc/src/main/groovy/jhipster.node-gradle-conventions.gradle.ejs +++ b/generators/gradle/generators/node-gradle/templates/buildSrc/src/main/groovy/jhipster.node-gradle-conventions.gradle.ejs @@ -66,22 +66,21 @@ task webapp_test(type: NpmTask) { inputs.files("tsconfig.json", "tsconfig.app.json") .withPropertyName("tsconfig") .withPathSensitivity(PathSensitivity.RELATIVE) - <%_ if (microfrontend) { _%> - def webpackDevFiles = fileTree("<%= CLIENT_WEBPACK_DIR %>") - webpackDevFiles.exclude("webpack.prod.js") - inputs.files(webpackDevFiles) - .withPropertyName("webpack-dir") + inputs.files(".postcssrc") + .withPropertyName("postcssrc") .withPathSensitivity(PathSensitivity.RELATIVE) - <%_ } else { _%> + <%_ } _%> + <%_ if (clientBundlerVite && clientFrameworkBuiltIn) { _%> - inputs.files("vite.config.ts") + inputs.files("vite.config.mts") .withPropertyName("vite") .withPathSensitivity(PathSensitivity.RELATIVE) - <%_ } _%> + <%_ } _%> + <%_ if (microfrontend && clientFrameworkBuiltIn) { _%> - inputs.files(".postcssrc") - .withPropertyName("postcssrc") + inputs.files("module-federation.config.mts") + .withPropertyName("module-federation") .withPathSensitivity(PathSensitivity.RELATIVE) <%_ } _%> diff --git a/generators/maven/generators/frontend-plugin/generator.ts b/generators/maven/generators/frontend-plugin/generator.ts index bb6b3c2b3811..0045acdf836e 100644 --- a/generators/maven/generators/frontend-plugin/generator.ts +++ b/generators/maven/generators/frontend-plugin/generator.ts @@ -40,6 +40,8 @@ export default class FrontendPluginGenerator extends BaseApplicationGenerator { clientFrameworkAngular, clientFrameworkReact, clientFrameworkVue, + clientFrameworkBuiltIn, + clientBundlerVite, microfrontend, srcMainWebapp, } = application; @@ -57,10 +59,13 @@ export default class FrontendPluginGenerator extends BaseApplicationGenerator { checksumIncludedFiles.push('.postcss.config.js', 'webpack/*.*'); } else if (clientFrameworkVue) { checksumIncludedFiles.push('.postcssrc.js', 'tsconfig.app.json'); + } + if (clientFrameworkBuiltIn && clientFrameworkVue) { + if (clientBundlerVite) { + checksumIncludedFiles.push('vite.config.mts'); + } if (microfrontend) { - checksumIncludedFiles.push('webpack/*.*'); - } else { - checksumIncludedFiles.push('vite.config.ts'); + checksumIncludedFiles.push('module-federation.config.mts'); } } source.addMavenDefinition!({ diff --git a/generators/spring-boot/templates/src/main/java/_package_/config/SecurityConfiguration_imperative.java.ejs b/generators/spring-boot/templates/src/main/java/_package_/config/SecurityConfiguration_imperative.java.ejs index 5fa690b4919b..d53d7f19369b 100644 --- a/generators/spring-boot/templates/src/main/java/_package_/config/SecurityConfiguration_imperative.java.ejs +++ b/generators/spring-boot/templates/src/main/java/_package_/config/SecurityConfiguration_imperative.java.ejs @@ -194,17 +194,13 @@ public class SecurityConfiguration { <%_ if (!skipClient) { _%> .requestMatchers(mvc.pattern("/index.html"), mvc.pattern("/*.js"), mvc.pattern("/*.txt"), mvc.pattern("/*.json"), mvc.pattern("/*.map"), mvc.pattern("/*.css")).permitAll() .requestMatchers(mvc.pattern("/*.ico"), mvc.pattern("/*.png"), mvc.pattern("/*.svg"), mvc.pattern("/*.webapp")).permitAll() - <%_ if (clientFrameworkVue) { _%> + <%_ if (clientBundlerVite) { _%> .requestMatchers(mvc.pattern("/assets/**")).permitAll() - <%_ if (microfrontend) { _%> - .requestMatchers(mvc.pattern("/app/**")).permitAll() - .requestMatchers(mvc.pattern("/i18n/**")).permitAll() - <%_ } _%> <%_ } else { _%> .requestMatchers(mvc.pattern("/app/**")).permitAll() .requestMatchers(mvc.pattern("/i18n/**")).permitAll() - <%_ } _%> .requestMatchers(mvc.pattern("/content/**")).permitAll() + <%_ } _%> .requestMatchers(mvc.pattern("/swagger-ui/**")).permitAll() <%_ } _%> <%_ if (authenticationTypeJwt) { _%> @@ -227,7 +223,11 @@ public class SecurityConfiguration { <%_ if (applicationTypeGateway) { _%> <%_ if (microfrontend) { _%> // microfrontend resources are loaded by webpack without authentication, they need to be public + <%_ if (clientBundlerVite) { _%> + .requestMatchers(mvc.pattern("/services/*/assets/**")).permitAll() + <%_ } _%> .requestMatchers(mvc.pattern("/services/*/*.js")).permitAll() + .requestMatchers(mvc.pattern("/services/*/content/*.js")).permitAll() .requestMatchers(mvc.pattern("/services/*/*.txt")).permitAll() .requestMatchers(mvc.pattern("/services/*/*.json")).permitAll() .requestMatchers(mvc.pattern("/services/*/*.js.map")).permitAll() diff --git a/generators/spring-boot/templates/src/main/java/_package_/config/SecurityConfiguration_reactive.java.ejs b/generators/spring-boot/templates/src/main/java/_package_/config/SecurityConfiguration_reactive.java.ejs index dc987fc12acb..d823c736fc5e 100644 --- a/generators/spring-boot/templates/src/main/java/_package_/config/SecurityConfiguration_reactive.java.ejs +++ b/generators/spring-boot/templates/src/main/java/_package_/config/SecurityConfiguration_reactive.java.ejs @@ -214,19 +214,16 @@ public class SecurityConfiguration { public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { http .securityMatcher(new NegatedServerWebExchangeMatcher(new OrServerWebExchangeMatcher( - <%_ if (clientFrameworkVue) { _%> pathMatchers( + <%_ if (clientBundlerVite) { _%> "/assets/**", - <%_ if (microfrontend) { _%> + <%_ } else { _%> "/app/**", "/i18n/**", "/content/**", - <%_ } _%> + <%_ } _%> "/swagger-ui/**" ) - <%_ } else { _%> - pathMatchers("/app/**", "/i18n/**", "/content/**", "/swagger-ui/**") - <%_ } _%> ))) <%_ if (!applicationTypeMicroservice) { _%> .cors(withDefaults()) @@ -286,10 +283,16 @@ public class SecurityConfiguration { <%_ if (applicationTypeGateway) { _%> <%_ if (microfrontend) { _%> // microfrontend resources are loaded by webpack without authentication, they need to be public + <%_ if (clientBundlerVite) { _%> + .pathMatchers("/services/*/assets/**").permitAll() + .pathMatchers("/services/*/*.js").permitAll() + <%_ } else { _%> .pathMatchers("/services/*/*.js").permitAll() + .pathMatchers("/services/*/content/*.js").permitAll() .pathMatchers("/services/*/*.txt").permitAll() .pathMatchers("/services/*/*.json").permitAll() .pathMatchers("/services/*/*.js.map").permitAll() + <%_ } _%> <%_ } _%> .pathMatchers("/services/*/management/health/readiness").permitAll() .pathMatchers("/services/*/v3/api-docs").hasAuthority(AuthoritiesConstants.ADMIN) diff --git a/generators/vue/__snapshots__/generator.spec.ts.snap b/generators/vue/__snapshots__/generator.spec.ts.snap index 81c96d44f772..6e242a0a8c87 100644 --- a/generators/vue/__snapshots__/generator.spec.ts.snap +++ b/generators/vue/__snapshots__/generator.spec.ts.snap @@ -1092,9 +1092,6 @@ exports[`generator - vue microservice-jwt-skipUserManagement(false)-withAdminUi( "clientRoot/src/main/webapp/app/entities/entities-menu.component.ts": { "stateCleared": "modified", }, - "clientRoot/src/main/webapp/app/entities/entities-menu.spec.ts": { - "stateCleared": "modified", - }, "clientRoot/src/main/webapp/app/entities/entities-menu.vue": { "stateCleared": "modified", }, @@ -1395,9 +1392,6 @@ exports[`generator - vue microservice-jwt-skipUserManagement(false)-withAdminUi( "clientRoot/src/main/webapp/manifest.webapp": { "stateCleared": "modified", }, - "clientRoot/src/main/webapp/microfrontends/entities-menu-test.vue": { - "stateCleared": "modified", - }, "clientRoot/src/main/webapp/microfrontends/entities-menu.component-test.ts": { "stateCleared": "modified", }, @@ -1431,22 +1425,10 @@ exports[`generator - vue microservice-jwt-skipUserManagement(false)-withAdminUi( "clientRoot/vitest.config.mts": { "stateCleared": "modified", }, - "clientRoot/webpack/config.js": { - "stateCleared": "modified", - }, - "clientRoot/webpack/vue.utils.js": { - "stateCleared": "modified", - }, - "clientRoot/webpack/webpack.common.js": { - "stateCleared": "modified", - }, - "clientRoot/webpack/webpack.dev.js": { + "module-federation.config.mts": { "stateCleared": "modified", }, - "clientRoot/webpack/webpack.microfrontend.js": { - "stateCleared": "modified", - }, - "clientRoot/webpack/webpack.prod.js": { + "package.json": { "stateCleared": "modified", }, } @@ -1475,6 +1457,9 @@ exports[`generator - vue microservice-oauth2-withAdminUi(true)-skipJhipsterDepen "eslint.config.mjs": { "stateCleared": "modified", }, + "module-federation.config.mts": { + "stateCleared": "modified", + }, "package.json": { "stateCleared": "modified", }, @@ -1907,24 +1892,6 @@ exports[`generator - vue microservice-oauth2-withAdminUi(true)-skipJhipsterDepen "vitest.config.mts": { "stateCleared": "modified", }, - "webpack/config.js": { - "stateCleared": "modified", - }, - "webpack/vue.utils.js": { - "stateCleared": "modified", - }, - "webpack/webpack.common.js": { - "stateCleared": "modified", - }, - "webpack/webpack.dev.js": { - "stateCleared": "modified", - }, - "webpack/webpack.microfrontend.js": { - "stateCleared": "modified", - }, - "webpack/webpack.prod.js": { - "stateCleared": "modified", - }, } `; diff --git a/generators/vue/files-vue.ts b/generators/vue/files-vue.ts index e70af0479916..977717002d05 100644 --- a/generators/vue/files-vue.ts +++ b/generators/vue/files-vue.ts @@ -37,17 +37,10 @@ export const vueFiles = { }), ], microfrontend: [ - clientRootTemplatesBlock({ + { condition: generator => generator.microfrontend, - templates: [ - 'webpack/config.js', - 'webpack/webpack.common.js', - 'webpack/webpack.dev.js', - 'webpack/webpack.prod.js', - 'webpack/vue.utils.js', - 'webpack/webpack.microfrontend.js.jhi.vue', - ], - }), + templates: ['module-federation.config.mts'], + }, { condition: generator => generator.microfrontend, ...clientApplicationTemplatesBlock(), @@ -56,14 +49,15 @@ export const vueFiles = { { condition: generator => generator.microfrontend, ...clientSrcTemplatesBlock(), - templates: [ - 'microfrontends/entities-menu.component-test.ts', - 'microfrontends/entities-menu-test.vue', - 'microfrontends/entities-router-test.ts', - ], + templates: ['microfrontends/entities-menu.component-test.ts', 'microfrontends/entities-router-test.ts'], + }, + { + condition: generator => generator.enableTranslation && generator.microfrontend, + ...clientSrcTemplatesBlock(), + templates: ['microfrontends/entities-menu-test.vue'], }, { - condition: generator => generator.applicationTypeMicroservice, + condition: generator => generator.enableTranslation && generator.applicationTypeMicroservice, ...clientApplicationTemplatesBlock(), templates: ['entities/entities-menu.spec.ts'], }, diff --git a/generators/vue/generator.ts b/generators/vue/generator.ts index 8e174c41bdf0..a23a95b05d1a 100644 --- a/generators/vue/generator.ts +++ b/generators/vue/generator.ts @@ -31,7 +31,6 @@ import { getTypescriptKeyType as getTSKeyType, generateTestEntityId as getTestEntityId, } from '../client/support/index.js'; -import { createNeedleCallback } from '../base/support/index.js'; import { writeEslintClientRootConfigFile } from '../javascript/generators/eslint/support/tasks.js'; import { cleanupEntitiesFiles, postWriteEntityFiles, writeEntityFiles } from './entity-files-vue.js'; import cleanupOldFilesTask from './cleanup.js'; @@ -85,21 +84,8 @@ export default class VueGenerator extends BaseApplicationGenerator { webappEnumerationsDir: app => `${app.clientWebappDir}shared/model/enumerations/`, }); }, - prepareForTemplates({ application, source }) { + prepareForTemplates({ application }) { application.addPrettierExtensions?.(['html', 'vue', 'css', 'scss']); - - source.addWebpackConfig = args => { - const webpackPath = `${application.clientRootDir}webpack/webpack.common.js`; - const ignoreNonExisting = this.sharedData.getControl().ignoreNeedlesError && 'Webpack configuration file not found'; - this.editFile( - webpackPath, - { ignoreNonExisting }, - createNeedleCallback({ - needle: 'jhipster-needle-add-webpack-config', - contentToAdd: `,${args.config}`, - }), - ); - }; }, }); } @@ -145,9 +131,19 @@ export default class VueGenerator extends BaseApplicationGenerator { get writing() { return this.asWritingTaskGroup({ - async cleanup({ control }) { + async cleanup({ control, application }) { await control.cleanupFiles({ '8.6.1': ['.eslintrc.json', '.eslintignore'], + '8.7.2': [ + [ + application.microfrontend!, + 'webpack/config.js', + 'webpack/webpack.common.js', + 'webpack/webpack.dev.js', + 'webpack/webpack.prod.js', + 'webpack/vue.utils.js', + ], + ], }); }, cleanupOldFilesTask, @@ -174,8 +170,17 @@ export default class VueGenerator extends BaseApplicationGenerator { get postWriting() { return this.asPostWritingTaskGroup({ + addDependencies({ application }) { + if (application.microfrontend) { + this.packageJson.merge({ + devDependencies: { + '@module-federation/runtime': 'latest', + '@module-federation/vite': 'latest', + }, + }); + } + }, addIndexAsset({ source, application }) { - if (application.microfrontend) return; source.addExternalResourceToRoot!({ resource: '', comment: 'Workaround https://github.com/axios/axios/issues/5622', diff --git a/generators/vue/support/update-languages.ts b/generators/vue/support/update-languages.ts index fdf3eb45c3d5..e6631219ba46 100644 --- a/generators/vue/support/update-languages.ts +++ b/generators/vue/support/update-languages.ts @@ -70,26 +70,9 @@ function updateLanguagesInConfigTask(this: BaseGenerator, { application, control ); } -function updateLanguagesInWebpackTask(this: BaseGenerator, { application, control = {} }: UpdateClientLanguagesTaskParam) { - const { clientSrcDir, languages } = application; - const { ignoreNeedlesError: ignoreNonExisting } = control; - let newContent = 'groupBy: [\n'; - languages?.forEach(language => { - newContent += ` { pattern: './${clientSrcDir}i18n/${language}/*.json', fileName: './i18n/${language}.json' },\n`; - }); - newContent += ' // jhipster-needle-i18n-language-webpack - JHipster will add/remove languages in this array\n ]'; - - this.editFile('webpack/webpack.common.js', { ignoreNonExisting }, content => - content.replace(/groupBy:.*\[([^\]]*jhipster-needle-i18n-language-webpack[^\]]*)\]/g, newContent), - ); -} - export default function updateLanguagesTask(this: BaseGenerator, taskParam: UpdateClientLanguagesTaskParam) { updateLanguagesInPipeTask.call(this, taskParam); updateLanguagesInConfigTask.call(this, taskParam); - if (taskParam.application.microfrontend) { - updateLanguagesInWebpackTask.call(this, taskParam); - } updateLanguagesInDayjsConfigurationTask.call(this, taskParam, { configurationFile: `${taskParam.application.clientSrcDir}app/shared/config/dayjs.ts`, commonjs: true, diff --git a/generators/vue/templates/module-federation.config.mts.ejs b/generators/vue/templates/module-federation.config.mts.ejs new file mode 100644 index 000000000000..677a893a9a7c --- /dev/null +++ b/generators/vue/templates/module-federation.config.mts.ejs @@ -0,0 +1,54 @@ +<%# + Copyright 2013-2024 the original author or authors from the JHipster project. + + This file is part of the JHipster project, see https://www.jhipster.tech/ + for more information. + + 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 + + https://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. +-%> +import packageJson from './package.json' assert { type: 'json' }; +import type { Options } from '@module-federation/runtime'; + +// Microfrontend api, should match across gateway and microservices. +const apiVersion = '0.0.1'; + +const sharedDefaults = { singleton: true, strictVersion: true, requiredVersion: apiVersion }; +const shareMappings = (...mappings) => Object.fromEntries(mappings.map(map => [map, { ...sharedDefaults, version: apiVersion }])); + +const shareDependencies = ({ skipList = [] } = {}) => + Object.fromEntries( + Object.entries(packageJson.dependencies) + .filter(([dependency]) => !skipList.includes(dependency)) + .map(([dependency, version]) => [dependency, { ...sharedDefaults, version, requiredVersion: version }]), + ); + +const mfConfig = { + name: '<%= lowercaseBaseName %>', +<%_ if (applicationTypeMicroservice) { _%> + exposes: { + './entities-router': './app/router/entities', + './entities-menu': './app/entities/entities-menu', + }, +<%_ } _%> + filename: 'remoteEntry.js', + shared: { + ...shareDependencies(), + ...shareMappings( + '@/shared/security/authority', + '@/shared/alert/alert.service', + '@/locale/translation.service', + ), + }, +} satisfies Options; + +export default mfConfig; diff --git a/generators/vue/templates/package.json.ejs b/generators/vue/templates/package.json.ejs index bae1f6baa4f9..113ed53167d6 100644 --- a/generators/vue/templates/package.json.ejs +++ b/generators/vue/templates/package.json.ejs @@ -59,29 +59,6 @@ "@module-federation/utilities": "<%= nodeDependencies['@module-federation/utilities'] %>", <%_ } _%> <%_ if (microfrontend) { _%> - "@originjs/vite-plugin-federation": "1.3.3", - "browser-sync-webpack-plugin": "<%= nodeDependencies['browser-sync-webpack-plugin'] %>", - "copy-webpack-plugin": "<%= nodeDependencies['copy-webpack-plugin'] %>", - "css-loader": "<%= nodeDependencies['css-loader'] %>", - "css-minimizer-webpack-plugin": "<%= nodeDependencies['css-minimizer-webpack-plugin'] %>", - "html-webpack-plugin": "<%= nodeDependencies['html-webpack-plugin'] %>", - <%_ if (enableTranslation) { _%> - "folder-hash": "<%= nodeDependencies['folder-hash'] %>", - "merge-jsons-webpack-plugin": "<%= nodeDependencies['merge-jsons-webpack-plugin'] %>", - <%_ } _%> - "mini-css-extract-plugin": "<%= nodeDependencies['mini-css-extract-plugin'] %>", - "postcss-loader": "<%= nodeDependencies['postcss-loader'] %>", - "sass-loader": "<%= nodeDependencies['sass-loader'] %>", - "terser-webpack-plugin": "<%= nodeDependencies['terser-webpack-plugin'] %>", - "ts-loader": "<%= nodeDependencies['ts-loader'] %>", - "vue-loader": "<%= nodeDependencies['vue-loader'] %>", - "vue-style-loader": "<%= nodeDependencies['vue-style-loader'] %>", - "webpack": "<%= nodeDependencies['webpack'] %>", - "webpack-bundle-analyzer": "<%= nodeDependencies['webpack-bundle-analyzer'] %>", - "webpack-cli": "<%= nodeDependencies['webpack-cli'] %>", - "webpack-dev-server": "<%= nodeDependencies['webpack-dev-server'] %>", - "webpack-merge": "<%= nodeDependencies['webpack-merge'] %>", - "workbox-webpack-plugin": "<%= nodeDependencies['workbox-webpack-plugin'] %>", <%_ } _%> "@eslint/js": null, "@pinia/testing": "<%= nodeDependencies['@pinia/testing'] %>", @@ -125,13 +102,8 @@ "default_environment": "prod" }, "scripts": { -<%_ if (microfrontend) { %> - "prettier:check": "prettier --check \"{,src/**/,webpack/,.blueprint/**/}*.{<%= prettierExtensions %>}\"", - "prettier:format": "prettier --write \"{,src/**/,webpack/,.blueprint/**/}*.{<%= prettierExtensions %>}\"", -<%_ } else { %> "prettier:check": "prettier --check \"{,src/**/,.blueprint/**/}*.{<%= prettierExtensions %>}\"", "prettier:format": "prettier --write \"{,src/**/,.blueprint/**/}*.{<%= prettierExtensions %>}\"", -<%_ } %> "lint": "eslint .", "lint:fix": "eslint . --fix", "cleanup": "rimraf <%= this.relativeDir(clientRootDir, temporaryDir) %>", @@ -148,25 +120,14 @@ "test:watch": "<%= clientPackageManager %> run vitest", "watch": "concurrently npm:start<% if(!skipServer) { %> npm:backend:start<% } %>", "webapp:build": "<%= clientPackageManager %> run clean-www && <%= clientPackageManager %> run webapp:build:dev --", -<%_ if (microfrontend) { %> - "webapp:build:dev": "<%= clientPackageManager %> run webpack -- --mode development --env stats=minimal", - "webapp:build:prod": "<%= clientPackageManager %> run webpack -- --mode production --env stats=minimal", - "webapp:dev": "<%= clientPackageManager %> run webpack-dev-server -- --mode development --env stats=normal", -<%_ } else { %> + "vite-serve": "vite", + "vite-build": "vite build", "webapp:build:dev": "<%= clientPackageManager %> run vite-build", "webapp:build:prod": "<%= clientPackageManager %> run vite-build", "webapp:dev": "<%= clientPackageManager %> run vite-serve", "webapp:serve": "<%= clientPackageManager %> run vite-serve", -<%_ } %> "webapp:prod": "<%= clientPackageManager %> run clean-www && <%= clientPackageManager %> run webapp:build:prod --", - "webapp:test": "<%= clientPackageManager %> run test --", -<%_ if (microfrontend) { %> - "webpack-dev-server": "webpack serve --config webpack/webpack.common.js", - "webpack": "webpack --config webpack/webpack.common.js" -<%_ } else { %> - "vite-serve": "vite", - "vite-build": "vite build" -<%_ } %> + "webapp:test": "<%= clientPackageManager %> run test --" }, "browserslist": [ "> 1%", diff --git a/generators/vue/templates/src/main/webapp/app/main.ts.ejs b/generators/vue/templates/src/main/webapp/app/main.ts.ejs index 86c4dffc215b..26abcebc1252 100644 --- a/generators/vue/templates/src/main/webapp/app/main.ts.ejs +++ b/generators/vue/templates/src/main/webapp/app/main.ts.ejs @@ -186,7 +186,7 @@ const app = createApp({ provide('trackerService', useTrackerService({ authenticated })); <%_ } _%> -<%_ if (applicationTypeMicroservice && microfrontend) { _%> +<%_ if (enableTranslation && applicationTypeMicroservice && microfrontend) { _%> provide('microfrontendI18n', false); <%_ } _%> }, diff --git a/generators/vue/templates/vite.config.mts.ejs b/generators/vue/templates/vite.config.mts.ejs index b3728718d5a5..ab42b82ee068 100644 --- a/generators/vue/templates/vite.config.mts.ejs +++ b/generators/vue/templates/vite.config.mts.ejs @@ -19,20 +19,12 @@ import { fileURLToPath, URL } from 'node:url'; import { normalizePath } from 'vite' -import { -<%_ if (microfrontend) { _%> - mergeConfig, -<%_ } _%> - defineConfig, -} from 'vite'; +import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; import { viteStaticCopy } from 'vite-plugin-static-copy'; <%_ if (microfrontend) { _%> -import federation from "@originjs/vite-plugin-federation"; - - <%_ if (applicationTypeGateway) { _%> -const sharedAppVersion = '0.0.0'; - <%_ } _%> +import { federation } from '@module-federation/vite'; +import federationConfig from './module-federation.config.mts'; <%_ } _%> const { getAbsoluteFSPath } = await import('swagger-ui-dist'); @@ -55,6 +47,9 @@ let config = defineConfig({ }, ], }), +<%_ if (microfrontend) { _%> + federation(federationConfig), +<%_ } _%> ], root: fileURLToPath(new URL('./<%- this.relativeDir(clientRootDir, clientSrcDir) %>', import.meta.url)), publicDir: fileURLToPath(new URL('./<%- this.relativeDir(clientRootDir, clientDistDir) %>public', import.meta.url)), @@ -116,63 +111,6 @@ let config = defineConfig({ }, }); -<%_ if (microfrontend) { _%> -config = mergeConfig(config, { - build: { - modulePreload: false, - minify: false, - target: ['chrome89', 'edge89', 'firefox89', 'safari15'], - }, - plugins: [ - federation({ - name: '<%= lowercaseBaseName %>', -<%_ if (applicationTypeGateway) { _%> - remotes: { - <%_ for (const remote of microfrontends) { _%> - '@<%= remote.lowercaseBaseName %>': `/<%= remote.endpointPrefix %>/assets/remoteEntry.js`, - <%_ } _%> - }, -<%_ } _%> -<%_ if (applicationTypeMicroservice) { _%> - exposes: { - './entities-router': './<%= this.relativeDir(clientRootDir, clientSrcDir) %>app/router/entities', - './entities-menu': './<%= this.relativeDir(clientRootDir, clientSrcDir) %>app/entities/entities-menu.vue', - }, -<%_ } _%> - shared: { - '@vuelidate/core': {}, - '@vuelidate/validators': {}, - axios: {}, - // 'bootstrap-vue': {}, - vue: { - packagePath: '@vue/compat/dist/vue.esm-bundler.js', - }, - 'vue-i18n': {}, - 'vue-router': {}, - pinia: {}, - '@/shared/security/authority': { - packagePath: './<%= this.relativeDir(clientRootDir, clientSrcDir) %>app/shared/security/authority', -<%_ if (applicationTypeGateway) { _%> - version: sharedAppVersion, -<%_ } _%> - }, - '@/shared/alert/alert.service': { - packagePath: './<%= this.relativeDir(clientRootDir, clientSrcDir) %>app/shared/alert/alert.service', -<%_ if (applicationTypeGateway) { _%> - version: sharedAppVersion, -<%_ } _%> - }, - '@/locale/translation.service': { - packagePath: './<%= this.relativeDir(clientRootDir, clientSrcDir) %>app/locale/translation.service', -<%_ if (applicationTypeGateway) { _%> - version: sharedAppVersion, -<%_ } _%> - }, - }, - }), - ], -}); -<%_ } _%> // jhipster-needle-add-vite-config - JHipster will add custom config export default config; diff --git a/generators/vue/templates/webpack/config.js.ejs b/generators/vue/templates/webpack/config.js.ejs deleted file mode 100644 index fe10699613d9..000000000000 --- a/generators/vue/templates/webpack/config.js.ejs +++ /dev/null @@ -1,48 +0,0 @@ -'use strict'; - -<%_ if (buildToolUnknown) { _%> -const packageJson = require('./../package.json'); -<%_ } _%> - -module.exports = { - serverApiUrl: '', -<%_ if (buildToolUnknown) { _%> - version: packageJson.version, -<%_ } else { _%> - // APP_VERSION is passed as an environment variable from the Gradle / Maven build tasks. - version: process.env.APP_VERSION || 'DEV', -<%_ } _%> - - dev: { - hotReload: <%= !microfrontend %>, - - // https://webpack.js.org/configuration/devtool/#development - devtool: 'cheap-module-source-map', - - // If you have problems debugging vue-files in devtools, - // set this to false - it *may* help - // https://vue-loader.vuejs.org/en/options.html#cachebusting - cacheBusting: true, - - cssSourceMap: true, - }, - - build: { - productionSourceMap: true, - // https://webpack.js.org/configuration/devtool/#production - devtool: 'source-map', - - // Gzip off by default as many popular static hosts such as - // Surge or Netlify already gzip all static assets for you. - // Before setting to `true`, make sure to: - // npm install --save-dev compression-webpack-plugin - productionGzip: false, - productionGzipExtensions: ['js', 'css'], - - // Run the build command with an extra argument to - // View the bundle analyzer report after build finishes: - // `npm run build --report` - // Set to `true` or `false` to always turn it on or off - bundleAnalyzerReport: process.env.npm_config_report, - }, -}; diff --git a/generators/vue/templates/webpack/vue.utils.js.ejs b/generators/vue/templates/webpack/vue.utils.js.ejs deleted file mode 100644 index 6552b8c4165e..000000000000 --- a/generators/vue/templates/webpack/vue.utils.js.ejs +++ /dev/null @@ -1,92 +0,0 @@ -'use strict'; -const MiniCssExtractPlugin = require('mini-css-extract-plugin'); -const sass = require('sass'); - -const config = require('./config'); - -const sourceMapEnabled = production => (production ? config.build.productionSourceMap : config.dev.cssSourceMap); - -const cssLoaders = options => { - options = options || {}; - - const cssLoader = { - loader: 'css-loader', - options: { - url: false, - sourceMap: options.sourceMap, - esModule: false, - }, - }; - - const postcssLoader = { - loader: 'postcss-loader', - options: { - sourceMap: options.sourceMap, - }, - }; - - // generate loader string to be used with extract text plugin - function generateLoaders(loader, loaderOptions) { - const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]; - - if (loader) { - loaders.push({ - loader: `${loader}-loader`, - options: { ...loaderOptions, sourceMap: options.sourceMap }, - }); - } - - // Extract CSS when that option is specified - // (which is the case during production build) - return [options.extract ? MiniCssExtractPlugin.loader : 'vue-style-loader'].concat(loaders); - } - - // https://vue-loader.vuejs.org/en/configurations/extract-css.html - return { - css: generateLoaders(), - postcss: generateLoaders(), - less: generateLoaders('less'), - sass: generateLoaders('sass', { indentedSyntax: true, implementation: sass }), - scss: generateLoaders('sass', { implementation: sass }), - stylus: generateLoaders('stylus'), - styl: generateLoaders('stylus'), - }; -}; - -// Generate loaders for standalone style files (outside of .vue) -const styleLoaders = options => { - const output = []; - const loaders = cssLoaders(options); - - for (const extension in loaders) { - const loader = loaders[extension]; - output.push({ - test: new RegExp(`\\.${extension}$`), - use: loader, - }); - } - - return output; -}; - -const vueLoaderConfig = production => ({ - loaders: cssLoaders({ - sourceMap: sourceMapEnabled(production), - extract: production, - }), - cssSourceMap: sourceMapEnabled(production), - cacheBusting: config.dev.cacheBusting, - transformToRequire: { - video: ['src', 'poster'], - source: 'src', - img: 'src', - image: 'xlink:href', - }, - hotReload: config.dev.hotReload, -}); - -module.exports = { - cssLoaders, - styleLoaders, - vueLoaderConfig, -}; diff --git a/generators/vue/templates/webpack/webpack.common.js.ejs b/generators/vue/templates/webpack/webpack.common.js.ejs deleted file mode 100644 index fcaa45cf8d4b..000000000000 --- a/generators/vue/templates/webpack/webpack.common.js.ejs +++ /dev/null @@ -1,185 +0,0 @@ -'use strict'; -const path = require('path'); -const { merge } = require('webpack-merge'); -const { VueLoaderPlugin } = require('vue-loader'); -const CopyWebpackPlugin = require('copy-webpack-plugin'); -const HtmlWebpackPlugin = require('html-webpack-plugin'); -<%_ if (enableTranslation) { _%> -const { hashElement } = require('folder-hash'); -const MergeJsonWebpackPlugin = require('merge-jsons-webpack-plugin'); -<%_ } _%> - -const { DefinePlugin } = require('webpack'); -const { vueLoaderConfig } = require('./vue.utils'); -const config = require('./config'); - -function resolve(dir = '') { - return path.join(__dirname, '..', dir); -} - -module.exports = async (env, options) => { - const development = options.mode === 'development'; -<%_ if (enableTranslation) { _%> - const languagesHash = await hashElement(resolve('<%= this.relativeDir(clientRootDir, clientSrcDir) %>i18n'), { - algo: 'md5', - encoding: 'hex', - files: { include: ['*.json'] }, - }); - -<%_ } _%> - return merge( - { - mode: options.mode, - context: resolve(), -<%_ if (applicationTypeGateway && microfrontend) { _%> - experiments: { - topLevelAwait: true, - }, -<%_ } _%> - entry: { - app: './<%= this.relativeDir(clientRootDir, clientSrcDir) %>app/<%= microfrontend ? 'index' : 'main' %>.ts', - }, - output: { - path: resolve('<%= this.relativeDir(clientRootDir, clientDistDir) %>'), - }, - resolve: { - extensions: ['.ts', '.js', '.vue', '.json'], - alias: { - vue$: '@vue/compat/dist/vue.esm-bundler.js', - '@': resolve('<%= this.relativeDir(clientRootDir, clientSrcDir) %>app'), - }, - }, - devServer: { -<%_ if (microfrontend) { _%> - hot: config.dev.hotReload, -<%_ } _%> - static: { - directory: './<%= this.relativeDir(clientRootDir, clientDistDir) %>', - }, - port: <%= devServerPort %>, - proxy: [ - { - context: [ - '/api', - '/services', - '/management', - '/v3/api-docs', - '/h2-console', -<%_ if (authenticationTypeOauth2) { _%> - '/oauth2', - '/login', -<%_ } _%> - '/auth' - ], - target: 'http://localhost:<%= applicationTypeMicroservice ? gatewayServerPort : serverPort %>', - secure: false, - }, -<%_ if (communicationSpringWebsocket) { _%> - { - context: [ - '/websocket' - ], - target: 'ws://localhost:<%= applicationTypeMicroservice ? gatewayServerPort : serverPort %>', - ws: true - } -<%_ } _%> - ], - historyApiFallback: true, - }, - cache: { - // 1. Set cache type to filesystem - type: 'filesystem', - cacheDirectory: resolve('<%= this.relativeDir(clientRootDir, temporaryDir) %>webpack'), - buildDependencies: { - // 2. Add your config as buildDependency to get cache invalidation on config change - config: [ - __filename, - path.resolve(__dirname, 'config.js'), - path.resolve(__dirname, 'vue.utils.js'), - path.resolve(__dirname, `webpack.${development ? 'dev' : 'prod'}.js`), - path.resolve(__dirname, '../.postcssrc.js'), - path.resolve(__dirname, '../tsconfig.json'), - ], - }, - }, - module: { - rules: [ - { - test: /\.vue$/, - loader: 'vue-loader', - options: { - ...vueLoaderConfig(!development), - }, - }, - { - test: /\.ts$/, - use: [ - { - loader: 'ts-loader', - options: { - appendTsSuffixTo: ['\\.vue$'], - happyPackMode: true, - transpileOnly: true, - configFile: 'tsconfig.app.json', - }, - }, - ], - include: [resolve('src'), resolve('test')], - }, - { - test: /\.(png|jpe?g|gif|svg|mp4|webm|ogg|mp3|wav|flac|aac|woff2?|eot|ttf|otf)/, - type: 'asset/resource', - }, - ], - }, - plugins: [ - new DefinePlugin({ -<%_ if (enableTranslation) { _%> - I18N_HASH: JSON.stringify(languagesHash.hash), -<%_ } _%> - APP_VERSION: JSON.stringify(config.version), - SERVER_API_URL: JSON.stringify(config.serverApiUrl), - __VUE_PROD_DEVTOOLS__: false, - }), - new HtmlWebpackPlugin({ - base: '/', - template: './<%= this.relativeDir(clientRootDir, clientSrcDir) %>index.html', - }), - new VueLoaderPlugin(), - new CopyWebpackPlugin({ - patterns: [ - { - // https://github.com/swagger-api/swagger-ui/blob/v4.6.1/swagger-ui-dist-package/README.md - context: require('swagger-ui-dist').getAbsoluteFSPath(), - from: '*.{js,css,html,png}', - to: 'swagger-ui/', - globOptions: { ignore: ['**/index.html'] }, - }, - { - from: path.join(path.dirname(require.resolve('axios/package.json')), 'dist/axios.min.js'), - to: 'swagger-ui/', - }, - { from: './<%= this.relativeDir(clientRootDir, clientSrcDir) %>swagger-ui/', to: 'swagger-ui/' }, - { from: './<%= this.relativeDir(clientRootDir, clientSrcDir) %>content/', to: 'content/' }, - { from: './<%= this.relativeDir(clientRootDir, clientSrcDir) %>favicon.ico', to: 'favicon.ico' }, - { - from: './<%= this.relativeDir(clientRootDir, clientSrcDir) %>manifest.webapp', - to: 'manifest.webapp', - }, - // jhipster-needle-add-assets-to-webpack - JHipster will add/remove third-party resources in this array - { from: './<%= this.relativeDir(clientRootDir, clientSrcDir) %>robots.txt', to: 'robots.txt' }, - ], - })<% if (enableTranslation) { %>, - new MergeJsonWebpackPlugin({ - output: { - groupBy: [ - // jhipster-needle-i18n-language-webpack - JHipster will add/remove languages in this array - ], - }, - }),<% } %> - ], - }, - await require(`./webpack.${development ? 'dev' : 'prod'}`)(env, options) - // jhipster-needle-add-webpack-config - JHipster will add custom config - ); -}; diff --git a/generators/vue/templates/webpack/webpack.dev.js.ejs b/generators/vue/templates/webpack/webpack.dev.js.ejs deleted file mode 100644 index 56341dd4d9a9..000000000000 --- a/generators/vue/templates/webpack/webpack.dev.js.ejs +++ /dev/null @@ -1,70 +0,0 @@ -<%# - Copyright 2013-2024 the original author or authors from the JHipster project. - - This file is part of the JHipster project, see https://www.jhipster.tech/ - for more information. - - 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 - - https://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. --%> -'use strict'; -const BrowserSyncPlugin = require('browser-sync-webpack-plugin'); - -const { styleLoaders } = require('./vue.utils'); -const config = require('./config'); - -module.exports = (env, options) => { - const devConfig = { - module: { - rules: styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }), - }, - // cheap-module-eval-source-map is faster for development - devtool: config.dev.devtool, - output: { - filename: 'app/[name].[contenthash].bundle.js', - chunkFilename: 'app/[id].[chunkhash].chunk.js', - }, - optimization: { - moduleIds: 'named', - }, - plugins: [], - }; - if (!options.env.WEBPACK_SERVE) return devConfig; - devConfig.plugins.push( - new BrowserSyncPlugin( - { - host: 'localhost', - port: 9000, - proxy: { - target: `http://localhost:${options.watch ? '<%= applicationTypeMicroservice ? gatewayServerPort : serverPort %>' : '<%= devServerPort %>'}`, - ws: true, - }, - socket: { - clients: { - heartbeatTimeout: 60000, - }, - }, - /* - ,ghostMode: { // uncomment this part to disable BrowserSync ghostMode; https://github.com/jhipster/generator-jhipster/issues/11116 - clicks: false, - location: false, - forms: false, - scroll: false - } */ - }, - { - reload: true, - }, - ), - ); - return devConfig; -}; diff --git a/generators/vue/templates/webpack/webpack.microfrontend.js.jhi.vue.ejs b/generators/vue/templates/webpack/webpack.microfrontend.js.jhi.vue.ejs deleted file mode 100644 index e30946d381c2..000000000000 --- a/generators/vue/templates/webpack/webpack.microfrontend.js.jhi.vue.ejs +++ /dev/null @@ -1,45 +0,0 @@ -<%# - Copyright 2013-2024 the original author or authors from the JHipster project. - - This file is part of the JHipster project, see https://www.jhipster.tech/ - for more information. - - 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 - - https://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. --%> -<&_ if (fragment.configSection) { -&> -<%_ if (applicationTypeGateway && clientFrameworkVue) { _%> - resolve: { - fallback: { - // Workaround https://github.com/module-federation/universe/issues/1575 - path: false, - }, - }, -<%_ } _%> -<&_ } -&> - -<&_ if (fragment.moduleFederationSection) { -&> -<%_ if (applicationTypeMicroservice) { _%> - exposes: { - './entities-router': './<%= this.relativeDir(clientRootDir, clientSrcDir) %>app/router/entities', - './entities-menu': './<%= this.relativeDir(clientRootDir, clientSrcDir) %>app/entities/entities-menu', - }, -<%_ } _%> - shared: { - ...shareDependencies(), - ...shareMappings( - '@/shared/security/authority', - '@/shared/alert/alert.service', - '@/locale/translation.service', - ), - }, -<&_ } -&> diff --git a/generators/vue/templates/webpack/webpack.prod.js.ejs b/generators/vue/templates/webpack/webpack.prod.js.ejs deleted file mode 100644 index 18dba308038f..000000000000 --- a/generators/vue/templates/webpack/webpack.prod.js.ejs +++ /dev/null @@ -1,126 +0,0 @@ -<%# - Copyright 2013-2024 the original author or authors from the JHipster project. - - This file is part of the JHipster project, see https://www.jhipster.tech/ - for more information. - - 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 - - https://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. --%> -'use strict'; -const MiniCssExtractPlugin = require('mini-css-extract-plugin'); -const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); -const WorkboxPlugin = require('workbox-webpack-plugin'); -const TerserPlugin = require('terser-webpack-plugin'); - -const { styleLoaders } = require('./vue.utils'); -const config = require('./config'); - -const webpackConfig = { - module: { - rules: styleLoaders({ - sourceMap: config.build.productionSourceMap, - extract: true, - usePostCSS: true, - }), - }, - devtool: config.build.productionSourceMap ? config.build.devtool : false, - output: { - filename: 'app/[name].[contenthash].bundle.js', - chunkFilename: 'app/[id].[chunkhash].chunk.js', - }, - optimization: { - moduleIds: 'deterministic', - minimizer: [ - '...', - new CssMinimizerPlugin({ - parallel: true, - }), - ], - splitChunks: { - cacheGroups: { - commons: { - test: /[\\/]node_modules[\\/]/, - name: 'vendors', - chunks: 'all', - }, - }, - }, - }, - plugins: [ - new TerserPlugin({ - terserOptions: { - compress: { - arrows: false, - collapse_vars: false, - comparisons: false, - computed_props: false, - hoist_funs: false, - hoist_props: false, - hoist_vars: false, - inline: false, - loops: false, - negate_iife: false, - properties: false, - reduce_funcs: false, - reduce_vars: false, - switches: false, - toplevel: false, - typeofs: false, - booleans: true, - if_return: true, - sequences: true, - unused: true, - conditionals: true, - dead_code: true, - evaluate: true, - }, - mangle: { - safari10: true, - }, - }, - parallel: true, - extractComments: false, - }), - // extract css into its own file - new MiniCssExtractPlugin({ - filename: 'content/[name].[contenthash].css', - chunkFilename: 'content/[id].css', - }), - new WorkboxPlugin.GenerateSW({ - clientsClaim: true, - skipWaiting: true, - exclude: [/swagger-ui/], - }), - ], -}; - -if (config.build.productionGzip) { - const CompressionWebpackPlugin = require('compression-webpack-plugin'); - - webpackConfig.plugins.push( - new CompressionWebpackPlugin({ - asset: '[path].gz[query]', - algorithm: 'gzip', - test: new RegExp(`\\.(${config.build.productionGzipExtensions.join('|')})$`), - threshold: 10240, - minRatio: 0.8, - }), - ); -} - -if (config.build.bundleAnalyzerReport) { - const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; - webpackConfig.plugins.push(new BundleAnalyzerPlugin()); -} - -module.exports = async () => webpackConfig; diff --git a/lib/jhipster/default-application-options.ts b/lib/jhipster/default-application-options.ts index 02350746f4db..44232bd1571d 100644 --- a/lib/jhipster/default-application-options.ts +++ b/lib/jhipster/default-application-options.ts @@ -76,7 +76,9 @@ const { GRADLE_ENTERPRISE_HOST, } = OptionNames; -const commonDefaultOptions: Partial = { +type ApplicationDefaults = Partial; + +const commonDefaultOptions: ApplicationDefaults = { [AUTHENTICATION_TYPE]: JWT, [BUILD_TOOL]: MAVEN, [DTO_SUFFIX]: OptionValues[DTO_SUFFIX], @@ -89,8 +91,8 @@ const commonDefaultOptions: Partial = { [WEBSOCKET]: (OptionValues[WEBSOCKET] as Record).no, }; -export function getConfigWithDefaults(customOptions: string | Record = {}) { - const applicationType = typeof customOptions === 'string' ? customOptions : customOptions.applicationType; +export function getConfigWithDefaults(customOptions: ApplicationDefaults = {}) { + const applicationType = customOptions.applicationType; if (applicationType === GATEWAY) { return getConfigForGatewayApplication(customOptions); } @@ -100,7 +102,7 @@ export function getConfigWithDefaults(customOptions: string | Record