feat(module-federation): consolidate module federation utils into module-federation package (#28919)
- feat(module-federation): consolidate module federation utils into module-federation package - chore(module-federation): fix tests and linting <!-- 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` --> <!-- If this is a particularly complex change or feature addition, you can request a dedicated Nx release for this pull request branch. Mention someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they will confirm if the PR warrants its own release for testing purposes, and generate it for you if appropriate. --> ## Current Behavior <!-- This is the behavior we have today --> Our current support for Module Federation relies on utilities that are spread and duplicated across the `@nx/webpack` package and the `@nx/rspack` package. ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> Now that we have a `@nx/module-federation` package, dedupe the utils and consolidate them into a single package ## Todo - [x] Migrations for React + Angular to install `@nx/module-federation` and point `ModuleFederationConfig` export to that package from webpack.config and rspack.config files
This commit is contained in:
parent
0407b7a7b4
commit
76d61ea5e6
@ -94,10 +94,9 @@ rust-toolchain @nrwl/nx-native-reviewers
|
||||
/packages/web/** @nrwl/nx-js-reviewers
|
||||
/e2e/web/** @nrwl/nx-js-reviewers
|
||||
/packages/webpack/** @nrwl/nx-js-reviewers
|
||||
/packages/webpack/src/utils/module-federation @jaysoo @Coly010
|
||||
/e2e/webpack/** @nrwl/nx-js-reviewers
|
||||
/packages/rspack/** @nrwl/nx-js-reviewers
|
||||
/packages/rspack/src/utils/module-federation @jaysoo @Coly010
|
||||
/packages/rspack/src/utils/module-federation @nrwl/nx-js-reviewers
|
||||
/e2e/rspack/** @nrwl/nx-js-reviewers
|
||||
/packages/esbuild/** @nrwl/nx-js-reviewers
|
||||
/e2e/esbuild/** @nrwl/nx-js-reviewers
|
||||
|
||||
@ -266,6 +266,12 @@
|
||||
"version": "19.6.1-beta.0",
|
||||
"description": "Ensure Target Defaults are set correctly for Module Federation.",
|
||||
"factory": "./src/migrations/update-19-6-1/ensure-depends-on-for-mf"
|
||||
},
|
||||
"update-20-2-0-update-module-federation-config-import": {
|
||||
"cli": "nx",
|
||||
"version": "20.2.0-beta.2",
|
||||
"description": "Update the ModuleFederationConfig import use @nx/module-federation.",
|
||||
"factory": "./src/migrations/update-20-2-0/migrate-mf-imports-to-new-package"
|
||||
}
|
||||
},
|
||||
"packageJsonUpdates": {
|
||||
|
||||
@ -63,6 +63,7 @@
|
||||
"@nx/js": "file:../js",
|
||||
"@nx/eslint": "file:../eslint",
|
||||
"@nx/webpack": "file:../webpack",
|
||||
"@nx/module-federation": "file:../module-federation",
|
||||
"@nx/web": "file:../web",
|
||||
"@nx/workspace": "file:../workspace",
|
||||
"piscina": "^4.4.0"
|
||||
|
||||
@ -4,7 +4,7 @@ import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
|
||||
import { fork } from 'node:child_process';
|
||||
import { join } from 'node:path';
|
||||
import { createWriteStream } from 'node:fs';
|
||||
import type { StaticRemotesConfig } from '@nx/webpack/src/utils/module-federation/parse-static-remotes-config';
|
||||
import type { StaticRemotesConfig } from '@nx/module-federation/src/utils';
|
||||
|
||||
export async function buildStaticRemotes(
|
||||
staticRemotesConfig: StaticRemotesConfig,
|
||||
|
||||
@ -3,7 +3,7 @@ import { type Schema } from '../schema';
|
||||
import fileServerExecutor from '@nx/web/src/executors/file-server/file-server.impl';
|
||||
import { join } from 'path';
|
||||
import { cpSync } from 'fs';
|
||||
import type { StaticRemotesConfig } from '@nx/webpack/src/utils/module-federation/parse-static-remotes-config';
|
||||
import type { StaticRemotesConfig } from '@nx/module-federation/src/utils';
|
||||
|
||||
export function startStaticRemotesFileServer(
|
||||
staticRemotesConfig: StaticRemotesConfig,
|
||||
|
||||
@ -19,7 +19,9 @@ import {
|
||||
import {
|
||||
getModuleFederationConfig,
|
||||
getRemotes,
|
||||
} from '@nx/webpack/src/utils/module-federation';
|
||||
startRemoteProxies,
|
||||
parseStaticRemotesConfig,
|
||||
} from '@nx/module-federation/src/utils';
|
||||
import { waitForPortOpen } from '@nx/web/src/utils/wait-for-port-open';
|
||||
import fileServerExecutor from '@nx/web/src/executors/file-server/file-server.impl';
|
||||
import { createBuilderContext } from 'nx/src/adapter/ngcli-adapter';
|
||||
@ -30,8 +32,6 @@ import {
|
||||
} from '../../builders/utilities/module-federation';
|
||||
import { extname, join } from 'path';
|
||||
import { existsSync } from 'fs';
|
||||
import { startRemoteProxies } from '@nx/webpack/src/utils/module-federation/start-remote-proxies';
|
||||
import { parseStaticRemotesConfig } from '@nx/webpack/src/utils/module-federation/parse-static-remotes-config';
|
||||
|
||||
export async function* moduleFederationDevServerExecutor(
|
||||
schema: Schema,
|
||||
|
||||
@ -4,7 +4,7 @@ import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
|
||||
import { fork } from 'node:child_process';
|
||||
import { join } from 'node:path';
|
||||
import { createWriteStream } from 'node:fs';
|
||||
import type { StaticRemotesConfig } from '@nx/webpack/src/utils/module-federation/parse-static-remotes-config';
|
||||
import type { StaticRemotesConfig } from '@nx/module-federation/src/utils';
|
||||
|
||||
export async function buildStaticRemotes(
|
||||
staticRemotesConfig: StaticRemotesConfig,
|
||||
|
||||
@ -3,7 +3,7 @@ import { type Schema } from '../schema';
|
||||
import fileServerExecutor from '@nx/web/src/executors/file-server/file-server.impl';
|
||||
import { join } from 'path';
|
||||
import { cpSync, rmSync } from 'fs';
|
||||
import type { StaticRemotesConfig } from '@nx/webpack/src/utils/module-federation/parse-static-remotes-config';
|
||||
import type { StaticRemotesConfig } from '@nx/module-federation/src/utils';
|
||||
import { createAsyncIterable } from '@nx/devkit/src/utils/async-iterable';
|
||||
|
||||
export function startStaticRemotes(
|
||||
|
||||
@ -10,8 +10,9 @@ import type { Schema } from './schema';
|
||||
import {
|
||||
getModuleFederationConfig,
|
||||
getRemotes,
|
||||
} from '@nx/webpack/src/utils/module-federation';
|
||||
import { parseStaticSsrRemotesConfig } from '@nx/webpack/src/utils/module-federation/parse-static-remotes-config';
|
||||
parseStaticSsrRemotesConfig,
|
||||
startSsrRemoteProxies,
|
||||
} from '@nx/module-federation/src/utils';
|
||||
import { buildStaticRemotes } from './lib/build-static-remotes';
|
||||
import { startRemotes } from './lib/start-dev-remotes';
|
||||
import { startStaticRemotes } from './lib/start-static-remotes';
|
||||
@ -24,7 +25,6 @@ import { eachValueFrom } from '@nx/devkit/src/utils/rxjs-for-await';
|
||||
import { createBuilderContext } from 'nx/src/adapter/ngcli-adapter';
|
||||
import { normalizeOptions } from './lib/normalize-options';
|
||||
import { waitForPortOpen } from '@nx/web/src/utils/wait-for-port-open';
|
||||
import { startSsrRemoteProxies } from '@nx/webpack/src/utils/module-federation/start-ssr-remote-proxies';
|
||||
import { getInstalledAngularVersionInfo } from '../utilities/angular-version-utils';
|
||||
|
||||
export async function* moduleFederationSsrDevServerExecutor(
|
||||
|
||||
@ -642,7 +642,7 @@ export default bootstrap;
|
||||
exports[`Host App Generator --ssr should generate the correct files for standalone when --typescript=true 4`] = `"import('./src/main.server');"`;
|
||||
|
||||
exports[`Host App Generator --ssr should generate the correct files for standalone when --typescript=true 5`] = `
|
||||
"import { ModuleFederationConfig } from '@nx/webpack';
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
name: 'test',
|
||||
@ -880,7 +880,7 @@ export default AppServerModule;
|
||||
exports[`Host App Generator --ssr should generate the correct files when --typescript=true 5`] = `"import('./src/main.server');"`;
|
||||
|
||||
exports[`Host App Generator --ssr should generate the correct files when --typescript=true 6`] = `
|
||||
"import { ModuleFederationConfig } from '@nx/webpack';
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
name: 'test',
|
||||
|
||||
@ -435,7 +435,7 @@ export default AppServerModule;
|
||||
exports[`MF Remote App Generator --ssr should generate the correct files when --typescriptConfiguration=true 5`] = `"import('./src/main.server');"`;
|
||||
|
||||
exports[`MF Remote App Generator --ssr should generate the correct files when --typescriptConfiguration=true 6`] = `
|
||||
"import { ModuleFederationConfig } from '@nx/webpack';
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
name: 'test',
|
||||
@ -691,7 +691,7 @@ bootstrapApplication(RemoteEntryComponent, appConfig).catch((err) =>
|
||||
`;
|
||||
|
||||
exports[`MF Remote App Generator should generate the a remote setup for standalone components when --typescriptConfiguration=true 2`] = `
|
||||
"import { ModuleFederationConfig } from '@nx/webpack';
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
name: 'test',
|
||||
|
||||
@ -98,7 +98,7 @@ module.exports = {
|
||||
`;
|
||||
|
||||
exports[`Init MF should add a remote application and add it to a specified host applications webpack config that contains a remote application already when --typescriptConfiguration=true 1`] = `
|
||||
"import { ModuleFederationConfig } from '@nx/webpack';
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
name: 'app1',
|
||||
@ -148,7 +148,7 @@ module.exports = {
|
||||
`;
|
||||
|
||||
exports[`Init MF should add a remote application and add it to a specified host applications webpack config when no other remote has been added to it when --typescriptConfiguration=true 1`] = `
|
||||
"import { ModuleFederationConfig } from '@nx/webpack';
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
name: 'app1',
|
||||
@ -284,7 +284,7 @@ export default withModuleFederation(config, { dts: false });
|
||||
`;
|
||||
|
||||
exports[`Init MF should create webpack and mf configs correctly when --typescriptConfiguration=true 2`] = `
|
||||
"import { ModuleFederationConfig } from '@nx/webpack';
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
name: 'app1',
|
||||
@ -324,7 +324,7 @@ export default withModuleFederation(config, { dts: false });
|
||||
`;
|
||||
|
||||
exports[`Init MF should create webpack and mf configs correctly when --typescriptConfiguration=true 4`] = `
|
||||
"import { ModuleFederationConfig } from '@nx/webpack';
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
name: 'remote1',
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ModuleFederationConfig } from '@nx/webpack';
|
||||
import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
name: '<%= name %>',<% if(type === 'host') { %>
|
||||
|
||||
@ -0,0 +1,334 @@
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import migrateMfImportsToNewPackage from './migrate-mf-imports-to-new-package';
|
||||
|
||||
jest.mock('@nx/devkit', () => {
|
||||
const original = jest.requireActual('@nx/devkit');
|
||||
return {
|
||||
...original,
|
||||
createProjectGraphAsync: jest.fn().mockResolvedValue(
|
||||
Promise.resolve({
|
||||
dependencies: {
|
||||
shell: [
|
||||
{
|
||||
source: 'shell',
|
||||
target: 'npm:@nx/webpack',
|
||||
type: 'static',
|
||||
},
|
||||
],
|
||||
remote: [
|
||||
{
|
||||
source: 'remote',
|
||||
target: 'npm:@nx/webpack',
|
||||
type: 'static',
|
||||
},
|
||||
],
|
||||
},
|
||||
nodes: {
|
||||
shell: {
|
||||
name: 'shell',
|
||||
type: 'app',
|
||||
data: {
|
||||
root: 'apps/shell',
|
||||
sourceRoot: 'shell/src',
|
||||
targets: {},
|
||||
},
|
||||
},
|
||||
remote: {
|
||||
name: 'remote',
|
||||
type: 'app',
|
||||
data: {
|
||||
root: 'apps/remote',
|
||||
sourceRoot: 'remote/src',
|
||||
targets: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
describe('migrate-mf-imports-to-new-package', () => {
|
||||
it('should update the ModuleFederationConfig import to change the import when its a single line', async () => {
|
||||
// ARRANGE
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
tree.write(
|
||||
'apps/shell/webpack.config.js',
|
||||
`import { ModuleFederationConfig } from '@nx/webpack';`
|
||||
);
|
||||
tree.write(
|
||||
'apps/shell/module-federation.config.ts',
|
||||
`import { ModuleFederationConfig } from '@nx/webpack';`
|
||||
);
|
||||
tree.write(
|
||||
'apps/remote/webpack.config.ts',
|
||||
`import { ModuleFederationConfig } from '@nx/webpack';`
|
||||
);
|
||||
tree.write(
|
||||
'apps/remote/module-federation.config.js',
|
||||
`import { ModuleFederationConfig } from '@nx/webpack';`
|
||||
);
|
||||
|
||||
// ACT
|
||||
await migrateMfImportsToNewPackage(tree);
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read('apps/shell/webpack.config.js', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
"
|
||||
`);
|
||||
expect(tree.read('apps/remote/webpack.config.ts', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
"
|
||||
`);
|
||||
expect(tree.read('apps/shell/module-federation.config.ts', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
"
|
||||
`);
|
||||
expect(tree.read('apps/remote/module-federation.config.js', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should not update the ModuleFederationConfig import when its correct', async () => {
|
||||
// ARRANGE
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
tree.write(
|
||||
'apps/shell/webpack.config.js',
|
||||
`import { ModuleFederationConfig } from '@nx/module-federation';`
|
||||
);
|
||||
tree.write(
|
||||
'apps/shell/module-federation.config.ts',
|
||||
`import { ModuleFederationConfig } from '@nx/module-federation';`
|
||||
);
|
||||
tree.write(
|
||||
'apps/remote/webpack.config.ts',
|
||||
`import { ModuleFederationConfig } from '@nx/module-federation';`
|
||||
);
|
||||
tree.write(
|
||||
'apps/remote/module-federation.config.js',
|
||||
`import { ModuleFederationConfig } from '@nx/module-federation';`
|
||||
);
|
||||
|
||||
// ACT
|
||||
await migrateMfImportsToNewPackage(tree);
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read('apps/shell/webpack.config.js', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
"
|
||||
`);
|
||||
expect(tree.read('apps/remote/webpack.config.ts', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
"
|
||||
`);
|
||||
expect(tree.read('apps/shell/module-federation.config.ts', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
"
|
||||
`);
|
||||
expect(tree.read('apps/remote/module-federation.config.js', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should update the ModuleFederationConfig import to change the import when its across multiple lines', async () => {
|
||||
// ARRANGE
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
tree.write(
|
||||
'apps/shell/webpack.config.js',
|
||||
`import {
|
||||
ModuleFederationConfig
|
||||
} from '@nx/webpack';`
|
||||
);
|
||||
tree.write(
|
||||
'apps/shell/module-federation.config.ts',
|
||||
`import {
|
||||
ModuleFederationConfig
|
||||
} from '@nx/webpack';`
|
||||
);
|
||||
tree.write(
|
||||
'apps/remote/webpack.config.ts',
|
||||
`import {
|
||||
ModuleFederationConfig
|
||||
} from '@nx/webpack';`
|
||||
);
|
||||
tree.write(
|
||||
'apps/remote/module-federation.config.js',
|
||||
`import {
|
||||
ModuleFederationConfig
|
||||
} from '@nx/webpack';`
|
||||
);
|
||||
|
||||
// ACT
|
||||
await migrateMfImportsToNewPackage(tree);
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read('apps/shell/webpack.config.js', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
"
|
||||
`);
|
||||
expect(tree.read('apps/remote/webpack.config.ts', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
"
|
||||
`);
|
||||
expect(tree.read('apps/shell/module-federation.config.ts', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
"
|
||||
`);
|
||||
expect(tree.read('apps/remote/module-federation.config.js', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should update the ModuleFederationConfig import to change the import when its a part of multiple imports', async () => {
|
||||
// ARRANGE
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
tree.write(
|
||||
'apps/shell/webpack.config.js',
|
||||
`import { something, ModuleFederationConfig } from '@nx/webpack';`
|
||||
);
|
||||
tree.write(
|
||||
'apps/shell/module-federation.config.ts',
|
||||
`import { ModuleFederationConfig, something} from '@nx/webpack';`
|
||||
);
|
||||
tree.write(
|
||||
'apps/remote/webpack.config.ts',
|
||||
`import { something, ModuleFederationConfig } from '@nx/webpack';`
|
||||
);
|
||||
tree.write(
|
||||
'apps/remote/module-federation.config.js',
|
||||
`import { ModuleFederationConfig, something } from '@nx/webpack';`
|
||||
);
|
||||
|
||||
// ACT
|
||||
await migrateMfImportsToNewPackage(tree);
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read('apps/shell/webpack.config.js', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
import { something } from '@nx/webpack';
|
||||
"
|
||||
`);
|
||||
expect(tree.read('apps/remote/webpack.config.ts', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
import { something } from '@nx/webpack';
|
||||
"
|
||||
`);
|
||||
expect(tree.read('apps/shell/module-federation.config.ts', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
import { something } from '@nx/webpack';
|
||||
"
|
||||
`);
|
||||
expect(tree.read('apps/remote/module-federation.config.js', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
import { something } from '@nx/webpack';
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should update the ModuleFederationConfig import to change the import when its a part of multiple imports across multiple lines', async () => {
|
||||
// ARRANGE
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
tree.write(
|
||||
'apps/shell/webpack.config.js',
|
||||
`import {
|
||||
something,
|
||||
ModuleFederationConfig
|
||||
} from '@nx/webpack';`
|
||||
);
|
||||
tree.write(
|
||||
'apps/shell/module-federation.config.ts',
|
||||
`import {
|
||||
ModuleFederationConfig,
|
||||
something
|
||||
} from '@nx/webpack';`
|
||||
);
|
||||
tree.write(
|
||||
'apps/remote/webpack.config.ts',
|
||||
`import {
|
||||
something,
|
||||
ModuleFederationConfig
|
||||
} from '@nx/webpack';`
|
||||
);
|
||||
tree.write(
|
||||
'apps/remote/module-federation.config.js',
|
||||
`import {
|
||||
ModuleFederationConfig,
|
||||
something
|
||||
} from '@nx/webpack';`
|
||||
);
|
||||
|
||||
// ACT
|
||||
await migrateMfImportsToNewPackage(tree);
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read('apps/shell/webpack.config.js', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
import { something } from '@nx/webpack';
|
||||
"
|
||||
`);
|
||||
expect(tree.read('apps/remote/webpack.config.ts', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
import { something } from '@nx/webpack';
|
||||
"
|
||||
`);
|
||||
expect(tree.read('apps/shell/module-federation.config.ts', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
import { something } from '@nx/webpack';
|
||||
"
|
||||
`);
|
||||
expect(tree.read('apps/remote/module-federation.config.js', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
import { something } from '@nx/webpack';
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should not incorrectly update import when it is run twice', async () => {
|
||||
// ARRANGE
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
tree.write(
|
||||
`apps/shell/webpack.config.ts`,
|
||||
`import { composePlugins, withNx, ModuleFederationConfig } from '@nx/webpack';
|
||||
import { withReact } from '@nx/react';
|
||||
import { withModuleFederation } from '@nx/react/module-federation';`
|
||||
);
|
||||
|
||||
// ACT
|
||||
await migrateMfImportsToNewPackage(tree);
|
||||
await migrateMfImportsToNewPackage(tree);
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read('apps/shell/webpack.config.ts', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
import { composePlugins, withNx } from '@nx/webpack';
|
||||
import { withReact } from '@nx/react';
|
||||
import { withModuleFederation } from '@nx/react/module-federation';
|
||||
"
|
||||
`);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,78 @@
|
||||
import { createProjectGraphAsync, Tree } from '@nx/devkit';
|
||||
import { formatFiles, visitNotIgnoredFiles } from '@nx/devkit';
|
||||
import { tsquery } from '@phenomnomnominal/tsquery';
|
||||
|
||||
const MF_IMPORT_TO_UPDATE = 'ModuleFederationConfig';
|
||||
const MF_CONFIG_IMPORT_SELECTOR = `ImportDeclaration:has(StringLiteral[value=@nx/webpack]):has(Identifier[name=ModuleFederationConfig])`;
|
||||
const IMPORT_TOKENS_SELECTOR = `ImportClause ImportSpecifier`;
|
||||
const MF_CONFIG_IMPORT_SPECIFIER_SELECTOR = `ImportClause ImportSpecifier > Identifier[name=ModuleFederationConfig]`;
|
||||
const WEBPACK_IMPORT_SELECTOR = `ImportDeclaration > StringLiteral[value=@nx/webpack]`;
|
||||
|
||||
export default async function migrateMfImportsToNewPackage(tree: Tree) {
|
||||
const rootsToCheck = new Set<string>();
|
||||
|
||||
const graph = await createProjectGraphAsync();
|
||||
for (const [project, dependencies] of Object.entries(graph.dependencies)) {
|
||||
const usesNxWebpack = dependencies.some(
|
||||
(dep) => dep.target === 'npm:@nx/webpack'
|
||||
);
|
||||
if (usesNxWebpack) {
|
||||
const root = graph.nodes[project].data.root;
|
||||
rootsToCheck.add(root);
|
||||
}
|
||||
}
|
||||
|
||||
for (const root of rootsToCheck) {
|
||||
visitNotIgnoredFiles(tree, root, (filePath) => {
|
||||
if (!filePath.endsWith('.ts') && !filePath.endsWith('.js')) {
|
||||
return;
|
||||
}
|
||||
|
||||
let contents = tree.read(filePath, 'utf-8');
|
||||
if (!contents.includes(MF_IMPORT_TO_UPDATE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ast = tsquery.ast(contents);
|
||||
const importNodes = tsquery(ast, MF_CONFIG_IMPORT_SELECTOR);
|
||||
if (importNodes.length === 0) {
|
||||
return;
|
||||
}
|
||||
const importNode = importNodes[0];
|
||||
const importSpecifiers = tsquery(importNode, IMPORT_TOKENS_SELECTOR);
|
||||
if (importSpecifiers.length > 1) {
|
||||
const mfConfigImportSpecifierNode = tsquery(
|
||||
importNode,
|
||||
MF_CONFIG_IMPORT_SPECIFIER_SELECTOR
|
||||
)[0];
|
||||
const end =
|
||||
contents.charAt(mfConfigImportSpecifierNode.getEnd()) === ','
|
||||
? mfConfigImportSpecifierNode.getEnd() + 1
|
||||
: mfConfigImportSpecifierNode.getEnd();
|
||||
contents = `import { ${MF_IMPORT_TO_UPDATE} } from '@nx/module-federation';
|
||||
${contents.slice(
|
||||
0,
|
||||
mfConfigImportSpecifierNode.getStart()
|
||||
)}${contents.slice(end)}`;
|
||||
} else {
|
||||
const nxWebpackImportStringNodes = tsquery(
|
||||
ast,
|
||||
WEBPACK_IMPORT_SELECTOR
|
||||
);
|
||||
if (nxWebpackImportStringNodes.length === 0) {
|
||||
return;
|
||||
}
|
||||
const nxWebpackImportStringNode = nxWebpackImportStringNodes[0];
|
||||
contents = `${contents.slice(
|
||||
0,
|
||||
nxWebpackImportStringNode.getStart()
|
||||
)}'@nx/module-federation'${contents.slice(
|
||||
nxWebpackImportStringNode.getEnd()
|
||||
)}`;
|
||||
}
|
||||
tree.write(filePath, contents);
|
||||
});
|
||||
}
|
||||
|
||||
await formatFiles(tree);
|
||||
}
|
||||
@ -8,7 +8,7 @@ import {
|
||||
SharedLibraryConfig,
|
||||
sharePackages,
|
||||
shareWorkspaceLibraries,
|
||||
} from '@nx/webpack/src/utils/module-federation';
|
||||
} from '@nx/module-federation';
|
||||
|
||||
import {
|
||||
createProjectGraphAsync,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type {
|
||||
ModuleFederationConfig,
|
||||
NxModuleFederationConfigOverride,
|
||||
} from '@nx/webpack/src/utils/module-federation';
|
||||
} from '@nx/module-federation';
|
||||
import { getModuleFederationConfig } from './utils';
|
||||
|
||||
export async function withModuleFederationForSSR(
|
||||
@ -58,7 +58,7 @@ export async function withModuleFederationForSSR(
|
||||
...(configOverride?.runtimePlugins ?? []),
|
||||
require.resolve('@module-federation/node/runtimePlugin'),
|
||||
require.resolve(
|
||||
'@nx/webpack/src/utils/module-federation/plugins/runtime-library-control.plugin.js'
|
||||
'@nx/module-federation/src/utils/plugins/runtime-library-control.plugin.js'
|
||||
),
|
||||
]
|
||||
: [
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type {
|
||||
ModuleFederationConfig,
|
||||
NxModuleFederationConfigOverride,
|
||||
} from '@nx/webpack/src/utils/module-federation';
|
||||
} from '@nx/module-federation';
|
||||
import { getModuleFederationConfig } from './utils';
|
||||
import { ModuleFederationPlugin } from '@module-federation/enhanced/webpack';
|
||||
|
||||
@ -62,7 +62,7 @@ export async function withModuleFederation(
|
||||
? [
|
||||
...(configOverride?.runtimePlugins ?? []),
|
||||
require.resolve(
|
||||
'@nx/webpack/src/utils/module-federation/plugins/runtime-library-control.plugin.js'
|
||||
'@nx/module-federation/src/utils/plugins/runtime-library-control.plugin.js'
|
||||
),
|
||||
]
|
||||
: configOverride?.runtimePlugins,
|
||||
|
||||
@ -0,0 +1 @@
|
||||
export * from './src/utils/public-api';
|
||||
@ -24,7 +24,16 @@
|
||||
"main": "index.js",
|
||||
"executors": "./executors.json",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
"tslib": "^2.3.0",
|
||||
"@nx/devkit": "file:../devkit",
|
||||
"@nx/js": "file:../js",
|
||||
"picocolors": "^1.1.0",
|
||||
"@module-federation/sdk": "0.6.9",
|
||||
"webpack": "5.88.0",
|
||||
"@rspack/core": "1.0.5",
|
||||
"@module-federation/enhanced": "0.6.9",
|
||||
"express": "^4.19.2",
|
||||
"http-proxy-middleware": "^3.0.3"
|
||||
},
|
||||
"peerDependencies": {},
|
||||
"nx-migrations": {
|
||||
|
||||
@ -10,11 +10,6 @@
|
||||
"outputPath": "build/packages/module-federation",
|
||||
"tsConfig": "packages/module-federation/tsconfig.lib.json",
|
||||
"main": "packages/module-federation/index.ts",
|
||||
"generateExportsField": true,
|
||||
"additionalEntryPoints": [
|
||||
"{projectRoot}/{executors,generators,migrations}.json",
|
||||
"{projectRoot}/plugin.ts"
|
||||
],
|
||||
"assets": [
|
||||
{
|
||||
"input": "packages/module-federation",
|
||||
|
||||
@ -4,7 +4,7 @@ import { findMatchingProjects } from 'nx/src/utils/find-matching-projects';
|
||||
import * as pc from 'picocolors';
|
||||
import { join } from 'path';
|
||||
import { existsSync, readFileSync } from 'fs';
|
||||
import { ModuleFederationConfig } from './models/index';
|
||||
import { ModuleFederationConfig } from './models';
|
||||
|
||||
interface ModuleFederationExecutorContext {
|
||||
projectName: string;
|
||||
@ -4,3 +4,6 @@ export * from './package-json';
|
||||
export * from './remotes';
|
||||
export * from './models';
|
||||
export * from './get-remotes-for-host';
|
||||
export * from './parse-static-remotes-config';
|
||||
export * from './start-remote-proxies';
|
||||
export * from './start-ssr-remote-proxies';
|
||||
@ -1,5 +1,5 @@
|
||||
import type { moduleFederationPlugin } from '@module-federation/sdk';
|
||||
import type { NormalModuleReplacementPlugin } from '@rspack/core';
|
||||
import type { NormalModuleReplacementPlugin as RspackNormalModuleReplacementPlugin } from '@rspack/core';
|
||||
|
||||
export type ModuleFederationLibrary = { type: string; name: string };
|
||||
|
||||
@ -15,7 +15,9 @@ export type SharedWorkspaceLibraryConfig = {
|
||||
projectRoot: string,
|
||||
eager?: boolean
|
||||
) => Record<string, SharedLibraryConfig>;
|
||||
getReplacementPlugin: () => NormalModuleReplacementPlugin;
|
||||
getReplacementPlugin: () =>
|
||||
| RspackNormalModuleReplacementPlugin
|
||||
| import('webpack').NormalModuleReplacementPlugin;
|
||||
};
|
||||
|
||||
export type Remotes = Array<string | [remoteName: string, remoteUrl: string]>;
|
||||
@ -55,13 +57,7 @@ export interface ModuleFederationConfig {
|
||||
|
||||
export type NxModuleFederationConfigOverride = Omit<
|
||||
moduleFederationPlugin.ModuleFederationPluginOptions,
|
||||
| 'exposes'
|
||||
| 'remotes'
|
||||
| 'name'
|
||||
| 'library'
|
||||
| 'shared'
|
||||
| 'filename'
|
||||
| 'remoteType'
|
||||
'exposes' | 'remotes' | 'name' | 'shared' | 'filename'
|
||||
>;
|
||||
|
||||
export type WorkspaceLibrarySecondaryEntryPoint = {
|
||||
@ -2,6 +2,7 @@ import {
|
||||
AdditionalSharedConfig,
|
||||
ModuleFederationConfig,
|
||||
ModuleFederationLibrary,
|
||||
NxModuleFederationConfigOverride,
|
||||
Remotes,
|
||||
SharedFunction,
|
||||
SharedLibraryConfig,
|
||||
@ -26,6 +27,7 @@ import { readRootPackageJson } from './package-json';
|
||||
|
||||
export {
|
||||
ModuleFederationConfig,
|
||||
NxModuleFederationConfigOverride,
|
||||
SharedLibraryConfig,
|
||||
SharedWorkspaceLibraryConfig,
|
||||
AdditionalSharedConfig,
|
||||
@ -20,6 +20,7 @@ import {
|
||||
} from '@nx/devkit';
|
||||
import { existsSync } from 'fs';
|
||||
import type { PackageJson } from 'nx/src/utils/package-json';
|
||||
import { NormalModuleReplacementPlugin as RspackNormalModuleReplacementPlugin } from '@rspack/core';
|
||||
|
||||
/**
|
||||
* Build an object of functions to be used with the ModuleFederationPlugin to
|
||||
@ -27,10 +28,12 @@ import type { PackageJson } from 'nx/src/utils/package-json';
|
||||
*
|
||||
* @param workspaceLibs - The Nx Workspace Libraries to share
|
||||
* @param tsConfigPath - The path to TS Config File that contains the Path Mappings for the Libraries
|
||||
* @param bundler - The bundler to use for the replacement plugin
|
||||
*/
|
||||
export function shareWorkspaceLibraries(
|
||||
workspaceLibs: WorkspaceLibrary[],
|
||||
tsConfigPath = process.env.NX_TSCONFIG_PATH ?? getRootTsConfigPath()
|
||||
tsConfigPath = process.env.NX_TSCONFIG_PATH ?? getRootTsConfigPath(),
|
||||
bundler: 'rspack' | 'webpack' = 'rspack'
|
||||
): SharedWorkspaceLibraryConfig {
|
||||
if (!workspaceLibs) {
|
||||
return getEmptySharedLibrariesConfig();
|
||||
@ -76,14 +79,17 @@ export function shareWorkspaceLibraries(
|
||||
});
|
||||
}
|
||||
|
||||
const webpack = require('webpack');
|
||||
const normalModuleReplacementPluginImpl =
|
||||
bundler === 'rspack'
|
||||
? RspackNormalModuleReplacementPlugin
|
||||
: require('webpack').NormalModuleReplacementPlugin;
|
||||
|
||||
return {
|
||||
getAliases: () =>
|
||||
pathMappings.reduce(
|
||||
(aliases, library) => ({
|
||||
...aliases,
|
||||
// If the library path ends in a wildcard, remove it as webpack can't handle this in resolve.alias
|
||||
// If the library path ends in a wildcard, remove it as webpack/rspack can't handle this in resolve.alias
|
||||
// e.g. path/to/my/lib/* -> path/to/my/lib
|
||||
[library.name]: library.path.replace(/\/\*$/, ''),
|
||||
}),
|
||||
@ -139,7 +145,7 @@ export function shareWorkspaceLibraries(
|
||||
}, {} as Record<string, SharedLibraryConfig>);
|
||||
},
|
||||
getReplacementPlugin: () =>
|
||||
new webpack.NormalModuleReplacementPlugin(/./, (req) => {
|
||||
new normalModuleReplacementPluginImpl(/./, (req) => {
|
||||
if (!req.request.startsWith('.')) {
|
||||
return;
|
||||
}
|
||||
@ -156,7 +162,7 @@ export function shareWorkspaceLibraries(
|
||||
* library.path is usually in the form of "/Users/username/path/to/Workspace/path/to/library"
|
||||
*
|
||||
* When a wildcard is used in the TS path mappings, we want to get everything after the import to
|
||||
* re-route the request correctly inline with the webpack resolve.alias
|
||||
* re-route the request correctly inline with the webpack/rspack resolve.alias
|
||||
*/
|
||||
join(
|
||||
library.name,
|
||||
@ -317,12 +323,17 @@ function addStringDependencyToSharedConfig(
|
||||
}
|
||||
}
|
||||
|
||||
function getEmptySharedLibrariesConfig() {
|
||||
const webpack = require('webpack');
|
||||
function getEmptySharedLibrariesConfig(
|
||||
bundler: 'rspack' | 'webpack' = 'rspack'
|
||||
) {
|
||||
const normalModuleReplacementPluginImpl =
|
||||
bundler === 'rspack'
|
||||
? RspackNormalModuleReplacementPlugin
|
||||
: require('webpack').NormalModuleReplacementPlugin;
|
||||
return {
|
||||
getAliases: () => ({}),
|
||||
getLibraries: () => ({}),
|
||||
getReplacementPlugin: () =>
|
||||
new webpack.NormalModuleReplacementPlugin(/./, () => {}),
|
||||
new normalModuleReplacementPluginImpl(/./, () => {}),
|
||||
};
|
||||
}
|
||||
@ -29,6 +29,12 @@
|
||||
"version": "19.6.1-beta.0",
|
||||
"description": "Ensure Target Defaults are set correctly for Module Federation.",
|
||||
"factory": "./src/migrations/update-19-6-1/ensure-depends-on-for-mf"
|
||||
},
|
||||
"update-20-2-0-update-module-federation-config-import": {
|
||||
"cli": "nx",
|
||||
"version": "20.2.0-beta.2",
|
||||
"description": "Update the ModuleFederationConfig import use @nx/module-federation.",
|
||||
"factory": "./src/migrations/update-20-2-0/migrate-mf-imports-to-new-package"
|
||||
}
|
||||
},
|
||||
"packageJsonUpdates": {
|
||||
|
||||
@ -43,6 +43,7 @@
|
||||
"@nx/js": "file:../js",
|
||||
"@nx/eslint": "file:../eslint",
|
||||
"@nx/web": "file:../web",
|
||||
"@nx/module-federation": "file:../module-federation",
|
||||
"express": "^4.19.2",
|
||||
"http-proxy-middleware": "^3.0.3"
|
||||
},
|
||||
|
||||
@ -12,7 +12,10 @@ import { ModuleFederationDevServerOptions } from './schema';
|
||||
import {
|
||||
getModuleFederationConfig,
|
||||
getRemotes,
|
||||
} from '@nx/webpack/src/utils/module-federation';
|
||||
startRemoteProxies,
|
||||
parseStaticRemotesConfig,
|
||||
type StaticRemotesConfig,
|
||||
} from '@nx/module-federation/src/utils';
|
||||
import {
|
||||
combineAsyncIterables,
|
||||
createAsyncIterable,
|
||||
@ -20,11 +23,6 @@ import {
|
||||
import { waitForPortOpen } from '@nx/web/src/utils/wait-for-port-open';
|
||||
import { cpSync, existsSync } from 'fs';
|
||||
import { extname, join } from 'path';
|
||||
import { startRemoteProxies } from '@nx/webpack/src/utils/module-federation/start-remote-proxies';
|
||||
import {
|
||||
parseStaticRemotesConfig,
|
||||
type StaticRemotesConfig,
|
||||
} from '@nx/webpack/src/utils/module-federation/parse-static-remotes-config';
|
||||
import { buildStaticRemotes } from '../../utils/build-static.remotes';
|
||||
|
||||
function getBuildOptions(buildTarget: string, context: ExecutorContext) {
|
||||
|
||||
@ -12,7 +12,10 @@ import { extname, join } from 'path';
|
||||
import {
|
||||
getModuleFederationConfig,
|
||||
getRemotes,
|
||||
} from '@nx/webpack/src/utils/module-federation';
|
||||
parseStaticSsrRemotesConfig,
|
||||
type StaticRemotesConfig,
|
||||
startSsrRemoteProxies,
|
||||
} from '@nx/module-federation/src/utils';
|
||||
|
||||
import {
|
||||
combineAsyncIterables,
|
||||
@ -21,14 +24,8 @@ import {
|
||||
import { fork } from 'child_process';
|
||||
import { cpSync, createWriteStream, existsSync } from 'fs';
|
||||
|
||||
import {
|
||||
parseStaticSsrRemotesConfig,
|
||||
type StaticRemotesConfig,
|
||||
} from '@nx/webpack/src/utils/module-federation/parse-static-remotes-config';
|
||||
|
||||
import fileServerExecutor from '@nx/web/src/executors/file-server/file-server.impl';
|
||||
import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
|
||||
import { startSsrRemoteProxies } from '@nx/webpack/src/utils/module-federation/start-ssr-remote-proxies';
|
||||
import { waitForPortOpen } from '@nx/web/src/utils/wait-for-port-open';
|
||||
|
||||
type ModuleFederationSsrDevServerOptions = WebSsrDevServerOptions & {
|
||||
|
||||
@ -13,11 +13,9 @@ import { cpSync, existsSync, readFileSync, rmSync } from 'fs';
|
||||
import {
|
||||
getModuleFederationConfig,
|
||||
getRemotes,
|
||||
} from '@nx/webpack/src/utils/module-federation';
|
||||
import {
|
||||
parseStaticRemotesConfig,
|
||||
StaticRemotesConfig,
|
||||
} from '@nx/webpack/src/utils/module-federation/parse-static-remotes-config';
|
||||
} from '@nx/module-federation/src/utils';
|
||||
import { buildStaticRemotes } from '../../utils/build-static.remotes';
|
||||
import { fork } from 'child_process';
|
||||
import type { WebpackExecutorOptions } from '@nx/webpack';
|
||||
|
||||
@ -28,7 +28,7 @@ exports[`hostGenerator bundler=rspack should generate host files and configs for
|
||||
"// @ts-check
|
||||
|
||||
/**
|
||||
* @type {import('@nx/rspack/module-federation').ModuleFederationConfig}
|
||||
* @type {import('@nx/module-federation').ModuleFederationConfig}
|
||||
**/
|
||||
const moduleFederationConfig = {
|
||||
name: 'test',
|
||||
@ -67,7 +67,7 @@ export default composePlugins(
|
||||
`;
|
||||
|
||||
exports[`hostGenerator bundler=rspack should generate host files and configs for SSR when --typescriptConfiguration=true 2`] = `
|
||||
"import { ModuleFederationConfig } from '@nx/rspack/module-federation';
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
name: 'test',
|
||||
@ -130,7 +130,8 @@ module.exports = {
|
||||
|
||||
exports[`hostGenerator bundler=rspack should generate host files and configs when --typescriptConfiguration=true 1`] = `
|
||||
"import {composePlugins, withNx, withReact} from '@nx/rspack';
|
||||
import {withModuleFederation, ModuleFederationConfig} from '@nx/rspack/module-federation';
|
||||
import { withModuleFederation } from '@nx/rspack/module-federation';
|
||||
import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
|
||||
import baseConfig from './module-federation.config';
|
||||
|
||||
@ -149,7 +150,7 @@ export default composePlugins(withNx(), withReact(), withModuleFederation(config
|
||||
`;
|
||||
|
||||
exports[`hostGenerator bundler=rspack should generate host files and configs when --typescriptConfiguration=true 2`] = `
|
||||
"import { ModuleFederationConfig } from '@nx/rspack/module-federation';
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
name: 'test',
|
||||
|
||||
@ -29,7 +29,7 @@ exports[`hostGenerator bundler=webpack should generate host files and configs fo
|
||||
"// @ts-check
|
||||
|
||||
/**
|
||||
* @type {import('@nx/webpack').ModuleFederationConfig}
|
||||
* @type {import('@nx/module-federation').ModuleFederationConfig}
|
||||
**/
|
||||
const moduleFederationConfig = {
|
||||
name: 'test',
|
||||
@ -69,7 +69,7 @@ export default composePlugins(
|
||||
`;
|
||||
|
||||
exports[`hostGenerator bundler=webpack should generate host files and configs for SSR when --typescriptConfiguration=true 2`] = `
|
||||
"import { ModuleFederationConfig } from '@nx/webpack';
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
name: 'test',
|
||||
@ -132,9 +132,10 @@ module.exports = {
|
||||
`;
|
||||
|
||||
exports[`hostGenerator bundler=webpack should generate host files and configs when --typescriptConfiguration=true 1`] = `
|
||||
"import {composePlugins, withNx, ModuleFederationConfig} from '@nx/webpack';
|
||||
"import {composePlugins, withNx} from '@nx/webpack';
|
||||
import {withReact} from '@nx/react';
|
||||
import {withModuleFederation} from '@nx/react/module-federation';
|
||||
import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
|
||||
import baseConfig from './module-federation.config';
|
||||
|
||||
@ -153,7 +154,7 @@ export default composePlugins(withNx(), withReact(), withModuleFederation(config
|
||||
`;
|
||||
|
||||
exports[`hostGenerator bundler=webpack should generate host files and configs when --typescriptConfiguration=true 2`] = `
|
||||
"import { ModuleFederationConfig } from '@nx/webpack';
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
name: 'test',
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ModuleFederationConfig } from '@nx/rspack/module-federation';
|
||||
import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
name: '<%= projectName %>',
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @type {import('@nx/rspack/module-federation').ModuleFederationConfig}
|
||||
* @type {import('@nx/module-federation').ModuleFederationConfig}
|
||||
**/
|
||||
const moduleFederationConfig = {
|
||||
name: '<%= projectName %>',
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ModuleFederationConfig } from '@nx/rspack/module-federation';
|
||||
import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
name: '<%= projectName %>',
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { composePlugins, withNx, withReact } from '@nx/rspack';
|
||||
import { withModuleFederation, ModuleFederationConfig } from '@nx/rspack/module-federation';
|
||||
import { withModuleFederation } from '@nx/rspack/module-federation';
|
||||
import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
|
||||
import baseConfig from './module-federation.config';
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import {composePlugins, withNx, withReact} from '@nx/rspack';
|
||||
import {withModuleFederation, ModuleFederationConfig} from '@nx/rspack/module-federation';
|
||||
import { withModuleFederation } from '@nx/rspack/module-federation';
|
||||
import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
|
||||
import baseConfig from './module-federation.config';
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ModuleFederationConfig } from '@nx/webpack';
|
||||
import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
name: '<%= projectName %>',
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @type {import('@nx/webpack').ModuleFederationConfig}
|
||||
* @type {import('@nx/module-federation').ModuleFederationConfig}
|
||||
**/
|
||||
const moduleFederationConfig = {
|
||||
name: '<%= projectName %>',
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ModuleFederationConfig } from '@nx/webpack';
|
||||
import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
name: '<%= projectName %>',
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { composePlugins, withNx } from '@nx/webpack';
|
||||
import { withReact } from '@nx/react';
|
||||
import { withModuleFederation } from '@nx/react/module-federation';
|
||||
import { ModuleFederationConfig } from '@nx/webpack';
|
||||
import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
|
||||
import baseConfig from './module-federation.config';
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import {composePlugins, withNx, ModuleFederationConfig} from '@nx/webpack';
|
||||
import {composePlugins, withNx} from '@nx/webpack';
|
||||
import {withReact} from '@nx/react';
|
||||
import {withModuleFederation} from '@nx/react/module-federation';
|
||||
import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
|
||||
import baseConfig from './module-federation.config';
|
||||
|
||||
|
||||
@ -100,7 +100,7 @@ exports[`remote generator bundler=rspack should create the remote with the corre
|
||||
`;
|
||||
|
||||
exports[`remote generator bundler=rspack should create the remote with the correct config files when --typescriptConfiguration=true 3`] = `
|
||||
"import { ModuleFederationConfig } from '@nx/rspack/module-federation';
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
name: 'test',
|
||||
@ -174,7 +174,7 @@ export default composePlugins(
|
||||
`;
|
||||
|
||||
exports[`remote generator bundler=rspack should generate correct remote with config files when using --ssr and --typescriptConfiguration=true 2`] = `
|
||||
"import { ModuleFederationConfig } from '@nx/rspack/module-federation';
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
name: 'test',
|
||||
|
||||
@ -103,7 +103,7 @@ exports[`remote generator bundler=webpack should create the remote with the corr
|
||||
`;
|
||||
|
||||
exports[`remote generator bundler=webpack should create the remote with the correct config files when --typescriptConfiguration=true 3`] = `
|
||||
"import { ModuleFederationConfig } from '@nx/webpack';
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
name: 'test',
|
||||
@ -179,7 +179,7 @@ export default composePlugins(
|
||||
`;
|
||||
|
||||
exports[`remote generator bundler=webpack should generate correct remote with config files when using --ssr and --typescriptConfiguration=true 2`] = `
|
||||
"import { ModuleFederationConfig } from '@nx/webpack';
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
name: 'test',
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import {ModuleFederationConfig} from '@nx/rspack/module-federation';
|
||||
import {ModuleFederationConfig} from '@nx/module-federation';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
name: '<%= projectName %>',
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import {ModuleFederationConfig} from '@nx/rspack/module-federation';
|
||||
import {ModuleFederationConfig} from '@nx/module-federation';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
name: '<%= projectName %>',
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import {ModuleFederationConfig} from '@nx/webpack';
|
||||
import {ModuleFederationConfig} from '@nx/module-federation';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
name: '<%= projectName %>',
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import {ModuleFederationConfig} from '@nx/webpack';
|
||||
import {ModuleFederationConfig} from '@nx/module-federation';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
name: '<%= projectName %>',
|
||||
|
||||
@ -0,0 +1,340 @@
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import migrateMfImportsToNewPackage from './migrate-mf-imports-to-new-package';
|
||||
|
||||
jest.mock('@nx/devkit', () => {
|
||||
const original = jest.requireActual('@nx/devkit');
|
||||
return {
|
||||
...original,
|
||||
createProjectGraphAsync: jest.fn().mockResolvedValue(
|
||||
Promise.resolve({
|
||||
dependencies: {
|
||||
shell: [
|
||||
{
|
||||
source: 'shell',
|
||||
target: 'npm:@nx/webpack',
|
||||
type: 'static',
|
||||
},
|
||||
{
|
||||
source: 'shell',
|
||||
target: 'npm:@nx/rspack',
|
||||
type: 'static',
|
||||
},
|
||||
],
|
||||
remote: [
|
||||
{
|
||||
source: 'remote',
|
||||
target: 'npm:@nx/webpack',
|
||||
type: 'static',
|
||||
},
|
||||
],
|
||||
},
|
||||
nodes: {
|
||||
shell: {
|
||||
name: 'shell',
|
||||
type: 'app',
|
||||
data: {
|
||||
root: 'apps/shell',
|
||||
sourceRoot: 'shell/src',
|
||||
targets: {},
|
||||
},
|
||||
},
|
||||
remote: {
|
||||
name: 'remote',
|
||||
type: 'app',
|
||||
data: {
|
||||
root: 'apps/remote',
|
||||
sourceRoot: 'remote/src',
|
||||
targets: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
describe.each([
|
||||
['webpack', '@nx/webpack'],
|
||||
['rspack', '@nx/rspack/module-federation'],
|
||||
])('migrate-mf-imports-to-new-package --bundler=%s', (bundler, importPath) => {
|
||||
it('should update the ModuleFederationConfig import to change the import when its a single line', async () => {
|
||||
// ARRANGE
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
tree.write(
|
||||
`apps/shell/${bundler}.config.js`,
|
||||
`import { ModuleFederationConfig } from '${importPath}';`
|
||||
);
|
||||
tree.write(
|
||||
'apps/shell/module-federation.config.ts',
|
||||
`import { ModuleFederationConfig } from '${importPath}';`
|
||||
);
|
||||
tree.write(
|
||||
`apps/remote/${bundler}.config.ts`,
|
||||
`import { ModuleFederationConfig } from '${importPath}';`
|
||||
);
|
||||
tree.write(
|
||||
'apps/remote/module-federation.config.js',
|
||||
`import { ModuleFederationConfig } from '${importPath}';`
|
||||
);
|
||||
|
||||
// ACT
|
||||
await migrateMfImportsToNewPackage(tree);
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read(`apps/shell/${bundler}.config.js`, 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
"
|
||||
`);
|
||||
expect(tree.read(`apps/remote/${bundler}.config.ts`, 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
"
|
||||
`);
|
||||
expect(tree.read('apps/shell/module-federation.config.ts', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
"
|
||||
`);
|
||||
expect(tree.read('apps/remote/module-federation.config.js', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should not update the ModuleFederationConfig import when its correct', async () => {
|
||||
// ARRANGE
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
tree.write(
|
||||
`apps/shell/${bundler}.config.js`,
|
||||
`import { ModuleFederationConfig } from '@nx/module-federation';`
|
||||
);
|
||||
tree.write(
|
||||
'apps/shell/module-federation.config.ts',
|
||||
`import { ModuleFederationConfig } from '@nx/module-federation';`
|
||||
);
|
||||
tree.write(
|
||||
`apps/remote/${bundler}.config.ts`,
|
||||
`import { ModuleFederationConfig } from '@nx/module-federation';`
|
||||
);
|
||||
tree.write(
|
||||
'apps/remote/module-federation.config.js',
|
||||
`import { ModuleFederationConfig } from '@nx/module-federation';`
|
||||
);
|
||||
|
||||
// ACT
|
||||
await migrateMfImportsToNewPackage(tree);
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read(`apps/shell/${bundler}.config.js`, 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
"
|
||||
`);
|
||||
expect(tree.read(`apps/remote/${bundler}.config.ts`, 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
"
|
||||
`);
|
||||
expect(tree.read('apps/shell/module-federation.config.ts', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
"
|
||||
`);
|
||||
expect(tree.read('apps/remote/module-federation.config.js', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should update the ModuleFederationConfig import to change the import when its across multiple lines', async () => {
|
||||
// ARRANGE
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
tree.write(
|
||||
`apps/shell/${bundler}.config.js`,
|
||||
`import {
|
||||
ModuleFederationConfig
|
||||
} from '${importPath}';`
|
||||
);
|
||||
tree.write(
|
||||
'apps/shell/module-federation.config.ts',
|
||||
`import {
|
||||
ModuleFederationConfig
|
||||
} from '${importPath}';`
|
||||
);
|
||||
tree.write(
|
||||
`apps/remote/${bundler}.config.ts`,
|
||||
`import {
|
||||
ModuleFederationConfig
|
||||
} from '${importPath}';`
|
||||
);
|
||||
tree.write(
|
||||
'apps/remote/module-federation.config.js',
|
||||
`import {
|
||||
ModuleFederationConfig
|
||||
} from '${importPath}';`
|
||||
);
|
||||
|
||||
// ACT
|
||||
await migrateMfImportsToNewPackage(tree);
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read(`apps/shell/${bundler}.config.js`, 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
"
|
||||
`);
|
||||
expect(tree.read(`apps/remote/${bundler}.config.ts`, 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
"
|
||||
`);
|
||||
expect(tree.read('apps/shell/module-federation.config.ts', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
"
|
||||
`);
|
||||
expect(tree.read('apps/remote/module-federation.config.js', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should update the ModuleFederationConfig import to change the import when its a part of multiple imports', async () => {
|
||||
// ARRANGE
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
tree.write(
|
||||
`apps/shell/${bundler}.config.js`,
|
||||
`import { something, ModuleFederationConfig } from '${importPath}';`
|
||||
);
|
||||
tree.write(
|
||||
'apps/shell/module-federation.config.ts',
|
||||
`import { ModuleFederationConfig, something} from '${importPath}';`
|
||||
);
|
||||
tree.write(
|
||||
`apps/remote/${bundler}.config.ts`,
|
||||
`import { something, ModuleFederationConfig } from '${importPath}';`
|
||||
);
|
||||
tree.write(
|
||||
'apps/remote/module-federation.config.js',
|
||||
`import { ModuleFederationConfig, something } from '${importPath}';`
|
||||
);
|
||||
|
||||
// ACT
|
||||
await migrateMfImportsToNewPackage(tree);
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read(`apps/shell/${bundler}.config.js`, 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
import { something } from '${importPath}';
|
||||
"
|
||||
`);
|
||||
expect(tree.read(`apps/remote/${bundler}.config.ts`, 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
import { something } from '${importPath}';
|
||||
"
|
||||
`);
|
||||
expect(tree.read('apps/shell/module-federation.config.ts', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
import { something } from '${importPath}';
|
||||
"
|
||||
`);
|
||||
expect(tree.read('apps/remote/module-federation.config.js', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
import { something } from '${importPath}';
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should update the ModuleFederationConfig import to change the import when its a part of multiple imports across multiple lines', async () => {
|
||||
// ARRANGE
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
tree.write(
|
||||
`apps/shell/${bundler}.config.js`,
|
||||
`import {
|
||||
something,
|
||||
ModuleFederationConfig
|
||||
} from '${importPath}';`
|
||||
);
|
||||
tree.write(
|
||||
'apps/shell/module-federation.config.ts',
|
||||
`import {
|
||||
ModuleFederationConfig,
|
||||
something
|
||||
} from '${importPath}';`
|
||||
);
|
||||
tree.write(
|
||||
`apps/remote/${bundler}.config.ts`,
|
||||
`import {
|
||||
something,
|
||||
ModuleFederationConfig
|
||||
} from '${importPath}';`
|
||||
);
|
||||
tree.write(
|
||||
'apps/remote/module-federation.config.js',
|
||||
`import {
|
||||
ModuleFederationConfig,
|
||||
something
|
||||
} from '${importPath}';`
|
||||
);
|
||||
|
||||
// ACT
|
||||
await migrateMfImportsToNewPackage(tree);
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read(`apps/shell/${bundler}.config.js`, 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
import { something } from '${importPath}';
|
||||
"
|
||||
`);
|
||||
expect(tree.read(`apps/remote/${bundler}.config.ts`, 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
import { something } from '${importPath}';
|
||||
"
|
||||
`);
|
||||
expect(tree.read('apps/shell/module-federation.config.ts', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
import { something } from '${importPath}';
|
||||
"
|
||||
`);
|
||||
expect(tree.read('apps/remote/module-federation.config.js', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
import { something } from '${importPath}';
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should update the correct import when there are multiple from the bundler', async () => {
|
||||
// ARRANGE
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
tree.write(
|
||||
`apps/shell/${bundler}.config.js`,
|
||||
`import {something} from '${importPath}';
|
||||
import {
|
||||
ModuleFederationConfig
|
||||
} from '${importPath}';`
|
||||
);
|
||||
|
||||
// ACT
|
||||
await migrateMfImportsToNewPackage(tree);
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read(`apps/shell/${bundler}.config.js`, 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { something } from '${importPath}';
|
||||
import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
"
|
||||
`);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,87 @@
|
||||
import { createProjectGraphAsync, Tree } from '@nx/devkit';
|
||||
import { formatFiles, visitNotIgnoredFiles } from '@nx/devkit';
|
||||
import { tsquery } from '@phenomnomnominal/tsquery';
|
||||
|
||||
const MF_IMPORT_TO_UPDATE = 'ModuleFederationConfig';
|
||||
const MF_CONFIG_IMPORT_SELECTOR = `ImportDeclaration:has(StringLiteral[value=@nx/webpack]):has(Identifier[name=ModuleFederationConfig]),ImportDeclaration:has(StringLiteral[value=@nx/rspack/module-federation]):has(Identifier[name=ModuleFederationConfig])`;
|
||||
const IMPORT_TOKENS_SELECTOR = `ImportClause ImportSpecifier`;
|
||||
const MF_CONFIG_IMPORT_SPECIFIER_SELECTOR = `ImportClause ImportSpecifier > Identifier[name=ModuleFederationConfig]`;
|
||||
const WEBPACK_IMPORT_SELECTOR = `ImportDeclaration > StringLiteral[value=@nx/webpack]`;
|
||||
const RSPACK_IMPORT_SELECTOR = `ImportDeclaration > StringLiteral[value=@nx/rspack/module-federation]`;
|
||||
|
||||
export default async function migrateMfImportsToNewPackage(tree: Tree) {
|
||||
const rootsToCheck = new Set<string>();
|
||||
|
||||
const graph = await createProjectGraphAsync();
|
||||
for (const [project, dependencies] of Object.entries(graph.dependencies)) {
|
||||
const usesNxWebpackOrRspack = dependencies.some(
|
||||
(dep) =>
|
||||
dep.target === 'npm:@nx/webpack' || dep.target === 'npm:@nx/rspack'
|
||||
);
|
||||
if (usesNxWebpackOrRspack) {
|
||||
const root = graph.nodes[project].data.root;
|
||||
rootsToCheck.add(root);
|
||||
}
|
||||
}
|
||||
for (const root of rootsToCheck) {
|
||||
visitNotIgnoredFiles(tree, root, (filePath) => {
|
||||
if (!filePath.endsWith('.ts') && !filePath.endsWith('.js')) {
|
||||
return;
|
||||
}
|
||||
let contents = tree.read(filePath, 'utf-8');
|
||||
if (!contents.includes(MF_IMPORT_TO_UPDATE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ast = tsquery.ast(contents);
|
||||
const importNodes = tsquery(ast, MF_CONFIG_IMPORT_SELECTOR);
|
||||
if (importNodes.length === 0) {
|
||||
return;
|
||||
}
|
||||
const importNode = importNodes[0];
|
||||
const importSpecifiers = tsquery(importNode, IMPORT_TOKENS_SELECTOR);
|
||||
if (importSpecifiers.length > 1) {
|
||||
const mfConfigImportSpecifierNode = tsquery(
|
||||
importNode,
|
||||
MF_CONFIG_IMPORT_SPECIFIER_SELECTOR
|
||||
)[0];
|
||||
const end =
|
||||
contents.charAt(mfConfigImportSpecifierNode.getEnd()) === ','
|
||||
? mfConfigImportSpecifierNode.getEnd() + 1
|
||||
: mfConfigImportSpecifierNode.getEnd();
|
||||
contents = `import { ${MF_IMPORT_TO_UPDATE} } from '@nx/module-federation';
|
||||
${contents.slice(
|
||||
0,
|
||||
mfConfigImportSpecifierNode.getStart()
|
||||
)}${contents.slice(end)}`;
|
||||
} else {
|
||||
const nxWebpackImportStringNodes = tsquery(
|
||||
importNode,
|
||||
WEBPACK_IMPORT_SELECTOR
|
||||
);
|
||||
const nxRspackImportStringNodes = tsquery(
|
||||
importNode,
|
||||
RSPACK_IMPORT_SELECTOR
|
||||
);
|
||||
if (
|
||||
nxWebpackImportStringNodes.length === 0 &&
|
||||
nxRspackImportStringNodes.length === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const bundlerImportStringNode = nxWebpackImportStringNodes.length
|
||||
? nxWebpackImportStringNodes[0]
|
||||
: nxRspackImportStringNodes[0];
|
||||
contents = `${contents.slice(
|
||||
0,
|
||||
bundlerImportStringNode.getStart()
|
||||
)}'@nx/module-federation'${contents.slice(
|
||||
bundlerImportStringNode.getEnd()
|
||||
)}`;
|
||||
}
|
||||
tree.write(filePath, contents);
|
||||
});
|
||||
}
|
||||
|
||||
await formatFiles(tree);
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
import { ExecutorContext } from '@nx/devkit';
|
||||
import { join } from 'path';
|
||||
import { ModuleFederationConfig } from './models';
|
||||
|
||||
export function loadModuleFederationConfigFromContext(
|
||||
context: ExecutorContext
|
||||
): ModuleFederationConfig {
|
||||
const p = context.projectsConfigurations.projects[context.projectName];
|
||||
const moduleFederationConfigPath = join(
|
||||
context.root,
|
||||
p.root,
|
||||
'module-federation.config.js'
|
||||
);
|
||||
|
||||
try {
|
||||
return require(moduleFederationConfigPath) as ModuleFederationConfig;
|
||||
} catch {
|
||||
// TODO(jack): Add a link to guide
|
||||
throw new Error(
|
||||
`Could not load ${moduleFederationConfigPath}. Was this project generated with "@nx/react:host"?`
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
export type ModuleFederationLibrary = { type: string; name: string };
|
||||
|
||||
export type Remotes = string[] | [remoteName: string, remoteUrl: string][];
|
||||
|
||||
export interface SharedLibraryConfig {
|
||||
singleton?: boolean;
|
||||
strictVersion?: boolean;
|
||||
requiredVersion?: false | string;
|
||||
eager?: boolean;
|
||||
}
|
||||
|
||||
export type SharedFunction = (
|
||||
libraryName: string,
|
||||
sharedConfig: SharedLibraryConfig
|
||||
) => undefined | false | SharedLibraryConfig;
|
||||
|
||||
export type AdditionalSharedConfig = Array<
|
||||
| string
|
||||
| [libraryName: string, sharedConfig: SharedLibraryConfig]
|
||||
| { libraryName: string; sharedConfig: SharedLibraryConfig }
|
||||
>;
|
||||
|
||||
export interface ModuleFederationConfig {
|
||||
name: string;
|
||||
remotes?: Remotes;
|
||||
library?: ModuleFederationLibrary;
|
||||
exposes?: Record<string, string>;
|
||||
shared?: SharedFunction;
|
||||
additionalShared?: AdditionalSharedConfig;
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
import { joinPathFragments, readJsonFile, workspaceRoot } from '@nx/devkit';
|
||||
import { existsSync } from 'fs';
|
||||
|
||||
export function readRootPackageJson(): {
|
||||
dependencies?: { [key: string]: string };
|
||||
devDependencies?: { [key: string]: string };
|
||||
} {
|
||||
const pkgJsonPath = joinPathFragments(workspaceRoot, 'package.json');
|
||||
if (!existsSync(pkgJsonPath)) {
|
||||
throw new Error(
|
||||
'NX MFE: Could not find root package.json to determine dependency versions.'
|
||||
);
|
||||
}
|
||||
|
||||
return readJsonFile(pkgJsonPath);
|
||||
}
|
||||
@ -7,7 +7,7 @@ import {
|
||||
ModuleFederationConfig,
|
||||
sharePackages,
|
||||
shareWorkspaceLibraries,
|
||||
} from '@nx/webpack/src/utils/module-federation';
|
||||
} from '@nx/module-federation';
|
||||
|
||||
import {
|
||||
createProjectGraphAsync,
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import {
|
||||
ModuleFederationConfig,
|
||||
NxModuleFederationConfigOverride,
|
||||
} from '@nx/webpack/src/utils/module-federation';
|
||||
} from '@nx/module-federation';
|
||||
import { getModuleFederationConfig } from './utils';
|
||||
import type { NormalModuleReplacementPlugin } from 'webpack';
|
||||
|
||||
export async function withModuleFederationForSSR(
|
||||
options: ModuleFederationConfig,
|
||||
@ -45,7 +46,7 @@ export async function withModuleFederationForSSR(
|
||||
? [
|
||||
...(configOverride?.runtimePlugins ?? []),
|
||||
require.resolve(
|
||||
'@nx/webpack/src/utils/module-federation/plugins/runtime-library-control.plugin.js'
|
||||
'@nx/module-federation/src/utils/plugins/runtime-library-control.plugin.js'
|
||||
),
|
||||
]
|
||||
: [
|
||||
@ -56,7 +57,7 @@ export async function withModuleFederationForSSR(
|
||||
},
|
||||
{}
|
||||
),
|
||||
sharedLibraries.getReplacementPlugin()
|
||||
sharedLibraries.getReplacementPlugin() as NormalModuleReplacementPlugin
|
||||
);
|
||||
|
||||
// The env var is only set from the module-federation-dev-server
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import {
|
||||
ModuleFederationConfig,
|
||||
NxModuleFederationConfigOverride,
|
||||
} from '@nx/webpack/src/utils/module-federation';
|
||||
} from '@nx/module-federation';
|
||||
import { getModuleFederationConfig } from './utils';
|
||||
import type { AsyncNxComposableWebpackPlugin } from '@nx/webpack';
|
||||
import { ModuleFederationPlugin } from '@module-federation/enhanced/webpack';
|
||||
import type { NormalModuleReplacementPlugin } from 'webpack';
|
||||
|
||||
/**
|
||||
* @param {ModuleFederationConfig} options
|
||||
@ -65,13 +66,13 @@ export async function withModuleFederation(
|
||||
? [
|
||||
...(configOverride?.runtimePlugins ?? []),
|
||||
require.resolve(
|
||||
'@nx/webpack/src/utils/module-federation/plugins/runtime-library-control.plugin.js'
|
||||
'@nx/module-federation/src/utils/plugins/runtime-library-control.plugin.js'
|
||||
),
|
||||
]
|
||||
: configOverride?.runtimePlugins,
|
||||
virtualRuntimeEntry: true,
|
||||
}),
|
||||
sharedLibraries.getReplacementPlugin()
|
||||
sharedLibraries.getReplacementPlugin() as NormalModuleReplacementPlugin
|
||||
);
|
||||
|
||||
// The env var is only set from the module-federation-dev-server
|
||||
|
||||
@ -27,6 +27,7 @@
|
||||
"@nx/js": "file:../js",
|
||||
"@nx/devkit": "file:../devkit",
|
||||
"@nx/web": "file:../web",
|
||||
"@nx/module-federation": "file:../module-federation",
|
||||
"@phenomnomnominal/tsquery": "~5.0.1",
|
||||
"@rspack/core": "^1.0.4",
|
||||
"@rspack/dev-server": "^1.0.4",
|
||||
|
||||
@ -17,13 +17,11 @@ import { extname, join } from 'path';
|
||||
import {
|
||||
getModuleFederationConfig,
|
||||
getRemotes,
|
||||
} from '../../utils/module-federation';
|
||||
import { buildStaticRemotes } from '../../utils/module-federation/build-static.remotes';
|
||||
import {
|
||||
parseStaticRemotesConfig,
|
||||
type StaticRemotesConfig,
|
||||
} from '../../utils/module-federation/parse-static-remotes-config';
|
||||
import { startRemoteProxies } from '../../utils/module-federation/start-remote-proxies';
|
||||
startRemoteProxies,
|
||||
} from '@nx/module-federation/src/utils';
|
||||
import { buildStaticRemotes } from '../../utils/module-federation/build-static.remotes';
|
||||
import devServerExecutor from '../dev-server/dev-server.impl';
|
||||
import { ModuleFederationDevServerOptions } from './schema';
|
||||
|
||||
|
||||
@ -10,7 +10,10 @@ import { extname, join } from 'path';
|
||||
import {
|
||||
getModuleFederationConfig,
|
||||
getRemotes,
|
||||
} from '../../utils/module-federation';
|
||||
parseStaticSsrRemotesConfig,
|
||||
type StaticRemotesConfig,
|
||||
startSsrRemoteProxies,
|
||||
} from '@nx/module-federation/src/utils';
|
||||
import { RspackSsrDevServerOptions } from '../ssr-dev-server/schema';
|
||||
import ssrDevServerExecutor from '../ssr-dev-server/ssr-dev-server.impl';
|
||||
|
||||
@ -21,15 +24,9 @@ import {
|
||||
import { fork } from 'child_process';
|
||||
import { cpSync, createWriteStream, existsSync } from 'fs';
|
||||
|
||||
import {
|
||||
parseStaticSsrRemotesConfig,
|
||||
type StaticRemotesConfig,
|
||||
} from '../../utils/module-federation/parse-static-remotes-config';
|
||||
|
||||
import fileServerExecutor from '@nx/web/src/executors/file-server/file-server.impl';
|
||||
import { waitForPortOpen } from '@nx/web/src/utils/wait-for-port-open';
|
||||
import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
|
||||
import { startSsrRemoteProxies } from '../../utils/module-federation/start-ssr-remote-proxies';
|
||||
|
||||
type ModuleFederationSsrDevServerOptions = RspackSsrDevServerOptions & {
|
||||
devRemotes?: (
|
||||
|
||||
@ -19,12 +19,10 @@ import { basename, extname, join } from 'path';
|
||||
import {
|
||||
getModuleFederationConfig,
|
||||
getRemotes,
|
||||
} from '../../utils/module-federation';
|
||||
import { buildStaticRemotes } from '../../utils/module-federation/build-static.remotes';
|
||||
import {
|
||||
parseStaticRemotesConfig,
|
||||
StaticRemotesConfig,
|
||||
} from '../../utils/module-federation/parse-static-remotes-config';
|
||||
} from '@nx/module-federation/src/utils';
|
||||
import { buildStaticRemotes } from '../../utils/module-federation/build-static.remotes';
|
||||
import { ModuleFederationDevServerOptions } from '../module-federation-dev-server/schema';
|
||||
import type { RspackExecutorSchema } from '../rspack/schema';
|
||||
import { ModuleFederationStaticServerSchema } from './schema';
|
||||
|
||||
@ -141,11 +141,12 @@ describe('Convert webpack', () => {
|
||||
expect(tree.exists('demo/rspack.config.ts')).toBeTruthy();
|
||||
expect(tree.read('demo/rspack.config.ts', 'utf-8')).toMatchInlineSnapshot(`
|
||||
"import { withModuleFederation } from '@nx/rspack/module-federation';
|
||||
import { ModuleFederationConfig } from '@nx/rspack/module-federation';
|
||||
import { withReact } from '@nx/rspack';
|
||||
import { withNx } from '@nx/rspack';
|
||||
import { composePlugins } from '@nx/rspack';
|
||||
|
||||
import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
|
||||
import baseConfig from './module-federation.config';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
|
||||
@ -5,7 +5,7 @@ import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
|
||||
import { logger } from 'nx/src/utils/logger';
|
||||
import { join } from 'path';
|
||||
import { ModuleFederationDevServerOptions } from '../../executors/module-federation-dev-server/schema';
|
||||
import type { StaticRemotesConfig } from './parse-static-remotes-config';
|
||||
import type { StaticRemotesConfig } from '@nx/module-federation/src/utils';
|
||||
|
||||
export async function buildStaticRemotes(
|
||||
staticRemotesConfig: StaticRemotesConfig,
|
||||
|
||||
@ -1,195 +0,0 @@
|
||||
import { logger, type ProjectGraph } from '@nx/devkit';
|
||||
import { registerTsProject } from '@nx/js/src/internal';
|
||||
import chalk from 'chalk';
|
||||
import { existsSync, readFileSync } from 'fs';
|
||||
import { findMatchingProjects } from 'nx/src/utils/find-matching-projects';
|
||||
import { join } from 'path';
|
||||
import { ModuleFederationConfig } from './models';
|
||||
|
||||
interface ModuleFederationExecutorContext {
|
||||
projectName: string;
|
||||
projectGraph: ProjectGraph;
|
||||
root: string;
|
||||
}
|
||||
|
||||
function extractRemoteProjectsFromConfig(
|
||||
config: ModuleFederationConfig,
|
||||
pathToManifestFile?: string
|
||||
) {
|
||||
const remotes = [];
|
||||
const dynamicRemotes = [];
|
||||
if (pathToManifestFile && existsSync(pathToManifestFile)) {
|
||||
const moduleFederationManifestJson = readFileSync(
|
||||
pathToManifestFile,
|
||||
'utf-8'
|
||||
);
|
||||
|
||||
if (moduleFederationManifestJson) {
|
||||
// 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'
|
||||
)
|
||||
) {
|
||||
dynamicRemotes.push(...Object.keys(parsedManifest));
|
||||
}
|
||||
}
|
||||
}
|
||||
const staticRemotes =
|
||||
config.remotes?.map((r) => (Array.isArray(r) ? r[0] : r)) ?? [];
|
||||
remotes.push(...staticRemotes);
|
||||
return { remotes, dynamicRemotes };
|
||||
}
|
||||
|
||||
function collectRemoteProjects(
|
||||
remote: string,
|
||||
collected: Set<string>,
|
||||
context: ModuleFederationExecutorContext
|
||||
) {
|
||||
const remoteProject = context.projectGraph.nodes[remote]?.data;
|
||||
if (!context.projectGraph.nodes[remote] || collected.has(remote)) {
|
||||
return;
|
||||
}
|
||||
|
||||
collected.add(remote);
|
||||
|
||||
const remoteProjectRoot = remoteProject.root;
|
||||
const remoteProjectTsConfig = remoteProject.targets['build'].options.tsConfig;
|
||||
const remoteProjectConfig = getModuleFederationConfig(
|
||||
remoteProjectTsConfig,
|
||||
context.root,
|
||||
remoteProjectRoot
|
||||
);
|
||||
const { remotes: remoteProjectRemotes } =
|
||||
extractRemoteProjectsFromConfig(remoteProjectConfig);
|
||||
|
||||
remoteProjectRemotes.forEach((r) =>
|
||||
collectRemoteProjects(r, collected, context)
|
||||
);
|
||||
}
|
||||
|
||||
export function getRemotes(
|
||||
devRemotes: string[],
|
||||
skipRemotes: string[],
|
||||
config: ModuleFederationConfig,
|
||||
context: ModuleFederationExecutorContext,
|
||||
pathToManifestFile?: string
|
||||
) {
|
||||
const collectedRemotes = new Set<string>();
|
||||
const { remotes, dynamicRemotes } = extractRemoteProjectsFromConfig(
|
||||
config,
|
||||
pathToManifestFile
|
||||
);
|
||||
remotes.forEach((r) => collectRemoteProjects(r, collectedRemotes, context));
|
||||
const remotesToSkip = new Set(
|
||||
findMatchingProjects(skipRemotes, context.projectGraph.nodes) ?? []
|
||||
);
|
||||
|
||||
if (remotesToSkip.size > 0) {
|
||||
logger.info(
|
||||
`Remotes not served automatically: ${[...remotesToSkip.values()].join(
|
||||
', '
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
const knownRemotes = Array.from(collectedRemotes).filter(
|
||||
(r) => !remotesToSkip.has(r)
|
||||
);
|
||||
|
||||
const knownDynamicRemotes = dynamicRemotes.filter(
|
||||
(r) => !remotesToSkip.has(r) && context.projectGraph.nodes[r]
|
||||
);
|
||||
|
||||
logger.info(
|
||||
`NX Starting module federation dev-server for ${chalk.bold(
|
||||
context.projectName
|
||||
)} with ${[...knownRemotes, ...knownDynamicRemotes].length} remotes`
|
||||
);
|
||||
|
||||
const devServeApps = new Set(
|
||||
!devRemotes
|
||||
? []
|
||||
: Array.isArray(devRemotes)
|
||||
? findMatchingProjects(devRemotes, context.projectGraph.nodes)
|
||||
: findMatchingProjects([devRemotes], context.projectGraph.nodes)
|
||||
);
|
||||
|
||||
const staticRemotes = knownRemotes.filter((r) => !devServeApps.has(r));
|
||||
const devServeRemotes = [...knownRemotes, ...knownDynamicRemotes].filter(
|
||||
(r) => devServeApps.has(r)
|
||||
);
|
||||
const staticDynamicRemotes = knownDynamicRemotes.filter(
|
||||
(r) => !devServeApps.has(r)
|
||||
);
|
||||
const remotePorts = [...devServeRemotes, ...staticDynamicRemotes].map(
|
||||
(r) => context.projectGraph.nodes[r].data.targets['serve'].options.port
|
||||
);
|
||||
const staticRemotePort =
|
||||
Math.max(
|
||||
...([
|
||||
...remotePorts,
|
||||
...staticRemotes.map(
|
||||
(r) =>
|
||||
context.projectGraph.nodes[r].data.targets['serve'].options.port
|
||||
),
|
||||
] as number[])
|
||||
) +
|
||||
(remotesToSkip.size + 1);
|
||||
|
||||
return {
|
||||
staticRemotes,
|
||||
devRemotes: devServeRemotes,
|
||||
dynamicRemotes: staticDynamicRemotes,
|
||||
remotePorts,
|
||||
staticRemotePort,
|
||||
};
|
||||
}
|
||||
|
||||
export function getModuleFederationConfig(
|
||||
tsconfigPath: string,
|
||||
workspaceRoot: string,
|
||||
projectRoot: string,
|
||||
pluginName: 'react' | 'angular' = 'react'
|
||||
) {
|
||||
const moduleFederationConfigPathJS = join(
|
||||
workspaceRoot,
|
||||
projectRoot,
|
||||
'module-federation.config.js'
|
||||
);
|
||||
|
||||
const moduleFederationConfigPathTS = join(
|
||||
workspaceRoot,
|
||||
projectRoot,
|
||||
'module-federation.config.ts'
|
||||
);
|
||||
|
||||
let moduleFederationConfigPath = moduleFederationConfigPathJS;
|
||||
|
||||
// create a no-op so this can be called with issue
|
||||
const fullTSconfigPath = tsconfigPath.startsWith(workspaceRoot)
|
||||
? tsconfigPath
|
||||
: join(workspaceRoot, tsconfigPath);
|
||||
let cleanupTranspiler = () => undefined;
|
||||
if (existsSync(moduleFederationConfigPathTS)) {
|
||||
cleanupTranspiler = registerTsProject(fullTSconfigPath);
|
||||
moduleFederationConfigPath = moduleFederationConfigPathTS;
|
||||
}
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const config = require(moduleFederationConfigPath);
|
||||
cleanupTranspiler();
|
||||
|
||||
return config.default || config;
|
||||
} catch {
|
||||
throw new Error(
|
||||
`Could not load ${moduleFederationConfigPath}. Was this project generated with "@nx/${pluginName}:host"?\nSee: https://nx.dev/concepts/more-concepts/faster-builds-with-module-federation`
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,51 +1,4 @@
|
||||
import {
|
||||
AdditionalSharedConfig,
|
||||
ModuleFederationConfig,
|
||||
ModuleFederationLibrary,
|
||||
Remotes,
|
||||
SharedFunction,
|
||||
SharedLibraryConfig,
|
||||
SharedWorkspaceLibraryConfig,
|
||||
WorkspaceLibrary,
|
||||
WorkspaceLibrarySecondaryEntryPoint,
|
||||
} from './models';
|
||||
|
||||
import {
|
||||
applyAdditionalShared,
|
||||
applySharedFunction,
|
||||
getNpmPackageSharedConfig,
|
||||
sharePackages,
|
||||
shareWorkspaceLibraries,
|
||||
} from './share';
|
||||
|
||||
import { mapRemotes, mapRemotesForSSR } from './remotes';
|
||||
|
||||
import { getDependentPackagesForProject } from './dependencies';
|
||||
|
||||
import { readRootPackageJson } from './package-json';
|
||||
|
||||
import { withModuleFederation } from './with-module-federation/with-module-federation';
|
||||
import { withModuleFederationForSSR } from './with-module-federation/with-module-federation-ssr';
|
||||
|
||||
export {
|
||||
AdditionalSharedConfig,
|
||||
applyAdditionalShared,
|
||||
applySharedFunction,
|
||||
getDependentPackagesForProject,
|
||||
getNpmPackageSharedConfig,
|
||||
mapRemotes,
|
||||
mapRemotesForSSR,
|
||||
ModuleFederationConfig,
|
||||
ModuleFederationLibrary,
|
||||
readRootPackageJson,
|
||||
Remotes,
|
||||
SharedFunction,
|
||||
SharedLibraryConfig,
|
||||
SharedWorkspaceLibraryConfig,
|
||||
sharePackages,
|
||||
shareWorkspaceLibraries,
|
||||
withModuleFederation,
|
||||
withModuleFederationForSSR,
|
||||
WorkspaceLibrary,
|
||||
WorkspaceLibrarySecondaryEntryPoint,
|
||||
};
|
||||
export { withModuleFederation, withModuleFederationForSSR };
|
||||
|
||||
@ -1,119 +0,0 @@
|
||||
import { extname } from 'path';
|
||||
import { Remotes } from './models';
|
||||
|
||||
/**
|
||||
* Map remote names to a format that can be understood and used by Module
|
||||
* Federation.
|
||||
*
|
||||
* @param remotes - The remotes to map
|
||||
* @param remoteEntryExt - The file extension of the remoteEntry file
|
||||
* @param determineRemoteUrl - The function used to lookup the URL of the served remote
|
||||
*/
|
||||
export function mapRemotes(
|
||||
remotes: Remotes,
|
||||
remoteEntryExt: 'js' | 'mjs',
|
||||
determineRemoteUrl: (remote: string) => string
|
||||
): Record<string, string> {
|
||||
const mappedRemotes = {};
|
||||
|
||||
for (const nxRemoteProjectName of remotes) {
|
||||
if (Array.isArray(nxRemoteProjectName)) {
|
||||
const mfRemoteName = normalizeRemoteName(nxRemoteProjectName[0]);
|
||||
mappedRemotes[mfRemoteName] = handleArrayRemote(
|
||||
nxRemoteProjectName,
|
||||
remoteEntryExt
|
||||
);
|
||||
} else if (typeof nxRemoteProjectName === 'string') {
|
||||
const mfRemoteName = normalizeRemoteName(nxRemoteProjectName);
|
||||
mappedRemotes[mfRemoteName] = handleStringRemote(
|
||||
nxRemoteProjectName,
|
||||
determineRemoteUrl
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return mappedRemotes;
|
||||
}
|
||||
|
||||
// Helper function to deal with remotes that are arrays
|
||||
function handleArrayRemote(
|
||||
remote: [string, string],
|
||||
remoteEntryExt: 'js' | 'mjs'
|
||||
): string {
|
||||
let [nxRemoteProjectName, remoteLocation] = remote;
|
||||
const mfRemoteName = normalizeRemoteName(nxRemoteProjectName);
|
||||
const remoteLocationExt = extname(remoteLocation);
|
||||
|
||||
// If remote location already has .js or .mjs extension
|
||||
if (['.js', '.mjs', '.json'].includes(remoteLocationExt)) {
|
||||
return remoteLocation;
|
||||
}
|
||||
|
||||
const baseRemote = remoteLocation.endsWith('/')
|
||||
? remoteLocation.slice(0, -1)
|
||||
: remoteLocation;
|
||||
|
||||
const globalPrefix = `${normalizeRemoteName(mfRemoteName)}@`;
|
||||
|
||||
// if the remote is defined with anything other than http then we assume it's a promise based remote
|
||||
// In that case we should use what the user provides as the remote location
|
||||
if (!remoteLocation.startsWith('promise new Promise')) {
|
||||
return `${globalPrefix}${baseRemote}/remoteEntry.${remoteEntryExt}`;
|
||||
} else {
|
||||
return remoteLocation;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to deal with remotes that are strings
|
||||
function handleStringRemote(
|
||||
nxRemoteProjectName: string,
|
||||
determineRemoteUrl: (nxRemoteProjectName: string) => string
|
||||
): string {
|
||||
const globalPrefix = `${normalizeRemoteName(nxRemoteProjectName)}@`;
|
||||
|
||||
return `${globalPrefix}${determineRemoteUrl(nxRemoteProjectName)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map remote names to a format that can be understood and used by Module
|
||||
* Federation.
|
||||
*
|
||||
* @param remotes - The remotes to map
|
||||
* @param remoteEntryExt - The file extension of the remoteEntry file
|
||||
* @param determineRemoteUrl - The function used to lookup the URL of the served remote
|
||||
*/
|
||||
export function mapRemotesForSSR(
|
||||
remotes: Remotes,
|
||||
remoteEntryExt: 'js' | 'mjs',
|
||||
determineRemoteUrl: (remote: string) => string
|
||||
): Record<string, string> {
|
||||
const mappedRemotes = {};
|
||||
|
||||
for (const remote of remotes) {
|
||||
if (Array.isArray(remote)) {
|
||||
let [nxRemoteProjectName, remoteLocation] = remote;
|
||||
const mfRemoteName = normalizeRemoteName(nxRemoteProjectName);
|
||||
const remoteLocationExt = extname(remoteLocation);
|
||||
mappedRemotes[mfRemoteName] = `${mfRemoteName}@${
|
||||
['.js', '.mjs'].includes(remoteLocationExt)
|
||||
? remoteLocation
|
||||
: `${
|
||||
remoteLocation.endsWith('/')
|
||||
? remoteLocation.slice(0, -1)
|
||||
: remoteLocation
|
||||
}/remoteEntry.${remoteEntryExt}`
|
||||
}`;
|
||||
} else if (typeof remote === 'string') {
|
||||
const mfRemoteName = normalizeRemoteName(remote);
|
||||
mappedRemotes[mfRemoteName] = `${mfRemoteName}@${determineRemoteUrl(
|
||||
remote
|
||||
)}`;
|
||||
}
|
||||
}
|
||||
|
||||
return mappedRemotes;
|
||||
}
|
||||
|
||||
function normalizeRemoteName(nxRemoteProjectName: string): string {
|
||||
return nxRemoteProjectName.replace(/-/g, '_');
|
||||
}
|
||||
@ -1,144 +0,0 @@
|
||||
import { joinPathFragments, readJsonFile, workspaceRoot } from '@nx/devkit';
|
||||
import { existsSync, lstatSync, readdirSync } from 'fs';
|
||||
import { PackageJson, readModulePackageJson } from 'nx/src/utils/package-json';
|
||||
import { dirname, join, relative } from 'path';
|
||||
import type { WorkspaceLibrary } from './models';
|
||||
import { WorkspaceLibrarySecondaryEntryPoint } from './models';
|
||||
|
||||
export function collectWorkspaceLibrarySecondaryEntryPoints(
|
||||
library: WorkspaceLibrary,
|
||||
tsconfigPathAliases: Record<string, string[]>
|
||||
): WorkspaceLibrarySecondaryEntryPoint[] {
|
||||
const libraryRoot = join(workspaceRoot, library.root);
|
||||
const needsSecondaryEntryPointsCollected = existsSync(
|
||||
join(libraryRoot, 'ng-package.json')
|
||||
);
|
||||
|
||||
const secondaryEntryPoints: WorkspaceLibrarySecondaryEntryPoint[] = [];
|
||||
if (needsSecondaryEntryPointsCollected) {
|
||||
const tsConfigAliasesForLibWithSecondaryEntryPoints = Object.entries(
|
||||
tsconfigPathAliases
|
||||
).reduce((acc, [tsKey, tsPaths]) => {
|
||||
if (!tsKey.startsWith(library.importKey)) {
|
||||
return { ...acc };
|
||||
}
|
||||
|
||||
if (tsPaths.some((path) => path.startsWith(`${library.root}/`))) {
|
||||
acc = { ...acc, [tsKey]: tsPaths };
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
for (const [alias] of Object.entries(
|
||||
tsConfigAliasesForLibWithSecondaryEntryPoints
|
||||
)) {
|
||||
const pathToLib = dirname(
|
||||
join(workspaceRoot, tsconfigPathAliases[alias][0])
|
||||
);
|
||||
let searchDir = pathToLib;
|
||||
while (searchDir !== libraryRoot) {
|
||||
if (existsSync(join(searchDir, 'ng-package.json'))) {
|
||||
secondaryEntryPoints.push({ name: alias, path: pathToLib });
|
||||
break;
|
||||
}
|
||||
searchDir = dirname(searchDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return secondaryEntryPoints;
|
||||
}
|
||||
|
||||
export function getNonNodeModulesSubDirs(directory: string): string[] {
|
||||
return readdirSync(directory)
|
||||
.filter((file) => file !== 'node_modules')
|
||||
.map((file) => join(directory, file))
|
||||
.filter((file) => lstatSync(file).isDirectory());
|
||||
}
|
||||
|
||||
export function recursivelyCollectSecondaryEntryPointsFromDirectory(
|
||||
pkgName: string,
|
||||
pkgVersion: string,
|
||||
pkgRoot: string,
|
||||
mainEntryPointExports: any | undefined,
|
||||
directories: string[],
|
||||
collectedPackages: { name: string; version: string }[]
|
||||
): void {
|
||||
for (const directory of directories) {
|
||||
const packageJsonPath = join(directory, 'package.json');
|
||||
const relativeEntryPointPath = relative(pkgRoot, directory);
|
||||
const entryPointName = joinPathFragments(pkgName, relativeEntryPointPath);
|
||||
if (existsSync(packageJsonPath)) {
|
||||
try {
|
||||
// require the secondary entry point to try to rule out sample code
|
||||
require.resolve(entryPointName, { paths: [workspaceRoot] });
|
||||
const { name } = readJsonFile(packageJsonPath);
|
||||
// further check to make sure what we were able to require is the
|
||||
// same as the package name
|
||||
if (name === entryPointName) {
|
||||
collectedPackages.push({ name, version: pkgVersion });
|
||||
}
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
} else if (mainEntryPointExports) {
|
||||
// if the package.json doesn't exist, check if the directory is
|
||||
// exported by the main entry point
|
||||
const entryPointExportKey = `./${relativeEntryPointPath}`;
|
||||
const entryPointInfo = mainEntryPointExports[entryPointExportKey];
|
||||
if (entryPointInfo) {
|
||||
collectedPackages.push({
|
||||
name: entryPointName,
|
||||
version: pkgVersion,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const subDirs = getNonNodeModulesSubDirs(directory);
|
||||
recursivelyCollectSecondaryEntryPointsFromDirectory(
|
||||
pkgName,
|
||||
pkgVersion,
|
||||
pkgRoot,
|
||||
mainEntryPointExports,
|
||||
subDirs,
|
||||
collectedPackages
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function collectPackageSecondaryEntryPoints(
|
||||
pkgName: string,
|
||||
pkgVersion: string,
|
||||
collectedPackages: { name: string; version: string }[]
|
||||
): void {
|
||||
let pathToPackage: string;
|
||||
let packageJsonPath: string;
|
||||
let packageJson: PackageJson;
|
||||
try {
|
||||
({ path: packageJsonPath, packageJson } = readModulePackageJson(pkgName));
|
||||
pathToPackage = dirname(packageJsonPath);
|
||||
} catch {
|
||||
// the package.json might not resolve if the package has the "exports"
|
||||
// entry and is not exporting the package.json file, fall back to trying
|
||||
// to find it from the top-level node_modules
|
||||
pathToPackage = join(workspaceRoot, 'node_modules', pkgName);
|
||||
packageJsonPath = join(pathToPackage, 'package.json');
|
||||
if (!existsSync(packageJsonPath)) {
|
||||
// might not exist if it's nested in another package, just return here
|
||||
return;
|
||||
}
|
||||
packageJson = readJsonFile(packageJsonPath);
|
||||
}
|
||||
|
||||
const { exports } = packageJson;
|
||||
const subDirs = getNonNodeModulesSubDirs(pathToPackage);
|
||||
recursivelyCollectSecondaryEntryPointsFromDirectory(
|
||||
pkgName,
|
||||
pkgVersion,
|
||||
pathToPackage,
|
||||
exports,
|
||||
subDirs,
|
||||
collectedPackages
|
||||
);
|
||||
}
|
||||
@ -1,381 +0,0 @@
|
||||
import * as fs from 'fs';
|
||||
import * as nxFileutils from 'nx/src/devkit-exports';
|
||||
import { sharePackages, shareWorkspaceLibraries } from './share';
|
||||
import * as tsUtils from './typescript';
|
||||
|
||||
jest.mock('nx/src/devkit-exports', () => {
|
||||
return {
|
||||
...jest.requireActual('nx/src/devkit-exports'),
|
||||
readJsonFile: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('MF Share Utils', () => {
|
||||
afterEach(() => jest.clearAllMocks());
|
||||
|
||||
describe('ShareWorkspaceLibraries', () => {
|
||||
it('should error when the tsconfig file does not exist', () => {
|
||||
// ARRANGE
|
||||
jest
|
||||
.spyOn(fs, 'existsSync')
|
||||
.mockImplementation((p: string) => p?.endsWith('.node'));
|
||||
|
||||
// ACT
|
||||
try {
|
||||
shareWorkspaceLibraries([
|
||||
{ name: 'shared', root: 'libs/shared', importKey: '@myorg/shared' },
|
||||
]);
|
||||
} catch (error) {
|
||||
// ASSERT
|
||||
expect(error.message).toContain(
|
||||
'NX MF: TsConfig Path for workspace libraries does not exist!'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should create an object with correct setup', () => {
|
||||
// ARRANGE
|
||||
jest.spyOn(fs, 'existsSync').mockReturnValue(true);
|
||||
jest.spyOn(tsUtils, 'readTsPathMappings').mockReturnValue({
|
||||
'@myorg/shared': ['/libs/shared/src/index.ts'],
|
||||
});
|
||||
|
||||
// ACT
|
||||
const sharedLibraries = shareWorkspaceLibraries([
|
||||
{ name: 'shared', root: 'libs/shared', importKey: '@myorg/shared' },
|
||||
]);
|
||||
|
||||
// ASSERT
|
||||
expect(sharedLibraries.getAliases()).toHaveProperty('@myorg/shared');
|
||||
expect(sharedLibraries.getAliases()['@myorg/shared']).toContain(
|
||||
'libs/shared/src/index.ts'
|
||||
);
|
||||
expect(sharedLibraries.getLibraries('libs/shared')).toEqual({
|
||||
'@myorg/shared': {
|
||||
eager: undefined,
|
||||
requiredVersion: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should order nested projects first', () => {
|
||||
// ARRANGE
|
||||
jest.spyOn(fs, 'existsSync').mockReturnValue(true);
|
||||
jest.spyOn(tsUtils, 'readTsPathMappings').mockReturnValue({
|
||||
'@myorg/shared': ['/libs/shared/src/index.ts'],
|
||||
'@myorg/shared/components': ['/libs/shared/components/src/index.ts'],
|
||||
});
|
||||
|
||||
// ACT
|
||||
const sharedLibraries = shareWorkspaceLibraries([
|
||||
{ name: 'shared', root: 'libs/shared', importKey: '@myorg/shared' },
|
||||
{
|
||||
name: 'shared-components',
|
||||
root: 'libs/shared/components',
|
||||
importKey: '@myorg/shared/components',
|
||||
},
|
||||
]);
|
||||
|
||||
// ASSERT
|
||||
expect(Object.keys(sharedLibraries.getAliases())[0]).toEqual(
|
||||
'@myorg/shared/components'
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle path mappings with wildcards correctly in non-buildable libraries', () => {
|
||||
// ARRANGE
|
||||
jest.spyOn(fs, 'existsSync').mockImplementation(() => true);
|
||||
jest.spyOn(tsUtils, 'readTsPathMappings').mockReturnValue({
|
||||
'@myorg/shared': ['/libs/shared/src/index.ts'],
|
||||
'@myorg/shared/*': ['/libs/shared/src/lib/*'],
|
||||
});
|
||||
|
||||
// ACT
|
||||
const sharedLibraries = shareWorkspaceLibraries([
|
||||
{ name: 'shared', root: 'libs/shared', importKey: '@myorg/shared' },
|
||||
]);
|
||||
|
||||
// ASSERT
|
||||
expect(sharedLibraries.getAliases()).toHaveProperty('@myorg/shared');
|
||||
expect(sharedLibraries.getAliases()['@myorg/shared']).toContain(
|
||||
'libs/shared/src/index.ts'
|
||||
);
|
||||
expect(sharedLibraries.getLibraries('libs/shared')).toEqual({
|
||||
'@myorg/shared': {
|
||||
eager: undefined,
|
||||
requiredVersion: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should create an object with empty setup when tsconfig does not contain the shared lib', () => {
|
||||
// ARRANGE
|
||||
jest.spyOn(fs, 'existsSync').mockReturnValue(true);
|
||||
jest.spyOn(tsUtils, 'readTsPathMappings').mockReturnValue({});
|
||||
|
||||
// ACT
|
||||
const sharedLibraries = shareWorkspaceLibraries([
|
||||
{ name: 'shared', root: 'libs/shared', importKey: '@myorg/shared' },
|
||||
]);
|
||||
|
||||
// ASSERT
|
||||
expect(sharedLibraries.getAliases()).toEqual({});
|
||||
expect(sharedLibraries.getLibraries('libs/shared')).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('SharePackages', () => {
|
||||
it('should throw when it cannot find root package.json', () => {
|
||||
// ARRANGE
|
||||
jest
|
||||
.spyOn(fs, 'existsSync')
|
||||
.mockImplementation((p: string) => p.endsWith('.node'));
|
||||
|
||||
// ACT
|
||||
try {
|
||||
sharePackages(['@angular/core']);
|
||||
} catch (error) {
|
||||
// ASSERT
|
||||
expect(error.message).toEqual(
|
||||
'NX MF: Could not find root package.json to determine dependency versions.'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should correctly map the shared packages to objects', () => {
|
||||
// ARRANGE
|
||||
jest.spyOn(fs, 'existsSync').mockReturnValue(true);
|
||||
jest.spyOn(nxFileutils, 'readJsonFile').mockImplementation((file) => ({
|
||||
name: file.replace(/\\/g, '/').replace(/^.*node_modules[/]/, ''),
|
||||
dependencies: {
|
||||
'@angular/core': '~13.2.0',
|
||||
'@angular/common': '~13.2.0',
|
||||
rxjs: '~7.4.0',
|
||||
},
|
||||
}));
|
||||
jest.spyOn(fs, 'readdirSync').mockReturnValue([]);
|
||||
|
||||
// ACT
|
||||
const packages = sharePackages([
|
||||
'@angular/core',
|
||||
'@angular/common',
|
||||
'rxjs',
|
||||
]);
|
||||
// ASSERT
|
||||
expect(packages).toEqual({
|
||||
'@angular/core': {
|
||||
singleton: true,
|
||||
strictVersion: true,
|
||||
requiredVersion: '~13.2.0',
|
||||
},
|
||||
'@angular/common': {
|
||||
singleton: true,
|
||||
strictVersion: true,
|
||||
requiredVersion: '~13.2.0',
|
||||
},
|
||||
rxjs: {
|
||||
singleton: true,
|
||||
strictVersion: true,
|
||||
requiredVersion: '~7.4.0',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: Get with colum and figure out why this stopped working
|
||||
xit('should correctly map the shared packages to objects even with nested entry points', () => {
|
||||
// ARRANGE
|
||||
|
||||
/**
|
||||
* This creates a bunch of mocks that aims to test that
|
||||
* the sharePackages function can handle nested
|
||||
* entrypoints in the package that is being shared.
|
||||
*
|
||||
* This will set up a directory structure that matches
|
||||
* the following:
|
||||
*
|
||||
* - @angular/core/
|
||||
* - package.json
|
||||
* - @angular/common/
|
||||
* - http/
|
||||
* - testing/
|
||||
* - package.json
|
||||
* - package.json
|
||||
* - rxjs
|
||||
* - package.json
|
||||
*
|
||||
* The result is that there would be 4 packages that
|
||||
* need to be shared, as determined by the folders
|
||||
* containing the package.json files
|
||||
*/
|
||||
createMockedFSForNestedEntryPoints();
|
||||
|
||||
// ACT
|
||||
const packages = sharePackages([
|
||||
'@angular/core',
|
||||
'@angular/common',
|
||||
'rxjs',
|
||||
]);
|
||||
// ASSERT
|
||||
expect(packages).toEqual({
|
||||
'@angular/core': {
|
||||
singleton: true,
|
||||
strictVersion: true,
|
||||
requiredVersion: '~13.2.0',
|
||||
},
|
||||
'@angular/common': {
|
||||
singleton: true,
|
||||
strictVersion: true,
|
||||
requiredVersion: '~13.2.0',
|
||||
},
|
||||
'@angular/common/http/testing': {
|
||||
singleton: true,
|
||||
strictVersion: true,
|
||||
requiredVersion: '~13.2.0',
|
||||
},
|
||||
rxjs: {
|
||||
singleton: true,
|
||||
strictVersion: true,
|
||||
requiredVersion: '~7.4.0',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should not throw when the main entry point package.json cannot be required', () => {
|
||||
// ARRANGE
|
||||
jest
|
||||
.spyOn(fs, 'existsSync')
|
||||
.mockImplementation(
|
||||
(file: string) =>
|
||||
!file.endsWith('non-existent-top-level-package/package.json')
|
||||
);
|
||||
jest.spyOn(nxFileutils, 'readJsonFile').mockImplementation((file) => {
|
||||
return {
|
||||
name: file
|
||||
.replace(/\\/g, '/')
|
||||
.replace(/^.*node_modules[/]/, '')
|
||||
.replace('/package.json', ''),
|
||||
dependencies: { '@angular/core': '~13.2.0' },
|
||||
};
|
||||
});
|
||||
|
||||
// ACT & ASSERT
|
||||
expect(() =>
|
||||
sharePackages(['non-existent-top-level-package'])
|
||||
).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
it('should using shared library version from root package.json if available', () => {
|
||||
// ARRANGE
|
||||
jest.spyOn(fs, 'existsSync').mockReturnValue(true);
|
||||
jest
|
||||
.spyOn(nxFileutils, 'readJsonFile')
|
||||
.mockImplementation((file: string) => {
|
||||
if (file.endsWith('package.json')) {
|
||||
return {
|
||||
dependencies: {
|
||||
'@myorg/shared': '1.0.0',
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
jest.spyOn(tsUtils, 'readTsPathMappings').mockReturnValue({
|
||||
'@myorg/shared': ['/libs/shared/src/index.ts'],
|
||||
'@myorg/shared/*': ['/libs/shared/src/lib/*'],
|
||||
});
|
||||
|
||||
// ACT
|
||||
const sharedLibraries = shareWorkspaceLibraries(
|
||||
[{ name: 'shared', root: 'libs/shared', importKey: '@myorg/shared' }],
|
||||
'/'
|
||||
);
|
||||
|
||||
// ASSERT
|
||||
expect(sharedLibraries.getLibraries('libs/shared')).toEqual({
|
||||
'@myorg/shared': {
|
||||
eager: undefined,
|
||||
requiredVersion: '1.0.0',
|
||||
singleton: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should use shared library version from library package.json if project package.json does not have it', () => {
|
||||
// ARRANGE
|
||||
jest.spyOn(fs, 'existsSync').mockReturnValue(true);
|
||||
jest
|
||||
.spyOn(nxFileutils, 'readJsonFile')
|
||||
.mockImplementation((file: string) => {
|
||||
if (file.endsWith('libs/shared/package.json')) {
|
||||
return {
|
||||
version: '1.0.0',
|
||||
};
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
});
|
||||
|
||||
jest.spyOn(tsUtils, 'readTsPathMappings').mockReturnValue({
|
||||
'@myorg/shared': ['/libs/shared/src/index.ts'],
|
||||
'@myorg/shared/*': ['/libs/shared/src/lib/*'],
|
||||
});
|
||||
|
||||
// ACT
|
||||
const sharedLibraries = shareWorkspaceLibraries(
|
||||
[{ name: 'shared', root: 'libs/shared', importKey: '@myorg/shared' }],
|
||||
null
|
||||
);
|
||||
|
||||
// ASSERT
|
||||
expect(sharedLibraries.getLibraries('libs/shared')).toEqual({
|
||||
'@myorg/shared': {
|
||||
eager: undefined,
|
||||
requiredVersion: '1.0.0',
|
||||
singleton: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createMockedFSForNestedEntryPoints() {
|
||||
jest.spyOn(fs, 'existsSync').mockImplementation((file: string) => {
|
||||
if (file.endsWith('http/package.json')) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
jest.spyOn(nxFileutils, 'readJsonFile').mockImplementation((file) => ({
|
||||
name: file
|
||||
.replace(/\\/g, '/')
|
||||
.replace(/^.*node_modules[/]/, '')
|
||||
.replace('/package.json', ''),
|
||||
dependencies: {
|
||||
'@angular/core': '~13.2.0',
|
||||
'@angular/common': '~13.2.0',
|
||||
rxjs: '~7.4.0',
|
||||
},
|
||||
}));
|
||||
|
||||
jest.spyOn(fs, 'readdirSync').mockImplementation((directoryPath: string) => {
|
||||
const PACKAGE_SETUP = {
|
||||
'@angular/core': [],
|
||||
'@angular/common': ['http'],
|
||||
http: ['testing'],
|
||||
testing: [],
|
||||
};
|
||||
|
||||
for (const key of Object.keys(PACKAGE_SETUP)) {
|
||||
if (directoryPath.endsWith(key)) {
|
||||
return PACKAGE_SETUP[key];
|
||||
}
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
jest
|
||||
.spyOn(fs, 'lstatSync')
|
||||
.mockReturnValue({ isDirectory: () => true } as any);
|
||||
}
|
||||
@ -1,322 +0,0 @@
|
||||
import {
|
||||
joinPathFragments,
|
||||
logger,
|
||||
type ProjectGraph,
|
||||
readJsonFile,
|
||||
workspaceRoot,
|
||||
} from '@nx/devkit';
|
||||
import { NormalModuleReplacementPlugin } from '@rspack/core';
|
||||
import { existsSync } from 'fs';
|
||||
import type { PackageJson } from 'nx/src/utils/package-json';
|
||||
import { dirname, join, normalize } from 'path';
|
||||
import type {
|
||||
SharedLibraryConfig,
|
||||
SharedWorkspaceLibraryConfig,
|
||||
WorkspaceLibrary,
|
||||
} from './models';
|
||||
import { AdditionalSharedConfig, SharedFunction } from './models';
|
||||
import { readRootPackageJson } from './package-json';
|
||||
import {
|
||||
collectPackageSecondaryEntryPoints,
|
||||
collectWorkspaceLibrarySecondaryEntryPoints,
|
||||
} from './secondary-entry-points';
|
||||
import { getRootTsConfigPath, readTsPathMappings } from './typescript';
|
||||
|
||||
/**
|
||||
* Build an object of functions to be used with the ModuleFederationPlugin to
|
||||
* share Nx Workspace Libraries between Hosts and Remotes.
|
||||
*
|
||||
* @param workspaceLibs - The Nx Workspace Libraries to share
|
||||
* @param tsConfigPath - The path to TS Config File that contains the Path Mappings for the Libraries
|
||||
*/
|
||||
export function shareWorkspaceLibraries(
|
||||
workspaceLibs: WorkspaceLibrary[],
|
||||
tsConfigPath = process.env.NX_TSCONFIG_PATH ?? getRootTsConfigPath()
|
||||
): SharedWorkspaceLibraryConfig {
|
||||
if (!workspaceLibs) {
|
||||
return getEmptySharedLibrariesConfig();
|
||||
}
|
||||
|
||||
const tsconfigPathAliases = readTsPathMappings(tsConfigPath);
|
||||
if (!Object.keys(tsconfigPathAliases).length) {
|
||||
return getEmptySharedLibrariesConfig();
|
||||
}
|
||||
|
||||
// Nested projects must come first, sort them as such
|
||||
const sortedTsConfigPathAliases = {};
|
||||
Object.keys(tsconfigPathAliases)
|
||||
.sort((a, b) => {
|
||||
return b.split('/').length - a.split('/').length;
|
||||
})
|
||||
.forEach(
|
||||
(key) => (sortedTsConfigPathAliases[key] = tsconfigPathAliases[key])
|
||||
);
|
||||
|
||||
const pathMappings: { name: string; path: string }[] = [];
|
||||
for (const [key, paths] of Object.entries(sortedTsConfigPathAliases)) {
|
||||
const library = workspaceLibs.find((lib) => lib.importKey === key);
|
||||
if (!library) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// This is for Angular Projects that use ng-package.json
|
||||
// It will do nothing for React Projects
|
||||
collectWorkspaceLibrarySecondaryEntryPoints(
|
||||
library,
|
||||
sortedTsConfigPathAliases
|
||||
).forEach(({ name, path }) =>
|
||||
pathMappings.push({
|
||||
name,
|
||||
path,
|
||||
})
|
||||
);
|
||||
|
||||
pathMappings.push({
|
||||
name: key,
|
||||
path: normalize(join(workspaceRoot, paths[0])),
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
getAliases: () =>
|
||||
pathMappings.reduce(
|
||||
(aliases, library) => ({
|
||||
...aliases,
|
||||
// If the library path ends in a wildcard, remove it as rspack can't handle this in resolve.alias
|
||||
// e.g. path/to/my/lib/* -> path/to/my/lib
|
||||
[library.name]: library.path.replace(/\/\*$/, ''),
|
||||
}),
|
||||
{}
|
||||
),
|
||||
getLibraries: (
|
||||
projectRoot: string,
|
||||
eager?: boolean
|
||||
): Record<string, SharedLibraryConfig> => {
|
||||
let pkgJson: PackageJson = null;
|
||||
if (
|
||||
projectRoot &&
|
||||
existsSync(
|
||||
joinPathFragments(workspaceRoot, projectRoot, 'package.json')
|
||||
)
|
||||
) {
|
||||
pkgJson = readJsonFile(
|
||||
joinPathFragments(workspaceRoot, projectRoot, 'package.json')
|
||||
);
|
||||
}
|
||||
return pathMappings.reduce((libraries, library) => {
|
||||
// Check to see if the library version is declared in the app's package.json
|
||||
let version = pkgJson?.dependencies?.[library.name];
|
||||
if (!version && workspaceLibs.length > 0) {
|
||||
const workspaceLib = workspaceLibs.find(
|
||||
(lib) => lib.importKey === library.name
|
||||
);
|
||||
|
||||
const libPackageJsonPath = workspaceLib
|
||||
? join(workspaceLib.root, 'package.json')
|
||||
: null;
|
||||
if (libPackageJsonPath && existsSync(libPackageJsonPath)) {
|
||||
pkgJson = readJsonFile(libPackageJsonPath);
|
||||
|
||||
if (pkgJson) {
|
||||
version = pkgJson.version;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...libraries,
|
||||
[library.name]: {
|
||||
...(version
|
||||
? {
|
||||
requiredVersion: version,
|
||||
singleton: true,
|
||||
}
|
||||
: { requiredVersion: false }),
|
||||
eager,
|
||||
},
|
||||
};
|
||||
}, {} as Record<string, SharedLibraryConfig>);
|
||||
},
|
||||
getReplacementPlugin: () =>
|
||||
new NormalModuleReplacementPlugin(/./, (req) => {
|
||||
if (!req.request.startsWith('.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const from = req.context;
|
||||
const to = normalize(join(req.context, req.request));
|
||||
|
||||
for (const library of pathMappings) {
|
||||
const libFolder = normalize(dirname(library.path));
|
||||
if (!from.startsWith(libFolder) && to.startsWith(libFolder)) {
|
||||
const newReq = library.name.endsWith('/*')
|
||||
? /**
|
||||
* req usually is in the form of "../../../path/to/file"
|
||||
* library.path is usually in the form of "/Users/username/path/to/Workspace/path/to/library"
|
||||
*
|
||||
* When a wildcard is used in the TS path mappings, we want to get everything after the import to
|
||||
* re-route the request correctly inline with the rspack resolve.alias
|
||||
*/
|
||||
join(
|
||||
library.name,
|
||||
req.request.split(
|
||||
library.path.replace(workspaceRoot, '').replace('/*', '')
|
||||
)[1]
|
||||
)
|
||||
: library.name;
|
||||
req.request = newReq;
|
||||
}
|
||||
}
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the Module Federation Share Config for a specific package and the
|
||||
* specified version of that package.
|
||||
* @param pkgName - Name of the package to share
|
||||
* @param version - Version of the package to require by other apps in the Module Federation setup
|
||||
*/
|
||||
export function getNpmPackageSharedConfig(
|
||||
pkgName: string,
|
||||
version: string
|
||||
): SharedLibraryConfig | undefined {
|
||||
if (!version) {
|
||||
logger.warn(
|
||||
`Could not find a version for "${pkgName}" in the root "package.json" ` +
|
||||
'when collecting shared packages for the Module Federation setup. ' +
|
||||
'The package will not be shared.'
|
||||
);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return { singleton: true, strictVersion: true, requiredVersion: version };
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a dictionary of packages and their Module Federation Shared Config
|
||||
* from an array of package names.
|
||||
*
|
||||
* Lookup the versions of the packages from the root package.json file in the
|
||||
* workspace.
|
||||
* @param packages - Array of package names as strings
|
||||
*/
|
||||
export function sharePackages(
|
||||
packages: string[]
|
||||
): Record<string, SharedLibraryConfig> {
|
||||
const pkgJson = readRootPackageJson();
|
||||
const allPackages: { name: string; version: string }[] = [];
|
||||
packages.forEach((pkg) => {
|
||||
const pkgVersion =
|
||||
pkgJson.dependencies?.[pkg] ?? pkgJson.devDependencies?.[pkg];
|
||||
allPackages.push({ name: pkg, version: pkgVersion });
|
||||
collectPackageSecondaryEntryPoints(pkg, pkgVersion, allPackages);
|
||||
});
|
||||
|
||||
return allPackages.reduce((shared, pkg) => {
|
||||
const config = getNpmPackageSharedConfig(pkg.name, pkg.version);
|
||||
if (config) {
|
||||
shared[pkg.name] = config;
|
||||
}
|
||||
|
||||
return shared;
|
||||
}, {} as Record<string, SharedLibraryConfig>);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a custom function provided by the user that will modify the Shared Config
|
||||
* of the dependencies for the Module Federation build.
|
||||
*
|
||||
* @param sharedConfig - The original Shared Config to be modified
|
||||
* @param sharedFn - The custom function to run
|
||||
*/
|
||||
export function applySharedFunction(
|
||||
sharedConfig: Record<string, SharedLibraryConfig>,
|
||||
sharedFn: SharedFunction | undefined
|
||||
): void {
|
||||
if (!sharedFn) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [libraryName, library] of Object.entries(sharedConfig)) {
|
||||
const mappedDependency = sharedFn(libraryName, library);
|
||||
if (mappedDependency === false) {
|
||||
delete sharedConfig[libraryName];
|
||||
continue;
|
||||
} else if (!mappedDependency) {
|
||||
continue;
|
||||
}
|
||||
|
||||
sharedConfig[libraryName] = mappedDependency;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additional dependencies to the shared package that may not have been
|
||||
* discovered by the project graph.
|
||||
*
|
||||
* This can be useful for applications that use a Dependency Injection system
|
||||
* that expects certain Singleton values to be present in the shared injection
|
||||
* hierarchy.
|
||||
*
|
||||
* @param sharedConfig - The original Shared Config
|
||||
* @param additionalShared - The additional dependencies to add
|
||||
* @param projectGraph - The Nx project graph
|
||||
*/
|
||||
export function applyAdditionalShared(
|
||||
sharedConfig: Record<string, SharedLibraryConfig>,
|
||||
additionalShared: AdditionalSharedConfig | undefined,
|
||||
projectGraph: ProjectGraph
|
||||
): void {
|
||||
if (!additionalShared) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const shared of additionalShared) {
|
||||
if (typeof shared === 'string') {
|
||||
addStringDependencyToSharedConfig(sharedConfig, shared, projectGraph);
|
||||
} else if (Array.isArray(shared)) {
|
||||
sharedConfig[shared[0]] = shared[1];
|
||||
} else if (typeof shared === 'object') {
|
||||
sharedConfig[shared.libraryName] = shared.sharedConfig;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addStringDependencyToSharedConfig(
|
||||
sharedConfig: Record<string, SharedLibraryConfig>,
|
||||
dependency: string,
|
||||
projectGraph: ProjectGraph
|
||||
): void {
|
||||
if (projectGraph.nodes[dependency]) {
|
||||
sharedConfig[dependency] = { requiredVersion: false };
|
||||
} else if (projectGraph.externalNodes?.[`npm:${dependency}`]) {
|
||||
const pkgJson = readRootPackageJson();
|
||||
const config = getNpmPackageSharedConfig(
|
||||
dependency,
|
||||
pkgJson.dependencies?.[dependency] ??
|
||||
pkgJson.devDependencies?.[dependency]
|
||||
);
|
||||
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
|
||||
sharedConfig[dependency] = config;
|
||||
} else {
|
||||
throw new Error(
|
||||
`The specified dependency "${dependency}" in the additionalShared configuration does not exist in the project graph. ` +
|
||||
`Please check your additionalShared configuration and make sure you are including valid workspace projects or npm packages.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function getEmptySharedLibrariesConfig() {
|
||||
return {
|
||||
getAliases: () => ({}),
|
||||
getLibraries: () => ({}),
|
||||
getReplacementPlugin: () =>
|
||||
new NormalModuleReplacementPlugin(/./, () => undefined),
|
||||
};
|
||||
}
|
||||
@ -1,62 +0,0 @@
|
||||
import { logger } from '@nx/devkit';
|
||||
import type { Express } from 'express';
|
||||
import { existsSync, readFileSync } from 'fs';
|
||||
import { StaticRemotesConfig } from './parse-static-remotes-config';
|
||||
|
||||
export function startRemoteProxies(
|
||||
staticRemotesConfig: StaticRemotesConfig,
|
||||
mappedLocationsOfRemotes: Record<string, string>,
|
||||
sslOptions?: { pathToCert: string; pathToKey: string }
|
||||
) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { createProxyMiddleware } = require('http-proxy-middleware');
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const express = require('express');
|
||||
let sslCert: Buffer;
|
||||
let sslKey: Buffer;
|
||||
if (sslOptions && sslOptions.pathToCert && sslOptions.pathToKey) {
|
||||
if (existsSync(sslOptions.pathToCert) && existsSync(sslOptions.pathToKey)) {
|
||||
sslCert = readFileSync(sslOptions.pathToCert);
|
||||
sslKey = readFileSync(sslOptions.pathToKey);
|
||||
} else {
|
||||
logger.warn(
|
||||
`Encountered SSL options in project.json, however, the certificate files do not exist in the filesystem. Using http.`
|
||||
);
|
||||
logger.warn(
|
||||
`Attempted to find '${sslOptions.pathToCert}' and '${sslOptions.pathToKey}'.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const http: typeof import('http') = require('http');
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const https: typeof import('https') = require('https');
|
||||
|
||||
logger.info(`NX Starting static remotes proxies...`);
|
||||
for (const app of staticRemotesConfig.remotes) {
|
||||
const expressProxy: Express = express();
|
||||
expressProxy.use(
|
||||
createProxyMiddleware({
|
||||
target: mappedLocationsOfRemotes[app],
|
||||
changeOrigin: true,
|
||||
secure: sslCert ? false : undefined,
|
||||
})
|
||||
);
|
||||
const proxyServer = (
|
||||
sslCert
|
||||
? https.createServer(
|
||||
{
|
||||
cert: sslCert,
|
||||
key: sslKey,
|
||||
},
|
||||
expressProxy
|
||||
)
|
||||
: http.createServer(expressProxy)
|
||||
).listen(staticRemotesConfig.config[app].port);
|
||||
process.on('SIGTERM', () => proxyServer.close());
|
||||
process.on('exit', () => proxyServer.close());
|
||||
}
|
||||
logger.info(`NX Static remotes proxies started successfully`);
|
||||
}
|
||||
@ -1,77 +0,0 @@
|
||||
import { logger } from '@nx/devkit';
|
||||
import type { Express } from 'express';
|
||||
import { existsSync, readFileSync } from 'fs';
|
||||
import type { StaticRemotesConfig } from './parse-static-remotes-config';
|
||||
|
||||
export function startSsrRemoteProxies(
|
||||
staticRemotesConfig: StaticRemotesConfig,
|
||||
mappedLocationsOfRemotes: Record<string, string>,
|
||||
sslOptions?: { pathToCert: string; pathToKey: string }
|
||||
) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { createProxyMiddleware } = require('http-proxy-middleware');
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const express = require('express');
|
||||
|
||||
let sslCert: Buffer;
|
||||
let sslKey: Buffer;
|
||||
if (sslOptions && sslOptions.pathToCert && sslOptions.pathToKey) {
|
||||
if (existsSync(sslOptions.pathToCert) && existsSync(sslOptions.pathToKey)) {
|
||||
sslCert = readFileSync(sslOptions.pathToCert);
|
||||
sslKey = readFileSync(sslOptions.pathToKey);
|
||||
} else {
|
||||
logger.warn(
|
||||
`Encountered SSL options in project.json, however, the certificate files do not exist in the filesystem. Using http.`
|
||||
);
|
||||
logger.warn(
|
||||
`Attempted to find '${sslOptions.pathToCert}' and '${sslOptions.pathToKey}'.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const http: typeof import('http') = require('http');
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const https: typeof import('https') = require('https');
|
||||
|
||||
logger.info(`NX Starting static remotes proxies...`);
|
||||
for (const app of staticRemotesConfig.remotes) {
|
||||
const expressProxy: Express = express();
|
||||
/**
|
||||
* SSR remotes have two output paths: one for the browser and one for the server.
|
||||
* We need to handle paths for both of them.
|
||||
* The browser output path is used to serve the client-side code.
|
||||
* The server output path is used to serve the server-side code.
|
||||
*/
|
||||
|
||||
expressProxy.use(
|
||||
createProxyMiddleware({
|
||||
target: `${mappedLocationsOfRemotes[app]}`,
|
||||
secure: sslCert ? false : undefined,
|
||||
changeOrigin: true,
|
||||
pathRewrite: (path) => {
|
||||
if (path.includes('/server')) {
|
||||
return path;
|
||||
} else {
|
||||
return `browser/${path}`;
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const proxyServer = (
|
||||
sslCert
|
||||
? https.createServer(
|
||||
{
|
||||
cert: sslCert,
|
||||
key: sslKey,
|
||||
},
|
||||
expressProxy
|
||||
)
|
||||
: http.createServer(expressProxy)
|
||||
).listen(staticRemotesConfig.config[app].port);
|
||||
process.on('SIGTERM', () => proxyServer.close());
|
||||
process.on('exit', () => proxyServer.close());
|
||||
}
|
||||
logger.info(`Nx SSR Static remotes proxies started successfully`);
|
||||
}
|
||||
@ -1,75 +0,0 @@
|
||||
import { existsSync } from 'fs';
|
||||
import { ParsedCommandLine } from 'typescript';
|
||||
import { dirname, join } from 'path';
|
||||
import { workspaceRoot } from '@nx/devkit';
|
||||
|
||||
const tsConfig: Map<string, ParsedCommandLine> = new Map();
|
||||
const tsPathMappings: Map<string, ParsedCommandLine['options']['paths']> =
|
||||
new Map();
|
||||
|
||||
export function readTsPathMappings(
|
||||
tsConfigPath: string = process.env.NX_TSCONFIG_PATH ?? getRootTsConfigPath()
|
||||
): ParsedCommandLine['options']['paths'] {
|
||||
if (tsPathMappings.has(tsConfigPath)) {
|
||||
return tsPathMappings.get(tsConfigPath);
|
||||
}
|
||||
|
||||
if (!tsConfig.has(tsConfigPath)) {
|
||||
tsConfig.set(tsConfigPath, readTsConfiguration(tsConfigPath));
|
||||
}
|
||||
tsPathMappings.set(tsConfigPath, {});
|
||||
Object.entries(tsConfig.get(tsConfigPath).options?.paths ?? {}).forEach(
|
||||
([alias, paths]) => {
|
||||
tsPathMappings.set(tsConfigPath, {
|
||||
...tsPathMappings.get(tsConfigPath),
|
||||
[alias]: paths.map((path) => path.replace(/^\.\//, '')),
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return tsPathMappings.get(tsConfigPath);
|
||||
}
|
||||
|
||||
function readTsConfiguration(tsConfigPath: string): ParsedCommandLine {
|
||||
if (!existsSync(tsConfigPath)) {
|
||||
throw new Error(
|
||||
`NX MF: TsConfig Path for workspace libraries does not exist! (${tsConfigPath}).`
|
||||
);
|
||||
}
|
||||
|
||||
return readTsConfig(tsConfigPath);
|
||||
}
|
||||
|
||||
let tsModule: typeof import('typescript');
|
||||
|
||||
export function readTsConfig(tsConfigPath: string): ParsedCommandLine {
|
||||
if (!tsModule) {
|
||||
tsModule = require('typescript');
|
||||
}
|
||||
const readResult = tsModule.readConfigFile(
|
||||
tsConfigPath,
|
||||
tsModule.sys.readFile
|
||||
);
|
||||
return tsModule.parseJsonConfigFileContent(
|
||||
readResult.config,
|
||||
tsModule.sys,
|
||||
dirname(tsConfigPath)
|
||||
);
|
||||
}
|
||||
|
||||
export function getRootTsConfigPath(): string | null {
|
||||
const tsConfigFileName = getRootTsConfigFileName();
|
||||
|
||||
return tsConfigFileName ? join(workspaceRoot, tsConfigFileName) : null;
|
||||
}
|
||||
|
||||
function getRootTsConfigFileName(): string | null {
|
||||
for (const tsConfigName of ['tsconfig.base.json', 'tsconfig.json']) {
|
||||
const tsConfigPath = join(workspaceRoot, tsConfigName);
|
||||
if (existsSync(tsConfigPath)) {
|
||||
return tsConfigName;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
import { joinPathFragments, readJsonFile, workspaceRoot } from '@nx/devkit';
|
||||
import { existsSync } from 'fs';
|
||||
|
||||
export function readRootPackageJson(): {
|
||||
dependencies?: { [key: string]: string };
|
||||
devDependencies?: { [key: string]: string };
|
||||
} {
|
||||
const pkgJsonPath = joinPathFragments(workspaceRoot, 'package.json');
|
||||
if (!existsSync(pkgJsonPath)) {
|
||||
throw new Error(
|
||||
'NX MFE: Could not find root package.json to determine dependency versions.'
|
||||
);
|
||||
}
|
||||
|
||||
return readJsonFile(pkgJsonPath);
|
||||
}
|
||||
@ -4,15 +4,16 @@ import {
|
||||
readCachedProjectGraph,
|
||||
} from '@nx/devkit';
|
||||
import { readCachedProjectConfiguration } from 'nx/src/project-graph/project-graph';
|
||||
import { getDependentPackagesForProject } from '../dependencies';
|
||||
import { ModuleFederationConfig } from '../models';
|
||||
import { mapRemotes, mapRemotesForSSR } from '../remotes';
|
||||
import {
|
||||
ModuleFederationConfig,
|
||||
applyAdditionalShared,
|
||||
applySharedFunction,
|
||||
sharePackages,
|
||||
shareWorkspaceLibraries,
|
||||
} from '../share';
|
||||
mapRemotes,
|
||||
mapRemotesForSSR,
|
||||
getDependentPackagesForProject,
|
||||
} from '@nx/module-federation';
|
||||
|
||||
export function getFunctionDeterminateRemoteUrl(isServer = false) {
|
||||
const target = 'serve';
|
||||
@ -123,7 +124,8 @@ export async function getModuleFederationConfig(
|
||||
mappedRemotes = mapRemotesFunction(
|
||||
mfConfig.remotes,
|
||||
'js',
|
||||
determineRemoteUrlFunction
|
||||
determineRemoteUrlFunction,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import { DefinePlugin } from '@rspack/core';
|
||||
import {
|
||||
ModuleFederationConfig,
|
||||
NxModuleFederationConfigOverride,
|
||||
} from '../models';
|
||||
} from '@nx/module-federation';
|
||||
import { getModuleFederationConfig } from './utils';
|
||||
import { NxRspackExecutionContext } from '../../config';
|
||||
|
||||
@ -57,7 +57,7 @@ export async function withModuleFederationForSSR(
|
||||
...(configOverride?.runtimePlugins ?? []),
|
||||
require.resolve('@module-federation/node/runtimePlugin'),
|
||||
require.resolve(
|
||||
'@nx/rspack/src/utils/module-federation/plugins/runtime-library-control.plugin.js'
|
||||
'@nx/module-federation/src/utils/plugins/runtime-library-control.plugin.js'
|
||||
),
|
||||
]
|
||||
: [
|
||||
|
||||
@ -4,7 +4,7 @@ import { DefinePlugin } from '@rspack/core';
|
||||
import {
|
||||
ModuleFederationConfig,
|
||||
NxModuleFederationConfigOverride,
|
||||
} from '../models';
|
||||
} from '@nx/module-federation';
|
||||
import { getModuleFederationConfig } from './utils';
|
||||
import { NxRspackExecutionContext } from '../../config';
|
||||
|
||||
@ -79,7 +79,7 @@ export async function withModuleFederation(
|
||||
? [
|
||||
...(configOverride?.runtimePlugins ?? []),
|
||||
require.resolve(
|
||||
'@nx/rspack/src/utils/module-federation/plugins/runtime-library-control.plugin.js'
|
||||
'@nx/module-federation/src/utils/plugins/runtime-library-control.plugin.js'
|
||||
),
|
||||
]
|
||||
: configOverride?.runtimePlugins,
|
||||
|
||||
@ -36,5 +36,4 @@ export * from './src/executors/webpack/webpack.impl';
|
||||
export * from './src/utils/get-css-module-local-ident';
|
||||
export * from './src/utils/with-nx';
|
||||
export * from './src/utils/with-web';
|
||||
export * from './src/utils/module-federation/public-api';
|
||||
export * from './src/utils/e2e-web-server-info-utils';
|
||||
|
||||
@ -32,8 +32,6 @@
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.23.2",
|
||||
"@phenomnomnominal/tsquery": "~5.0.1",
|
||||
"@module-federation/sdk": "^0.6.0",
|
||||
"@module-federation/enhanced": "^0.6.0",
|
||||
"ajv": "^8.12.0",
|
||||
"autoprefixer": "^10.4.9",
|
||||
"babel-loader": "^9.1.2",
|
||||
@ -42,9 +40,7 @@
|
||||
"copy-webpack-plugin": "^10.2.4",
|
||||
"css-loader": "^6.4.0",
|
||||
"css-minimizer-webpack-plugin": "^5.0.0",
|
||||
"express": "^4.19.2",
|
||||
"fork-ts-checker-webpack-plugin": "7.2.13",
|
||||
"http-proxy-middleware": "^3.0.3",
|
||||
"less": "4.1.3",
|
||||
"less-loader": "11.1.0",
|
||||
"license-webpack-plugin": "^4.0.2",
|
||||
|
||||
@ -1,158 +0,0 @@
|
||||
import * as tsUtils from './typescript';
|
||||
import { getDependentPackagesForProject } from './dependencies';
|
||||
|
||||
describe('getDependentPackagesForProject', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should collect npm packages and workspaces libraries without duplicates', () => {
|
||||
jest.spyOn(tsUtils, 'readTsPathMappings').mockReturnValue({
|
||||
'@myorg/lib1': ['libs/lib1/src/index.ts'],
|
||||
'@myorg/lib2': ['libs/lib2/src/index.ts'],
|
||||
});
|
||||
|
||||
const dependencies = getDependentPackagesForProject(
|
||||
{
|
||||
dependencies: {
|
||||
shell: [
|
||||
{ source: 'shell', target: 'lib1', type: 'static' },
|
||||
{ source: 'shell', target: 'lib2', type: 'static' },
|
||||
{ source: 'shell', target: 'npm:lodash', type: 'static' },
|
||||
],
|
||||
lib1: [{ source: 'lib1', target: 'lib2', type: 'static' }],
|
||||
lib2: [{ source: 'lib2', target: 'npm:lodash', type: 'static' }],
|
||||
},
|
||||
nodes: {
|
||||
shell: {
|
||||
name: 'shell',
|
||||
data: { root: 'apps/shell', sourceRoot: 'apps/shell/src' },
|
||||
type: 'app',
|
||||
},
|
||||
lib1: {
|
||||
name: 'lib1',
|
||||
data: { root: 'libs/lib1', sourceRoot: 'libs/lib1/src' },
|
||||
type: 'lib',
|
||||
},
|
||||
lib2: {
|
||||
name: 'lib2',
|
||||
data: { root: 'libs/lib2', sourceRoot: 'libs/lib2/src' },
|
||||
type: 'lib',
|
||||
},
|
||||
} as any,
|
||||
},
|
||||
'shell'
|
||||
);
|
||||
|
||||
expect(dependencies).toEqual({
|
||||
workspaceLibraries: [
|
||||
{ name: 'lib1', root: 'libs/lib1', importKey: '@myorg/lib1' },
|
||||
{ name: 'lib2', root: 'libs/lib2', importKey: '@myorg/lib2' },
|
||||
],
|
||||
npmPackages: ['lodash'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should collect workspaces libraries recursively', () => {
|
||||
jest.spyOn(tsUtils, 'readTsPathMappings').mockReturnValue({
|
||||
'@myorg/lib1': ['libs/lib1/src/index.ts'],
|
||||
'@myorg/lib2': ['libs/lib2/src/index.ts'],
|
||||
'@myorg/lib3': ['libs/lib3/src/index.ts'],
|
||||
});
|
||||
|
||||
const dependencies = getDependentPackagesForProject(
|
||||
{
|
||||
dependencies: {
|
||||
shell: [{ source: 'shell', target: 'lib1', type: 'static' }],
|
||||
lib1: [{ source: 'lib1', target: 'lib2', type: 'static' }],
|
||||
lib2: [{ source: 'lib2', target: 'lib3', type: 'static' }],
|
||||
},
|
||||
nodes: {
|
||||
shell: {
|
||||
name: 'shell',
|
||||
data: { root: 'apps/shell', sourceRoot: 'apps/shell/src' },
|
||||
type: 'app',
|
||||
},
|
||||
lib1: {
|
||||
name: 'lib1',
|
||||
data: { root: 'libs/lib1', sourceRoot: 'libs/lib1/src' },
|
||||
type: 'lib',
|
||||
},
|
||||
lib2: {
|
||||
name: 'lib2',
|
||||
data: { root: 'libs/lib2', sourceRoot: 'libs/lib2/src' },
|
||||
type: 'lib',
|
||||
},
|
||||
lib3: {
|
||||
name: 'lib3',
|
||||
data: { root: 'libs/lib3', sourceRoot: 'libs/lib3/src' },
|
||||
type: 'lib',
|
||||
},
|
||||
} as any,
|
||||
},
|
||||
'shell'
|
||||
);
|
||||
|
||||
expect(dependencies).toEqual({
|
||||
workspaceLibraries: [
|
||||
{ name: 'lib1', root: 'libs/lib1', importKey: '@myorg/lib1' },
|
||||
{ name: 'lib2', root: 'libs/lib2', importKey: '@myorg/lib2' },
|
||||
{ name: 'lib3', root: 'libs/lib3', importKey: '@myorg/lib3' },
|
||||
],
|
||||
npmPackages: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('should ignore TS path mappings with wildcards', () => {
|
||||
jest.spyOn(tsUtils, 'readTsPathMappings').mockReturnValue({
|
||||
'@myorg/lib1': ['libs/lib1/src/index.ts'],
|
||||
'@myorg/lib1/*': ['libs/lib1/src/lib/*'],
|
||||
'@myorg/lib2': ['libs/lib2/src/index.ts'],
|
||||
'@myorg/lib2/*': ['libs/lib2/src/lib/*'],
|
||||
'@myorg/lib3': ['libs/lib3/src/index.ts'],
|
||||
'@myorg/lib3/*': ['libs/lib3/src/lib/*'],
|
||||
});
|
||||
|
||||
const dependencies = getDependentPackagesForProject(
|
||||
{
|
||||
dependencies: {
|
||||
shell: [{ source: 'shell', target: 'lib1', type: 'static' }],
|
||||
lib1: [{ source: 'lib1', target: 'lib2', type: 'static' }],
|
||||
lib2: [{ source: 'lib2', target: 'lib3', type: 'static' }],
|
||||
},
|
||||
nodes: {
|
||||
shell: {
|
||||
name: 'shell',
|
||||
data: { root: 'apps/shell', sourceRoot: 'apps/shell/src' },
|
||||
type: 'app',
|
||||
},
|
||||
lib1: {
|
||||
name: 'lib1',
|
||||
data: { root: 'libs/lib1', sourceRoot: 'libs/lib1/src' },
|
||||
type: 'lib',
|
||||
},
|
||||
lib2: {
|
||||
name: 'lib2',
|
||||
data: { root: 'libs/lib2', sourceRoot: 'libs/lib2/src' },
|
||||
type: 'lib',
|
||||
},
|
||||
lib3: {
|
||||
name: 'lib3',
|
||||
data: { root: 'libs/lib3', sourceRoot: 'libs/lib3/src' },
|
||||
type: 'lib',
|
||||
},
|
||||
} as any,
|
||||
},
|
||||
'shell'
|
||||
);
|
||||
|
||||
expect(dependencies).toEqual({
|
||||
workspaceLibraries: [
|
||||
{ name: 'lib1', root: 'libs/lib1', importKey: '@myorg/lib1' },
|
||||
{ name: 'lib2', root: 'libs/lib2', importKey: '@myorg/lib2' },
|
||||
{ name: 'lib3', root: 'libs/lib3', importKey: '@myorg/lib3' },
|
||||
],
|
||||
npmPackages: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,94 +0,0 @@
|
||||
import type { ProjectGraph } from '@nx/devkit';
|
||||
import type { WorkspaceLibrary } from './models';
|
||||
import { readTsPathMappings } from './typescript';
|
||||
import {
|
||||
getOutputsForTargetAndConfiguration,
|
||||
parseTargetString,
|
||||
} from '@nx/devkit';
|
||||
|
||||
export function getDependentPackagesForProject(
|
||||
projectGraph: ProjectGraph,
|
||||
name: string
|
||||
): {
|
||||
workspaceLibraries: WorkspaceLibrary[];
|
||||
npmPackages: string[];
|
||||
} {
|
||||
const { npmPackages, workspaceLibraries } = collectDependencies(
|
||||
projectGraph,
|
||||
name
|
||||
);
|
||||
|
||||
return {
|
||||
workspaceLibraries: [...workspaceLibraries.values()],
|
||||
npmPackages: [...npmPackages],
|
||||
};
|
||||
}
|
||||
|
||||
function collectDependencies(
|
||||
projectGraph: ProjectGraph,
|
||||
name: string,
|
||||
dependencies = {
|
||||
workspaceLibraries: new Map<string, WorkspaceLibrary>(),
|
||||
npmPackages: new Set<string>(),
|
||||
},
|
||||
seen: Set<string> = new Set()
|
||||
): {
|
||||
workspaceLibraries: Map<string, WorkspaceLibrary>;
|
||||
npmPackages: Set<string>;
|
||||
} {
|
||||
if (seen.has(name)) {
|
||||
return dependencies;
|
||||
}
|
||||
seen.add(name);
|
||||
|
||||
(projectGraph.dependencies[name] ?? []).forEach((dependency) => {
|
||||
if (dependency.target.startsWith('npm:')) {
|
||||
dependencies.npmPackages.add(dependency.target.replace('npm:', ''));
|
||||
} else {
|
||||
dependencies.workspaceLibraries.set(dependency.target, {
|
||||
name: dependency.target,
|
||||
root: projectGraph.nodes[dependency.target].data.root,
|
||||
importKey: getLibraryImportPath(dependency.target, projectGraph),
|
||||
});
|
||||
collectDependencies(projectGraph, dependency.target, dependencies, seen);
|
||||
}
|
||||
});
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
function getLibraryImportPath(
|
||||
library: string,
|
||||
projectGraph: ProjectGraph
|
||||
): string | undefined {
|
||||
let buildLibsFromSource = true;
|
||||
if (process.env.NX_BUILD_LIBS_FROM_SOURCE) {
|
||||
buildLibsFromSource = process.env.NX_BUILD_LIBS_FROM_SOURCE === 'true';
|
||||
}
|
||||
const libraryNode = projectGraph.nodes[library];
|
||||
let sourceRoots = [libraryNode.data.sourceRoot];
|
||||
|
||||
if (!buildLibsFromSource && process.env.NX_BUILD_TARGET) {
|
||||
const buildTarget = parseTargetString(
|
||||
process.env.NX_BUILD_TARGET,
|
||||
projectGraph
|
||||
);
|
||||
sourceRoots = getOutputsForTargetAndConfiguration(
|
||||
buildTarget,
|
||||
{},
|
||||
libraryNode
|
||||
);
|
||||
}
|
||||
|
||||
const tsConfigPathMappings = readTsPathMappings();
|
||||
|
||||
for (const [key, value] of Object.entries(tsConfigPathMappings)) {
|
||||
for (const src of sourceRoots) {
|
||||
if (value.find((path) => path.startsWith(src))) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
export * from './share';
|
||||
export * from './dependencies';
|
||||
export * from './package-json';
|
||||
export * from './remotes';
|
||||
export * from './models';
|
||||
export * from './get-remotes-for-host';
|
||||
@ -1,70 +0,0 @@
|
||||
import type { NormalModuleReplacementPlugin } from 'webpack';
|
||||
import type { moduleFederationPlugin } from '@module-federation/sdk';
|
||||
|
||||
export type ModuleFederationLibrary = { type: string; name: string };
|
||||
|
||||
export type WorkspaceLibrary = {
|
||||
name: string;
|
||||
root: string;
|
||||
importKey: string | undefined;
|
||||
};
|
||||
|
||||
export type SharedWorkspaceLibraryConfig = {
|
||||
getAliases: () => Record<string, string>;
|
||||
getLibraries: (
|
||||
projectRoot: string,
|
||||
eager?: boolean
|
||||
) => Record<string, SharedLibraryConfig>;
|
||||
getReplacementPlugin: () => NormalModuleReplacementPlugin;
|
||||
};
|
||||
|
||||
export type Remotes = Array<string | [remoteName: string, remoteUrl: string]>;
|
||||
|
||||
export interface SharedLibraryConfig {
|
||||
singleton?: boolean;
|
||||
strictVersion?: boolean;
|
||||
requiredVersion?: false | string;
|
||||
eager?: boolean;
|
||||
}
|
||||
|
||||
export type SharedFunction = (
|
||||
libraryName: string,
|
||||
sharedConfig: SharedLibraryConfig
|
||||
) => undefined | false | SharedLibraryConfig;
|
||||
|
||||
export type AdditionalSharedConfig = Array<
|
||||
| string
|
||||
| [libraryName: string, sharedConfig: SharedLibraryConfig]
|
||||
| { libraryName: string; sharedConfig: SharedLibraryConfig }
|
||||
>;
|
||||
|
||||
export interface ModuleFederationConfig {
|
||||
name: string;
|
||||
remotes?: Remotes;
|
||||
library?: ModuleFederationLibrary;
|
||||
exposes?: Record<string, string>;
|
||||
shared?: SharedFunction;
|
||||
additionalShared?: AdditionalSharedConfig;
|
||||
/**
|
||||
* `nxRuntimeLibraryControlPlugin` is a runtime module federation plugin to ensure
|
||||
* that shared libraries are resolved from a remote with live reload capabilities.
|
||||
* If you run into any issues with loading shared libraries, try disabling this option.
|
||||
*/
|
||||
disableNxRuntimeLibraryControlPlugin?: boolean;
|
||||
}
|
||||
|
||||
export type NxModuleFederationConfigOverride = Omit<
|
||||
moduleFederationPlugin.ModuleFederationPluginOptions,
|
||||
| 'exposes'
|
||||
| 'remotes'
|
||||
| 'name'
|
||||
| 'library'
|
||||
| 'shared'
|
||||
| 'filename'
|
||||
| 'remoteType'
|
||||
>;
|
||||
|
||||
export type WorkspaceLibrarySecondaryEntryPoint = {
|
||||
name: string;
|
||||
path: string;
|
||||
};
|
||||
@ -1,16 +0,0 @@
|
||||
import { existsSync } from 'fs';
|
||||
import { workspaceRoot, readJsonFile, joinPathFragments } from '@nx/devkit';
|
||||
|
||||
export function readRootPackageJson(): {
|
||||
dependencies?: { [key: string]: string };
|
||||
devDependencies?: { [key: string]: string };
|
||||
} {
|
||||
const pkgJsonPath = joinPathFragments(workspaceRoot, 'package.json');
|
||||
if (!existsSync(pkgJsonPath)) {
|
||||
throw new Error(
|
||||
'NX MF: Could not find root package.json to determine dependency versions.'
|
||||
);
|
||||
}
|
||||
|
||||
return readJsonFile(pkgJsonPath);
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
import type { ExecutorContext } from '@nx/devkit';
|
||||
import { basename, dirname } from 'path';
|
||||
|
||||
export type StaticRemoteConfig = {
|
||||
basePath: string;
|
||||
outputPath: string;
|
||||
urlSegment: string;
|
||||
port: number;
|
||||
};
|
||||
export type StaticRemotesConfig = {
|
||||
remotes: string[];
|
||||
config: Record<string, StaticRemoteConfig> | undefined;
|
||||
};
|
||||
|
||||
export function parseStaticRemotesConfig(
|
||||
staticRemotes: string[] | undefined,
|
||||
context: ExecutorContext
|
||||
): StaticRemotesConfig {
|
||||
if (!staticRemotes?.length) {
|
||||
return { remotes: [], config: undefined };
|
||||
}
|
||||
|
||||
const config: Record<string, StaticRemoteConfig> = {};
|
||||
for (const app of staticRemotes) {
|
||||
const outputPath =
|
||||
context.projectGraph.nodes[app].data.targets['build'].options.outputPath;
|
||||
const basePath = dirname(outputPath);
|
||||
const urlSegment = basename(outputPath);
|
||||
const port =
|
||||
context.projectGraph.nodes[app].data.targets['serve'].options.port;
|
||||
config[app] = { basePath, outputPath, urlSegment, port };
|
||||
}
|
||||
|
||||
return { remotes: staticRemotes, config };
|
||||
}
|
||||
|
||||
export function parseStaticSsrRemotesConfig(
|
||||
staticRemotes: string[] | undefined,
|
||||
context: ExecutorContext
|
||||
): StaticRemotesConfig {
|
||||
if (!staticRemotes?.length) {
|
||||
return { remotes: [], config: undefined };
|
||||
}
|
||||
const config: Record<string, StaticRemoteConfig> = {};
|
||||
for (const app of staticRemotes) {
|
||||
const outputPath = dirname(
|
||||
context.projectGraph.nodes[app].data.targets['build'].options.outputPath // dist/checkout/browser -> checkout
|
||||
) as string;
|
||||
const basePath = dirname(outputPath); // dist/checkout -> dist
|
||||
const urlSegment = basename(outputPath); // dist/checkout -> checkout
|
||||
const port =
|
||||
context.projectGraph.nodes[app].data.targets['serve'].options.port;
|
||||
config[app] = { basePath, outputPath, urlSegment, port };
|
||||
}
|
||||
|
||||
return { remotes: staticRemotes, config };
|
||||
}
|
||||
@ -1,71 +0,0 @@
|
||||
import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime';
|
||||
|
||||
const runtimeStore: {
|
||||
name?: string;
|
||||
devRemotes?: string[];
|
||||
sharedPackagesFromDev: Record<string, string>;
|
||||
} = {
|
||||
sharedPackagesFromDev: {},
|
||||
};
|
||||
|
||||
if (process.env.NX_MF_DEV_REMOTES) {
|
||||
// process.env.NX_MF_DEV_REMOTES is replaced by an array value via DefinePlugin, even though the original value is a stringified array.
|
||||
runtimeStore.devRemotes = process.env
|
||||
.NX_MF_DEV_REMOTES as unknown as string[];
|
||||
}
|
||||
|
||||
const nxRuntimeLibraryControlPlugin: () => FederationRuntimePlugin =
|
||||
function () {
|
||||
return {
|
||||
name: 'nx-runtime-library-control-plugin',
|
||||
beforeInit(args) {
|
||||
runtimeStore.name = args.options.name;
|
||||
return args;
|
||||
},
|
||||
resolveShare: (args) => {
|
||||
const { shareScopeMap, scope, pkgName, version, GlobalFederation } =
|
||||
args;
|
||||
|
||||
const originalResolver = args.resolver;
|
||||
args.resolver = function () {
|
||||
if (!runtimeStore.sharedPackagesFromDev[pkgName]) {
|
||||
if (!GlobalFederation.__INSTANCES__) {
|
||||
return originalResolver();
|
||||
} else if (!runtimeStore.devRemotes) {
|
||||
return originalResolver();
|
||||
}
|
||||
const devRemoteInstanceToUse = GlobalFederation.__INSTANCES__.find(
|
||||
(instance) =>
|
||||
instance.options.shared[pkgName] &&
|
||||
runtimeStore.devRemotes.find((dr) => instance.name === dr)
|
||||
);
|
||||
if (!devRemoteInstanceToUse) {
|
||||
return originalResolver();
|
||||
}
|
||||
runtimeStore.sharedPackagesFromDev[pkgName] =
|
||||
devRemoteInstanceToUse.name;
|
||||
}
|
||||
|
||||
const remoteInstanceName =
|
||||
runtimeStore.sharedPackagesFromDev[pkgName];
|
||||
const remoteInstance = GlobalFederation.__INSTANCES__.find(
|
||||
(instance) => instance.name === remoteInstanceName
|
||||
);
|
||||
try {
|
||||
const remotePkgInfo = remoteInstance.options.shared[pkgName].find(
|
||||
(shared) => shared.from === remoteInstanceName
|
||||
);
|
||||
remotePkgInfo.useIn.push(runtimeStore.name);
|
||||
remotePkgInfo.useIn = Array.from(new Set(remotePkgInfo.useIn));
|
||||
shareScopeMap[scope][pkgName][version] = remotePkgInfo;
|
||||
return remotePkgInfo;
|
||||
} catch {
|
||||
return originalResolver();
|
||||
}
|
||||
};
|
||||
return args;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default nxRuntimeLibraryControlPlugin;
|
||||
@ -1,45 +0,0 @@
|
||||
import * as fs from 'fs';
|
||||
import { readTsPathMappings } from './typescript';
|
||||
|
||||
let readConfigFileResult: any;
|
||||
let parseJsonConfigFileContentResult: any;
|
||||
jest.mock('typescript', () => ({
|
||||
...jest.requireActual('typescript'),
|
||||
readConfigFile: jest.fn().mockImplementation(() => readConfigFileResult),
|
||||
parseJsonConfigFileContent: jest
|
||||
.fn()
|
||||
.mockImplementation(() => parseJsonConfigFileContentResult),
|
||||
}));
|
||||
|
||||
describe('readTsPathMappings', () => {
|
||||
it('should normalize paths', () => {
|
||||
jest.spyOn(fs, 'existsSync').mockReturnValue(true);
|
||||
readConfigFileResult = {
|
||||
config: {
|
||||
options: {
|
||||
paths: {
|
||||
'@myorg/lib1': ['./libs/lib1/src/index.ts'],
|
||||
'@myorg/lib2': ['libs/lib2/src/index.ts'],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
parseJsonConfigFileContentResult = {
|
||||
options: {
|
||||
paths: {
|
||||
'@myorg/lib1': ['./libs/lib1/src/index.ts'],
|
||||
'@myorg/lib2': ['libs/lib2/src/index.ts'],
|
||||
},
|
||||
},
|
||||
fileNames: [],
|
||||
errors: [],
|
||||
};
|
||||
|
||||
const paths = readTsPathMappings('/path/to/tsconfig.json');
|
||||
|
||||
expect(paths).toEqual({
|
||||
'@myorg/lib1': ['libs/lib1/src/index.ts'],
|
||||
'@myorg/lib2': ['libs/lib2/src/index.ts'],
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -47,6 +47,8 @@
|
||||
"@nx/jest/*": ["packages/jest/*"],
|
||||
"@nx/js": ["packages/js/src/index.ts"],
|
||||
"@nx/js/*": ["packages/js/*"],
|
||||
"@nx/module-federation": ["packages/module-federation"],
|
||||
"@nx/module-federation/*": ["packages/module-federation/*"],
|
||||
"@nx/nest": ["packages/nest"],
|
||||
"@nx/next": ["packages/next"],
|
||||
"@nx/next/*": ["packages/next/*"],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user