diff --git a/angular.json b/angular.json index fc2b509..2d559ac 100644 --- a/angular.json +++ b/angular.json @@ -141,7 +141,7 @@ "builder": "ngx-build-plus:build", "options": { "singleBundle": true, - "outputHashing": "none", + "outputHashing": "all", "outputPath": "projects/bookings/dist", "index": "projects/bookings/src/index.html", "main": "projects/bookings/src/main.ts", diff --git a/projects/bookings/netlify.toml b/projects/bookings/netlify.toml index d2aa979..a1e8d04 100644 --- a/projects/bookings/netlify.toml +++ b/projects/bookings/netlify.toml @@ -1,3 +1,3 @@ [build] publish = "dist" -command = "npm ci && npm run lint bookings && npm run test bookings && npm run build bookings -- --configuration production" \ No newline at end of file +command = "npm ci && npm run lint bookings && npm run test bookings && npm run build bookings -- --configuration production && node scripts/generate-frontend-meta-json.js buildDir='projects/bookings/dist'" \ No newline at end of file diff --git a/projects/train-platform/src/micro-frontends/bookings-host.module.ts b/projects/train-platform/src/micro-frontends/bookings-host.module.ts index c45780c..a7cfa59 100644 --- a/projects/train-platform/src/micro-frontends/bookings-host.module.ts +++ b/projects/train-platform/src/micro-frontends/bookings-host.module.ts @@ -6,8 +6,14 @@ import { LoadMicroFrontendGuard } from './load-micro-frontend.guard'; import { MicroFrontendLanguageDirective } from './micro-frontend-language.directive'; import { MicroFrontendRoutingDirective } from './micro-frontend-routing.directive'; -const getMicrofrontendBundleUrl = (frontendName: 'bookings') => - `/frontends/${frontendName}/main.js`; +const getMicrofrontendBundleUrl = async (frontendName: 'bookings') => { + const metaDataJsonUrl = `/frontends/${frontendName}/frontend-meta.json`; + const frontendMetaData = await fetch(metaDataJsonUrl).then((response) => + response.json() + ); + const entryPointBundleName = frontendMetaData.entryPointBundleName; + return `/frontends/${frontendName}/${entryPointBundleName}`; +}; @NgModule({ declarations: [ @@ -24,7 +30,7 @@ const getMicrofrontendBundleUrl = (frontendName: 'bookings') => data: { bundleUrl: environment.production ? getMicrofrontendBundleUrl('bookings') - : 'http://localhost:4201/main.js', + : Promise.resolve('http://localhost:4201/main.js'), }, }, ]), diff --git a/projects/train-platform/src/micro-frontends/load-micro-frontend.guard.ts b/projects/train-platform/src/micro-frontends/load-micro-frontend.guard.ts index 1a4080b..8437171 100644 --- a/projects/train-platform/src/micro-frontends/load-micro-frontend.guard.ts +++ b/projects/train-platform/src/micro-frontends/load-micro-frontend.guard.ts @@ -9,11 +9,12 @@ export class LoadMicroFrontendGuard implements CanActivate { ) {} canActivate(route: ActivatedRouteSnapshot): Promise { - const bundleUrl = route.data.bundleUrl as unknown; - if (!(typeof bundleUrl === 'string')) { + const bundleUrl: Promise = route.data.bundleUrl; + if (!(typeof bundleUrl.then === 'function')) { console.error(` - The LoadMicroFrontendGuard is missing information on which bundle to load. - Did you forget to provide a bundleUrl: string as data to the route? + You need to provide the Promise which loads the frontend-meta.json + and maps to the entryPointBundleName to the LoadMicroFrontendGuard. + Did you forget to provide the bundleUrl as route data? `); return Promise.resolve(false); } diff --git a/projects/train-platform/src/micro-frontends/micro-frontend-registry.service.ts b/projects/train-platform/src/micro-frontends/micro-frontend-registry.service.ts index a218f5b..0159b2a 100644 --- a/projects/train-platform/src/micro-frontends/micro-frontend-registry.service.ts +++ b/projects/train-platform/src/micro-frontends/micro-frontend-registry.service.ts @@ -34,9 +34,10 @@ export class MicroFrontendRegistryService { /** * Loads the given bundle if not already loaded, registering its custom elements in the browser. * - * @param bundleUrl The url of the bundle, can be absolute or relative to the domain + base href. + * @param bundleUrl$ The url of the bundle, can be absolute or relative to the domain + base href. */ - async loadBundle(bundleUrl: string): Promise { + async loadBundle(bundleUrl$: Promise): Promise { + const bundleUrl = await bundleUrl$; if (['LOADING', 'LOADED'].includes(this.getLoadingState(bundleUrl))) { return true; } diff --git a/scripts/generate-frontend-meta-json.js b/scripts/generate-frontend-meta-json.js new file mode 100644 index 0000000..07b1ea7 --- /dev/null +++ b/scripts/generate-frontend-meta-json.js @@ -0,0 +1,68 @@ +/* eslint-env es6 */ +/* + * HOW TO USE: + * First execute the build command for your frontend, e.g. nx run reports:build + * Now call this script and provide it with the build output directory: + * node generate-frontend-meta-json.js buildDir='projects/bookings/dist' + * + * It will write a "frontend-meta.json" into the provided directory. + */ + +const path = require("path"); +const fs = require("fs"); + +const projectRoot = path.resolve(__dirname, "../"); +const buildDirectory = path.resolve(projectRoot, getBuildDirectoryInput()); + +fs.readdir(buildDirectory, (err, filesInBuildDirectory) => { + if (err) { + throw new Error(`Directory ${buildDirectory} could not be found`); + } + + const frontendMetaData = generateFrontendMetaData(filesInBuildDirectory); + const targetFile = path.resolve(buildDirectory, "frontend-meta.json"); + const frontendMetaDataString = JSON.stringify(frontendMetaData); + fs.writeFileSync(targetFile, frontendMetaDataString); + + console.log( + `Wrote the following data to ${targetFile}: ${frontendMetaDataString}` + ); +}); + +/** + * @typedef FrontendMetaData + * @type {object} + * @property {string} entryPointBundleName - The name of the bundle used as entrypoint for the frontend application + */ + +/** + * @param {string[]} filesInBuildDirectory + * @returns {FrontendMetaData} + */ +function generateFrontendMetaData(filesInBuildDirectory) { + const mainBundleName = filesInBuildDirectory.find((fileName) => { + return fileName.startsWith("main.") && fileName.endsWith(".js"); + }); + + if (!mainBundleName) { + throw new Error("Could not find the entrypoint main bundle"); + } + + return { + entryPointBundleName: mainBundleName, + }; +} + +/** + * @returns {string} + */ +function getBuildDirectoryInput() { + const firstInput = process.argv[2]; + const [paramName, paramValue] = firstInput.split("="); + if (paramName !== "buildDir") { + throw new Error( + `You need to provide a value for the "buildDir" parameter. E.g. "node generate-frontend-meta-json.js buildDir='dist/apps/reports'` + ); + } + return paramValue; +}