Skip to content

Optionally disable handling node_modules as externals #29050

@MaximilianKlein

Description

@MaximilianKlein

Describe the feature you'd like to request

I would like to disable turning some npm dependencies into externals for the SSR build and package them with webpack. I'm talking about the handleExternals function that is called in the ExternalModuleFactoryPlugin.
When I add a dependency like react-intl it is currently turned into an external which simply uses require to load the dependency and that usually makes sense.

But when working with ModuleFederation it is necessary that those are 'factorized' to enable sharing of dependencies in SSR builds. Otherwise other remotes cannot reuse the dependency.

Describe the solution you'd like

I'm thinking of a configuration option in the next.config.js like

{
   "nonExternals" ["react-intl"]
}

( sorry I couldn't think of a better name )

This would then be used in the handleExternals function and instead of returning commonjs react-intl it would return nothing (which would allow the next tapped-in factorize function to do its thing).

Describe alternatives you've considered

I'm currently monkey-patching it like this:

// inside your next.config.js
webpack(config, options) {
        const { webpack } = options;

        if (options.isServer) {
          /// nextjs adds a function into externals that explicitly turns node_module requires
          /// into standard 'require' statements. That happens in the hook `NormalModuleFactory.factorize`
          /// which normally would create the share definition for ModuleFederation shares.
          /// `factorize` is a Bail-Hook meaning that if one tapped in function returns something
          /// it will stop processing it (order matters here and nextjs puts its ExternalModuleFactoryPlugin
          /// before ours and we cannot do anything agains that).
          /// We now need to make sure that this function does not prevent the ModuleFederation Plugin to do
          /// its work. For that we short-circuit the next externals when we find our shared module.
          const originalExternals = config.externals[0]; // maybe not the best way to 'find' the handleExternal function
          config.externals[0] = ({ context , request , dependencyType , getResolve  }) => {
            
            if (request == 'react-intl') { // example with react-intl, you probably want to fix all shares here
              /// return something empty to prevent bailing (it is a bit more complicated, but this works)
              return Promise.resolve();
            }
            return originalExternals({ context , request , dependencyType , getResolve  });
          }
        }
// ... 

But that is rather ugly and will probably break in the future.

Another way of doing it might be to put the ModuleFederationPlugin before the ExternalModuleFactoryPlugin so that it taps into the NormalModuleFactory.factorize first. But I was not able to do that.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions