fix(module-federation): remote names should follow JS variable naming schema (#28401)

<!-- 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 -->
We previously had a schema restriction on the characters allowed for
remote names. It was to prevent names that violated the JS spec for a
variable declaration.


## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Ensure invalid project names fail error allowing the user to fix it at
generation


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

Fixes #28354, #28408

---------

Co-authored-by: Jack Hsu <jack.hsu@gmail.com>
This commit is contained in:
Colum Ferry 2024-10-15 00:42:42 +01:00 committed by GitHub
parent 1c466d03e1
commit 1badac82c9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 331 additions and 73 deletions

View File

@ -12,9 +12,9 @@ import { E2eTestRunner, UnitTestRunner } from '../../utils/test-runners';
describe('federate-module', () => { describe('federate-module', () => {
let schema: Schema = { let schema: Schema = {
name: 'my-federated-module', name: 'my-federated-module',
remote: 'my-remote', remote: 'myremote',
path: 'apps/my-remote/src/my-federated-module.ts', path: 'apps/myremote/src/my-federated-module.ts',
remoteDirectory: 'apps/my-remote', remoteDirectory: 'apps/myremote',
}; };
describe('no remote', () => { describe('no remote', () => {
@ -29,32 +29,32 @@ describe('federate-module', () => {
const projects = getProjects(tree); const projects = getProjects(tree);
expect(projects.get('my-remote').root).toEqual('apps/my-remote'); expect(projects.get('myremote').root).toEqual('apps/myremote');
expect(tree.exists('apps/my-remote/module-federation.config.ts')).toBe( expect(tree.exists('apps/myremote/module-federation.config.ts')).toBe(
true true
); );
const content = tree.read( const content = tree.read(
'apps/my-remote/module-federation.config.ts', 'apps/myremote/module-federation.config.ts',
'utf-8' 'utf-8'
); );
expect(content).toContain( expect(content).toContain(
`'./my-federated-module': 'apps/my-remote/src/my-federated-module.ts'` `'./my-federated-module': 'apps/myremote/src/my-federated-module.ts'`
); );
const tsconfig = JSON.parse(tree.read('tsconfig.base.json', 'utf-8')); const tsconfig = JSON.parse(tree.read('tsconfig.base.json', 'utf-8'));
expect( expect(
tsconfig.compilerOptions.paths['my-remote/my-federated-module'] tsconfig.compilerOptions.paths['myremote/my-federated-module']
).toEqual(['apps/my-remote/src/my-federated-module.ts']); ).toEqual(['apps/myremote/src/my-federated-module.ts']);
}); });
}); });
describe('with remote', () => { describe('with remote', () => {
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
let remoteSchema: remoteSchma = { let remoteSchema: remoteSchma = {
name: 'my-remote', name: 'myremote',
directory: 'my-remote', directory: 'myremote',
e2eTestRunner: E2eTestRunner.Cypress, e2eTestRunner: E2eTestRunner.Cypress,
skipFormat: true, skipFormat: true,
linter: Linter.EsLint, linter: Linter.EsLint,
@ -78,7 +78,7 @@ describe('federate-module', () => {
); );
expect(content).not.toContain( expect(content).not.toContain(
`'./my-federated-module': 'apps/my-remote/src/my-federated-module.ts'` `'./my-federated-module': 'apps/myremote/src/my-federated-module.ts'`
); );
await federateModuleGenerator(tree, { await federateModuleGenerator(tree, {
@ -92,7 +92,7 @@ describe('federate-module', () => {
'utf-8' 'utf-8'
); );
expect(content).toContain( expect(content).toContain(
`'./my-federated-module': 'apps/my-remote/src/my-federated-module.ts'` `'./my-federated-module': 'apps/myremote/src/my-federated-module.ts'`
); );
const tsconfig = JSON.parse(tree.read('tsconfig.base.json', 'utf-8')); const tsconfig = JSON.parse(tree.read('tsconfig.base.json', 'utf-8'));
@ -100,7 +100,7 @@ describe('federate-module', () => {
tsconfig.compilerOptions.paths[ tsconfig.compilerOptions.paths[
`${remoteSchema.name}/my-federated-module` `${remoteSchema.name}/my-federated-module`
] ]
).toEqual(['apps/my-remote/src/my-federated-module.ts']); ).toEqual(['apps/myremote/src/my-federated-module.ts']);
}); });
}); });
}); });

View File

@ -443,4 +443,19 @@ describe('MF Remote App Generator', () => {
const packageJson = readJson(tree, 'package.json'); const packageJson = readJson(tree, 'package.json');
expect(packageJson).toEqual(initialPackageJson); expect(packageJson).toEqual(initialPackageJson);
}); });
it('should error when an invalid remote name is passed to the remote generator', async () => {
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
await expect(
generateTestRemoteApplication(tree, {
directory: 'test/my-remote',
})
).rejects.toMatchInlineSnapshot(`
[Error: Invalid remote name: my-remote. Remote project names must:
- Start with a letter, dollar sign ($) or underscore (_)
- Followed by any valid character (letters, digits, underscores, or dollar signs)
The regular expression used is ^[a-zA-Z_$][a-zA-Z_$0-9]*$.]
`);
});
}); });

View File

@ -3,6 +3,7 @@ import {
formatFiles, formatFiles,
getProjects, getProjects,
runTasksInSerial, runTasksInSerial,
stripIndents,
Tree, Tree,
} from '@nx/devkit'; } from '@nx/devkit';
import { import {
@ -39,6 +40,16 @@ export async function remote(tree: Tree, schema: Schema) {
directory: options.directory, directory: options.directory,
}); });
const REMOTE_NAME_REGEX = '^[a-zA-Z_$][a-zA-Z_$0-9]*$';
const remoteNameRegex = new RegExp(REMOTE_NAME_REGEX);
if (!remoteNameRegex.test(remoteProjectName)) {
throw new Error(
stripIndents`Invalid remote name: ${remoteProjectName}. Remote project names must:
- Start with a letter, dollar sign ($) or underscore (_)
- Followed by any valid character (letters, digits, underscores, or dollar signs)
The regular expression used is ${REMOTE_NAME_REGEX}.`
);
}
const port = options.port ?? findNextAvailablePort(tree); const port = options.port ?? findNextAvailablePort(tree);
const appInstallTask = await applicationGenerator(tree, { const appInstallTask = await applicationGenerator(tree, {

View File

@ -12,9 +12,9 @@ describe('federate-module', () => {
let tree: Tree; let tree: Tree;
let schema: Schema = { let schema: Schema = {
name: 'my-federated-module', name: 'my-federated-module',
remote: 'my-remote', remote: 'myremote',
remoteDirectory: 'my-remote', remoteDirectory: 'myremote',
path: 'my-remote/src/my-federated-module.ts', path: 'myremote/src/my-federated-module.ts',
style: 'css', style: 'css',
skipFormat: true, skipFormat: true,
bundler: 'webpack', bundler: 'webpack',
@ -33,7 +33,7 @@ describe('federate-module', () => {
beforeAll(() => { beforeAll(() => {
tree = createTreeWithEmptyWorkspace(); tree = createTreeWithEmptyWorkspace();
tree.write('my-remote/src/my-federated-module.ts', ''); // Ensure that the file exists tree.write('myremote/src/my-federated-module.ts', ''); // Ensure that the file exists
}); });
describe('no remote', () => { describe('no remote', () => {
it('should generate a remote and e2e', async () => { it('should generate a remote and e2e', async () => {
@ -41,27 +41,27 @@ describe('federate-module', () => {
const projects = getProjects(tree); const projects = getProjects(tree);
expect(projects.get('my-remote').root).toEqual('my-remote'); expect(projects.get('myremote').root).toEqual('myremote');
expect(projects.get('my-remote-e2e').root).toEqual('my-remote-e2e'); expect(projects.get('myremote-e2e').root).toEqual('myremote-e2e');
}); });
it('should contain an entry for the new path for module federation', async () => { it('should contain an entry for the new path for module federation', async () => {
await federateModuleGenerator(tree, schema); await federateModuleGenerator(tree, schema);
expect(tree.exists('my-remote/module-federation.config.ts')).toBe(true); expect(tree.exists('myremote/module-federation.config.ts')).toBe(true);
const content = tree.read( const content = tree.read(
'my-remote/module-federation.config.ts', 'myremote/module-federation.config.ts',
'utf-8' 'utf-8'
); );
expect(content).toContain( expect(content).toContain(
`'./my-federated-module': 'my-remote/src/my-federated-module.ts'` `'./my-federated-module': 'myremote/src/my-federated-module.ts'`
); );
const tsconfig = JSON.parse(tree.read('tsconfig.base.json', 'utf-8')); const tsconfig = JSON.parse(tree.read('tsconfig.base.json', 'utf-8'));
expect( expect(
tsconfig.compilerOptions.paths['my-remote/my-federated-module'] tsconfig.compilerOptions.paths['myremote/my-federated-module']
).toEqual(['my-remote/src/my-federated-module.ts']); ).toEqual(['myremote/src/my-federated-module.ts']);
}); });
it('should error when invalid path is provided', async () => { it('should error when invalid path is provided', async () => {
@ -99,7 +99,7 @@ describe('federate-module', () => {
); );
expect(content).not.toContain( expect(content).not.toContain(
`'./my-federated-module': 'my-remote/src/my-federated-module.ts'` `'./my-federated-module': 'myremote/src/my-federated-module.ts'`
); );
await federateModuleGenerator(tree, { await federateModuleGenerator(tree, {
@ -112,13 +112,13 @@ describe('federate-module', () => {
'utf-8' 'utf-8'
); );
expect(content).toContain( expect(content).toContain(
`'./my-federated-module': 'my-remote/src/my-federated-module.ts'` `'./my-federated-module': 'myremote/src/my-federated-module.ts'`
); );
const tsconfig = JSON.parse(tree.read('tsconfig.base.json', 'utf-8')); const tsconfig = JSON.parse(tree.read('tsconfig.base.json', 'utf-8'));
expect( expect(
tsconfig.compilerOptions.paths[`${schema.remote}/my-federated-module`] tsconfig.compilerOptions.paths[`${schema.remote}/my-federated-module`]
).toEqual(['my-remote/src/my-federated-module.ts']); ).toEqual(['myremote/src/my-federated-module.ts']);
}); });
}); });
}); });

View File

@ -0,0 +1,179 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`remote generator bundler=rspack should create the remote with the correct config files 1`] = `
"const { composePlugins, withNx, withReact } = require('@nx/rspack');
const { withModuleFederation } = require('@nx/rspack/module-federation');
const baseConfig = require('./module-federation.config');
const config = {
...baseConfig,
};
// Nx plugins for rspack to build config object from Nx options and context.
/**
* DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support Module Federation
* The DTS Plugin can be enabled by setting dts: true
* Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html
*/
module.exports = composePlugins(withNx(), withReact(), withModuleFederation(config, { dts: false }));
"
`;
exports[`remote generator bundler=rspack should create the remote with the correct config files 2`] = `"module.exports = require('./rspack.config');"`;
exports[`remote generator bundler=rspack should create the remote with the correct config files 3`] = `
"module.exports = {
name: 'test',
exposes: {
'./Module': './src/remote-entry.ts',
},
};
"
`;
exports[`remote generator bundler=rspack should create the remote with the correct config files when --js=true 1`] = `
"const { composePlugins, withNx, withReact } = require('@nx/rspack');
const { withModuleFederation } = require('@nx/rspack/module-federation');
const baseConfig = require('./module-federation.config');
const config = {
...baseConfig,
};
// Nx plugins for rspack to build config object from Nx options and context.
/**
* DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support Module Federation
* The DTS Plugin can be enabled by setting dts: true
* Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html
*/
module.exports = composePlugins(withNx(), withReact(), withModuleFederation(config, { dts: false }));
"
`;
exports[`remote generator bundler=rspack should create the remote with the correct config files when --js=true 2`] = `"module.exports = require('./rspack.config');"`;
exports[`remote generator bundler=rspack should create the remote with the correct config files when --js=true 3`] = `
"module.exports = {
name: 'test',
exposes: {
'./Module': './src/remote-entry.js',
},
};
"
`;
exports[`remote generator bundler=rspack should create the remote with the correct config files when --typescriptConfiguration=true 1`] = `
"import { composePlugins, withNx, withReact } from '@nx/rspack';
import { withModuleFederation } from '@nx/rspack/module-federation';
import baseConfig from './module-federation.config';
const config = {
...baseConfig,
};
// Nx plugins for rspack to build config object from Nx options and context.
/**
* DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support Module Federation
* The DTS Plugin can be enabled by setting dts: true
* Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html
*/
export default composePlugins(
withNx(),
withReact(),
withModuleFederation(config, { dts: false })
);
"
`;
exports[`remote generator bundler=rspack should create the remote with the correct config files when --typescriptConfiguration=true 2`] = `
"export default require('./rspack.config');
"
`;
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';
const config: ModuleFederationConfig = {
name: 'test',
exposes: {
'./Module': './src/remote-entry.ts',
},
};
export default config;
"
`;
exports[`remote generator bundler=rspack should generate correct remote with config files when using --ssr 1`] = `
"const {composePlugins, withNx, withReact} = require('@nx/rspack');
const {withModuleFederationForSSR} = require('@nx/rspack/module-federation');
const baseConfig = require("./module-federation.server.config");
const defaultConfig = {
...baseConfig,
};
// Nx plugins for rspack to build config object from Nx options and context.
/**
* DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support Module Federation
* The DTS Plugin can be enabled by setting dts: true
* Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html
*/
module.exports = composePlugins(withNx(), withReact({ssr: true}), withModuleFederationForSSR(defaultConfig, { dts: false }));
"
`;
exports[`remote generator bundler=rspack should generate correct remote with config files when using --ssr 2`] = `
"module.exports = {
name: 'test',
exposes: {
'./Module': './src/remote-entry.ts',
},
};
"
`;
exports[`remote generator bundler=rspack should generate correct remote with config files when using --ssr and --typescriptConfiguration=true 1`] = `
"import { composePlugins, withNx, withReact } from '@nx/rspack';
import { withModuleFederationForSSR } from '@nx/rspack/module-federation';
import baseConfig from './module-federation.server.config';
const defaultConfig = {
...baseConfig,
};
// Nx plugins for rspack to build config object from Nx options and context.
/**
* DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support Module Federation
* The DTS Plugin can be enabled by setting dts: true
* Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html
*/
export default composePlugins(
withNx(),
withReact({ ssr: true }),
withModuleFederationForSSR(defaultConfig, { dts: false })
);
"
`;
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';
const config: ModuleFederationConfig = {
name: 'test',
exposes: {
'./Module': './src/remote-entry.ts',
},
};
export default config;
"
`;

View File

@ -86,8 +86,7 @@ jest.mock('@nx/devkit', () => {
}; };
}); });
// TODO(colum): turn these on when rspack is moved into the main repo describe('remote generator', () => {
xdescribe('remote generator', () => {
// TODO(@jaysoo): Turn this back to adding the plugin // TODO(@jaysoo): Turn this back to adding the plugin
let originalEnv: string; let originalEnv: string;
@ -324,5 +323,28 @@ xdescribe('remote generator', () => {
}) })
).rejects.toThrowError(`Invalid remote name provided: ${name}.`); ).rejects.toThrowError(`Invalid remote name provided: ${name}.`);
}); });
it('should throw an error when an invalid remote name is used', async () => {
const tree = createTreeWithEmptyWorkspace();
await expect(
remote(tree, {
directory: 'test/my-app',
devServerPort: 4209,
e2eTestRunner: 'cypress',
linter: Linter.EsLint,
skipFormat: false,
style: 'css',
unitTestRunner: 'jest',
ssr: true,
typescriptConfiguration: true,
bundler: 'rspack',
})
).rejects.toMatchInlineSnapshot(`
[Error: Invalid remote name: my-app. Remote project names must:
- Start with a letter, dollar sign ($) or underscore (_)
- Followed by any valid character (letters, digits, underscores, or dollar signs)
The regular expression used is ^[a-zA-Z_$][a-zA-Z_$0-9]*$.]
`);
});
}); });
}); });

View File

@ -8,6 +8,7 @@ import {
names, names,
readProjectConfiguration, readProjectConfiguration,
runTasksInSerial, runTasksInSerial,
stripIndents,
Tree, Tree,
updateProjectConfiguration, updateProjectConfiguration,
} from '@nx/devkit'; } from '@nx/devkit';
@ -25,7 +26,10 @@ import { addRemoteToDynamicHost } from './lib/add-remote-to-dynamic-host';
import { addMfEnvToTargetDefaultInputs } from '../../utils/add-mf-env-to-inputs'; import { addMfEnvToTargetDefaultInputs } from '../../utils/add-mf-env-to-inputs';
import { maybeJs } from '../../utils/maybe-js'; import { maybeJs } from '../../utils/maybe-js';
import { isValidVariable } from '@nx/js'; import { isValidVariable } from '@nx/js';
import { moduleFederationEnhancedVersion } from '../../utils/versions'; import {
moduleFederationEnhancedVersion,
nxVersion,
} from '../../utils/versions';
import { ensureProjectName } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { ensureProjectName } from '@nx/devkit/src/generators/project-name-and-root-utils';
import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
@ -119,6 +123,16 @@ export async function remoteGenerator(host: Tree, schema: Schema) {
} }
await ensureProjectName(host, options, 'application'); await ensureProjectName(host, options, 'application');
const REMOTE_NAME_REGEX = '^[a-zA-Z_$][a-zA-Z_$0-9]*$';
const remoteNameRegex = new RegExp(REMOTE_NAME_REGEX);
if (!remoteNameRegex.test(options.projectName)) {
throw new Error(
stripIndents`Invalid remote name: ${options.projectName}. Remote project names must:
- Start with a letter, dollar sign ($) or underscore (_)
- Followed by any valid character (letters, digits, underscores, or dollar signs)
The regular expression used is ${REMOTE_NAME_REGEX}.`
);
}
const initAppTask = await applicationGenerator(host, { const initAppTask = await applicationGenerator(host, {
...options, ...options,
name: options.projectName, name: options.projectName,
@ -211,7 +225,10 @@ export async function remoteGenerator(host: Tree, schema: Schema) {
const installTask = addDependenciesToPackageJson( const installTask = addDependenciesToPackageJson(
host, host,
{}, {},
{ '@module-federation/enhanced': moduleFederationEnhancedVersion } {
'@module-federation/enhanced': moduleFederationEnhancedVersion,
'@nx/web': nxVersion,
}
); );
tasks.push(installTask); tasks.push(installTask);

View File

@ -16,12 +16,19 @@ export function mapRemotes(
): Record<string, string> { ): Record<string, string> {
const mappedRemotes = {}; const mappedRemotes = {};
for (const remote of remotes) { for (const nxRemoteProjectName of remotes) {
if (Array.isArray(remote)) { if (Array.isArray(nxRemoteProjectName)) {
const remoteName = normalizeRemoteName(remote[0]); const mfRemoteName = normalizeRemoteName(nxRemoteProjectName[0]);
mappedRemotes[remoteName] = handleArrayRemote(remote, remoteEntryExt); mappedRemotes[mfRemoteName] = handleArrayRemote(
} else if (typeof remote === 'string') { nxRemoteProjectName,
mappedRemotes[remote] = handleStringRemote(remote, determineRemoteUrl); remoteEntryExt
);
} else if (typeof nxRemoteProjectName === 'string') {
const mfRemoteName = normalizeRemoteName(nxRemoteProjectName[0]);
mappedRemotes[mfRemoteName] = handleStringRemote(
nxRemoteProjectName,
determineRemoteUrl
);
} }
} }
@ -33,8 +40,8 @@ function handleArrayRemote(
remote: [string, string], remote: [string, string],
remoteEntryExt: 'js' | 'mjs' remoteEntryExt: 'js' | 'mjs'
): string { ): string {
let [remoteName, remoteLocation] = remote; let [nxRemoteProjectName, remoteLocation] = remote;
remoteName = normalizeRemoteName(remoteName); const mfRemoteName = normalizeRemoteName(nxRemoteProjectName);
const remoteLocationExt = extname(remoteLocation); const remoteLocationExt = extname(remoteLocation);
// If remote location already has .js or .mjs extension // If remote location already has .js or .mjs extension
@ -46,7 +53,7 @@ function handleArrayRemote(
? remoteLocation.slice(0, -1) ? remoteLocation.slice(0, -1)
: remoteLocation; : remoteLocation;
const globalPrefix = `${remoteName.replace(/-/g, '_')}@`; const globalPrefix = `${normalizeRemoteName(mfRemoteName)}@`;
// if the remote is defined with anything other than http then we assume it's a promise based remote // 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 // In that case we should use what the user provides as the remote location
@ -59,12 +66,12 @@ function handleArrayRemote(
// Helper function to deal with remotes that are strings // Helper function to deal with remotes that are strings
function handleStringRemote( function handleStringRemote(
remote: string, nxRemoteProjectName: string,
determineRemoteUrl: (remote: string) => string determineRemoteUrl: (nxRemoteProjectName: string) => string
): string { ): string {
const globalPrefix = `${remote.replace(/-/g, '_')}@`; const globalPrefix = `${normalizeRemoteName(nxRemoteProjectName)}@`;
return `${globalPrefix}${determineRemoteUrl(remote)}`; return `${globalPrefix}${determineRemoteUrl(nxRemoteProjectName)}`;
} }
/** /**
@ -84,10 +91,10 @@ export function mapRemotesForSSR(
for (const remote of remotes) { for (const remote of remotes) {
if (Array.isArray(remote)) { if (Array.isArray(remote)) {
let [remoteName, remoteLocation] = remote; let [nxRemoteProjectName, remoteLocation] = remote;
remoteName = normalizeRemoteName(remoteName); const mfRemoteName = normalizeRemoteName(nxRemoteProjectName);
const remoteLocationExt = extname(remoteLocation); const remoteLocationExt = extname(remoteLocation);
mappedRemotes[remoteName] = `${remoteName}@${ mappedRemotes[mfRemoteName] = `${mfRemoteName}@${
['.js', '.mjs'].includes(remoteLocationExt) ['.js', '.mjs'].includes(remoteLocationExt)
? remoteLocation ? remoteLocation
: `${ : `${
@ -97,14 +104,16 @@ export function mapRemotesForSSR(
}/remoteEntry.${remoteEntryExt}` }/remoteEntry.${remoteEntryExt}`
}`; }`;
} else if (typeof remote === 'string') { } else if (typeof remote === 'string') {
const remoteName = normalizeRemoteName(remote); const mfRemoteName = normalizeRemoteName(remote);
mappedRemotes[remoteName] = `${remoteName}@${determineRemoteUrl(remote)}`; mappedRemotes[mfRemoteName] = `${mfRemoteName}@${determineRemoteUrl(
remote
)}`;
} }
} }
return mappedRemotes; return mappedRemotes;
} }
function normalizeRemoteName(remote: string): string { function normalizeRemoteName(nxRemoteProjectName: string): string {
return remote.replace(/-/g, '_'); return nxRemoteProjectName.replace(/-/g, '_');
} }

View File

@ -17,18 +17,19 @@ export function mapRemotes(
): Record<string, string> { ): Record<string, string> {
const mappedRemotes = {}; const mappedRemotes = {};
for (const remote of remotes) { for (const nxRemoteProjectName of remotes) {
if (Array.isArray(remote)) { if (Array.isArray(nxRemoteProjectName)) {
const remoteName = normalizeRemoteName(remote[0]); const mfRemoteName = normalizeRemoteName(nxRemoteProjectName[0]);
mappedRemotes[remoteName] = handleArrayRemote( mappedRemotes[mfRemoteName] = handleArrayRemote(
remote, nxRemoteProjectName,
remoteEntryExt, remoteEntryExt,
isRemoteGlobal isRemoteGlobal
); );
} else if (typeof remote === 'string') { } else if (typeof nxRemoteProjectName === 'string') {
const remoteName = normalizeRemoteName(remote); const mfRemoteName = normalizeRemoteName(nxRemoteProjectName);
mappedRemotes[remoteName] = handleStringRemote( mappedRemotes[mfRemoteName] = handleStringRemote(
remote, nxRemoteProjectName,
determineRemoteUrl, determineRemoteUrl,
isRemoteGlobal isRemoteGlobal
); );
@ -44,8 +45,8 @@ function handleArrayRemote(
remoteEntryExt: 'js' | 'mjs', remoteEntryExt: 'js' | 'mjs',
isRemoteGlobal: boolean isRemoteGlobal: boolean
): string { ): string {
let [remoteName, remoteLocation] = remote; let [nxRemoteProjectName, remoteLocation] = remote;
remoteName = normalizeRemoteName(remoteName); const mfRemoteName = normalizeRemoteName(nxRemoteProjectName);
const remoteLocationExt = extname(remoteLocation); const remoteLocationExt = extname(remoteLocation);
// If remote location already has .js or .mjs extension // If remote location already has .js or .mjs extension
@ -58,7 +59,7 @@ function handleArrayRemote(
: remoteLocation; : remoteLocation;
const globalPrefix = isRemoteGlobal const globalPrefix = isRemoteGlobal
? `${remoteName.replace(/-/g, '_')}@` ? `${normalizeRemoteName(nxRemoteProjectName)}@`
: ''; : '';
// if the remote is defined with anything other than http then we assume it's a promise based remote // if the remote is defined with anything other than http then we assume it's a promise based remote
@ -72,13 +73,15 @@ function handleArrayRemote(
// Helper function to deal with remotes that are strings // Helper function to deal with remotes that are strings
function handleStringRemote( function handleStringRemote(
remote: string, nxRemoteProjectName: string,
determineRemoteUrl: (remote: string) => string, determineRemoteUrl: (nxRemoteProjectName: string) => string,
isRemoteGlobal: boolean isRemoteGlobal: boolean
): string { ): string {
const globalPrefix = isRemoteGlobal ? `${remote.replace(/-/g, '_')}@` : ''; const globalPrefix = isRemoteGlobal
? `${normalizeRemoteName(nxRemoteProjectName)}@`
: '';
return `${globalPrefix}${determineRemoteUrl(remote)}`; return `${globalPrefix}${determineRemoteUrl(nxRemoteProjectName)}`;
} }
/** /**
@ -98,10 +101,10 @@ export function mapRemotesForSSR(
for (const remote of remotes) { for (const remote of remotes) {
if (Array.isArray(remote)) { if (Array.isArray(remote)) {
let [remoteName, remoteLocation] = remote; let [nxRemoteProjectName, remoteLocation] = remote;
remoteName = normalizeRemoteName(remoteName); const mfRemoteName = normalizeRemoteName(nxRemoteProjectName);
const remoteLocationExt = extname(remoteLocation); const remoteLocationExt = extname(remoteLocation);
mappedRemotes[remoteName] = `${remoteName}@${ mappedRemotes[mfRemoteName] = `${mfRemoteName}@${
['.js', '.mjs', '.json'].includes(remoteLocationExt) ['.js', '.mjs', '.json'].includes(remoteLocationExt)
? remoteLocation ? remoteLocation
: `${ : `${
@ -111,8 +114,10 @@ export function mapRemotesForSSR(
}/remoteEntry.${remoteEntryExt}` }/remoteEntry.${remoteEntryExt}`
}`; }`;
} else if (typeof remote === 'string') { } else if (typeof remote === 'string') {
const remoteName = normalizeRemoteName(remote); const mfRemoteName = normalizeRemoteName(remote);
mappedRemotes[remoteName] = `${remoteName}@${determineRemoteUrl(remote)}`; mappedRemotes[mfRemoteName] = `${mfRemoteName}@${determineRemoteUrl(
remote
)}`;
} }
} }