module-federation / module-federation-examples

Implementation examples of module federation , by the creators of module federation
https://module-federation.io/
MIT License
5.56k stars 1.73k forks source link

nextjs(host)+ react webpack(remote):No ModuleFederationPlugin(s) found. #3647

Closed mihuartuanr closed 7 months ago

mihuartuanr commented 7 months ago

Current:

  1. remote react webpack app bundle successed.
  2. host next app:npm run dev occur error
    Disabled SWC as replacement for Babel because of custom Babel configuration ".babelrc" https://nextjs.org/docs/messages/swc-disabled
    `compiler` options in `next.config.js` will be ignored while using Babel https://nextjs.org/docs/messages/ignored-compiler-options
    No ModuleFederationPlugin(s) found.
    Error: Compiling RuleSet failed: Expected condition but got falsy value (at ruleSet[1].rules[14].oneOf[5].exclude[0]: undefined)

i has checked examples at least 3ds in https://github.com/module-federation/module-federation-examples/blob/master/nextjs-react/host-app/package.json.

Expect:

  1. nextjs(host)+ react webpack(remote)can work.
  2. relize why No ModuleFederationPlugin(s) found.

nextjs host app

package.json:

  "scripts": {
    "dev": "rm -rf ./.next && NEXT_PRIVATE_LOCAL_WEBPACK=true NODE_OPTIONS=\"--max_old_space_size=8000\"  next dev"
  },
  "resolutions": {
    "webpack": "5.90.3"
  },
  "dependencies": {
    "@module-federation/nextjs-mf": "^8.1.7",
    "next": "14.1.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    ....
    "webpack": "^5.90.3",
    "webpack-cli": "^5.1.4",
    "webpack-dev-server": "^5.0.2"
  }

next.config.js

import NextFederationPlugin from '@module-federation/nextjs-mf';

const nextConfig = withBundleAnalyzer({
  webpack(config, options) {
    if (!options.isServer) {
      config.plugins.push(
        new NextFederationPlugin({
          name: 'hostApp',
          remotes: {
            remoteApp: 'remoteApp@http://localhost:3000/remoteEntry.js'
          },
          filename: 'static/chunks/remoteEntry.js'
        })
      );
    }
    return config;
  },
});

export default nextConfig;

consumer

import { lazy, Suspense, useEffect, useState } from 'react';
export default function Home() {
  const [Component, setComponent] = useState<any>(null);
  useEffect(() => {
    if (typeof window !== 'undefined') {
      setComponent(lazy(() => import('docbtn/RemoteButton')));
    }
  }, []);
  return (
    <main className="flex flex-col items-center justify-between p-24">
      <div className="animate__animated animate__bounce">主页</div>
      {Component && <Component />}
    </main>
  );

react webpack remote app

import path from 'path'
import { fileURLToPath } from 'url';
import webpack from 'webpack'
const ModuleFederationPlugin = webpack.container.ModuleFederationPlugin;

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
  mode: 'development',
  entry: './src/main.jsx',
  experiments: {
    outputModule: true
  },
  output: {
    module: true,
    path: path.resolve(__dirname, 'wdist'),
    scriptType: 'text/javascript'
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/, // .js and .jsx files
        exclude: /node_modules/, // excluding the node_modules folder
        use: {
          loader: "babel-loader",
        },
      },
      {
        test: /\.(sa|sc|c)ss$/, // styles files
        use: ["style-loader", "css-loader", "sass-loader"],
      },
      {
        test: /\.(png|woff|woff2|eot|ttf|svg)$/, // to import images and fonts
        loader: "url-loader",
        options: { limit: false },
      },
    ],
  },
  plugins: [
    new ModuleFederationPlugin({
        name: 'remoteApp',
        library: {
          type: 'var',
          name: 'remoteApp'
        },
        filename: 'remoteEntry.js',
        exposes: {
          './RemoteButton': './src/components/Button.jsx',
        },
        shared: {
          'react': {
            requiredVersion: '^18.2.0'
          },
          'react-dom': {
            requiredVersion: '^18.2.0'
          }
        },
    }),
  ],
  resolve: {
    extensions: ['.js', '.jsx']
  },
  devServer: {
    contentBase: path.join(__dirname, 'dist'),
    compress: true,
    port: 9000
  }
};
ScriptedAlchemy commented 7 months ago

"No ModuleFederationPlugin(s) found" is a false flag, not a real error. Leftover issue from years old plugin that still runs data correlation deeper down in next's implementation

This issue should disappear when i rewrite that part in the future

mihuartuanr commented 7 months ago

"No ModuleFederationPlugin(s) found" is a false flag, not a real error. Leftover issue from years old plugin that still runs data correlation deeper down in next's implementation

This issue should disappear when i rewrite that part in the future

module-federation can't work in next14, so what i should do to solve it current?

ScriptedAlchemy commented 6 months ago

https://github.com/module-federation/universe/pull/2149 Found ruleset issue

mihuartuanr commented 6 months ago

@module-federation/nextjs-mf": "^8.1.7", update to "^8.2.0",new issue occur.

terminal console:

https://nextjs.org/docs/messages/module-not-found
 ⨯ ./src/app/page.tsx:1:0
Module not found: Can't resolve 'remoteApp/RemoteButton'

chrome devtool console:

Uncaught TypeError: _Users_Workplaces_myapp_node_modules_module_federation_nextjs_mf_dist_src_plugins_container_runtimePlugin_js__WEBPACK_IMPORTED_MODULE_1__ is not a function
ScriptedAlchemy commented 6 months ago

is federation applied server side too? if not then you need to use resolve alias false for remote imports on server config

mihuartuanr commented 6 months ago

now i change federation applied in client side, terminal has no error, but chrome devtool still print error.

devtool prints new message :

entry.df7c6e8affba247d606231c558befe8a.js:32constains:


import federation from '/Users/Workplaces/myapp/node_modules/@module-federation/webpack-bundler-runtime/dist/index.cjs.js';
import plugin_0 from '/Users/Workplaces/myapp/node_modules/@module-federation/nextjs-mf/dist/src/plugins/container/runtimePlugin.js';

__webpack_require__.federation = {...federation,...__webpack_require__.federation};
if(!__webpack_require__.federation.instance){
    __webpack_require__.federation.initOptions.plugins = ([
        plugin_0(),
    ])
    __webpack_require__.federation.instance = __webpack_require__.federation.runtime.init(__webpack_require__.federation.initOptions);
    if(__webpack_require__.federation.attachShareScopeMap){
        __webpack_require__.federation.attachShareScopeMap(__webpack_require__)
    }
    if(__webpack_require__.federation.installInitialConsumes){
        __webpack_require__.federation.installInitialConsumes()
    }
}```
mihuartuanr commented 6 months ago

nest.config.jscontains:

  webpack(config, options) {
    // if (options.isServer) {
    //   config.resolve.alias = {
    //     ...config.resolve.alias,
    //     'docbtn/RemoteButton': false
    //   };
    // }
    config.output = {
      ...config.output,
      environment: {
        asyncFunction: true
      }
    };
    if (!options.isServer) {
      config.plugins.push(
        new NextFederationPlugin({
          name: 'hostApp',
          remotes: {
            docbtn: 'remoteApp@http://localhost:3002/assets/remoteEntry.js'
            // docbtn: `promise import("remoteApp@http://localhost:3000/assets/remoteEntry.js")`
          },
          filename: 'remoteEntry.js'
        })
      );
    }
    return config;
  },

consumer is:

'use client';
import React from 'react';
import { lazy, useEffect, useState } from 'react';
export default function Home() {
  const [Component, setComponent] = useState<React.FunctionComponent | null>(
    null
  );
  useEffect(() => {
    if (typeof window !== 'undefined') {
      setComponent(lazy(() => import('docbtn/RemoteButton')));
    }
  }, []);
  return (
    <main>
      123213213
      {Component && <Component />}
      35435435
    </main>
  );
}
ScriptedAlchemy commented 6 months ago

@mihuartuanr your using app router, app router is not supported

mihuartuanr commented 6 months ago

Thanks, app router is used indeed.

ScriptedAlchemy commented 6 months ago

yeah i just saw the example had app router in it, switched it back to pages and all seems good

mihuartuanr commented 6 months ago

ok. still expect follow-up support for app router.

ScriptedAlchemy commented 6 months ago

Probably will take a long time. Next may drop webpack support before I can find a hack for next. We have rsc working elsewhere. But next is the worst imaginable thing to pair with MFP.

Maybe in 2025

mihuartuanr commented 6 months ago
'use client';
import { importRemote } from '@module-federation/utilities';
import { lazy, useEffect, useState } from 'react';
const RemoteService = () => {
  const [Module, setModule] = useState<any>(null);
  useEffect(() => {
    importRemote({
      url: 'http://127.0.0.1:3000',
      remoteEntryFileName: 'remoteEntry.js',
      scope: 'remoteApp', 
      module: 'RemoteButton'
    }).then((res) => {
      // const { Button } =res;
      setModule(res);
      console.log('-------importRemote------', res);
    });
  }, []);
  // const Bar = lazy(() =>
  //   importRemote({
  //     url: 'http://localhost:3000',
  //     remoteEntryFileName: 'remoteEntry.js',
  //     scope: 'remoteApp',
  //     module: 'RemoteButton',
  //     esm: true
  //   })
  // );
  // console.log('-------------lazy-----------', Bar);
  return (
    <>
      {Module && <Module.default></Module.default>}
    </>
  );
};

it works.

mihuartuanr commented 6 months ago
'use client';
import { importRemote } from '@module-federation/utilities';
import { lazy, useEffect, useState } from 'react';
const RemoteService = () => {
  const [Module, setModule] = useState<any>(null);
  useEffect(() => {
    importRemote({
      url: 'http://127.0.0.1:3000',
      remoteEntryFileName: 'remoteEntry.js',
      scope: 'remoteApp', 
      module: 'RemoteButton'
    }).then((res) => {
      // const { Button } =res;
      setModule(res);
      console.log('-------importRemote------', res);
    });
  }, []);
  // const Bar = lazy(() =>
  //   importRemote({
  //     url: 'http://localhost:3000',
  //     remoteEntryFileName: 'remoteEntry.js',
  //     scope: 'remoteApp',
  //     module: 'RemoteButton',
  //     esm: true
  //   })
  // );
  // console.log('-------------lazy-----------', Bar);
  return (
    <>
      {Module && <Module.default></Module.default>}
    </>
  );
};

it works.

i want to know the feasibility that @module-federation/nextjs-mf fast to support app router by using '@module-federation/utilities'

ScriptedAlchemy commented 6 months ago

Not with utilities. You can try using module-federation/runtime. If anything that might work.

Nextjs-MF will most likely never support app router tho. Vercel is building proprietary version of federation for rsc and next. No chance my stuff will work with build plug-in. Again maybe if you use federation run-time bit I have not tried this. If it works please report back.