Getting Started
This page walks the
React reference example
end-to-end — a federation.config.js, a
build.mjs that drives the adapter, and a host page that
mounts the remote as a custom element. The whole setup is roughly
fifty lines.
1. Install
npm i -D esbuild \
@softarc/native-federation \
@softarc/native-federation-esbuild \
@chialab/esbuild-plugin-commonjs
npm i @softarc/native-federation-orchestrator
Also make sure your package.json has
"type": "module" — the adapter and its generated
federation.config.js are native ESM.
2. federation.config.js
Declare the remote's name, what it exposes, and how it shares dependencies. This is the exact configuration the React example uses:
import {
withNativeFederation,
shareAll,
} from "@softarc/native-federation/config";
export default withNativeFederation({
name: "@team/mfe1",
exposes: {
"./Bootstrap": "./src/bootstrap.tsx"
},
shared: {
...shareAll({
singleton: true,
strictVersion: true,
requiredVersion: "auto",
}, {
overrides: {
"react": {
singleton: true,
strictVersion: true,
requiredVersion: "auto",
includeSecondaries: { keepAll: true }
},
"react-dom": {
singleton: true,
strictVersion: true,
requiredVersion: "auto",
includeSecondaries: { keepAll: true }
}
}
}),
},
features: {
ignoreUnusedDeps: true
},
skip: [
'react-dom/server',
'react-dom/server.node',
'react-dom/server.browser',
'react-dom/test-utils'
]
});
A few things are specifically shaped for React; the rest is standard federation config — see federation.config.js for the full schema.
-
reactandreact-domare pinned as singletons withincludeSecondaries: { keepAll: true }— React's internal sub-modules (react/jsx-runtime,react-dom/client, …) must all resolve to the same copy. -
The
skiplist removes server entry points that React ships but the browser build should not try to bundle. -
features.ignoreUnusedDeps: truetells the core to skip sharing anything the build didn't actually pull in — useful for apps that list everything inpackage.json.
3. build.mjs
The adapter does not ship a CLI. You call
runEsBuildBuilder from a small Node script:
import { createServer } from 'http';
import { readFileSync, existsSync } from 'fs';
import { extname, join } from 'path';
import { runEsBuildBuilder } from '@softarc/native-federation-esbuild';
const isDev = process.argv.includes('--dev');
const federation = await runEsBuildBuilder('federation.config.js', {
outputPath: 'dist',
tsConfig: 'tsconfig.json',
dev: isDev,
watch: isDev,
entryPoints: ['src/bootstrap.tsx'],
adapterConfig: { plugins: [] },
});
if (isDev) {
// …simple static server for dist/ + public/ on :3000 …
process.on('SIGINT', async () => { await federation.close(); process.exit(0); });
process.on('SIGTERM', async () => { await federation.close(); process.exit(0); });
} else {
await federation.close();
console.log('Build complete.');
}
The option names mirror EsBuildBuilderOptions one-to-one
(see Builder). In short:
-
outputPath— where federation artifacts and the bundled entry points are written.remoteEntry.jsonlands here. dev: true— sourcemaps on, minification off.-
watch: true— start the file watcher and rebuild queue; callingfederation.close()stops them. -
entryPoints— the source files esbuild starts from. You normally list the files named inexposes. -
adapterConfig.plugins— extra esbuild plugins for the source bundle (Sass, SVG loader, …). Leave empty if you don't need any.
In production mode (npm run build), the script awaits
federation.close() to tear esbuild down cleanly and
exits. In dev mode, watch: true keeps the process alive —
the adapter rebuilds on file changes and the tiny HTTP server serves
dist/ and public/.
The example uses a hand-written
http.createServer because the adapter is deliberately
framework-agnostic. You can just as easily pipe its output into
Vite, a Fastify static server, or @web/dev-server.
4. Host page (public/index.html)
The host page declares the remotes, pulls in the orchestrator runtime, and mounts the remote's custom element. No JS framework needed on the host itself:
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Todos</title>
<!-- 1. Declare your micro frontends (remotes) -->
<script type="application/json" id="mfe-manifest">
{ "@team/mfe1": "./remoteEntry.json" }
</script>
<!-- 2. Trigger module loading once the orchestrator is ready -->
<script>
window.addEventListener(
"mfe-loader-available",
(e) => { e.detail.loadRemoteModule("@team/mfe1", "./Bootstrap"); },
{ once: true }
);
</script>
<!-- 3. Include the orchestrator runtime -->
<script src="https://unpkg.com/@softarc/native-federation-orchestrator@4.0.2/quickstart.mjs"></script>
</head>
<body>
<!-- 4. Use your loaded components -->
<my-react-app></my-react-app>
</body>
</html>
See the
orchestrator docs for the
full quickstart.mjs story, or
initFederation if you
want to drive loading manually.
5. src/bootstrap.tsx
The exposed entry point registers a custom element that owns a Shadow DOM — this keeps the remote's styles from leaking into the host page:
import { StrictMode } from 'react';
import { createRoot, type Root } from 'react-dom/client';
import { styles } from './styles';
import { TodoApp } from './TodoApp';
class MyReactAppElement extends HTMLElement {
private root: Root | null = null;
connectedCallback() {
const shadow = this.shadowRoot ?? this.attachShadow({ mode: 'open' });
const styleEl = document.createElement('style');
styleEl.textContent = styles;
shadow.appendChild(styleEl);
const mount = document.createElement('div');
shadow.appendChild(mount);
this.root = createRoot(mount);
this.root.render(<StrictMode><TodoApp /></StrictMode>);
}
disconnectedCallback() {
this.root?.unmount();
this.root = null;
}
}
if (!customElements.get('my-react-app')) {
customElements.define('my-react-app', MyReactAppElement);
}
The custom-element-plus-Shadow-DOM pattern is covered in more detail on the React & CommonJS Interop page.
6. Run it
Wire the two scripts in package.json:
{
"scripts": {
"start": "node build.mjs --dev",
"build": "node build.mjs"
}
}
npm start # dev server on http://localhost:3000 with file watching
npm run build # writes dist/remoteEntry.json + bundled artifacts
Next Steps
-
Builder — the full
EsBuildBuilderOptionsreference. - Adapter Configuration — add esbuild plugins, file replacements and custom loaders.
- React & CommonJS Interop — what to do when a CJS library doesn't Just Work.