Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
feat(angular): add withModuleFederation helper (#9289)
* feat(angular): withModuleFederation helper
  • Loading branch information
Coly010 committed Mar 16, 2022
1 parent 3c17e80 commit 2d08719
Show file tree
Hide file tree
Showing 9 changed files with 725 additions and 25 deletions.
3 changes: 2 additions & 1 deletion packages/angular/package.json
Expand Up @@ -21,7 +21,8 @@
"./generators": "./generators.js",
"./executors": "./executors.js",
"./tailwind": "./tailwind.js",
"./src/generators/utils": "./src/generators/utils/index.js"
"./src/generators/utils": "./src/generators/utils/index.js",
"./module-federation": "./src/utils/mfe/with-module-federation.js"
},
"author": "Victor Savkin",
"license": "MIT",
Expand Down
Expand Up @@ -69,22 +69,21 @@ function buildAppWithCustomWebpackConfiguration(
pathToWebpackConfig,
options.tsConfig
);
// The extra Webpack configuration file can also export a Promise, for instance:
// `module.exports = new Promise(...)`. If it exports a single object, but not a Promise,
// then await will just resolve that object.
const config = await customWebpackConfiguration;

// The extra Webpack configuration file can export a synchronous or asynchronous function,
// for instance: `module.exports = async config => { ... }`.
if (typeof customWebpackConfiguration === 'function') {
if (typeof config === 'function') {
return customWebpackConfiguration(
baseWebpackConfig,
options,
context.target
);
} else {
return merge(
baseWebpackConfig,
// The extra Webpack configuration file can also export a Promise, for instance:
// `module.exports = new Promise(...)`. If it exports a single object, but not a Promise,
// then await will just resolve that object.
await customWebpackConfiguration
);
return merge(baseWebpackConfig, config);
}
},
});
Expand Down
Expand Up @@ -55,22 +55,21 @@ export function webpackServer(schema: Schema, context: BuilderContext) {
pathToWebpackConfig,
buildTarget.options.tsConfig
);
// The extra Webpack configuration file can also export a Promise, for instance:
// `module.exports = new Promise(...)`. If it exports a single object, but not a Promise,
// then await will just resolve that object.
const config = await customWebpackConfiguration;

// The extra Webpack configuration file can export a synchronous or asynchronous function,
// for instance: `module.exports = async config => { ... }`.
if (typeof customWebpackConfiguration === 'function') {
return customWebpackConfiguration(
if (typeof config === 'function') {
return config(
baseWebpackConfig,
selectedConfiguration,
context.target
);
} else {
return merge(
baseWebpackConfig,
// The extra Webpack configuration file can also export a Promise, for instance:
// `module.exports = new Promise(...)`. If it exports a single object, but not a Promise,
// then await will just resolve that object.
await customWebpackConfiguration
);
return merge(baseWebpackConfig, config);
}
},
}
Expand Down
@@ -0,0 +1,141 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`withModuleFederation should collect dependencies correctly 1`] = `
Array [
ModuleFederationPlugin {
"_options": Object {
"exposes": Object {
"./Module": "apps/remote1/src/module.ts",
},
"filename": "remoteEntry.mjs",
"library": Object {
"type": "module",
},
"name": "remote1",
"remotes": Object {},
"shared": Object {
"@angular/core": Object {
"requiredVersion": "~13.2.0",
"singleton": true,
"strictVersion": true,
},
"core": Object {
"eager": undefined,
"requiredVersion": false,
},
"shared": Object {
"eager": undefined,
"requiredVersion": false,
},
},
},
},
NormalModuleReplacementPlugin {
"newResource": [Function],
"resourceRegExp": /\\./,
},
]
`;

exports[`withModuleFederation should create a host config correctly 1`] = `
Array [
ModuleFederationPlugin {
"_options": Object {
"exposes": undefined,
"filename": "remoteEntry.mjs",
"library": Object {
"type": "module",
},
"name": "host",
"remotes": Object {
"remote1": "http:/localhost:4201/remoteEntry.mjs",
},
"shared": Object {
"@angular/core": Object {
"requiredVersion": "~13.2.0",
"singleton": true,
"strictVersion": true,
},
"shared": Object {
"eager": undefined,
"requiredVersion": false,
},
},
},
},
NormalModuleReplacementPlugin {
"newResource": [Function],
"resourceRegExp": /\\./,
},
]
`;

exports[`withModuleFederation should create a remote config correctly 1`] = `
Array [
ModuleFederationPlugin {
"_options": Object {
"exposes": Object {
"./Module": "apps/remote1/src/module.ts",
},
"filename": "remoteEntry.mjs",
"library": Object {
"type": "module",
},
"name": "remote1",
"remotes": Object {},
"shared": Object {
"@angular/core": Object {
"requiredVersion": "~13.2.0",
"singleton": true,
"strictVersion": true,
},
"shared": Object {
"eager": undefined,
"requiredVersion": false,
},
},
},
},
NormalModuleReplacementPlugin {
"newResource": [Function],
"resourceRegExp": /\\./,
},
]
`;

exports[`withModuleFederation should map dependencies from project name to import name 1`] = `
Array [
ModuleFederationPlugin {
"_options": Object {
"exposes": Object {
"./Module": "apps/remote1/src/module.ts",
},
"filename": "remoteEntry.mjs",
"library": Object {
"type": "module",
},
"name": "remote1",
"remotes": Object {},
"shared": Object {
"@angular/core": Object {
"requiredVersion": "~13.2.0",
"singleton": true,
"strictVersion": true,
},
"@myorg/core": Object {
"eager": undefined,
"requiredVersion": false,
},
"shared": Object {
"eager": undefined,
"requiredVersion": false,
},
},
},
},
NormalModuleReplacementPlugin {
"newResource": [Function],
"resourceRegExp": /\\./,
},
]
`;
Expand Up @@ -18,8 +18,8 @@ describe('MFE Webpack Utils', () => {
shareWorkspaceLibraries(['@myorg/shared']);
} catch (error) {
// ASSERT
expect(error.message).toEqual(
'NX MFE: TsConfig Path for workspace libraries does not exist! (undefined)'
expect(error.message).toContain(
'NX MFE: TsConfig Path for workspace libraries does not exist!'
);
}
});
Expand Down
@@ -1,14 +1,24 @@
import { readTsConfig } from '@nrwl/workspace/src/utilities/typescript';
import { existsSync, readFileSync } from 'fs';
import { NormalModuleReplacementPlugin } from 'webpack';
import { appRootPath as rootPath } from 'nx/src/utils/app-root';
import { normalizePath, joinPathFragments } from '@nrwl/devkit';
import { dirname } from 'path';
import { ParsedCommandLine } from 'typescript';
import {
getRootTsConfigPath,
readTsConfig,
} from '@nrwl/workspace/src/utilities/typescript';

export interface SharedLibraryConfig {
singleton: boolean;
strictVersion: boolean;
requiredVersion: string;
eager: boolean;
}

export function shareWorkspaceLibraries(
libraries: string[],
tsConfigPath = process.env.NX_TSCONFIG_PATH
tsConfigPath = process.env.NX_TSCONFIG_PATH ?? getRootTsConfigPath()
) {
if (!existsSync(tsConfigPath)) {
throw new Error(
Expand All @@ -22,7 +32,7 @@ export function shareWorkspaceLibraries(
if (!tsconfigPathAliases) {
return {
getAliases: () => [],
getLibraries: () => [],
getLibraries: () => {},
getReplacementPlugin: () =>
new NormalModuleReplacementPlugin(/./, () => {}),
};
Expand All @@ -45,7 +55,7 @@ export function shareWorkspaceLibraries(
(aliases, library) => ({ ...aliases, [library.name]: library.path }),
{}
),
getLibraries: (eager?: boolean) =>
getLibraries: (eager?: boolean): Record<string, SharedLibraryConfig> =>
pathMappings.reduce(
(libraries, library) => ({
...libraries,
Expand All @@ -72,7 +82,9 @@ export function shareWorkspaceLibraries(
};
}

export function sharePackages(packages: string[]) {
export function sharePackages(
packages: string[]
): Record<string, SharedLibraryConfig> {
const pkgJsonPath = joinPathFragments(rootPath, 'package.json');
if (!existsSync(pkgJsonPath)) {
throw new Error(
Expand Down

0 comments on commit 2d08719

Please sign in to comment.