Skip to content

Instantly share code, notes, and snippets.

@tyler-austin
Created September 10, 2024 12:34
Show Gist options
  • Save tyler-austin/186e7e28e1f09a06d92769ac06c391da to your computer and use it in GitHub Desktop.
Save tyler-austin/186e7e28e1f09a06d92769ac06c391da to your computer and use it in GitHub Desktop.
@module-federation/nextjs-mf NextJs config with rewrites
const path = require('path');
const fs = require('fs');
const NextFederationPlugin = require('@module-federation/nextjs-mf');
const { FederatedTypesPlugin } = require('@module-federation/typescript');
const MODULES = {
"host": {
"port": 3100
},
"home": {
"port": 3001
}
};
const findExposures = (root, ignoreDirs) => {
// content of root directory
const content = fs.readdirSync(root, { withFileTypes: true });
// filter out ignored directories
const directories = content.filter(dir => {
return dir.isDirectory() && !ignoreDirs.includes(dir.name);
});
// walk through directories recursively and find all .ts and .tsx files
const fileMap = directories.reduce((acc, directory) => {
const dir = path.join(root, directory.name);
const walk = curr => {
const dirents = fs.readdirSync(curr, { withFileTypes: true });
dirents.forEach(dirent => {
const rel = path.relative(root, path.join(curr, dirent.name));
if (dirent.isDirectory()) {
walk(path.join(curr, dirent.name));
} else if (dirent.name.endsWith('/index.ts') || dirent.name.endsWith('/index.tsx')) {
const key = rel.split('/').slice(0, -1).join('/');
acc[`./${key}`] = `./${rel}`;
} else if (dirent.name.endsWith('.ts') || dirent.name.endsWith('.tsx')) {
const key = rel.replace(/\.tsx?$/, '');
acc[`./${key}`] = `./${rel}`;
}
});
};
walk(dir);
return acc;
}, {});
// return the map of file paths
return fileMap;
};
const findRemotes = ({ modules, currentModule, isServer }) => {
const location = isServer ? 'ssr' : 'chunks';
return Object.entries(modules).reduce((acc, [name, mod]) => {
if (name !== currentModule) {
const { port } = mod ?? {};
const domain = process.env.NEXT_PUBLIC_LOGIN_REDIRECT_DOMAIN ?? 'http://localhost:3100'
acc[name] = `${name}@${domain}/${name}/_next/static/${location}/remoteEntry.js`;
}
return acc;
}, {});
};
const ignoreDirs = ['node_modules', '@mf-types', 'dist', '.next', 'pages', 'lib', 'styles', 'public'];
/** @type {import('next').NextConfig} */
module.exports = {
reactStrictMode: false,
images: { unoptimized: true },
async rewrites() {
const mfRewrites = Object.keys(MODULES).filter(key => key !== 'host').map((key) => {
return {
source: `/${key}/:path*`,
destination: `http://localhost:${MODULES[key].port}/:path*`,
}
})
return mfRewrites
},
webpack: (config, context) => {
const { isServer } = context;
const name = 'host';
const remotes = findRemotes({
modules: MODULES,
currentModule: name,
isServer,
});
const exposes = {
...findExposures(__dirname, ignoreDirs),
'./pages-map': './pages-map.ts',
};
const federationConfig = {
filename: 'static/chunks/remoteEntry.js',
name,
remotes,
exposes,
// https://module-federation.io/guide/framework/nextjs.html#options
extraOptions: {
enableImageLoaderFix: true,
enableUrlLoaderFix: true,
exposePages: true,
skipSharingNextInternals: false,
automaticPageStitching: false,
debug: true,
},
shared: {
'@tanstack/react-query': {
requiredVersion: false,
singleton: true,
},
'@tanstack/query-core': {
requiredVersion: false,
singleton: true,
},
},
};
config.plugins.push(
new NextFederationPlugin(federationConfig),
new FederatedTypesPlugin({
federationConfig,
typeFetchOptions: {
downloadRemoteTypesTimeout: 1000,
retryDelay: 1000,
},
}),
);
return config;
},
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment