@nx/react
Version:
138 lines (137 loc) • 6.63 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.consumerGenerator = consumerGenerator;
const tslib_1 = require("tslib");
const devkit_1 = require("@nx/devkit");
const assert_supported_react_version_1 = require("../../utils/assert-supported-react-version");
const versions_1 = require("@nx/js/src/utils/versions");
const versions_2 = require("../../utils/versions");
const mfVersions = tslib_1.__importStar(require("../_utils/mf-versions"));
const mf_dependencies_1 = require("../_utils/mf-dependencies");
const normalize_1 = require("../_utils/normalize");
const provider_1 = tslib_1.__importDefault(require("../provider/provider"));
// `--mode=...` is rspack's portable equivalent of `NODE_ENV=...` and works on
// Windows without a shell prefix.
const BUNDLER_DEV_COMMAND = {
vite: 'vite',
rsbuild: 'rsbuild dev',
rspack: 'rspack serve --mode=development',
};
const BUNDLER_BUILD_COMMAND = {
vite: 'vite build',
rsbuild: 'rsbuild build',
rspack: 'rspack build --mode=production',
};
async function consumerGenerator(tree, schema) {
(0, assert_supported_react_version_1.assertSupportedReactVersion)(tree);
const opts = await (0, normalize_1.normalizeScaffoldOptions)(tree, {
directory: schema.directory,
surface: 'consumer',
bundler: schema.bundler,
port: schema.port,
});
// When the user explicitly passes `--providerNames=p1,p2`, generate a
// sibling provider app per entry and wire them into the consumer's
// PROVIDERS list + App.tsx. When the flag is omitted, ship a placeholder
// `my-provider` entry pointing at the per-bundler provider-default port
// without generating an actual provider project.
const providerNames = schema.providerNames?.length
? schema.providerNames
: ['my-provider'];
const shouldGenerateProviders = !!schema.providerNames?.length;
const providerDefaultPort = (0, normalize_1.getDefaultPort)(opts.bundler, 'provider');
const providers = providerNames.map((name, i) => ({
name,
// The federation container name the provider build emits. Must match the
// provider's federation `name`, which is derived the same way (see
// provider.ts / normalize.ts) - it strips chars that aren't valid in a JS
// identifier, so a naive `alias.replace(/-/g, '_')` in the consumer would
// diverge for names with dots/slashes/@ and loadRemote() would resolve the
// URL but look up the wrong container.
federationName: (0, normalize_1.toFederationName)((0, devkit_1.names)(name).fileName),
componentName: `Provider${(0, devkit_1.names)(name).className}`,
directory: (0, devkit_1.joinPathFragments)(opts.projectRoot, '..', name),
port: providerDefaultPort + i,
}));
(0, devkit_1.addProjectConfiguration)(tree, opts.projectName, {
root: opts.projectRoot,
sourceRoot: (0, devkit_1.joinPathFragments)(opts.projectRoot, 'src'),
projectType: 'application',
targets: {
serve: {
executor: 'nx:run-commands',
continuous: true,
options: {
command: BUNDLER_DEV_COMMAND[opts.bundler],
cwd: opts.projectRoot,
},
},
build: {
executor: 'nx:run-commands',
options: {
command: BUNDLER_BUILD_COMMAND[opts.bundler],
cwd: opts.projectRoot,
},
},
},
});
const tasks = [];
if (shouldGenerateProviders) {
for (const p of providers) {
tasks.push(await (0, provider_1.default)(tree, {
directory: p.directory,
bundler: opts.bundler,
port: p.port,
// Wire `nx serve <provider>` to also spin up this consumer.
consumer: opts.projectName,
}));
}
}
// Point at `remoteEntry.js`, which every supported bundler emits at dev
// time. `mf-manifest.json` is richer (https://module-federation.io/configure/manifest-fields)
// but `@module-federation/vite` only emits it at build time, so a consumer
// serving against a dev-mode vite provider would get the SPA-fallback HTML
// and crash on JSON.parse. Swap to mf-manifest.json manually for prod if
// you want the extra metadata.
const remotes = providers.map((p) => ({
// `alias` is the key the consumer references (loadRemote/lazyProvider);
// `name` is the provider's federation container name; `entry` is its
// remoteEntry.js URL.
alias: p.name,
name: p.federationName,
entry: `http://localhost:${p.port}/remoteEntry.js`,
}));
// Vite emits ESM `remoteEntry.js`; rspack/rsbuild emit UMD. The federation
// runtime needs `type: 'module'` for ESM (else loads as a classic <script>
// and the browser throws #RUNTIME-001) and NO type for UMD (else
// #RUNTIME-002). We only generate same-bundler providers via --providerNames,
// so a single per-consumer setting is correct.
const remoteType = opts.bundler === 'vite' ? 'module' : null;
const templateSubstitutions = {
projectName: opts.projectName,
federationName: opts.federationName,
port: opts.port,
bundler: opts.bundler,
providers,
remotes,
remoteType,
versions: {
...mfVersions,
reactVersion: versions_2.reactVersion,
reactDomVersion: versions_2.reactDomVersion,
typesReactVersion: versions_2.typesReactVersion,
typesReactDomVersion: versions_2.typesReactDomVersion,
typescriptVersion: versions_1.typescriptVersion,
},
tmpl: '',
};
(0, devkit_1.generateFiles)(tree, (0, devkit_1.joinPathFragments)(__dirname, 'files', '_common'), opts.projectRoot, templateSubstitutions);
(0, devkit_1.generateFiles)(tree, (0, devkit_1.joinPathFragments)(__dirname, 'files', opts.bundler), opts.projectRoot, templateSubstitutions);
// Write deps into the workspace root package.json so bare bundler bins
// resolve when nx invokes the generated run-commands serve target.
const deps = (0, mf_dependencies_1.getConsumerDeps)(opts.bundler);
tasks.push((0, devkit_1.addDependenciesToPackageJson)(tree, deps.dependencies, deps.devDependencies, undefined, true));
await (0, devkit_1.formatFiles)(tree);
return (0, devkit_1.runTasksInSerial)(...tasks);
}
exports.default = consumerGenerator;