originjs / vite-plugin-federation

Module Federation for vite & rollup
Other
2.26k stars 235 forks source link

Getting TypeError: Cannot read properties of undefined (reading 'init') when fetching remote from host #506

Open MateoKruk opened 11 months ago

MateoKruk commented 11 months ago

Versions

Reproduction

The setup I have today is the following:

I'm migrating the remote to vite and for now preserve the host as it is. I've done all the remote migration as it's working flawleslly by its own.

The big problem I have is making the host work with the new remote. I'm getting the following error:

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'init')

Basically window[_scope] is undefined

Host fetching remote code

const RemoteComponent = ({ _url, _scope, _module, _onLoaded, _onError, ...props }) => {
  const { isReady, failed } = useDynamicScript({ url: _url });

  const Component = useMemo(() => {
    if (isReady) {
      return lazy(
        () =>
          new Promise((moduleResolve) => {
            new Promise((containerResolve) => {
              containerResolve(window[_scope].init(sharedScope));
            }).then(() => {
              window[_scope].get(_module).then((factory) => {
                moduleResolve(factory());
              });
            });
          })
      );
    }

    return () => {};
  }, [isReady, _scope, _module]);

  useEffect(() => {
    if (failed) {
      _onError();
    }
  }, [failed, _onError]);

  if (failed) {
    return <div />;
  }

  return isReady ? (
    <Suspense fallback={<LoadingState onLoaded={_onLoaded} />}>
      <Component {...props} />
    </Suspense>
  ) : (
    <div />
  );
};
const useDynamicScript = ({ url }) => {
  const [isReady, setIsReady] = useState(false);
  const [failed, setFailed] = useState(false);

  useEffect(() => {
    if (url) {
      setIsReady(false);
      setFailed(false);

      const element = document.createElement('script');
      element.src = url;
      element.type = 'text/javascript';
      element.async = true;

      element.onload = () => {
        setIsReady(true);
      };
      element.onerror = () => {
        setIsReady(false);
        setFailed(true);
      };

      document.head.appendChild(element);

      return () => {
        document.head.removeChild(element);
      };
    }

    return () => {};
  }, [url]);

  return { isReady, failed };
};

I'm doing it this way because the _url is dynamic when deployed.

<RemoteComponent
_url="http://localhost:3000/assets/remoteEntry.js"
_scope="app"
_module="./Button"
 _onError={() => {
 setError(true);
}}
/>

Remote code

vite.config.js

export default defineConfig({
  build: {
    outDir: 'build',
    modulePreload: false,
    target: 'esnext',
    minify: false,
    cssCodeSplit: false,
  },
  server: {
    port: 3000,
  },
  plugins: [
    react(),
    federation({
      name: 'app',
      filename: 'remoteEntry.js',
      exposes: {
        './Button': './src/components/Test',
      },
      shared: ['react', 'react-dom'],
    }),
  ],
});
echjordan0 commented 11 months ago

@MateoKruk did you manage to solve this? I have a similar situation but I'm using Vue, host is vite and remote is webpack

MateoKruk commented 11 months ago

@echjordan0 nope, I stopped the migration for now

echjordan0 commented 11 months ago

FWIW it turned out I had a naming mismatch because webpack allows prefixes on the remote url (e.g. prefix@localhost/remoreEntry.js -- once I removed the prefix and changed all the names of the remote on the remote and host side it worked.

sebastianestradaintcomex commented 7 months ago

FWIW it turned out I had a naming mismatch because webpack allows prefixes on the remote url (e.g. prefix@localhost/remoreEntry.js -- once I removed the prefix and changed all the names of the remote on the remote and host side it worked.

you mean changing it in webpack from app2: app2@http://localhost:3000/remoteEntry.js to app2: http://localhost:3000/remoteEntry.js ??

echjordan0 commented 7 months ago

@sebastianestradaintcomex Here's what worked for us: HOST config (vite)

    federation({
      name: 'freshebt_vue',
      remotes: {
        freshcard_app: {
          external: `${process.env.URL}/remoteEntry.js`,
          format: 'var',
          from: 'webpack',
        },
      },
      shared: ['vue', 'vuetify'],
    }),

REMOTE config (webpack)

    config.plugin('module-federation').use(ModuleFederationPlugin, [
      {
        name: 'freshcard_app',
        filename: 'remoteEntry.js',
        shared: {
          vue: {
            singleton: true,
            requiredVersion: dependencies.vue,
          },
          vuetify: {
            requiredVersion: dependencies.vuetify,
          },
        },
        exposes: { './microfrontend': './src/microfrontend.ts' },
      },
    ]);

Import of webpack remote: return (await import('freshcard_app/microfrontend')).default;

adirzoari commented 6 months ago

@MateoKruk I just finished migrating all of my project apps from Webpack to Vite, but when I deployed, I encountered this error. It feels like a waste of time; I wish I had known about this issue beforehand. did you find a solution?

sebastianestradaintcomex commented 6 months ago

@MateoKruk I just finished migrating all of my project apps from Webpack to Vite, but when I deployed, I encountered this error. It feels like a waste of time; I wish I had known about this issue beforehand. did you find a solution?

You should not have migrated to vite, even more so to use a feature that was implemented by default in webpack and this is just a library that tries to emulate its functionality but it is very unstable, if you are going to use ssr with module federation in vite let me tell you that you lost the time. Module Federation works best and with more stability in Webpack.

sebastianestradaintcomex commented 6 months ago

@sebastianestradaintcomex Here's what worked for us: HOST config (vite)

    federation({
      name: 'freshebt_vue',
      remotes: {
        freshcard_app: {
          external: `${process.env.URL}/remoteEntry.js`,
          format: 'var',
          from: 'webpack',
        },
      },
      shared: ['vue', 'vuetify'],
    }),

REMOTE config (webpack)

    config.plugin('module-federation').use(ModuleFederationPlugin, [
      {
        name: 'freshcard_app',
        filename: 'remoteEntry.js',
        shared: {
          vue: {
            singleton: true,
            requiredVersion: dependencies.vue,
          },
          vuetify: {
            requiredVersion: dependencies.vuetify,
          },
        },
        exposes: { './microfrontend': './src/microfrontend.ts' },
      },
    ]);

Import of webpack remote: return (await import('freshcard_app/microfrontend')).default;

We decided not to use Vite for projects with module federation due to its instability and difficulty with advanced configuration.