nx/packages/angular/src/builders/utilities/module-federation.ts
Leosvel Pérez Espinosa 37f02f7e6b
feat(angular): support angular 18.0.0 (#22509)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #25284
2024-05-23 17:50:04 +02:00

206 lines
5.4 KiB
TypeScript

import { join } from 'path';
import { existsSync, readFileSync } from 'fs';
import { logger, ProjectConfiguration } from '@nx/devkit';
import { registerTsProject } from '@nx/js/src/internal';
export function getDynamicRemotes(
project: ProjectConfiguration,
context: import('@angular-devkit/architect').BuilderContext,
workspaceProjects: Record<string, ProjectConfiguration>,
remotesToSkip: Set<string>,
pathToManifestFile: string | undefined
): string[] {
pathToManifestFile ??= getDynamicMfManifestFile(
project,
context.workspaceRoot
);
// check for dynamic remotes
// we should only check for dynamic based on what we generate
// and fallback to empty array
if (!pathToManifestFile || !existsSync(pathToManifestFile)) {
return [];
}
const moduleFederationManifestJson = readFileSync(
pathToManifestFile,
'utf-8'
);
if (!moduleFederationManifestJson) {
return [];
}
// This should have shape of
// {
// "remoteName": "remoteLocation",
// }
const parsedManifest = JSON.parse(moduleFederationManifestJson);
if (
!Object.keys(parsedManifest).every(
(key) =>
typeof key === 'string' && typeof parsedManifest[key] === 'string'
)
) {
return [];
}
const allDynamicRemotes = Object.entries(parsedManifest)
.map(([remoteName]) => remoteName)
.filter((r) => !remotesToSkip.has(r));
const remotesNotInWorkspace: string[] = [];
const dynamicRemotes = allDynamicRemotes.filter((remote) => {
if (!workspaceProjects[remote]) {
remotesNotInWorkspace.push(remote);
return false;
}
return true;
});
if (remotesNotInWorkspace.length > 0) {
logger.warn(
`Skipping serving ${remotesNotInWorkspace.join(
', '
)} as they could not be found in the workspace. Ensure they are served correctly.`
);
}
return dynamicRemotes;
}
function getModuleFederationConfig(
tsconfigPath: string,
workspaceRoot: string,
projectRoot: string
) {
const moduleFederationConfigPathJS = join(
workspaceRoot,
projectRoot,
'module-federation.config.js'
);
const moduleFederationConfigPathTS = join(
workspaceRoot,
projectRoot,
'module-federation.config.ts'
);
let moduleFederationConfigPath = moduleFederationConfigPathJS;
let cleanupTranspiler = () => {};
if (existsSync(moduleFederationConfigPathTS)) {
cleanupTranspiler = registerTsProject(join(workspaceRoot, tsconfigPath));
moduleFederationConfigPath = moduleFederationConfigPathTS;
}
try {
const config = require(moduleFederationConfigPath);
cleanupTranspiler();
return {
mfeConfig: config.default || config,
mfConfigPath: moduleFederationConfigPath,
};
} catch {
throw new Error(
`Could not load ${moduleFederationConfigPath}. Was this project generated with "@nx/angular:host"?`
);
}
}
export function getStaticRemotes(
project: ProjectConfiguration,
context: import('@angular-devkit/architect').BuilderContext,
workspaceProjects: Record<string, ProjectConfiguration>,
remotesToSkip: Set<string>
): string[] {
const { mfeConfig, mfConfigPath } = getModuleFederationConfig(
project.targets.build.options.tsConfig,
context.workspaceRoot,
project.root
);
const remotesConfig =
Array.isArray(mfeConfig.remotes) && mfeConfig.remotes.length > 0
? mfeConfig.remotes
: [];
const allStaticRemotes = remotesConfig
.map((remoteDefinition) =>
Array.isArray(remoteDefinition) ? remoteDefinition[0] : remoteDefinition
)
.filter((r) => !remotesToSkip.has(r));
const remotesNotInWorkspace: string[] = [];
const staticRemotes = allStaticRemotes.filter((remote) => {
if (!workspaceProjects[remote]) {
remotesNotInWorkspace.push(remote);
return false;
}
return true;
});
if (remotesNotInWorkspace.length > 0) {
logger.warn(
`Skipping serving ${remotesNotInWorkspace.join(
', '
)} as they could not be found in the workspace. Ensure they are served correctly.`
);
}
return staticRemotes;
}
export function validateDevRemotes(
options: {
devRemotes?: (
| string
| {
remoteName: string;
configuration: string;
}
)[];
},
workspaceProjects: Record<string, ProjectConfiguration>
): void {
const invalidDevRemotes =
options.devRemotes?.filter(
(remote) =>
!(typeof remote === 'string'
? workspaceProjects[remote]
: workspaceProjects[remote.remoteName])
) ?? [];
if (invalidDevRemotes.length) {
throw new Error(
invalidDevRemotes.length === 1
? `Invalid dev remote provided: ${invalidDevRemotes[0]}.`
: `Invalid dev remotes provided: ${invalidDevRemotes.join(', ')}.`
);
}
}
export function getDynamicMfManifestFile(
project: ProjectConfiguration,
workspaceRoot: string
): string | undefined {
// {sourceRoot}/assets/module-federation.manifest.json was the generated
// path for the manifest file in the past. We now generate the manifest
// file at {root}/public/module-federation.manifest.json. This check
// ensures that we can still support the old path for backwards
// compatibility since old projects may still have the manifest file
// at the old path.
return [
join(workspaceRoot, project.root, 'public/module-federation.manifest.json'),
join(
workspaceRoot,
project.sourceRoot,
'assets/module-federation.manifest.json'
),
].find((path) => existsSync(path));
}