App Router Not Supported

Next.js Integração Visão geral

Project Deprecation

Suporte for Next.js é ending read more

Este plugin enables Module Federation on Next.js.

Suporta

  • next ^15 || ^14 || ^13 || ^12
  • Servidor-Side Rendering
  • Pages router

I highly recommend referencing este aplicação que takes advantage do best capabilities: https://github.com/module-federation/module-federation-examples

Requirement

I definir process.env.NEXT_PRIVATE_LOCAL_WEBPACK = 'true' inside este plugin, but its best se its definir em env ou linha de comando exporte.

"Local Webpack" means você deve have webpack installed as uma dependência, e next vai não use its bundled copy de webpack que cannot ser usado as i need access para all de webpack internals

  • cross-env NEXT_PRIVATE_LOCAL_WEBPACK=true next dev ou next build
  • npm install webpack

Uso

import React, { lazy } from 'react';
const SampleComponent = lazy(() => import('next2/sampleComponent'));

To avoid hydration erros, use React.lazy instead de next/dynamic for carregamento lazy federated componentes.

See o implementation here: https://github.com/module-federation/module-federation-examples/tree/master/nextjs-v13/home/pages

Com async boundary installed at o page level. Você pode then do seguintes

const SomeHook = require('next2/someHook');
import SomeComponent from 'next2/someComponent';

Demo

Você pode see it em action here: https://github.com/module-federation/module-federation-examples/tree/master/nextjs-ssr

Opções

Este plugin works exactly like ModuleFederationPlugin, use it as você'd normally. Note esse nós já compartilhe react e next stuff for você automatically.

Also NextFederationPlugin has own opcional argument extraOptions onde você pode unlock additional features de este plugin:

new NextFederationPlugin({
  name: '',
  filename: '',
  remotes: {},
  exposes: {},
  shared: {},
  extraOptions: {
    debug: boolean, // `false` by default
    exposePages: boolean, // `false` by default
    enableImageLoaderFix: boolean, // `false` by default
    enableUrlLoaderFix: boolean, // `false` by default
    skipSharingNextInternals: boolean, // `false` by default
  },
});
  • debug – enables debug mode. It vai print additional information about o que é going on under o hood.
  • exposePages – exposes automatically all nextjs pages for você e theirs ./pages-map.
  • enableImageLoaderFix – adds public hostname para all assets bundled by nextjs-image-loader. So se você serve remoteEntry de http://example.com then all bundled assets vai get este hostname em runtime. It's something like Base URL em HTML but for módulos federados.
  • enableUrlLoaderFix – adds public hostname para all assets bundled by url-loader.
  • skipSharingNextInternals – disables compartilhamento de next internals. Você pode use it se você quiser compartilhe next internals yourself ou want para usar este plugin on non next aplicações

Demo

Você pode see it em action here: https://github.com/module-federation/module-federation-examples/pull/2147

Implementing o Plugin

  1. Use NextFederationPlugin em your next.config.js do app esse você wish para exponha módulos de. We'll call este "next2".
// next.config.js
// either from default
const NextFederationPlugin = require('@module-federation/nextjs-mf');

module.exports = {
  webpack(config, options) {
    const { isServer } = options;
    config.plugins.push(
      new NextFederationPlugin({
        name: 'next2',
        remotes: {
          next1: `next1@http://localhost:3001/_next/static/${
            isServer ? 'ssr' : 'chunks'
          }/remoteEntry.js`,
        },
        filename: 'static/chunks/remoteEntry.js',
        exposes: {
          './title': './components/exposedTitle.js',
          './checkout': './pages/checkout',
        },
        shared: {
          // whatever else
        },
      }),
    );

    return config;
  },
};
// next.config.js

const NextFederationPlugin = require('@module-federation/nextjs-mf');

module.exports = {
  webpack(config, options) {
    const { isServer } = options;
    config.plugins.push(
      new NextFederationPlugin({
        name: 'next1',
        remotes: {
          next2: `next2@http://localhost:3000/_next/static/${
            isServer ? 'ssr' : 'chunks'
          }/remoteEntry.js`,
        },
      }),
    );

    return config;
  },
};
  1. Use react.lazy, low level api, ou require/importe de para importe remotes.
import React, { lazy } from 'react';

const SampleComponent = lazy(() =>
  window.next2.get('./sampleComponent').then((factory) => {
    return { default: factory() };
  }),
);

// or

const SampleComponent = lazy(() => import('next2/sampleComponent'));

//or

import Sample from 'next2/sampleComponent';

RuntimePlugins

To forneça extensibility e "middleware" for federation, você pode refer para @module-federation/enhanced/runtime

// next.config.js
new NextFederationPlugin({
  runtimePlugins: [require.resolve('./path/to/myRuntimePlugin.js')],
});

Utilities

loadRemote has been removed - você pode take advantage do novo runtime apis: https://module-federation.io/guide/runtime/runtime-api.html#loadremote

revalidate

Enables hot reloading de node servidor (não cliente) em produção. Este é recomendado, sem it - servers vai não ser able para pull remote updates sem uma full restart.

More info here: https://github.com/module-federation/nextjs-mf/tree/main/packages/node#utilities

// __document.js

import { revalidate } from '@module-federation/nextjs-mf/utils';
import Document, { Html, Head, Main, NextScript } from 'next/document';
class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx);

    // can be any lifecycle or implementation you want
    ctx?.res?.on('finish', () => {
      revalidate().then((shouldUpdate) => {
        console.log('finished sending response', shouldUpdate);
      });
    });

    return initialProps;
  }
  render() {
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

For Express.js

Hot reloading Express.js obrigatório additional steps: https://github.com/module-federation/core/blob/main/packages/node/README.md

Whats shared por padrão?

Under o hood nós compartilhe some next internals automatically Você não precisa compartilhe these pacotes, compartilhamento next internals yourself vai cause erros.

Click to view DEFAULT_SHARE_SCOPE:
export const DEFAULT_SHARE_SCOPE: SharedObject = {
  'next/dynamic': {
    requiredVersion: undefined,
    singleton: true,
    import: undefined,
  },
  'next/head': {
    requiredVersion: undefined,
    singleton: true,
    import: undefined,
  },
  'next/link': {
    requiredVersion: undefined,
    singleton: true,
    import: undefined,
  },
  'next/router': {
    requiredVersion: false,
    singleton: true,
    import: undefined,
  },
  'next/image': {
    requiredVersion: undefined,
    singleton: true,
    import: undefined,
  },
  'next/script': {
    requiredVersion: undefined,
    singleton: true,
    import: undefined,
  },
  react: {
    singleton: true,
    requiredVersion: false,
    import: false,
  },
  'react/': {
    singleton: true,
    requiredVersion: false,
    import: false,
  },
  'react-dom/': {
    singleton: true,
    requiredVersion: false,
    import: false,
  },
  'react-dom': {
    singleton: true,
    requiredVersion: false,
    import: false,
  },
  'react/jsx-dev-runtime': {
    singleton: true,
    requiredVersion: false,
  },
  'react/jsx-runtime': {
    singleton: true,
    requiredVersion: false,
  },
  'styled-jsx': {
    singleton: true,
    import: undefined,
    version: require('styled-jsx/package.json').version,
    requiredVersion: '^' + require('styled-jsx/package.json').version,
  },
  'styled-jsx/style': {
    singleton: true,
    import: false,
    version: require('styled-jsx/package.json').version,
    requiredVersion: '^' + require('styled-jsx/package.json').version,
  },
  'styled-jsx/css': {
    singleton: true,
    import: undefined,
    version: require('styled-jsx/package.json').version,
    requiredVersion: '^' + require('styled-jsx/package.json').version,
  },
};