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', () => {
let schema: Schema = {
name: 'my-federated-module',
remote: 'my-remote',
path: 'apps/my-remote/src/my-federated-module.ts',
remoteDirectory: 'apps/my-remote',
remote: 'myremote',
path: 'apps/myremote/src/my-federated-module.ts',
remoteDirectory: 'apps/myremote',
};
describe('no remote', () => {
@ -29,32 +29,32 @@ describe('federate-module', () => {
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
);
const content = tree.read(
'apps/my-remote/module-federation.config.ts',
'apps/myremote/module-federation.config.ts',
'utf-8'
);
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'));
expect(
tsconfig.compilerOptions.paths['my-remote/my-federated-module']
).toEqual(['apps/my-remote/src/my-federated-module.ts']);
tsconfig.compilerOptions.paths['myremote/my-federated-module']
).toEqual(['apps/myremote/src/my-federated-module.ts']);
});
});
describe('with remote', () => {
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
let remoteSchema: remoteSchma = {
name: 'my-remote',
directory: 'my-remote',
name: 'myremote',
directory: 'myremote',
e2eTestRunner: E2eTestRunner.Cypress,
skipFormat: true,
linter: Linter.EsLint,
@ -78,7 +78,7 @@ describe('federate-module', () => {
);
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, {
@ -92,7 +92,7 @@ describe('federate-module', () => {
'utf-8'
);
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'));
@ -100,7 +100,7 @@ describe('federate-module', () => {
tsconfig.compilerOptions.paths[
`${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');
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,
getProjects,
runTasksInSerial,
stripIndents,
Tree,
} from '@nx/devkit';
import {
@ -39,6 +40,16 @@ export async function remote(tree: Tree, schema: Schema) {
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 appInstallTask = await applicationGenerator(tree, {

View File

@ -12,9 +12,9 @@ describe('federate-module', () => {
let tree: Tree;
let schema: Schema = {
name: 'my-federated-module',
remote: 'my-remote',
remoteDirectory: 'my-remote',
path: 'my-remote/src/my-federated-module.ts',
remote: 'myremote',
remoteDirectory: 'myremote',
path: 'myremote/src/my-federated-module.ts',
style: 'css',
skipFormat: true,
bundler: 'webpack',
@ -33,7 +33,7 @@ describe('federate-module', () => {
beforeAll(() => {
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', () => {
it('should generate a remote and e2e', async () => {
@ -41,27 +41,27 @@ describe('federate-module', () => {
const projects = getProjects(tree);
expect(projects.get('my-remote').root).toEqual('my-remote');
expect(projects.get('my-remote-e2e').root).toEqual('my-remote-e2e');
expect(projects.get('myremote').root).toEqual('myremote');
expect(projects.get('myremote-e2e').root).toEqual('myremote-e2e');
});
it('should contain an entry for the new path for module federation', async () => {
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(
'my-remote/module-federation.config.ts',
'myremote/module-federation.config.ts',
'utf-8'
);
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'));
expect(
tsconfig.compilerOptions.paths['my-remote/my-federated-module']
).toEqual(['my-remote/src/my-federated-module.ts']);
tsconfig.compilerOptions.paths['myremote/my-federated-module']
).toEqual(['myremote/src/my-federated-module.ts']);
});
it('should error when invalid path is provided', async () => {
@ -99,7 +99,7 @@ describe('federate-module', () => {
);
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, {
@ -112,13 +112,13 @@ describe('federate-module', () => {
'utf-8'
);
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'));
expect(
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
xdescribe('remote generator', () => {
describe('remote generator', () => {
// TODO(@jaysoo): Turn this back to adding the plugin
let originalEnv: string;
@ -324,5 +323,28 @@ xdescribe('remote generator', () => {
})
).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,
readProjectConfiguration,
runTasksInSerial,
stripIndents,
Tree,
updateProjectConfiguration,
} 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 { maybeJs } from '../../utils/maybe-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 { 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');
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, {
...options,
name: options.projectName,
@ -211,7 +225,10 @@ export async function remoteGenerator(host: Tree, schema: Schema) {
const installTask = addDependenciesToPackageJson(
host,
{},
{ '@module-federation/enhanced': moduleFederationEnhancedVersion }
{
'@module-federation/enhanced': moduleFederationEnhancedVersion,
'@nx/web': nxVersion,
}
);
tasks.push(installTask);

View File

@ -16,12 +16,19 @@ export function mapRemotes(
): Record<string, string> {
const mappedRemotes = {};
for (const remote of remotes) {
if (Array.isArray(remote)) {
const remoteName = normalizeRemoteName(remote[0]);
mappedRemotes[remoteName] = handleArrayRemote(remote, remoteEntryExt);
} else if (typeof remote === 'string') {
mappedRemotes[remote] = handleStringRemote(remote, determineRemoteUrl);
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[0]);
mappedRemotes[mfRemoteName] = handleStringRemote(
nxRemoteProjectName,
determineRemoteUrl
);
}
}
@ -33,8 +40,8 @@ function handleArrayRemote(
remote: [string, string],
remoteEntryExt: 'js' | 'mjs'
): string {
let [remoteName, remoteLocation] = remote;
remoteName = normalizeRemoteName(remoteName);
let [nxRemoteProjectName, remoteLocation] = remote;
const mfRemoteName = normalizeRemoteName(nxRemoteProjectName);
const remoteLocationExt = extname(remoteLocation);
// If remote location already has .js or .mjs extension
@ -46,7 +53,7 @@ function handleArrayRemote(
? remoteLocation.slice(0, -1)
: 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
// 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
function handleStringRemote(
remote: string,
determineRemoteUrl: (remote: string) => string
nxRemoteProjectName: string,
determineRemoteUrl: (nxRemoteProjectName: 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) {
if (Array.isArray(remote)) {
let [remoteName, remoteLocation] = remote;
remoteName = normalizeRemoteName(remoteName);
let [nxRemoteProjectName, remoteLocation] = remote;
const mfRemoteName = normalizeRemoteName(nxRemoteProjectName);
const remoteLocationExt = extname(remoteLocation);
mappedRemotes[remoteName] = `${remoteName}@${
mappedRemotes[mfRemoteName] = `${mfRemoteName}@${
['.js', '.mjs'].includes(remoteLocationExt)
? remoteLocation
: `${
@ -97,14 +104,16 @@ export function mapRemotesForSSR(
}/remoteEntry.${remoteEntryExt}`
}`;
} else if (typeof remote === 'string') {
const remoteName = normalizeRemoteName(remote);
mappedRemotes[remoteName] = `${remoteName}@${determineRemoteUrl(remote)}`;
const mfRemoteName = normalizeRemoteName(remote);
mappedRemotes[mfRemoteName] = `${mfRemoteName}@${determineRemoteUrl(
remote
)}`;
}
}
return mappedRemotes;
}
function normalizeRemoteName(remote: string): string {
return remote.replace(/-/g, '_');
function normalizeRemoteName(nxRemoteProjectName: string): string {
return nxRemoteProjectName.replace(/-/g, '_');
}

View File

@ -17,18 +17,19 @@ export function mapRemotes(
): Record<string, string> {
const mappedRemotes = {};
for (const remote of remotes) {
if (Array.isArray(remote)) {
const remoteName = normalizeRemoteName(remote[0]);
mappedRemotes[remoteName] = handleArrayRemote(
remote,
for (const nxRemoteProjectName of remotes) {
if (Array.isArray(nxRemoteProjectName)) {
const mfRemoteName = normalizeRemoteName(nxRemoteProjectName[0]);
mappedRemotes[mfRemoteName] = handleArrayRemote(
nxRemoteProjectName,
remoteEntryExt,
isRemoteGlobal
);
} else if (typeof remote === 'string') {
const remoteName = normalizeRemoteName(remote);
mappedRemotes[remoteName] = handleStringRemote(
remote,
} else if (typeof nxRemoteProjectName === 'string') {
const mfRemoteName = normalizeRemoteName(nxRemoteProjectName);
mappedRemotes[mfRemoteName] = handleStringRemote(
nxRemoteProjectName,
determineRemoteUrl,
isRemoteGlobal
);
@ -44,8 +45,8 @@ function handleArrayRemote(
remoteEntryExt: 'js' | 'mjs',
isRemoteGlobal: boolean
): string {
let [remoteName, remoteLocation] = remote;
remoteName = normalizeRemoteName(remoteName);
let [nxRemoteProjectName, remoteLocation] = remote;
const mfRemoteName = normalizeRemoteName(nxRemoteProjectName);
const remoteLocationExt = extname(remoteLocation);
// If remote location already has .js or .mjs extension
@ -58,7 +59,7 @@ function handleArrayRemote(
: remoteLocation;
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
@ -72,13 +73,15 @@ function handleArrayRemote(
// Helper function to deal with remotes that are strings
function handleStringRemote(
remote: string,
determineRemoteUrl: (remote: string) => string,
nxRemoteProjectName: string,
determineRemoteUrl: (nxRemoteProjectName: string) => string,
isRemoteGlobal: boolean
): 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) {
if (Array.isArray(remote)) {
let [remoteName, remoteLocation] = remote;
remoteName = normalizeRemoteName(remoteName);
let [nxRemoteProjectName, remoteLocation] = remote;
const mfRemoteName = normalizeRemoteName(nxRemoteProjectName);
const remoteLocationExt = extname(remoteLocation);
mappedRemotes[remoteName] = `${remoteName}@${
mappedRemotes[mfRemoteName] = `${mfRemoteName}@${
['.js', '.mjs', '.json'].includes(remoteLocationExt)
? remoteLocation
: `${
@ -111,8 +114,10 @@ export function mapRemotesForSSR(
}/remoteEntry.${remoteEntryExt}`
}`;
} else if (typeof remote === 'string') {
const remoteName = normalizeRemoteName(remote);
mappedRemotes[remoteName] = `${remoteName}@${determineRemoteUrl(remote)}`;
const mfRemoteName = normalizeRemoteName(remote);
mappedRemotes[mfRemoteName] = `${mfRemoteName}@${determineRemoteUrl(
remote
)}`;
}
}