Skip to content

Commit

Permalink
feat(angular): custom mf config path and ts support
Browse files Browse the repository at this point in the history
added an option to make the module-federation-dev-server and ssr builder accept a custom path for the module federation config file.

added a path to retrieve the module-federation.config file and to support typescript config file

Closes nrwl#15739
  • Loading branch information
peppoasap committed Apr 19, 2023
1 parent a798576 commit c403d40
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@
"pathToManifestFile": {
"type": "string",
"description": "Path to a Module Federation manifest file (e.g. `my/path/to/module-federation.manifest.json`) containing the dynamic remote applications relative to the workspace root."
},
"moduleFederationConfigOptions": {
"type": "object",
"description": "Custom Module Federation configuration to be used instead of the default configuration generated by the builder. This can be useful for multi-repository module federation setups where the host application uses a remote application from an external repository.",
"properties": { "path": { "type": "string" } }
}
},
"additionalProperties": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@
"pathToManifestFile": {
"type": "string",
"description": "Path to a Module Federation manifest file (e.g. `my/path/to/module-federation.manifest.json`) containing the dynamic remote applications relative to the workspace root."
},
"moduleFederationConfigOptions": {
"type": "object",
"description": "Custom Module Federation configuration to be used instead of the default configuration generated by the builder. This can be useful for multi-repository module federation setups where the host application uses a remote application from an external repository.",
"properties": { "path": { "type": "string" } }
}
},
"additionalProperties": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,45 @@ export function executeModuleFederationDevServerBuilder(
pathToManifestFile = userPathToManifestFile;
}

let pathToModuleFederationConfigFile = join(
context.workspaceRoot,
project.sourceRoot,
'module-federation.config.js'
);

if (options.moduleFederationConfigOptions) {
const userPathToModuleFederationConfigFile = join(
context.workspaceRoot,
options.moduleFederationConfigOptions.path
);

if (!existsSync(userPathToModuleFederationConfigFile)) {
throw new Error(
`The provided Module Federation config file path does not exist. Please check the file exists at "${userPathToModuleFederationConfigFile}".`
);
} else if (
extname(userPathToModuleFederationConfigFile) !== '.js' &&
extname(userPathToModuleFederationConfigFile) !== '.ts'
) {
throw new Error(
`The Module Federation config file must be a JS or TS file. Please ensure the file at ${userPathToModuleFederationConfigFile} is a JS or TS file. Extension found was "${extname(
userPathToModuleFederationConfigFile
)}".`
);
}

pathToModuleFederationConfigFile = userPathToModuleFederationConfigFile;
}

validateDevRemotes(options, workspaceProjects);

const remotesToSkip = new Set(options.skipRemotes ?? []);
const staticRemotes = getStaticRemotes(
project,
context,
workspaceProjects,
remotesToSkip
remotesToSkip,
pathToModuleFederationConfigFile
);
const dynamicRemotes = getDynamicRemotes(
project,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ export interface Schema {
devRemotes?: string[];
skipRemotes?: string[];
pathToManifestFile?: string;
moduleFederationConfigOptions?: { path: string };
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,13 @@
"pathToManifestFile": {
"type": "string",
"description": "Path to a Module Federation manifest file (e.g. `my/path/to/module-federation.manifest.json`) containing the dynamic remote applications relative to the workspace root."
},
"moduleFederationConfigOptions": {
"type": "object",
"description": "Custom Module Federation configuration to be used instead of the default configuration generated by the builder. This can be useful for multi-repository module federation setups where the host application uses a remote application from an external repository.",
"properties": {
"path": { "type": "string" }
}
}
},
"additionalProperties": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,42 @@ export function executeModuleFederationDevSSRBuilder(
pathToManifestFile = userPathToManifestFile;
}

let pathToModuleFederationConfigFile = join(
context.workspaceRoot,
project.sourceRoot,
'module-federation.config.js'
);

if (options.moduleFederationConfigOptions) {
const userPathToModuleFederationConfigFile = join(
context.workspaceRoot,
options.moduleFederationConfigOptions.path
);
if (!existsSync(userPathToModuleFederationConfigFile)) {
throw new Error(
`The provided Module Federation config file path does not exist. Please check the file exists at "${userPathToModuleFederationConfigFile}".`
);
} else if (
extname(userPathToModuleFederationConfigFile) !== '.js' &&
extname(userPathToModuleFederationConfigFile) !== '.ts'
) {
throw new Error(
`The Module Federation config file must be a JS or TS file. Please ensure the file at ${userPathToModuleFederationConfigFile} is a JS or TS file.`
);
}

pathToModuleFederationConfigFile = userPathToModuleFederationConfigFile;
}

validateDevRemotes(options, workspaceProjects);

const remotesToSkip = new Set(options.skipRemotes ?? []);
const staticRemotes = getStaticRemotes(
project,
context,
workspaceProjects,
remotesToSkip
remotesToSkip,
pathToModuleFederationConfigFile
);
const dynamicRemotes = getDynamicRemotes(
project,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ export interface Schema {
skipRemotes?: string[];
verbose: boolean;
pathToManifestFile?: string;
moduleFederationConfigOptions?: { path: string };
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@
"pathToManifestFile": {
"type": "string",
"description": "Path to a Module Federation manifest file (e.g. `my/path/to/module-federation.manifest.json`) containing the dynamic remote applications relative to the workspace root."
},
"moduleFederationConfigOptions": {
"type": "object",
"description": "Custom Module Federation configuration to be used instead of the default configuration generated by the builder. This can be useful for multi-repository module federation setups where the host application uses a remote application from an external repository.",
"properties": {
"path": { "type": "string" }
}
}
},
"additionalProperties": false,
Expand Down
61 changes: 51 additions & 10 deletions packages/angular/src/builders/utilities/module-federation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,20 +68,21 @@ export function getStaticRemotes(
project: ProjectConfiguration,
context: import('@angular-devkit/architect').BuilderContext,
workspaceProjects: Record<string, ProjectConfiguration>,
remotesToSkip: Set<string>
): string[] {
const mfConfigPath = join(
remotesToSkip: Set<string>,
pathToModuleFederationConfigFile = join(
context.workspaceRoot,
project.root,
project.sourceRoot,
'module-federation.config.js'
);

)
): string[] {
let mfeConfig: { remotes: Remotes };
try {
mfeConfig = require(mfConfigPath);
mfeConfig = resolveModuleFederationConfigFile(
pathToModuleFederationConfigFile
);
} catch {
throw new Error(
`Could not load ${mfConfigPath}. Was this project generated with "@nx/angular:host"?`
`Could not load ${pathToModuleFederationConfigFile}. Was this project generated with "@nrwl/angular:host"?`
);
}

Expand All @@ -101,8 +102,8 @@ export function getStaticRemotes(
if (invalidStaticRemotes.length) {
throw new Error(
invalidStaticRemotes.length === 1
? `Invalid static remote configured in "${mfConfigPath}": ${invalidStaticRemotes[0]}.`
: `Invalid static remotes configured in "${mfConfigPath}": ${invalidStaticRemotes.join(
? `Invalid static remote configured in "${pathToModuleFederationConfigFile}": ${invalidStaticRemotes[0]}.`
: `Invalid static remotes configured in "${pathToModuleFederationConfigFile}": ${invalidStaticRemotes.join(
', '
)}.`
);
Expand All @@ -127,3 +128,43 @@ export function validateDevRemotes(
);
}
}

export function resolveModuleFederationConfigFile(path: string): {
remotes: Remotes;
} {
tsNodeRegister(path);
const mfeConfigFile = require(path);
console.log('mfeConfigFile', mfeConfigFile);
return mfeConfigFile.default ?? mfeConfigFile;
}

export function tsNodeRegister(file: string = '', tsConfig?: string) {
if (!file?.endsWith('.ts')) return;

// Avoid double-registering which can lead to issues type-checking already transformed files.
if (isRegistered()) return;

// Register TS compiler lazily
require('ts-node').register({
project: tsConfig,
compilerOptions: {
module: 'CommonJS',
types: ['node'],
},
});

// Register paths in tsConfig
const tsconfigPaths = require('tsconfig-paths');
const { absoluteBaseUrl: baseUrl, paths } =
tsconfigPaths.loadConfig(tsConfig);
if (baseUrl && paths) {
tsconfigPaths.register({ baseUrl, paths });
}
}

export function isRegistered() {
return (
require.extensions['.ts'] != undefined ||
require.extensions['.tsx'] != undefined
);
}
3 changes: 3 additions & 0 deletions packages/angular/src/utils/mf/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ export async function getModuleFederationConfig(
determineRemoteUrl: (remote: string) => string,
options: { isServer: boolean } = { isServer: false }
) {
//check if mfConfig is from a js or ts configuration and take default values accordingly
mfConfig = (mfConfig as any).default ?? mfConfig;

let projectGraph: ProjectGraph;
try {
projectGraph = readCachedProjectGraph();
Expand Down

0 comments on commit c403d40

Please sign in to comment.