Skip to content

Use fingerprinted file name for microfrontend entrypoint bundle to enable caching it #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: recording
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion projects/bookings/netlify.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[build]
publish = "dist"
command = "npm ci && npm run lint bookings && npm run test bookings && npm run build bookings -- --configuration production"
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'"
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand All @@ -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'),
},
},
]),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ export class LoadMicroFrontendGuard implements CanActivate {
) {}

canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
const bundleUrl = route.data.bundleUrl as unknown;
if (!(typeof bundleUrl === 'string')) {
const bundleUrl: Promise<string> = 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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<boolean> {
async loadBundle(bundleUrl$: Promise<string>): Promise<boolean> {
const bundleUrl = await bundleUrl$;
if (['LOADING', 'LOADED'].includes(this.getLoadingState(bundleUrl))) {
return true;
}
Expand Down
68 changes: 68 additions & 0 deletions scripts/generate-frontend-meta-json.js
Original file line number Diff line number Diff line change
@@ -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;
}