Webpack: Merge Proposal: Module federation and code sharing between bundles. Many builds act as one

Created on 7 Feb 2020  ·  393Comments  ·  Source: webpack/webpack


This is a proposal to merge my existing work into the Webpack core. The base concept is federated application orchestration at runtime. What I hope will provide a new era for how we build apps at scale.

Due to the demand for more information, we have created a site that consolidates all content

https://module-federation.github.io/

https://github.com/module-federation/module-federation-examples

https://github.com/webpack/changelog-v5/blob/master/guides/module-federation.md

Feature request

@sokra as requested, I've opened a new issue for us to discuss planning and implementation.

This is the evolution of my original issue: https://github.com/webpack/webpack/issues/8524
edit: a better way to explain the request. I want the webpack equivalent of Apollos federation concept.
I want to share resources between separate bundles at runtime. Similar to DLL plugin but without needing to provide Static build context , and I want to do this at runtime in the browser. Not at build time. Thus enabling tech like micro-frontends to be efficient, easy to manage, and most importantly - erase page reloads between separate apps and Enable independent deploys of standalone applications.

For context, I've already implemented this feature request with great success. I feel this would have a major impact on frontend applications. I am proposing to refactor/rewrite my project and introduce it into the Webpack core. https://github.com/ScriptedAlchemy/webpack-external-import

What is the expected behavior?
import('website-one/MegaNav')
require('website-one/MegaNav')

  • I expect to be able to require modules or chunks from other Webpack bundles hosted elsewhere.
  • I don't want to manage externals and worry about synchronizing them across systems
  • I don't want a single point of failure, like a commons chunk
  • I want to load code from another build, like dynamic imports and code-splitting
  • I want multiple builds to look and feel like a monolith in the client
  • I am able to deploy frontend apps independently and expect Webpack to orchestrate at runtime
  • I don't want to use less integrated/framework oriented solutions like SingleSPA, browser events, service workers - I want to use Webpack as the host container for foreign chunks and modules, not load other app entry points, but rather load other apps chunks and use those modules
  • If desired, an entire company should be able to federate code across every UI, from user-facing to backend. Effectively turning a multi build, multi team, multi mono-repo company into one SPA in the browser. Removing the need for page reloads or downloading additional code and bundles containing mostly the same node modules

What is motivation or use case for adding/changing the behavior?
This would offer a major shift in frontend architecture.
Apps are getting larger and code-splitting on its own is not as effective with things such as micro-frontend technology.

All current solutions on the market are substandard - working around the problem instead of addressing it. Application interleaving at runtime enables many new avenues for engineering.

Heres some:

  • Applications can be self-healing, in the event there is a network failure - one could query other interleaved apps and load their copy of the missing dependency. This would solve deploy issues where pages break for a moment as the new code replaced old
  • Code can be distributed and managed by the team maintaining it, with options for evergreen code. Navigation, Pages, Routers - could be managed and deployed by the team who owns it - others would not have to redeploy their applications to get updates
  • Smaller bundles - interleaved apps wouldn't download chunks if Webpack already has the modules required by the interleaved app or fragment.
  • Easy and feasible micro-frontends - the tech right now involves some serious management and time to build supporting infrastructure
  • Shareable configs - interleave FE configurations without the need to rebundle or expose multiple configs.
  • Avoid long & slow builds due to the size of the monorepo. This would enable smaller builds and faster deploys with less coordination across teams
  • Introduction of "micro-service functions" modeling something similar to what backend engineers get to enjoy.
  • Better AB testing and marketing tooling. Right now AB tests are managed via a tag manager, plastering vanilla JS onto the dom which is usually not optimized, pollifilled, or compatible with how much javascript apps are written today. Markering teams and AB teams would write native components without having to get in the way of team delivery.
  • Better analytics - Tag managers and analytics engineers could write system native modules that could be woven into codebases. Most depend on some plugin or just reading the DOM. Interleaving would open a new world of slick integration.
  • third party modules - vendors could take advantage of this tech and offer managed modules instead of inefficient scripts - they'd still be secure but would have a new way of implementation. Iframes could be avoided in some cases
  • no global pollution of the window, globals can be accessed via the Webpack runtime
  • as more apps are interleaved, they too can supply their modules to another interleaved app, resulting in less and less code needing to be downloaded as more and more modules are added to webpack

The use cases are limitless, I've only begun to imagine possibilities.

Webpack is more than capable of doing this without needing any big changes to the core. Both 4 and 5 already have what I need.

How should this be implemented in your opinion?
Seeing as I've already done it. I have a clear picture of how to implement it. However, it can be improved with some guidance. I hold little opinion over the solution, as long as it offers a similar outcome.

  1. Add an additional require extension which can support interleaving. I'm currently using a slightly modified version of requireEnsure which already works very well with Webpacks chunk and module loading / caching. when requiring an interleaved module - users would specify a pattern like / - Because my current solution is based on requireEnsure, it's pretty robust and not hacky. To handle CSS I've also taken the code template from mini-css to support side-effect loading.
  2. Output an unhashed JS file that can be embedded onto other apps. This file would contain a small mapping of chunk.id to the cache busted file name.
  3. Hashed module ids based on contents + package.json version of dependency + usedExports to support tree-shaken code. Hashing needs to be effective at avoiding collisions as well as to support tree-shaken code. Hashing enables effective code sharing and prevents overwriting existing modules. Ideally, if there's an option to update module id after optimization - it would likely improve the reliability of hashing. However, I have not encountered any problems. Open to alternative solutions. This hashing mechanism I am using is used by amazon who built a CLI to orchestrate code sharing across the company.
  4. A slight addition to the chunk template to include some registration data or another function similar to webpackJsonp.push which registers chunks and provides some metadata to Webpack
    Heres what it would include:
  5. compilation hash: to detect if this chunk is being loaded by the build that created it, if so, no additional processing is needed
  6. an array of additional chunk.id's required by the current chunk
  7. a list of module ids which allow Webpack to determine if it has all modules required by the chunk about to be interleaved. Webpack would use a lazy find to search modules for the first instance of a missing module.id. At which point Webpack would load the extra chunk needed to fulfill any missing modules it does not already have
  8. an interface similar to externals, where a developer can specify what files/modules they intend to make available for interleaving. During the build, Webpack would not hash these module.ids, but instead, make the id be whatever the key is. Allowing humans to call the module by a predictable name. All dependencies of a module and all modules, in general, would be hashed - ensuring that nested dependencies can be tree shaken and will only use existing modules in Webpack if they are an exact match.
  9. Some automatic chunk splitting which would ensure interleaved files are placed into their own cache group. I also recommend chunking vendor bundles and any loader JS code out of the main chunk in order to avoid interleaving the entry point because of something like css-loader getting grouped into the main chunk. In order to prevent downloading a massive 200kb chunk, I am suggesting that there be some default limit for maxSize set on cache groups. Something along the lines of enabling aggressive code-splitting by default if interleaving is enabled. The goal is a happy medium between not downloading dozens of chunks and not downloading a massive chunk for one or two modules the host build cannot supply.
  "interleave": {
    "TitleComponent": "src/components/Title/index.js",
    "SomeExternalModule": "src/components/hello-world/index.js"
  }
  plugins: [
    new WebpackExternalImport({
      manifestName: "website-one"
    })
  ];

Which would be used in consumer apps like this:
require.interleaved('website-one/TitleComponeent')

For better understanding, I suggest reading the links below. Please note that I am aware of many of the less elegant parts of the codebase and eager to improve the implementation to meet internal standards of Webpack (perf, bundle size, implementation strategy, better hooks to use)

Are you willing to work on this yourself?
yes - I have been contributing to Webpack since v1 and have a deep understanding of its API.
I'd like to be the one to bring this feature to the wider JS community as well as to Webpack. I've built multiple large scale MFEs, some consisting of over 100 MFE's at one company. I am very familiar with the challenges and alternative methods to chunk federation/interleaving - all of them are terrible.

While this solution might not be for everyone, it will have major value to massive corporations consisting of hundreds of teams of FE stacks. I am also able to test implementation updates against my own userbase - providing additional assurance to Webpack. I used the same approach when merging extract-css-chunks with mini-css (bringing HMR to mini-css)

Current results
I've been using this system for AB tests, Next.js apps, and config sharing at scale.
In a simple comparison. Interleaving three separate React apps produced only 320kb of code transferred (1.2mb uncompressed) compared to 4mb using traditional solutions available on the market.

Extra considerations

  • Whitelisting instructions that would enable developers to tell Webpack to use the "host build's" version of a dependency. Cases like React where I can't have two on the page - I want to offer the option to have a chunk use the version already installed, even if its a patch version different.
  • Hashing bloats builds: I'm looking for an alternative solution to hashing to give the community options, however, the savings of code sharing drastically outweigh any overhead in my opinion. 4mb to 1.2mb speaks for itself.
  • Chunk manifests are inside the chunk - this could be in one single file to produce a single manifest, however, I like the idea of code splitting chunk needs into the chunk themselves. This would make the actual manifest file very small as all it looks up is chunk names to URL paths. Seeing as the manifest is either cache busted with Date.now or 304 cache control - I wanted to keep it lean in the event developers don't want to deal with cache headers and just cache bust the 2kb file on each page load. This would introduce one extra loading wave, as once the chunk is downloaded, it would specify what it needs, triggering Webpack to then download any additional files in one call. Download chunk->download any chunks containing modules Webpack host build cannot provide. Nested chunks would not create more loading waves as the chunk metadata contains a thorough map of what it needs in totality

SSR
Update: because this is not only a browser implementation - you are more than able to federate modules via common js and shared directory or volumes on s3 or some other store that system. Alternatively, one could npm install a node version of the dependency at CI, treating the federated code (which is bundled so there’s no need for nested npm installs) to install the latest copy at deploy rendering most recent deploy of it - but at runtime the most updated Versions are available.

Ultimately- if you use module federation for the node build and were to have a shared disk that a lambda could read from, you’d be able to require other code deployed, in the exact same manner as you do on the frontend.

Send a PR

Most helpful comment

All 393 comments

What is the expected behavior?

I try to generalize your proposal a little bit. It intentionally don't mention chunks and always speaks about modules being loaded. That modules are transferred via chunks is left as implementation detail here.

There is a host build and a remote build. The host build want to use some exposed modules from the remote build.

In the host build code:

  • you want to have some startup code that is executed on startup
  • you want to use import("<scope>/<request>") (plus nice to have: import "<scope>/<request>").

In the host build configuration:

  • you want define a name for <scope>
  • you want to define a remote URL for <scope> (alternative: this is set at runtime with some special instruction)
  • you want to define some modules which are forced to be used by the remote build runtime instead of their own modules (this could either happen by request string or by a global unique name, in most cases probably npm name)

    • optionally you may want to specify a list of exports used in these modules (as it's not known at build time which exports are used by the remote build)

In the remote build code:

  • You want to have some startup code which is used on normal startup, but not if the remote build is consumed by an host build.

In the remote build configuration:

  • you want to define this build as consumable by a host build, by exposing some modules for a host build.
  • you want to define which <request> from the host build points to which modules
  • you want to define which modules are override-able by host builds (This is needed to keep these modules as modules at runtime, and prevent some optimizations e. g. scope hoisting or side-effects which would remove modules)

Delivery:

  • For target: "web": the remote build may be loaded cross-origin. (e. g. via script tag and JSONP similar to how chunk loading works)
  • For target: "node": the remote build should be loaded with require()
  • For target: "async-node": the remote build should be loaded with fs + vm

Recursion:

  • Host builds may define multiple <scopes> and remote builds
  • The remote build may use the same feature again. It could be a host build for another remote build. This is possible in a circular way. In the smallest case A loads a modules from B which loads a module from A.

    • Override-able modules are inherited and apply recursively e. g. For A -> B -> C: Here A is able to override modules in C too.

Optimizations:

  • Override-able modules should still have their unused exports removed.
  • Remote and host build runtime try to download only modules that are needed for the application.
  • Host build:

    • A module used to override a module in the remote build, which is not used by the remote build should not be downloaded.

  • Remote build

    • A override-able module which is overriden by the host build should not be downloaded.

  • Exceptions may be made when download is very small, when this leads to reduced number of requests made in some cases (similar to splitChunks.minSize)

Host build and remote build may be updated independently. The following information is shared off the band:

  • The exposed modules by the remote build

    • The host build may use these modules as <request> in code

    • The shared list may not be complete or change over time

    • Using an non-exposed <request> will lead to a runtime error resp promise rejection.

  • The override-able modules

    • The host build may use these modules in configuration to override them

    • Trying to override a non-override-able module will be ignored

    • Optionally a list of exports used in these modules

  • The remote URL.

    • The URL must not change when the remote build is updated.

    • User should be hinted towards correct caching of the remote URL.

    • revalidate, etag

    • Alternative: Redirect to hashed URL

    • May be cached for a short timespan, delayed effect of deployment should be considered.

Did I miss something? Would you consider these requirements as full-filling for your use cases?

optionally you may want to specify a list of exports used in these modules (as it's not known at build time which exports are used by the remote build)

Currently, I mark the module as usedInUnknownWays to prevent tree shaking the exports on exposed moduled/files

I do not see anything missed, I believe this captures the requirements perfectly.
I did not think about async-node or node targets - but this sounds like a great addition id love to support

Your understanding is correct and meets requirements plus adds new aspects i had not yet considered 👍

Currently, I mark the module as usedInUnknownWays to prevent tree shaking the exports on exposed moduled/files

Yep, that makes sense.


I intentionally removed some points from your proposal:

  • The ability to automatically share modules if they match by hash.

    • I think this is too unsafe. webpack usually don't choose ways that may break in some cases.

    • Instead modules are only shared when opt-in into making them override-able and override them from the host build.

    • A more aggressive way of sharing modules may be provided later as separate plugin or opt-in

  • require.interleaved syntax

    • I think this should integrate with ESM imports instead.

    • This makes it possible to use native ESM and import maps without changing the code

    • More aligned by the spec

    • Users don't have to care much about the location of modules

    • Migrating from and to single build should be without code change

  • The ability to share modules between sibling remote builds.

    • I think this could lead to weird runtime errors, because loaded version depends on load order of remote builds.

    • So host build is in control of everything and sibling remote builds no not effect each other.

    • This should prevent problems that only occur in cases e. g. when page a is visited before page b.

    • If two sibling remote builds should share modules, the host build must override them.


I think I have a good idea how to solve these requirements in a very clean way. I can share that on Monday.

Okay, I'm on board with the proposed changes.

I believe, with your suggestions included, it will be a much smoother integration which would greatly benefit the js community!

Have a good weekend! Appreciate your input

@sokra Let me know your thoughts on how we can solve these in a clean way.

I was working with supporting externals yesterday and a lot of your proposed changes made sense. In terms of how to opt-in and out of sharing code.

It sounds similar to what you were talking about.

Host Build

new WebpackExternalImport({
  provideExternals: {
    react: "React"
  }
});

Remote Build

new WebpackExternalImport({
  useExternals: {
    react: "React"
  }
});

To make this work, I modified ExternalModuleFactoryPlugin

I then added the following, which changed the external module to a raw request like require('react'); which would be supplied by the host to the remote. This however only works for externals and does not allow the remote build to function on its own as require('react') = module.exports = React on the remote build

          const externalModule = new ExternalModule(
            value,
            type || globalType,
            dependency.request
          );
          externalModule.id = dependency.userRequest;

With this pattern, you could also have multiple webpack builds only use provideExternals - so when a remote app is loaded, it would be looking for react which would already exist - effectively providing the ability to use a hosts dependency because both would be renamed to react instead of depending on hashed module ids

So here are my idea:

host and remote runtimes are two complete separate webpack runtimes with separate __webpack_modules__ etc. There is no manifest json file, instead the remote runtime is compiled with an special entrypoints which exposes an interface like this:

{
    get(module: string) => Promise<(Module) => void>,
    override(module: string, getter: () => Promise<(Module) => void>) => void
}

It basically consists of two parts: a kind of exposed import() function (get), which allows to load modules from the runtime build. And an override function which allows to override override-able modules from the remote build. The entrypoint contains all information which would be in a manifest json.

Here are more details:

Remote build

Plugin

A new plugin (names up to discussion):

new ContainerPlugin({
  expose: {
    "Dashboard": "./src/DashboardComponent.js",
    "themes/dark": "./src/DarkTheme.css"
  },
  overridable: {
    "react": "react"
  },
  name: "remoteBuildABC",
  library: "remoteBuildABC",
  libraryTarget: "var"
})
  • expose: modules exposed to the host build. keys is the request the host build must use to access to module. value is request string which is resolved during remote build.
  • overridable: modules that may be overriden by the host build. key is the name the host must use to override the module. value is the fallback module request string which is used when module is not overridden.
  • In addition to a object syntax an array of request string may be given as shortcut. In this case the plugin determines the keys automatically: ["./src/Dashboard", "react"] => { "src/Dashboard": "./src/Dashboard", "react": "react" } (This is especially useful for the overridables)
  • name: A name for the entry.
  • library: A library name, defaults to name or nothing depending on libraryTarget. See also output.library.
  • libraryTarget: The used libary target for the entrypoint. See output.libraryTarget. This is how the remote build is exposed. Defaults to "var".

When the plugin is used it adds an additional entrypoint which represents the remote build. The remote url points to the generated file from this entrypoint.

It also switches the mode of all overridable modules. Later more.

The generated entry

export function get(module) {
  switch(module) {
    case "themes/dark":
      return __webpack_require__.e(12).then(() => __webpack_require__(34));
    case "Dashboard":
      return Promise.all([__webpack_require__.e(23), __webpack_require__.e(24)]).then(() => __webpack_require__(56));
    default:
      return Promise.resolve().then(() => { throw new Error(...); });
  }
};
export function override(module, getter) {
  __webpack_require__.overrides[module] = getter;
  // foreach child container, call override too
};

Overridables

Getting the overridable modules correct is a little bit tricky. Their code should only load when used and the overriden module should be used when set. So loading a overridable module is async, as it either has to load the separately bundled module or the module provided by the override function.

All overridable modules are hidden behind a wrapper module (by replacing them after resolving). The wrapper module has a special module type (e. g. "overrideable module"). The wrapper module has an async dependency on the original module (which puts it into a separate chunk during chunk graph building).

Modules with the "overrideable module" type generate extra code for the chunk loading, which loads the nested chunk (group) too (in parallel to the normal chunk loading). So far this should restore normal operation, but puts overridable modules into separate chunks.

To allow overriding the extra chunk loading code would check __webpack_require__.overrides instread of just loading the chunk group. When an override is set it would load this instead and inject a virtual module as module factory. Now either the original or the overridden module is loaded during chunk loading.

Host build

Plugin

new ContainerReferencePlugin({
  remotesType: "var",
  remotes: ["remoteBuildABC", "remoteBuildDEF"],
  override: {
    "react": "react"
  }
})
  • remotesType: An default external type used for remotes
  • remotes: A list of remote builds as externals with the same syntax.
  • override: A list of overrides used for the remote builds. key is a name chosen by overridable in the ContainerPlugin and value is a request resolved in the host build.

    • An advanced configuration e. g. {"react": { import: "react", usedExports: ["createElement", ["default", "createElement"]]}} might be used to only include specified exports.

Types of operation:

  • remotesType: "var". The remote url is used as already existing <script> tag. It sets a global variable with the specified name. This variable is used to access the remote build.
  • remotesType: "commonjs2". The remote build is access via require(). This makes sense for node.js applications
  • remotesType: "module". The remote url is used via import remote from "remote-url". (This kind of external is not yet implemented, but planned.)
  • remotesType: "import". The remote url is used via import("remote-url"). (This kind of external is not yet implemented, but planned.)
  • remotesType: "jsonp". The remote url is injected as <script> tag. It is expected to set a global variable on load. This variable is used to access the remote build. (This kind of external is not yet implemented, but doable.)
  • remotesType: "amd". The remote url is used via define(["remote-url"], (remote) => {}). Depends on an AMD loader on page and output.libraryTarget: "amd".
  • remotesType: "umd". Either commonjs, amd or global. Depends on output.libraryTarget: "umd".
  • etc. (see ExternalsPlugin)

Some types allow to load the container on demand when used, some expect it to be loaded on bootstrap. Depending on usage both makes sense.

When using this plugin all requests starting with one of the remotes keys are handled in special way: "remote modules"

Remote Modules

When an remote module should be "factorized" (created), a special module type "remote module" is used. And the module has a dependency to the "external module".

During chunk loading all "remote modules" are created by executing the function exposed by the remote build as received from the external module.

Loading and execution happen in two steps. Loading happens during chunk loading and execution happens in the normal evaluation flow. This makes sure that remote modules are not executed to early.
Execution happens by assinging to the exports property of a given Module object (and not by returning the exports). This is important to handle circular dependencies between module modules and host overriden modules.

Summary

The ContainerPlugin adds a container entrypoint which exposed modules to the consumer. Technically this creates a separate root to the module graph. In practice splitChunks deduplicates chunks between the container entry and other entries so most chunks are shared between applications and container.

The ContainerReferencePlugin allows to consume a container entrypoint, referenced as external. This allows to reference modules from the container via a specified scope.

An overriding mechnism allows the container to use modules from the host build. This is useful for multiple reasons:

  • Sharing modules: Shared modules do not need to be downloaded/executed multiple times. So for performance reasons.
  • Singleton modules: Some modules must only be instatiated once to be functional. e. g. react
  • State sharing: A store might be shared to have shared state between host and containers.
  • Functional changes: A module might be overriden to change the behavior of the containers modules. This might be better solved with arguments, but anyway.

Questions

  • Should overridables and override be renamed to shared?
  • How to ensure that each webpack build has a unique output.jsonpFunction?
  • How to load a remote module that is needed in the initial page load? There is no chunk loading in this case and loading would delay bootstrapping. Should we force to use import() in this case?
  • Should there be a validation step at initialization which checks if all expected remote modules are exposed from the remote build? This would make it fail earlier.

Love this but have a few questions. I’ll follow up on response tomorrow.

Really like this, very clean and solves some of my pitfalls in external-import

I definitely like your implementation idea though - I just wanted to make 1 comment however... It would be quite fantastic for the expose property to be expressed via code somehow.

Note that the config file is code and you can use any javascript code to generate the config for the plugin. e. g. you could glob your source code for all *.public.js files and expose them.

If you talking about a in source code annotation or somewhat like that: I don't think that's a good idea for multiple reasons: 1. It would depend on the file parsed by webpack in first place. Meaning it must be including in some other entrypoint. 2. The request on which a module is exposes is a somewhat global information. Having global information in modules is not that pretty. 3. It would add webpack-special code into your source code, which is something we want to avoid. 4. It's difficult to figure out the "interface" of a remote build, because it's no longer in the config file in one place, but distributed across all source files.

But anyway, if you like to have something like that you write a custom plugin for that.

Getting you your response - thank you for the detail provided! I'm going to begin rewriting it tonight, will create a new branch that ill use for merging progress into on my repo. This also will help get reviews.

I like this idea. This solves some problems I was thinking about today with external-import I was wondering how to offer fallbacks on overridable modules.

It makes a lot of sense - though I will have questions on implementation detail. I know your API well, but I’ve not utilized some of the parts that I might need to build this. I may DM on slack when rebuilding it.

It looks like I could also have a build that has both reference plugin and container plugin. In the event, you want to enable bidirectional host capabilities.

I really like your idea about using an entry point with a custom getter, the switch statement being both the map and the loader which hooks into require functions the runtime already has. Pretty much a webpack-to-webpack coordination instead of webpack-to-chunk like I have proposed.

I'm not sure how the import mechanism would look though. How would we differentiate a local import from a remote one inside the host build? Require.interleaved was the indicator to let Webpack know this was remote. I like not using a non-standard syntax, but not quite seeing how to route requests to the right location. Would the reference plugin be specifying the scope of a remote so when the request has <scope>/<id> it knows to treat it as a request to the scopes webpackJsonP belonging to a remote?

remotesType: would this be the scope? website-one for example.

I'll need to fiddle around to understand how to implement overrides, but it sounds like the host would push module references and the remote would use these over its own.

In response to your questions

  • let's call it shared its easier to understand the intent.

    • the unique webpackJsonP could be inferred from the package name. We could also throw an error if a remote build detects another webpackJsonP already on the window. Similar to how React throws errors if more than one copy is included. I use namespace on the plugin when is required to enforce a scope to be set. We could do the same thing for webpackJsonP
    • Initial loading, this sounds like my corsImport concept. I would say we start off with forcing dynamic import() for loading the remote entry point (switch statement function). Developers could delay the application or dynamic import it elsewhere in the application, close to when a remote might be used. What to do if a request is attempted without the remote entry point? How would we allow Webpack to import() a remote entry point without failing the build?
    • Yes, I think validation should be done - however depending on importance/complexity, it could be a follow-up modification?

    Additional comments

    • This specification is very thorough and covers multiple implementation methods with the remotesType Ill definitely have some questions on how to model all of this.

    • Creating new RemoteModules, this is something ill also have questions on. I've tinkered with mini-CSS CssModule but not really created entirely new ones.

    • This looks like a very large rewrite - I'm going to create a new branch on my repo and scrap most of the implementation. I'm going to start with setting up the plugin options interface and will likely request some form of review as I reach a milestone.

    • I'm also likely to ask questions on which hooks are optimal to use - there are many ways to do this but id like for it to be written as correctly as possible.

    • When you create new plugins, do you follow any specific patterns or have a small boilerplate reference that has schema validation and a few other repetitive steps already made?

    • What plugins should I read over within Webpack that would help me mimic existing functionality?

    • I'll definitely want to speak with you in detail about how to create cache groups and create async imports on the fly. I've not dynamically created an entrypoint, but I think that's pretty straight-forward.

Overall this solution is very clean - I can see it working in my head almost end to end.

'm not sure how the import mechanism would look though. How would we differentiate a local import from a remote one inside the host build? Require.interleaved was the indicator to let Webpack know this was remote. I like not using a non-standard syntax, but not quite seeing how to route requests to the right location. Would the reference plugin be specifying the scope of a remote so when the request has <scope>/<id> it knows to treat it as a request to the scopes webpackJsonP belonging to a remote?

Yep, based on the start of the request. You can check how the ExternalsPlugin replaces modules. Basically you hook into afterResolve of the NormalModuleFactory and replace the module with a different one.

remotesType: would this be the scope? website-one for example.

remotes would be a list of scopes. remotesType is the way how the remote is referenced. Basically this remotesType is passed along to the ExternalsPlugin. So the remote is referenced as external, similar to the DllPlugin references the Dll. This allows to use the different external types to have different ways to load or reference the remote bundle.

  • let's call it shared its easier to understand the intent.

👍

  • How would we allow Webpack to import() a remote entry point without failing the build?

This should not be an issue as webpack sees the stub modules for remote modules.

  • What to do if a request is attempted without the remote entry point?

If the remote build is not loaded/loadable at runtime we would reject the Promise returned from import().

  • Yes, I think validation should be done - however depending on importance/complexity, it could be a follow-up modification?

Yep.

  • This specification is very thorough and covers multiple implementation methods with the remotesType Ill definitely have some questions on how to model all of this.

There is nothing to do from the ContainerPlugin side. This would be handled by the existing ExternalsPlugin.

@sokra , @ScriptedAlchemy
This looks fantastic. I am currently working on similar tool https://github.com/ppiyush13/qubix
Right now this tool is very much minimal and fragile. I would wait for this issue to be resolved before doing any further development.
Based on my experience I have few points for considerations.

  1. We may need to devise a way to override public path of remote build within the host build.
    For efficient caching, remote build may be composed of multiple chunks.
    Entry chunk must be loaded with script tag. Then all the dependent chunks must be loaded from same remote URL from which entry chunk was loaded.

    publicPath overriding must only be done for for assests loaded from JS files.
    For assets loaded from CSS, publicPath should not overriden because partial URLs are interpreted relative to the source of the style sheet, not relative to the document.

    For the tool I have created, publiPath is overidden by webpack-require-from plugin. Please read the tool's source to understand how it is being done. I honestly feels that this mechanism
    can be improved further.

  2. I aggree with @ScriptedAlchemy for deriving unique webpackJsonP id from package name or as as another option for ContainerReferencePlugin constructor.

  3. dynamic import for loading remote build sounds like a good idea. In the tool I have created, I provide custom load function as "qubix.load" which loads the entry and all the required chunks.

@sokra
With that initial milestone near complete.

https://github.com/ScriptedAlchemy/webpack-external-import/pull/124/files#diff-0eb7c718f9460448d676917db1cad175

1) What would be the next logical step
2) What's missing / what should I correct on addEntry - as you see, its hard-coded.

The good news is that I am emitting an entry point that was dynamically created.

compiler.hooks.compilation.tap(PLUGIN_NAME, compilation => {
      const { mainTemplate, normalModuleFactory } = compilation;
      const containerEntryModuleFactory = new ContainerEntryModuleFactory();
      compilation.dependencyFactories.set(
        ContainerEntryDependency,
        containerEntryModuleFactory
      );

      compilation.addEntry(
        "./src", // what should go here?
        new ContainerEntryDependency(),
        "remoteEntry", // what should this be?
        () => {
          return new ContainerEntryModule(); // what else needs to go in here, if anything?
        }
      );
    });

Posting here in public space for visibility.

We had some challenges in implementation once switching to Webpack 5. This is an API that I've not actually worked against in development until this week.

In order to make this work - ContainerExposedDependency also needed a dependency factory.

We have separated these into a hook that happens earlier on in the process

    compiler.hooks.compilation.tap(PLUGIN_NAME, compilation => {
      compilation.dependencyFactories.set(
        ContainerEntryDependency,
        new ContainerEntryModuleFactory()
      );
      compilation.dependencyFactories.set(
        ContainerExposedDependency,
        normalModuleFactory
      );
    });

As for make there were some challenges - so a different route was taken. Now that remoteEntry is emitting - I think ill review this code block and reconstitute it back into ContainerEntryModule - Ideally, ill re-address this when I've got some free time. Id likes to make up for the lost time and keep our momentum going. Refactoring implementation details will be a little easier once we have the ContainerPlugin near completion, but before starting on the ContainerReferencePlugin

@sokra if you have any objections - I'll follow your advice if this is implemented in such a way that it will block further progress.

    compiler.hooks.make.tapAsync(PLUGIN_NAME, (compilation, callback) => {
      compilation.addEntry(
        compilation.context,
        new ContainerEntryDependency(
          Object.entries(this.options.expose).map(([name, request], idx) => {
            const dep = new ContainerExposedDependency(name, request);
            dep.loc = {
              name,
              index: idx
            };
            return dep;
          })
        ),
        this.options.name,
        callback
      );
    });

Next stages

We are currently working on the switch statement. So ill see how much progress we make on that portion.
I'll send a link to the PR file blob shortly.

@sokra - What would you suggest the next logical progression here is? shared?
How do we connect library and libraryTarget within this plugin appropriately?

Last comments

I didn't address this earlier - we got lost in other chatter. Regarding the naming of this plugin.

Id like to propose something like FederationPlugin FederationReferencePlugin or ModuleFederationPlugin ModuleFederationReferencePlugin

The reasoning here:

  • This is a high-impact feature for webpack and the JS community. Something easier to recognize/search on the internet would likely be beneficial.
  • I, and I'm sure others will write additional plugins, tools, and frameworks built on top of this foundation. Being able to easily find projects specifically designed to leverage this development pattern would help with the distribution and recognition of third-party tools.
  • Federation naming aligns with Apollos term for GraphQL federation - which effectively is the API version of this. It would align us more closely with the distributed architecture naming that's picked up in the community
  • It will be easy to find documentation and things like "Awesome Federation Tools List" could aggregate collections of tools.

I've already built a whole micro frontend platform, AB testing, and soon analytics tooling + tag management based on external-import that will now be based on FederationPlugin. This might be a good opportunity to introduce a more consistent language than the blanket "micro frontend" buzzword.

I'm considering writing some architectural best practices and a new architecture pattern based on this tech. F.O.S.A = Federation of Standalone Applications.

Tooling wise -
AutomaticFederationPlugin based on my initial idea but will revise stability.
FederatedABTests, FederatedAnalyticsIntegration, FederatedReactRouter, and so on.

Pretty much everything i build will be open-sourced and leveraging this - It would be a fantastic opportunity to establish strong brand recognition with this core concept, and as of right now - webpack is the only one in possession of something like this.

Open to ideas on naming, but id like to have a name recognizable enough to build a sub-culture / new technology branch around that aligns with distributed systems that work as one.

Much like how every third party plugin usually has some-name-webpack-plugin instantly recognizable and easy to search for

here's the blob for the plugin.
https://github.com/ScriptedAlchemy/webpack-external-import/pull/124/files#diff-0eb7c718f9460448d676917db1cad175

search for : src/webpack-core/ContainerPlugin.js - ill merge this tomorrow to you dont have to scroll through so much noise in the PR

Fully agree - it’s been worth the wait. I intend to become a regular maintainer once I’m familiar with the changes. It’s much easier to build with compared to v4

Following update: we have more or less completed ContainerPlugin and will be working on ContainerReferencePlugin in order to test end to end flows.

Both will need a few additional updates by we have achieved basic mvp capabilities

Is this feature only for webpack 5?

Hi guys,
great work so far! I have one question accordingly to your solution. What will happen if two micro apps will use different react versions?
This may happen in the time of the migration, when one team is faster than the other.
Also it may happen that one of the micro apps will introduce a breaking change in theirs shared component?
How you solve this?

What will happen if two micro apps will use different react versions?

The host app can override the react version for all remote apps. As only a single react version must exist on page, you must do that for react. For other libraries to have the choice to override and force one version for all, or to not override in which case each micro app keeps it's own version.

Anyway the host app is in control.

This may happen in the time of the migration, when one team is faster than the other.

In the case of a circular dependencies between two micro-apps:
Both need to upgrade the version and deploy the new version.
When all micro-apps are using the upgraded version, they may start to use new features.

Also it may happen that one of the micro apps will introduce a breaking change in theirs shared component?

That's bad and they may break the consumer apps. That need to be coordinated. It could make sense to publish a new major version of a component under a different name and expose both versions of the component at the same time to give consumers time to migrate.

@vankop ill be porting it back to Webpack 4 - but will be as a separate project, not in the core

On the ReferencePlugin side - I have it working with also a few caveats.

One issue is when importing a remote module, i end up with an unwanted module.default -> Promise -> RemoteModule.

It looks like I might need some kind of custom context dependency? @sokra what are your thoughts?

  componentDidMount() {
    import("websiteTwo/Title")
      .then(Module => {
        return Module.default; // returns a promise
      })
      .then(Module => {
        console.log(Module.Title)
      });
  }

I will also need some guidance on UMD/AMD modules and what the RemoteModule should look like for them. I've not used AMD/UMD and seeing as the variables require and ID reference, I don't know how one would be generated as its unknown. Unless i use the request and just point it to the variable similar to how module.exports =

@sokra I've been working on supporting the library{,Target} options to this feature, which is largely there and working ✅. However, if the main options has those fields set, the LibraryTemplatePlugin kicks in, and wraps all entry chunks in that, which for cjs,var,this,global,window etc.. it isnt an issue, but for umd,amd's they are.

What I mean by that is, If I warp our new remoteEntry in library: 'test', libraryTarget: 'global', in both ContainerPlugin and the root output options. You'll end up with

global['test'] =
global['test'] = (() => {...})()

which work, however for var, umd, amd's that obviously doesnt work. Just wondering if you can point me in the right direction? I was thinking to intercept the JavascriptModulesPlugin.render hook for SetVarTemplatePlugin and co. To not apply the template over our remote chunk created by this plugin. (And quite hopefully give me an example).

I couldn't just new LibraryTemplatePlugin().apply(compiler) in this plugin, as that'll wrap every entry, where the main bundle might not want that to happen. I could investigate pulling that plugin apart? To look at potential config on the chunk or something...??

On the core we are working on allowing target, library, etc. to be configured per entrypoint. Currently that's not possible -> https://github.com/webpack/webpack/projects/5#card-29122473

  • should our new entryChunk be marked as preventIntegration = true?

no

  • CachedConstDependency seeing as that extends NullDependency, does that mean it doesnt emit? I can see a Template is defined for it, so perhaps I need to call something in my codeGeneration method to _print_ that dep?

By defining your own codeGeneration these templates are not being used. The NormalModule uses the JavascriptGenerator in its codeGeneration, which calls these dependencyTemplates.

These DependencyTemplates also refer to ranges in the original source code. This is not something you have.

  • when expose is sent in as an array, we assume the keys right? Are you okay with this to derive those keys? exposeMap[exp.replace(/(^(?:[^\w])+)/, "")] = exp; eg: ['./src/test.js'] => {'src/test.js': './src/test.js'}

Seems reasonable. The Regexp could be simplified to /^[^\w]+/. Maybe to handle "../../test.js" -> /^([^\w]+/)+?

  • to keep jsonp methods unique, should that be a "warning" that we flick up when the output options hasnt deviated that from the default? Or be cleaver, and do something like compiler.options.output.jsonpFunction = compiler.options.output.jsonpFunction + remoteEntryName

One issue here is that the jsonpFunction is global. By the default it's computed from the (global) output.library option. I think we should keep this in mind but do nothing for now. Maybe it's possible to solve this when refactoring to add the ability for a per entry library.

  • I've opted for a new Map to house those mappings. Which I wanted your opinion on. Is a switch case, with 80 cases performant/memory efficient compared to a Map (and object for that matter) with 80 entries. The switch branches obviously will be stack allocated yeah? Whereas the Map, would be Heap allocated? I'm just thinking in a use case for my company where every component in our design system will be exposes, and 80+ cases could become quite expensive, when it really just a dictionary lookup?

Ok. Maybe use an Object instead of a Map. That's similar performance-wise, but allows the code to work in older browsers.

One issue is when importing a remote module, i end up with an unwanted module.default -> Promise -> RemoteModule.

That seem to be a bug in the implementation. All Promises should be resolved during chunk loading.

Update
@maraisr did a fantastic job with ContainerPlugin - to be renamed FederationPlugin - he gave my repo i good cleanup and re-organized all the code. Much more sexy.

Heres whats currently in the works
I'm working on SharedModule system. It took a few false starts to grasp the best way to handle this.

Both host and remote will take advantage of SharedModule anything that's listed as shared on either FederationPlugin or FederationReferencePlugin will use ShardModule when a request matches.

Unlike a NormalModule, SharedModule will export a function that acts as a global lookup for modules from the host, followed by a fallback require to the remote bundle in the event the host is unable to supply a module for whatever reason.

Itll look something like this:

module.exports =  __webpack_require__.shared('react') || Promise.resolve(__webpack_require__(297)) 

I'm working on the entry point function that will enable shared module lookups.
Thinking of something along the lines of this

const __SHARED_MODULES__ = (module,getter) => {
     __webpack_require__.shared[module] = getter;
}

Next.js

I've seen a growing demand around Next.js and if it'll support this. I use Next.js on a few projects and will be ensuring this system will work with Next.
Next tends to lock users out of a lot of Webpack control - mostly for their own good. I've used this plugin with Next.js but likely might require a supplementary Webpack plugin to perform some brute force overrides at the Webpack level allowing us to regain full control of Webpack.

@sokra when you have a moment - take a look at the progress on shared modules

SharedModule
https://github.com/ScriptedAlchemy/webpack-external-import/blob/shared-modules/packages/plugins/SharedModule.js#L128

ContainerEntryModule
https://github.com/ScriptedAlchemy/webpack-external-import/blob/shared-modules/packages/plugins/ContainerEntryModule.js#L107

I was thinking on RemoteModule

have it actually hydrate the shared function on execution

'(function() {',
    'module.exports =',
    `typeof ${type}["${requestScope}"] !== 'undefined' ? ${type}["${requestScope}"].shared(sharedModules, __webpack_require__.shared) &&["${requestScope}"].get(${objectLookup}) : `,
    `Promise.reject('Missing Remote Runtime: ${type}["${requestScope}"] cannot be found when trying to import ${objectLookup}'); `,
    '}());',

Just got module federation working in Next.js @sokra we are stable.

This is amazing work. I have been struggling to find a solution to this problem as well on a very large scale client site and you have knocked it out of the park. Thank you so much for all the hard work on this issue!

Discussion from call with @ScriptedAlchemy:

external1: () => {
    module.exports = remotebuild123;
}

remote1: () => {
    const remote = require("external1");
    remote.override({
        // ... (from config)
        react: () => {
            return __webpack_require__.e(18).then(() => () => __webpack_require__("react"))
        }
    })
    module.exports = remote;
}

runtime module:
    const chunkMapping = {
        12: ["remote-a", "remote-b"]
    }
    const nameMapping = {
        "remote-a": "react",
        "remote-b": "lodash",
    }

    ${RuntimeGlobals.ensureChunkHandlers}.remote = (chunkId, promise) => {
        chunkMapping[chunkId].forEach(id => {
            const promise = require("remote1").get(nameMapping[id])
            promises.push(promise.then((factory) => {
                __webpack_modules__[id] = module => {
                    module.exports = factory();
                }
            }));
        })
    }


() => {
    // import("remote-module-b");
    require("remote1").get(): Promise
}


// import("react");
__webpack_require__.e(12).then(() => __webpack_require__("remote-a"))

This looks interesting! Just wanted to add some questions/comments:

I don't want to use gimmicks like SingleSPA

To clarify this - singlespa is merely a router for microfrontends, it doesn't actually orchestrate them. The orchestration (aka runtime resolution) itself is generally handled by SystemJS, with each microfrontend built using webpack (or rollup/other bundlers).

Along those lines, I would be curious to know what the advantages/disadvantages of webpack rolling with its own runtime module resolution scheme, vs something that is in the process of being standardized by wicg (and has been implemented in chrome): import maps. Perhaps there are things from that spec that could be shared/agreed upon, along with feedback/improvements that could be suggested based on your work here as well? (Though to be clear, I'm no expert in these things; people like @guybedford are much more versed in these topics). I just hope that this doesn't diverge too much from the standards that are being worked on; or at the least, can potentially work in conjunction with those standards.

In the end, though, I love the work and direction this is going.

@frehner Good point, I've got some strong views about some of these things so I do apologize for being a bit abrasive hahaha. While I'm sure they do other things, SingleSPA was created more or less as a workaround. Granted SingleSPA will work on multiple builds and such.

It just doesn't live up to my dream of import('website-two/somecomponent')
We have already taken import maps into consideration and Webpack will be able to leverage that as well. I do think, regardless of improvements in the browser there always needs to be an orchestration head like webpack.

It'll be great when we start using import maps for sure, but I want to cater for all scenarios. Both in the browser and on the server/node.

I'm adding modules and factories to webpack processors - so if webpack can use import maps, then it shouldn't have much impact on where my modules or factories end up going or how they are wired up.

It'll be interesting once we get module sharing hooked up to see the feedback from the community

As the original author of single-spa, I also don't appreciate the label of "gimmick" 😄. Single-spa itself does not concern itself with bundlers and module formats. I have used single-spa with in-browser modules via rollup's module output, rollup externals, and the es-module-shims polyfill. Since webpack does not (yet) support compilation to ES format and because webpack is the primary bundler for angular and vue clis, the single-spa documentation encourages use of a module loader for in-browser communication. However, when webpack 5 supports libraryTarget module, single-spa will transition to encouraging in-browser modules.

For me, webpack 5's upcoming module libraryTarget is very exciting and will cover this use case rather well. I actually would challenge the need of a "host" application at all - in-browser javascript modules should be able to be bundled and depend on each other without the need of a central host application controlling it.

To me, webpack externals + libraryTarget module + import maps is all that is needed for this. A host container build of several micro apps is slower than separate builds to libraryTarget module. Also, doing so makes the output bundles less portable for use as in-browser modules consumed by multiple host applications.

I think the future of in-browser modules will likely involve browser proposals such as import maps, rather than a rethink of WebpackDLLPlugin.

@joeldenning apologies for being a little harsh, a poor choice of word 😅

I agree with the idea of not requiring a host - however for this release, we know it’s stable. I’ve built variations that enable automatic module sharing - and will write more enhancements of these modules as time goes on. However it’s important to note - any app can act as a host- the host pretty much is just the runtime on the page first. So it’s not centralized statically. Another plugin will be written incorporating both container and reference plugin. Making any build bidirectional

How would you handle multiple versions of in-browser modules? I was using module hashing to verify shareable modules exactly before loading a fallback module. It worked quite well but there were concerns about collisions.

I disagree on webpack externals. It’s just a central point of failure. Your apps are not standalone without it. No opportunities for self healing or on demand sharing. Things are just roughly grouped. Yeah this version has an externals like feel. But it’s providing new opportunities leveraged with other extensions or hooks.

How would you handle multiple versions of in-browser modules?

Import map scopes - https://github.com/WICG/import-maps#scoping-examples

Ahh yes. We do mention this as something to implement. It would be another switch statement case on this plugin, that would create a native esmodule.

That seems path based though - I want it based on hashed identifiers. This approach assumes the file paths might be predictable. I don’t care how nested a dependency is - if it’s the same cryptographicly then I want it used.

Perhaps I just need to read more about the implementation details.

For those tracking the issue: here are the three main pull requests open to webpack

ContainerPlugin https://github.com/webpack/webpack/pull/10440

ContainerReferencePlugin https://github.com/webpack/webpack/pull/10439

OverridablesPlugin https://github.com/webpack/webpack/pull/10420

To me "federated" means multiple entities communicating through a shared protocol to achieve a common goal. One of the most important aspects of federation in my mind is the ability to experiment with different solutions to various problems.

In the case of microfrontends the entities are the multiple frontends, the goal is (at least in my case) 1 cohesive monolith-like user experience, and the shared protocol is the shared platform api (esm/umd and the dom). In the microfrontend world this means choice of bundler: cutting edge bundlers with minimal configuration and incredible speed, legacy bundlers for legacy applications, or even minimalistic frontends with no bundler. The microfrontend architecture I've been working on happens to use webpack for every build, but only because that's what my team knows. Right now we have the ability to experiment with other tools such as rollup and parcel (some of which seem more lightweight and simpler to setup for a new microfrontend), and the huge product integrations we're going through will have no trouble integrating as long as they produce a js file as an entrypoint into their frontend. And we are able to achieve the same deduplication of shared dependencies with webpack externals + systemjs today with minimal configuration overhead between microfrontends.

Maybe this is just my ignorance of the changes described here because I haven't taken the time to understand the expected webpack distributable output of this change (so please correct me if I'm wrong), but it seems like this isn't solving the problem of decoupled dependencies in federated frontend modules better than the existing solution (as @joeldenning mentions and as recommended in single-spa docs) and will require webpack lock-in to achieve it.

If what you have implemented is compatible with other bundlers and simplifies the configuration of externals (not sure how it can get any simpler), then I'd love to see it in action. Otherwise I'd prefer to stick with my existing bundler-agnostic solution.

The point is being missed. This is foundational work that provides a functional and useful purpose out of the box, I needed the core modified to leverage more powerful levels of automatic orchestration. I already have written plugins that automate and share dependencies, enable tree shaken vendors and tree shaken exports merging. The argument of using externals: repeatedly I’ve stated I do not want to use externals. Yes, you can share code, in bulk. I want more granular capabilities that don’t depend on at build context. Do I have to deploy two things to share some vendor that I also have to be aware of?? Externals mean no more standalone app. Maybe I’m using this for more than a page. Maybe it’s my pattern library. On-demand. Evergreen. Component by component. Maybe it’s an analytics system deeply integrated with the core application. What about non-browser environments? What about a frontend that runs from the front of house to back of house, or a megacorp with over 150 independent frontend applications, dual-purpose apps - what if it runs as an embeddable widget & a standalone app? Must the consumer also have that externals file? Must it follow me everywhere, build yet another entry point?

My shared protocol is Webpack. I’m not interested in some other bundler that’s easier or faster. I want power, I’m not using Webpack to spit out some JavaScript. I’m integrating with it in dozens of ways, it’s just as much a part of the app and typical features might be.

GraphQL is a lock-in, react is as well, so what if yet another node module is too. You're already locked in. I’m using a tool I know very well, anything I need it to do I can make it do. Beyond that, I don't see multi-billion dollar enterprises switching to another bundler anytime soon. More risk than its worth

The effort to write another plugin that drives these new features is trivial. I’ve automated granular code sharing and contextless compatibility already. Anything thrown on a page together will “just work”.

Me pulling another page from a separate application results in 20kb, with no centralized dependency on externals. That page will work absolutely anywhere in any other frontend built by Webpack. Things can change and the application can and will recover, there’s no hard dependency.

I’ve spent months looking at existing systems and have built this type of functionality using most of the techniques already mentioned here. If anything else did what I wanted - I wouldn’t waste my time writing what I needed. Depending on import maps which realistically will take another 5 years to actually roll enough support to be used natively. Pollyfilling has limits and import maps, in any case, don't really solve orchestration at scale. If I want to support import maps, it's an extra case on my switch statement, seeing as I already support 'var','this','window','self','global','commonjs','commonjs2','amd','amd-require','umd','umd2','system', - I've got absolutely no concerns. My system works anywhere and I don't have to change development patterns at all. My system just works. Godspeed to anyone who wants to leave Webpack. You say you can solve this with System, but I bet you still had to build something to solve it. As you can see, its just another case in my switch statement.

I'm sick and tired of maintaining & rebuilding systems each time I move to another company. My solution is stable, supports any use case, and costs no maintenance overhead to my engineering departments. import('team-one/federatedModule') there's my maintenance overhead. If I get hit by a bus, no risk, nothing to maintain, nobody even needs to know how it works. Not to mention this solution is backed by Webpack, one of the most influential and well funded OSS projects around.

There's a better way to solve this than "just use externals". Years old architectural pattern is the best we cobbled together? I wholeheartedly refuse to accept that. Glorified workarounds. I'd rather change industry standards, not accept them.

I’m happy that your solution solves your problem. I’m solving mine.

Project to track progress now that we have an MVP: https://github.com/webpack/webpack/projects/6

I'm looking forward to learning more about this and seeing where it progresses. I've read through this thread and still don't quite understand how this solution solves externals any better than what externals currently are. It seems like if a dependency is missing in the browser that you're in trouble regardless of whether you're using global variables, systemjs (or other module loader), or webpack as the tool that resolves the runtime variables.

In your original post, it says the following is a problem:

I don't want to manage externals

And then it says that the following is the solution:

an interface similar to externals

It seems like the new solution is the same thing as externals, except that webpack can now resolve externals in the browser. Is that a fair way of describing it?

To me, it seems like module federation is roughly equivalent to turning webpack into a module loader? Meaning that webpack can resolve dependencies in the browser, instead of always at build time. If that's the case, I would suggest at least taking inspiration from standards / browser specs instead of formulating a webpack-specific format for the module manifest.

Import maps are the current (but not first!) proposal for defining that module manifest. They are impossible to fully polyfill (although es-module-shims gets really close), but webpack doesn't need a full polyfill for them. Since the in-browser module loading is fully controlled by webpack, I think it would make a lot of sense to consider using an import map as the definition of the module manifest. I view that as superior to the module manifest that was invented in https://github.com/ScriptedAlchemy/webpack-external-import. Cc'ing @domenic in case he has interest in this as the author of the import maps spec.

^ I hope my comments do not come off as combative - that is not my intent. I'm a primary maintainer of SystemJS and before that I long wished for webpack to properly do module loading. I didn't realize that module loading is something that would be seriously considered by webpack, and am excited about the possibility of it landing. I'd even be willing to jump in and help on any of the PRs to help it happen, if that would be useful. My main point is that I think the in-browser module manifest definition should be something that is well-thought out, without pushing forward with a webpack-specific version that may not be as complete as import maps.

I tried to use language / API that was similar to existing concepts to make it easier to understand. The picture in my head is hard to translate into text. So referring to it as a mix of things that kind of had some aspects I was looking for seemed like an effective way to communicate the concept. Also preserving a familiar API would be easier for users to digest.

If the dependency is missing, you're not screwed, it'll recover - pulling it from one of the other Webpack bundles. It's not centralized, so if any federated build has the dependency - it could be made to supply it. There's built-in redundancy. In the case of externals, there's literally only one spot and you're chained to having that externals file with you. Code cant run truly standalone. Before starting to merge with Webpack, id built out a solution that would let federated code "ask" anything else on the page if it had a copy of the missing dep, so if code goes missing - it was pretty hard to actually break the application. Off the top of my head, when deploying new code, for example, I could resolve code via any other build that might have a copy. Basically getting other remotes to load their copies of dependencies on behalf of another remote. Supporting tree-shaking is also a biggie that I had established and we plan to re-introduce here. I've done experimental stuff like merging module exports from multiple origins. Can't vouch for its stability but it was pretty cool and never broke under my conditions.

There's no need to apologize at all! If anything, it's me who is sorry for coming off abrasive. I've faced a lot of criticism from others who didn't understand the intent or that I failed to communicate clearly enough. My module manifest in external-import is over complex lol - I don't even like its mapping method in v2, but it was effective. I do like not being tied to path-based identifiers but rather have the option to refer to code and share it based on being cryptographically the same. Something id like to re-introduce as a supplemental plugin to drive the new work going into the core. Not sure if import maps supports something like that? I was able to reduce multiple standalone apps down a minuscule size using that tactic. Regardless of where a module was located, if it was the same code - it was reused. It helped federate code between mono-repos and flat ones where paths would never match up even though the dependency is the same. I'm also facing challenges where I might have 50k+ modules. So i did like the idea of code split / dynamic manifest mapping

We will be maintaining this plugin and perhaps a less Webpack specific manifest could be introduced. Off the bat, it seems like a huge overhaul right now - stretching beyond the scope of this project/PR haha.

Soon as we fix bi-directional dependency sharing, the MVP would be in a position to test out with the full workflow. We ran out of time today due to timezones.

The one big question which maybe if Webpack were to incorporate a manifest style similar to import maps is: will it work in non-browser environments as well. I've gotten SSR to work with the current setup and don't want to compromise my ability to SSR federated modules.

Lets definitely keep in touch somehow (twitter?) It's really nice to meet others in this mind space!
If you'd want to lend a hand - we spun up a project board today: https://github.com/webpack/webpack/projects/6

There's not a whole lot left, one that's a little tricky will be establishing a pattern for nested federation.

I talked with Marais over Twitter DM and he was kind enough to walk me through what has been built for this. I have a lot better understanding of what webpack module federation is now. He clarified that webpack module federation does not include url resolution of bare specifiers, and also that it does not include actual fetching of the module. But rather that the main thing it provides is a way of sharing build-time modules between different runtime modules via the overrides() function.

My perspective is often more oriented towards in-browser modules, which I think solve many of the same problems as federated webpack modules (should React be downloaded, or do we already have it? Do we have its dependencies already downloaded?). I don't think that making everything an in-browser module is a viable/responsible choice from a performance perspective (even with HTTP2 push for sending down lots of modules at once), so alternative ways of handling shared dependencies is interesting.

I think if in-browser ES modules one day support a single network request registering multiple modules in the browser that that would be a preferable alternative to webpack module federation. However, no such spec for that exists (as far as I know).

To a lesser degree, I think that ES modules as they are right now also can be used to solve the same problem. If entry and code split bundles are all ES modules loaded via import(), you can save redundant network requests by maintaining a map of module names to already-downloaded URLs. If a shared module is already downloaded, call import(urlWeKnowIsAlreadyDownloaded), otherwise call import(urlSpecificToBundle).

However, both of those are fantasy since the first option doesn't exist at all, and webpack doesn't yet support libraryTarget module which would be needed for the second option. I think I do see some of the benefit of module federation, now.

I've gotten SSR to work with the current setup and don't want to compromise my ability to SSR federated modules.

Just curious, @ScriptedAlchemy do you have still any problems with SSR?

Does it work as a single app (as in browser) or you have several apps that render their parts separately?

Module Federation does also work with target: "node". Instead of URLs pointing to the other micro-frontends, here file paths are used which point to the other micro-frontends. That way you can do SSR with the same codebase and a different webpack config for building for node.js. The same properties stay true for Module Federation in node.js: e. g. Separate builds, Separate deploys

@vankop I tested out a new solution and it does indeed work. Because the target node works. It requires a slight infra change for me to support seamless ssr. But pretty much my remotes point to an s3 path and i stream the file request back to webpack. It requires code across different s3 buckets like it would do with remotes in the browser.

Its purely a matter of how you set up the file system, at least for this implementation. If your code is accessible from a shared volume, then I can require it on the fly. Im also considering other options like remote code execution, but prefer to avoid evaluating code over the network if I can require the same code from another s3 bucket.

If all your deploys push their code to a shared volume, then just like in the browser - youd be able to access them from another build

Another idea:

You could use a private package registry where each team pushes their deployed SSR bundles too.

For dev and prod each team fetches the other teams SSR packages from the package registry (either with npm install or some custom tooling).

Yeah I've done this before as well, it was irritating to always have to manage dependencies. However, you could also have a CI step be to yarn upgrade those SSR dependencies.

How would you keep them out of the bundle though? or are you saying literally npm install them and federate the code out of node modules, similar to how externals would have worked with node.js services.

Host importing remote. Remote import host Application shell and injecting its routes into the first apps layout component. Three apps in total. Remotes importing hosts that import remotes.

Heres a video for those following this thread: https://twitter.com/ScriptedAlchemy/status/1233857747411582976?s=20

Can you clarify something for me? Sorry for my ignorance:

When you say stand alone apps, do you mean they can be run on their own and/or in an environment where the apps share a dependency (eg React)?

If so, how does that work if app1 imports a component from app2 when app1 is running in an environment that doesn’t have app2 available (aka in a “stand alone” environment)?

Does app1 break? Is it a standalone app at that point? And in this case, how does this differ from using externals?

Thanks again, and sorry if I’m not quick enough to get this as quickly as others.

@frehner play with this. It's not the same as externals.
https://github.com/ScriptedAlchemy/mfe-webpack-demo

https://medium.com/p/bcdd30e02669 (Draft)

Yes, they are each app that start and run on their own. like CRA - its an app it runs like expected.
They attempt to run the host's dependency if shared, otherwise, it does an async import of its own version - from its original build. If the host isn't sharing react, the remote will download the copy of React bundled with it.

app2 is probably on the internet or network somewhere. If there's no app2 then why would it be imported? Obviously is app2 is not in existence, the app won't work. it would be like importing a npm dependency that was never installed.

If you're going to federate code, it should always be available from somewhere - even if you point it to production assets. Otherwise, i guess you have a conditional?

App1 would break if you're depending on app2. If you wanted to ensure app1 has its own copy, of non-evergreen code, then i suppose that could happen, by reverse overriding. But we have not catered for that. I expect remote code to be there, just like you expect 3rd party vendor code to be on a CDN.

If you're depending on a federated app to supply part of another app, then you may lose sections if that application were not found on the internet.

So in my case. Let's say I've got about 45 repos. Maybe only one or two repos are running locally, the rest of the federated code is coming from production. Maintaining my standalone ability. If that team deployed their app via npm as well, then i could consume it as a non-evergreen fallback.
The whole flow works, I'm just sharing code with production assets, not bundling my own copies of them.

In some aspects, it has traits like the pika registry where modules are fetched from a CDN.

Standalone to me means it turns on with one me needing to turn other things on. Externals mean my code cant run without it. Missing federated code means someone else's code cant run.

I suppose the override feature that allows dependencies could be applied to hosts themselves, and have the sharing process reversed. Allowing remotes to tell hosts to depend on their dependencies, if unavailable, it should load a stale copy from its own build.. if one was installed locally. But that circles back to the same challenge of managing deps across repos. Would work though

If i want to work on a sidebar component, i want my repo so startup. I don't want to develop in another repo consuming it. Hence its ability to run on its own.

Also, my use cases are extensive and at scale. 100 micro frontends with 20 different apps on the page at any given point, in any given order, in any given configuration. Externals are too inflexible, cant enforce dependency consistency that effectively or share other vendor code that's installed. Externals is one point of failure. In my case, the federated code failing will only impact parts of the app that consume it. If the service is down, then the service is down - either way, it won't respond.

If configured a suspense fallback could be a code split node module of an older version of the remote. you'd be able to install the latest on each deploy. I don't plan to install everything into everything else. If the code isn't on the internet then the app should fail.

@ScriptedAlchemy thanks for the demo and idea. We use to technics micro frontends and have planned to migrate this plugin. But we have many micro frontends and have the opportunity to add new micro frontends without release apps. Now if we start to use ModuleFederationPlugin, we will need to put config all our micro frontends in config webpack https://github.com/ScriptedAlchemy/mfe-webpack-demo/blob/master/packages/01-host/webpack.config.js#L40 . For our will be better if we use const Dialog = React.lazy(() => import("federation/dialog")); instead https://github.com/ScriptedAlchemy/mfe-webpack-demo/blob/master/packages/01-host/src/pages/page1.jsx#L3 and webpack automatic search remote dialog without config webpack

In this case, our pipeline:

  • Load remoteEntry of micro frontend dialog
  • Run import("federation/dialog"))
  • FederationPlugin load all deps
  • used dialog

@Tom910 I don't know is this feature on project dashboard already. i think it was planned..

Will be great to define "service discovery" manually as opt-in

Should this feature respect "target" as well? (I mean could discovery work differently for 'target: node' and 'target: web' )

Also how to solve problem with experiments? (e.g. A/B tests)

From my perspective will be great to define "runtime discovery".. Will be great to hear other thoughts.

@vankop I think that a remote may expose AppA and AppB and a host may choose between of them or a host may choose between the remotes dynamically

Should this feature respect "target" as well? (I mean could discovery work differently for 'target: node' and 'target: web' )

On server need load js files and run with Module and runInThisContext node.js API. Maybe will be better if webpack puts low-level API. And community plugins have already implemented functionality load files on server and browser

@smelukov

I think that a remote may expose AppA and AppB and a host may choose between of them or a > host may choose between the remotes dynamically

Yep this is possible. However, kind of customizing request aka remoteA.com/button.18ffa.js?ab=56 will provide more transparent architecture..

plus would be great to operate experiments without changing/rebuilding code..

Got next.js working in production mode. Few issues with the dev middleware

@ScriptedAlchemy would it be possible to do composition of components from different apps with federation?

Scenario:

  • AppA exposes A component
  • AppB consumes A and exposes B component (B contains A component)
  • AppC consumes B in C component

Result would be C(B(A)).

I presume we would need somehow to resolve remote entry for AppA from AppC.

@ScriptedAlchemy would it be possible to do composition of components from different apps with federation?

This is possible!

C needs to reference both A and B's remote entry files.

I have an example repo here: https://github.com/mizx/mfe-webpack-demo

If you run it locally, go to the "Routing" example, click the "Bar" tab, you will see a pink styled-component button that's doing exactly what you mentioned.

@vedrani that's completely possible.

Also wrote an article on module federation: https://medium.com/@ScriptedAlchemy/webpack-5-module-federation-a-game-changer-to-javascript-architecture-bcdd30e02669

Wrote something about this also! 🚀 👀

https://dev.to/marais/webpack-5-and-module-federation-4j1i

Also wrote an article on module federation: https://medium.com/@ScriptedAlchemy/webpack-5-module-federation-a-game-changer-to-javascript-architecture-bcdd30e02669

It's paywall gated.

Wrote something about this also! 🚀 👀

https://dev.to/marais/webpack-5-and-module-federation-4j1i

Thanks for sharing. I am looking forward to using this feature.

@rafde use incognito mode ;)

C needs to reference both A and B's remote entry files.

Could it be possible that C only references B, and A's entry point is resolved automatically?
I'm asking that because C doesn't see it has implicit dependency on A.

Also it could be important in situation if you have multiple teams working each on their own MFE app and consuming parts from other teams. They will probably somehow want to define version of remote they consume, not always relying on latest. Each app could bundle and expose itself under version, so not just name: A but maybe name: [email protected]. You would have multiple deployments for each version. For example app B would consume component from [email protected], while app C would consume components from app B and [email protected]. In that way app C has dependency on apps B, [email protected] and [email protected]. It should work but it will be difficult to define all implicit remote entries to different versions of A.

Thanks, I hope I explained it in hopefully understandable way.

Could it be possible that C only references B, and A's entry point is resolved automatically?
I'm asking that because C doesn't see it has implicit dependency on A.

Yes. It uses the webpack externals system, so every external support by webpack works as reference here. We often used var external type, which expect the remote as global variable. Here you would add the remote script as script tag and it will load in parallel to your application.

If you use e. g. amd as external type, references to remotes would be as dependency in the define array. A AMD loader would take care of loading these remotes. Here you would only need to reference the application script and remotes would be loaded automatically.

A configuration could look like this:

new ModuleFederationPlugin({
  name: "app3",
  library: { type: "amd" },
  filename: "[name].remote.js",
  remotes: {
    app2: "https://path.to/app2.remote",
  }
});

In this example app2 could reference app1 with a similar config. The HTML of app3 could look like that:

<script data-main="dist/app3" src="scripts/require.js"></script>

So the approach totally works, but it comes with a downside: Your application takes multiple serial requests to load. require.js -> app3 -> app2 -> app1 -> init. Compared to the previous mentions approach with script tags: app3 + app2 + app1 (parallel) -> init.

You can workaround these problems by using <link rel="preload"> tags (or even script tags), that works fine, but you are back to the original problem that app3 need to know that app1 is used. (But at least your app doesn't fail if the preload tag is missing, it only degrades performance).

There is also an additional script loaded: the AMD loader. In future you might be able to use native ESM instead of AMD.

Anyway your critic is valid and there are some plans for new external types that should improve this workflow:

  • jsonp: A script tag is added. The script calls a global callback function when executing.
  • import: The native import() function is used to load the module.
  • amd-require: The AMD require() function is used to load the file.
  • module: The native import statement is used to reference the module.

The types could have an additional benefit: The file is only loaded when used, which means if only a part of your application uses a remote, the remote is only loaded when this part of the application is used.

And all these types would allow app3 to not know that app1 is used.

From the existing types: amd and system would also allow that.

Hi Zack,

I really appreciate you work. It is really great to have federation on the frontend with webpack! I only have two questions / doubts regarding that:

  1. Since pulling in code is done at runtime how can i.e. App A be sure that the API it is pulling in is still compatible? There is no coupled release and no compile time checks for that!
  2. IDEs wont be able to cope with the imports since the files are actually missing!
  3. Especially with the script tag solution for importing I don't think it scales for > 100 dependencies(Components). It will not only bloat the actual HTML but also execute too many request for a browser to handle without severe delay. How about a solution that dynamically inserts those tags and loads the remote code on demand?

I hope you can clearify...

Regards,

Chris

It does. The script tag serves only as something you place on the page to interface with remote bundles. The rest is injects on its own like a normal code split app. All aspects have been designed to scale - I face challenges of over 100 repos. That manual script is basically a special runtime that allows the Host to work with other bundles. It’s a small file too.

Try out this demo, inspect its code.

https://github.com/mizx/mfe-webpack-demo

https://itnext.io/webpack-5-module-federation-a-game-changer-to-javascript-architecture-bcdd30e02669

@ChrisSG

Hello again,

with regards to 3: I am talking about statically having like 100+ script tags in the HTML head section. This would cause 100+ concurrent initial requests (besides other ones for the page itself). I guess this causes heavy stall...

What about 1. and 2. ? @ScriptedAlchemy

Regards,

Christian

Ahh so for 3. We are cooking up a solution to register those remotes on demand later in the app so dynamic importing those remote files as needed. Without 100 up front.

Regarding IDEs, perhaps a plugin could be created

IDE autocomplete is a trickier one. But I know we were looking into how this would work with TS.

The on demand register is possible, just we don’t have a internal abstraction to make it work out the box right now - but we shall.

Explainer Video by someone in the community!
https://www.youtube.com/watch?v=D3XYAx30CNc

@sokra @ScriptedAlchemy I know it's late in the game and PRs are already being merged, but can we have shared take regular expressions and a function as well, like include and exclude in other plugins and loaders do? Ideally I'd love to be able to easily start with all dependencies being shared, and then handle special cases as they happen. I think this would really be more in line with the original requirement:

I don't want to manage externals and worry about synchronizing them across systems

With an array of strings only, whenever I add a dependency to my ecosystem I'd have to worry about adding it to shared.

@ScriptedAlchemy I have a few questions regarding how you would design a site with this new method:

  1. How would you suggest sharing application state between all the SPA's? Obviously there are many ways to do this but it sounds like this allows you to share any js file between apps, so would you suggest somehow implementing shared state in a shared js file? Really I would be curious to see an example where one app puts something onto the react context and when transitioning to another app the context is preserved.

  2. When developing locally, will remotes have to be up and running in order to run the app? In other words I start up App A on localhost:3000 and it needs a component from App B. It seems like on my local environment I would have to run all apps that share components. I'm not saying this is a bad thing but it does require developers to have the entire distributed system running on their computer. OR they could technically point to there "Test" environment to get the shared modules.

  3. I had already asked you this on twitter but wanted to put it hear for clarity. When executing unit tests, the app will need to import the remote modules. I feel it will be important to come up with an elegant solution that allows the app to get those modules from maybe dev dependencies. Knowing of-course, that the downside to this is that all distributed apps will need to be deployed npm packages and installed as dev dependencies.

  4. This works for shared JS files but what about shared css files? ( Assuming CSS is not bundled with the js components )

In the end love what you are doing here! This seems like a good path thus far.

What I'm trying to address here is a statement that you made in your federated article where you stated:

"It’s important to note that this system is designed so that each completely standalone build/app can be in its own repository, deployed independently, and run as its own independent SPA."

This is true however you really still rely on the other apps being "Alive". Again not saying this is an issue! Just trying to get an understanding of the best practices. Im assuming we can look to federated graphql as a template.

@MilosRasic Yes you are right, but a function could be created to automate this. I've already got it listed as an after release plugin to create. AutomaticVendorFederationPlugin - for the stability of webpacks wide, wide use cases - we descoped this work from the core and into a plugin that will be maintained in my module-federation org on github.

Totally understand where you are coming from and I've already thought of workaround helper functions that could hydrate shared for you - its not as slick but manageable, until i write the feature. Remember this is foundational work we put in the webpack core. Without this PR i can't do those things. I proposed doing them all in one go inside the core, but Tobias suggested not to. It also allows us to maintain these more automated features without having to release a new copy of webpack each time. Don't worry. Ive also considered versioning and such for more dynamic sharing. webpack-external-import@v2 did the whole automatically share everything.

@joepuzzo Yeah it is, i did this with react router which depends on shared context across them, ive also done it with redux. The key is that react is shared preserving the Symbols and redistributing them.

My plan for local dev would be to just point to production. Similar to how youd use minikube, have the ingress only start services you care about and point the rest to some cloud hosted location.

Unit tests: yep dev deps would work, otherwise, if you have a copy on local disk, the federation in common js allows you to walk across repos.

This works for sharing anything webpack knows how to process, tried and tested :) images, css, js, the works. Not sure about WASM - haven't tried it.

So my wording isn't great (i really need to make a video lol) but completely standalone - i mean that i can use something as a widget, an SPA, or electron app without depending on externals for my own feature to work. Think of how externals with react are, unless you have a commons.js bundle in your electron app or something, it cant run.

You've made a good point about what happens when the remote isn't alive. I've not thought too deeply about it, but i do have solutions. You could combine this with the old npm publish flow and it would essentially act as a fallback in suspense or something, if the remote fails, load the host's copy which isnt evergreen but still something to fall back on.

The mechanics for this, you could create them as overridable by using one of the three plugin we build under ModuleFederationPlugin. Ultimately, id like to introduce the same fallback capability "shared" has to "remote" - if you have a copy installed, the host will fallback to that.

I appreciate the engaging feedback - my ability to communicate over text might fail me here. But the core of what i am saying is that these concerns do have solutions and most could be done without depending on us to write another webpack plugin. That said, we will, of course, be extending functionality via third-party plugins we create and host elsewhere on GitHub.

The main issue I was tackling initially was around sharing code and the ability to share vendor code (where most the app bloat comes from) so fallbacks made perfect sense - a host might not have a dependency you use, so the ability for the remote to fetch its own, only if needed was a big must.

I think, what's really cool about all this is the fact that these are the discussions we are now able to entertain. Prior to its creation, i don't think this is something that would become a discussion topic because the basic abilities to do module federation at scale wasn't extremely seamless to achieve

@ScriptedAlchemy Thanks for the feedback. Much appreciated!

  1. Cool glad sharing state is achievable!

  2. Yeah so thats what I thought. Much like developing micro-service architectures in the backend, you point local to "Test" environments OR even run a full docker version via a docker compose.

  3. I would love to see an example of how "federation in common js allows you to walk across repos"

  4. Awesome! This could potentially be very powerful for sharing parts of stylesheets rather than pulling them from CDN's. I need to ponder this more 🤔

Also Totally Agree! Love that we can talk about this now!

We are working on SSR federation, but more important (to me) is getting next.js running with hmr.
While sharing styles is useful, sharing components and functions is the powerhouse for me. my styles are attached to components.

Thrilled you’re interested in what we are doing, itll have some cool impacts on JS

Sorry for all the questions:

I was originally under the impression that when navigating from one app to the other there was somehow a benefit where the apps wound not have to download all the dependencies if they were already requested by the browser. https://twitter.com/i/status/1234383702433468416 However It looks like when you navigate there are a bunch of network requests made either way. In other words I see a request made forhttp://localhost:3001/vendors-node_modules_react-dom_index_js. twice.

Can you just clarify where the cost savings are with the shared array?

Pull down a demo and check it out. Likely demoing the fallback capabilities. Also all my demos treat everything as bi-directional hosts. So anything shared is also overridable when a remote. As such, hosts chunk out their own shared dependencies in the event they are being consumed by another host. This is customizable if you use containerPlugin or containerReferencePlugin. 3001, is the host version of react dom. But 3001 can also be a remote - so react dom isn’t included in the main bundle. At least with the config I used in the demo

I pulled down and was using this demo https://github.com/ScriptedAlchemy/mfe-webpack-demo. Ok so it sounds like it can be configured in such a way such that react and react-dom are shared? I guess i'm confused because I thought thats what the shared: ['react', 'react-dom'] did in the webpack config. When I was "Pay close attention to the network tab. " I saw the beauty of the two bidirectional sites grabbing each others components. But React, and react dom were still requested again. I think this was just a misunderstanding of what I thought "shared" meant. Thanks again.

It is shared, its just that react and react-dom is not in main.bundle.js they are essentially giong thought the same process as splitChunks would.

So they are sharing code, its just a case of react is in its own chunk because tha host might be a remote (website 2 loads parts of website 1, so in that case, i want website 2's react to be use over website 1 - as its now the host)

So yes they can be shared as you expect them to be. Check this one out. https://github.com/module-federation/module-federation-examples

  • Since pulling in code is done at runtime how can i.e. App A be sure that the API it is pulling in is still compatible? There is no coupled release and no compile time checks for that!
  • IDEs wont be able to cope with the imports since the files are actually missing!

@ChrisSG You can publish a typings-only package for each MFE. This also helps to not break the API. You could embed a major version in the url to release a new major release next to the old version, but after all this have to managed manually.

3. Especially with the script tag solution for importing I don't think it scales for > 100 dependencies(Components). It will not only bloat the actual HTML but also execute too many request for a browser to handle without severe delay. How about a solution that dynamically inserts those tags and loads the remote code on demand?

@ChrisSG The scale of hundreds request should be fine with HTTP2. If you are in the scale of > 1000, you probably have issues. Here the solution would be to merge multiple requests. In your example you would put multiple components into a single chunk. Probably these one that are often requested together. webpack has a few plugins for that: splitChunks, MinChunkSizePlugin, MaxChunkCountPlugin.

I know it's late in the game and PRs are already being merged, but can we have shared take regular expressions and a function as well, like include and exclude in other plugins and loaders do?

@MilosRasic It's not too late. All this is still very experimental and we are open to changes. It's not even yet published to npm.

You can do something like shared: Object.keys(require("package.json").dependencies), if you want to share all your dependencies. This doesn't share transitive dependencies, but it's a good start.

In my opinion this is not a way to go. Each shared module has a cost (requests, worse compression, no tree shaking, no scope hoisting) and you should selectively choose which modules should be shared. If you share a module which is not even used by any remote, you would pay the cost for nothing. It's also important that shared modules need to be listed in the host and the remote build.

So the approach I would recommend is to start with the obvious shared modules, then measure and check which modules would benefit from being shared. Communicate and discuss this with the other MFE teams.

In some cases you may even benefit from adding shared modules which are not your own dependencies. e. g. if multiple remotes use the same library but the host build doesn't use it. In this case it makes sense to add this library as dependency to the host build and add is as shared module for all builds. Note that it's only loaded at runtime when requested from one of the remotes. But only once for all remotes.

AutomaticVendorFederationPlugin
I proposed doing them all in one go inside the core, but Tobias suggested not to.

There are probably way to automate this process, but they have additional infrastructure requirements (out-of-band communication between builds) and maybe even go into the direction of maschine learning.

Anyway this didn't fit into the webpack way of thinking, which is about control, determinism and infrastructure-independence. The ModuleFederationPlugin was also (re)designed with these concepts in mind. e. g. shared modules: The host is in control of which shared modules are used by remotes. Which modules are loaded is deterministic (indepent of order of remote loading, remotes do not shared module between each other, only with the host).

  1. How would you suggest sharing application state between all the SPA's?

@joepuzzo The runtime behavior of remote modules is equal to local modules. They have no restrictions in what they can to. You can use all existing methods here.

2. When developing locally, will remotes have to be up and running in order to run the app? In other words I start up App A on localhost:3000 and it needs a component from App B. It seems like on my local environment I would have to run all apps that share components.

I would recommend to reference all other remote MFE from the production environment. This way you only have to spin up local dev for your MFE. It also ensure that your are compatible with the deploy version. If you develop against a dev version of a remote MFE you make get deploy dependencies (which mean you need to deploy in a certain order) and can be problematic (it may break your app if you deploy in incorrect order or have circular deploy dependencies).

3. When executing unit tests, the app will need to import the remote modules.

Same as above. I would not install remotes as npm packages. You may use module federation with node.js as target. You may test all host builds before deploying a remote to ensure no other MFE breaks because of the release.

4. This works for shared JS files but what about shared css files? ( Assuming CSS is not bundled with the js components )

It's not restricted to JS modules. Any module in webpack should work. e. g. CSS, WASM, Assets. output.publicPath need to be setup correctly, maybe using an absolute URL if MFEs are deployed to different servers.

Maybe a bit more thought need to be put into CSS as class names may collide.

still rely on the other apps being "Alive".

The Promise retured by import() will reject and you can handle this. e. g. in react with error boundaries.

If using import "remote/module" the next import() higher in the graph will reject.

If using CommonJs require(), it will throw an error which can the catched (not implemented yet).

load the host's copy which isnt evergreen but still something to fall back on.

await import("remote/module").catch(() => import("./fallback"))

In other words I see a request made forhttp://localhost:3001/vendors-node_modules_react-dom_index_js. twice.

You see requests to
http://localhost:3001/vendors-node_modules_react-dom_index_js and
http://localhost:3000/vendors-node_modules_react-dom_index_js.

All shared modules are always served from the host build. These files may even be different because of different react-dom versions.

One approach could be to deploy all assets in a common directory and use content hashes for all files. This would deduplicate these requests. But this could also be considered as risk (In theory it's safe, but do you trust webpack) and people may not want to do that.

I think some of these aspects could be offered as extensions. One being (maybe) to choose a host to provide something like react to the rest of the app. If you turkey want to force sharing one one dependency on any system. But that starts sounding like externals. However you could also have an app shell which would always be the host if need be, and the app shell is consistent across other MFEs so it’s always got a consistent host. Defiantly some supplemental plugins we could create.

I know tree shaking is on the backlog at the moment. I need to stabilize next.js implementation before turning back to webpack core work. But I’m almost done with the upgrades.

Of course. We are open to change and ideas! This is a functional MVP. @sokra ill DM you next week once I have some bandwidth - I had some ideas I wanted to talk to you about based on community responses

I noticed that in all of the examples there is a boostrap.js file. Why cant that live in the index.js file?

Some thoughts. Feel free to correct me anywhere I made a false assumption or misunderstood anything:

1)

A configuration could look like this:

new ModuleFederationPlugin({
 name: "app3",
 library: { type: "amd" },
 filename: "[name].remote.js",
 remotes: {
   app2: "https://path.to/app2.remote",
 }
});

I like the idea of using a url to determine the remote. I think it would be even better if you could _call a function at runtime_ that gives you that url! In this way, you can always ensure that app2 is pointing the latest build, even if the last time that app3 (the current app) was built was months ago.

For example, in the single-spa / systemjs community we use something like the import map deployer that allows us to embed (or script tag) a mapping of app names to urls, e.g.

{"app2": "my.cdn.com/app2/{buildhash}/remoteEntry.js"}

and each app's url is updated on every deploy. So that the config for app3 could look like

new ModuleFederationPlugin({
  name: "app3",
  library: { type: "amd" },
  filename: "[name].remote.js",
  remotes: {
    app2: "getUrlFromAppName('app2')",
  }
});

This would also enable you to have all your webpack modules just using the production version, and you could set an override for the one module you're working on to point to localhost. That way all the webpack modules, when they call that getUrlFromAppName() function look at localhost for the one you're working on, and prod for everything else. A great DX :)

(I guess at that point we would essentially be leveraging import maps to help resolve the different webpack modules, and then just using module federation to have those modules share dependencies. Conceivably you could exchange getUrlFromAppName() with System.resolve())

Along those lines, perhaps we could also call a function that _returns a promise that resolves with the module_ (or executes it in the case of a global var)? E.g. remotes: {app2: "System.import('app2')"}

Seems like it would be a nice way to leverage runtime capabilities AND federated modules.

2)

Speaking about the exposes part of the plugin, would there be any advantage/disadvantage to having the option to point it to a exposes.js file that just uses the standard export syntax? It seems to me like it would be more familiar to devs to use that syntax in a normal JS file than have to update a webpack plugin in order to "export" a function/variable. Just a thought, though.

I think that's about it for the moment :) I'll keep playing around and giving feedback as I learn and understand more.

[edit]

@joeldenning has a good example repo here of how module federation could interact with SystemJS. This is great, and it works without any additional modifications/changes! However, the problem is that the remoteEntry.js chunks aren't actually lazy loaded in this manner, because of the way that webpack compiles to type:system: it puts the other apps' remoteEntry files as dependencies (System.register(['app_three', 'app_two'], ...) which means that SystemJS has to download those remote chunks before the main app can execute, _even if they're not currently needed_.

So I think my proposal in 1) still stands - I believe it would provide a way for truly lazy loaded remoteEntrys.

How to get typescript types when I consume a remote module in development ?

Can we achieve fully dynamic loading?
For example, I want to render an <input /> and user can type in the name of a micro-service. And I load that micro-service and render it on screen.

As the nested example show, the top app need to list all its transitive dependency. This is not scalable, IMO.
app1 only use app2 in its code, but app1 need to list app3 in its config, because it is depended by app2. app1 have to load its entry and declare it as remote!
The app3 should be abstracted by app2, and app1 should not care about it!

Comes down to Implementation details, also that’s not the case. Af far as I’m aware, this is already possible. We just didn’t make a example. Our examples just happen to demo this. If a nested is compiled then it already has its own remote modules created. Some refinements could be done, it most likely examples need to be shown that demonstrate this. The only thing required would be the access to the remote and a way to know how to implement it. We already have ideas on automated loading of remotes on demand if they are not already on the page the software does a lot more than demonstrated. I agree that explicit setting of remotes and shared is not as scalable. But that can be automated or written as an additional plugin. Some features and requests don’t need to be in the core, just extra hooks or plugins. Same way we plan to support versioned shared modules automatically. How it’s used is different from the fact that it’s possible to use at all. Webpacks responsibility is to provide a explicit system. It is in general a explicit only system. Implicit features can be additional extras. Less complexity to the core but achieves the same outcome.

Your points are valid - but have already been thought about and have solutions. This is also a MVP and we have not documented it in detail. If it won’t do it out the box closer to release time - it will be trivial to enhance.

My plan is to use the underlaying system at scale, so obviously scalability challenges in the base system will be solved. Regardless of what repo they end up in. Also we wrote this in less than 20 days, so there’s still enhancements we will be making. Both in webpack core and out

Can we achieve fully dynamic loading?

For example, I want to render an <input /> and user can type in the name of a micro-service. And I load that micro-service and render it on screen.

Yes you can. I’m currently doing so. There is one limitation, you’d have to have the remote scope specified in the plugin.

I’m working on an idea for an additional plugin that enables dynamic definition of remotes at runtime. Webpack prefers explicit over implicit. So dynamic remotes at runtime will be a plugin that hooks into RemoteModule and dependency factories

How to get typescript types when I consume a remote module in development ?

Might need a resolver but I believe some in the community have already achieved this. I’m not a big TS user so not sure on details

Some thoughts. Feel free to correct me anywhere I made a false assumption or misunderstood anything:

1)

A configuration could look like this:

```js

new ModuleFederationPlugin({

name: "app3",

library: { type: "amd" },

filename: "[name].remote.js",

remotes: {

app2: "https://path.to/app2.remote",

}

});

```

I like the idea of using a url to determine the remote. I think it would be even better if you could _call a function at runtime_ that gives you that url! In this way, you can always ensure that app2 is pointing the latest build, even if the last time that app3 (the current app) was built was months ago.

For example, in the single-spa / systemjs community we use something like the import map deployer that allows us to embed (or script tag) a mapping of app names to urls, e.g.

{"app2": "my.cdn.com/app2/{buildhash}/remoteEntry.js"}

and each app's url is updated on every deploy. So that the config for app3 could look like

new ModuleFederationPlugin({

  name: "app3",

  library: { type: "amd" },

  filename: "[name].remote.js",

  remotes: {

    app2: "getUrlFromAppName('app2')",

  }

});

This would also enable you to have all your webpack modules just using the production version, and you could set an override for the one module you're working on to point to localhost. That way all the webpack modules, when they call that getUrlFromAppName() function look at localhost for the one you're working on, and prod for everything else. A great DX :)

(I guess at that point we would essentially be leveraging import maps to help resolve the different webpack modules, and then just using module federation to have those modules share dependencies. Conceivably you could exchange getUrlFromAppName() with System.resolve())

Along those lines, perhaps we could also call a function that _returns a promise that resolves with the module_ (or executes it in the case of a global var)? E.g. remotes: {app2: "System.import('app2')"}

Seems like it would be a nice way to leverage runtime capabilities AND federated modules.

2)

Speaking about the exposes part of the plugin, would there be any advantage/disadvantage to having the option to point it to a exposes.js file that just uses the standard export syntax? It seems to me like it would be more familiar to devs to use that syntax in a normal JS file than have to update a webpack plugin in order to "export" a function/variable. Just a thought, though.

I think that's about it for the moment :) I'll keep playing around and giving feedback as I learn and understand more.

[edit]

@joeldenning has a good example repo here of how module federation could interact with SystemJS. This is great, and it works without any additional modifications/changes! However, the problem is that the remoteEntry.js chunks aren't actually lazy loaded in this manner, because of the way that webpack compiles to type:system: it puts the other apps' remoteEntry files as dependencies (System.register(['app_three', 'app_two'], ...) which means that SystemJS has to download those remote chunks before the main app can execute, _even if they're not currently needed_.

So I think my proposal in 1) still stands - I believe it would provide a way for truly lazy loaded remoteEntrys.

Something is currently being proposed by another developer group to solve some of this. I’m reviewing the PR at the moment.

I noticed that in all of the examples there is a boostrap.js file. Why cant that live in the index.js file?

Early mvp stuff, we use a promise to hoist shared code - preventing webpack from running code before it verifies if it needs its shared dependencies or if they are already on the page. Likely will internalize this closer to release.

Promise ensures that the app has what it needs to run, remote or host - shared goes through the same RemoteModule or OverrideModule pattern which needs to resolve either immediately or after it gets missing shared code (either from its own build or from a host depending on how it’s running)

For those asking about multiple versions and shared vendor code.
image

For those wondering about dynamic remotes at runtime. Less elegant but works:
image

And about dynamic import, must we have on the base webpack config an import of each remote or you have a way to dynamically import remote at runtime ?

It's for a react plugin architecture, you can add a plugin and remove it at runtime, and the core is builded, so I can't have a list of each remote.

Have read the discussion above and have few questions:

  • What if the main app doesn't have some shared deps that also configured as external in the sub app?
  • As using redux in sub app, will the store got fully imported or just the part that remote component is using?

For what it's worth, I've also had the need to customise publicPath and opened a PR for discussion https://github.com/ScriptedAlchemy/webpack/pull/4

I have a chunk file dependency issue when doing Micro Frontend.

host1:

  1. fetch asset-manifest.json from host2.
  2. load the main.js script
  3. failed to load 3.f5640802.chunk.js, due to it is looking for this file in host 1 instead of host2. :(

host2:
asset-manifest.json

    "main.js": "./static/js/main.43f8372f.js",
    "main.js.map": "./static/js/main.43f8372f.js.map",
    "static/js/3.f5640802.chunk.js": "./static/js/3.f5640802.chunk.js",
    "static/js/3.f5640802.chunk.js.map": "./static/js/3.f5640802.chunk.js.map",

I've overwrite webpack config generated by react-scripts:

  config.optimization.runtimeChunk = false;
  config.optimization.splitChunks = {
    cacheGroups: {
      default: false,
    },
  };

Any idea how webpack can identify the other static file hosted in server 2?

Hello,
Is it possible to use this plugin for web components ?

I mean such structure:

  1. HeaderApplication - used React, exported as custom element;
  2. FooterApplication - used React, exported as custom element;
  3. MainApplication - user VanillaJS, imports HeaderApplication and FooterApplication;
  4. Store - used VanillaJS, just exposes istself;

now, i would like to have Store inside HeaderApplication, FooterApplication and MainApplication. in this case, is the implementation rules the same as you provided here ?

@brendonco you probably dont have an absolute URL set in publicPath.

Also for those. wondering about totally dynamic remotes or shared code at runtime (also enabling webpack 4 consumers to accept federated code)

Not pretty, but works :)

image

@alecoder if webpack already works with web components, then it will work with module federation. Anything webpack can do today, can be done with federation

Pull request opened for setting publicPath at runtime (for remotes) #10703

Can we achieve fully dynamic loading?
For example, I want to render an <input /> and user can type in the name of a micro-service. And I load that micro-service and render it on screen.

@csr632 check my twitter, I've shown options for fully dynamic workarounds.

@ScriptedAlchemy You are right. it works for one entry point. But what if I have multiple entry points due to code splitting + runtime in host2. I'm only downloading static/js/main.ecd0088c.chunk.js but it stop downloading the rest. Should I download static/js/runtime~main.3e58c92b.js instead and the rest it will download e.g. vendor + main?

webpack config:

config.optimization.runtimeChunk = {
    name: entrypoint => `runtime~${entrypoint.name}`
  };
  config.optimization = {
    splitChunks: {
      cacheGroups: {
        // default: false,
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
          maxInitialRequests: 20, // for HTTP2
          maxAsyncRequests: 20, // for HTTP2
        },
        runtime: {
          enforce: true,
        },
      },
    }
  };

entry points:

0: "static/js/runtime~main.3e58c92b.js"
1: "static/js/vendors.93924992.chunk.js"
2: "static/js/main.ecd0088c.chunk.js"

Even though I tried to load all the entry points, I got exceptions TypeError: c["render".concat(...)] is not a function at HTMLScriptElement.e

[
  'runtime~main.3e58c92b.js',
  ' vendors.93924992.chunk.js',
  'main.ecd0088c.chunk.js'
].forEach(function(src) {
  var script = document.createElement('script');
  script.src = src;
  script.async = false;
  document.head.appendChild(script);
});

@brendonco I think right now you need to delete config.optimization.runtimeChunk for this to work.

This caused me an error, too, which I demoed here.

@ScriptedAlchemy told me:

RuntimeChunk cannot be single. Because module federation creates a specialized runtime chunk for remoteEntry.

In the future, we want to force it to still emit the runtime chunk even if you set it to single. The problem is that remoteEntry has a very different initialization process.

I agree that it would be good if emitting the runtime chunk was forced in the case of remote entry points, since the optimization I posted is extremely common (it's in Create React App) and the errors you get from using it are opaque.

@sebinsua yes, I've disabled the runtime chunk. I still seeing huge vendor files bloated without splitting the vendor file. Any suggestion on how to reduce vendor file size less than 1mb? Currently vendor size is ~3mb :(

Warning: tree-shaking is not yet enabled on shared. I’d need to see ur webpack config to isolate the likely candidate

@sokra @evilebottnawi on this subject, where’s a point of reference to enable tree shaking. Is it just a case of adding beta to the module with used exports?

Tree Shaking won't work on shared modules, as the build doesn't know which exports the remotes are using of the shared module. It would work if they were only overridable, but not shared. Maybe we should make a difference here.

Huge vendor sizes usually causes by inefficient splitChunks config with name: "vendor". Do not provide a constant name to allow webpack to create multiple vendor files. Otherwise it has no choice than to load all your vendors upfront, instead of only the vendors that are used.

@sokra how do I set to not provide a constant name?

@sokra if I disable the split chunk. the main.js file size became huge.

  config.optimization.runtimeChunk = false;
  config.optimization.splitChunks = {
    cacheGroups: {
      default: false,
    },
  };

image

Dont disable cache groups altogether, just don't give them a name.

optimization: {
    splitChunks: {
      chunks: 'all',
      maxInitialRequests: Infinity,
      minSize: 0,
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: false
        },
      },
    },
  },

@ScriptedAlchemy exciting stuff. short question. Am I going to be able to use something like scriptjs to async load a remote?

Yep easily. https://github.com/module-federation/module-federation-examples/blob/master/dynamic-system-host/app1/src/App.js

@airtonix - i highly recommend looking over this: https://github.com/module-federation/module-federation-examples

And probably checking my youtube channel where I go into various details that cover pretty much any question that's been posted here... https://www.youtube.com/playlist?list=PLWSiF9YHHK-DqsFHGYbeAMwbd9xcZbEWJ

chunks: 'all',
      maxInitialRequests: Infinity,
      minSize: 0,
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: false
        },
      },

Thanks @ScriptedAlchemy. If I set it this way, I will have multiple entries in asset-manifest.json file.

"entrypoints": [
    "static/js/vendor~main.b6047fbe.chunk.js",
    "static/js/main.0c6e87fd.js"
  ]

I tried to load all the entry points, and got exceptions TypeError: c["render".concat(...)] is not a function at HTMLScriptElement.e. Any idea?

Script loader:

[
  'vendor~main.b6047fbe.chunk.js',
  'main.0c6e87fd.js'
].forEach(function(src) {
  var script = document.createElement('script');
  script.src = src;
  script.async = false;
  document.head.appendChild(script);
});

I’d need to see a repo, I’m not using any asset manifest.json files to do any of this. You sure you’re implementing this correctly? Watch the YouTube videos - will probably answer your questions

@ScriptedAlchemy thank you for the awesome work, we found it at the right time, we are taking this approach to production in the belief that it will be merged with base webpack 5. Because no one will say no this this so many advantage this federation brings to the table.

@ScriptedAlchemy Cool stuff cant wait to try it out. can we merge webpack#dev-1 branch to beta 16?

https://github.com/module-federation/module-federation-examples/blob/master/dynamic-system-host/app1/src/App.js

If I have some json configuration that I want to pass down from container to app2, can I do that? How?

We still want to solve need for import() bootstrap. Causes lots of confusion so we likely will hold of on merging till @sokra can resolve it inside webpack

@ScriptedAlchemy I tried to run this sample. https://github.com/module-federation/module-federation-examples/blob/master/dynamic-system-host/app1/src/App.js and app2

I can see that app1 try to load remoteEntry2.js when I click the button Load App2 Widget, but I got an error that Failed to load dynamic script. Hmmm, didnt change anything though. I used npm run start instead of npm run serve.

well it's possible, i didn't write that demo and haven't actually used it lol

@yordis perhaps you know if / why its not working

You can also do the same thing with loadjs. but that remoteEntry has to be loaded before attempting to call federated code,

@ScriptedAlchemy Cool stuff cant wait to try it out. can we merge webpack#dev-1 branch to beta 16?

https://github.com/module-federation/module-federation-examples/blob/master/dynamic-system-host/app1/src/App.js

If I have some json configuration that I want to pass down from container to app2, can I do that? How?

Speak to @yordis - he's already doing this

@brendonco can we move the conversation to another platform so we don't use this thread to troubleshoot, I just downloaded the repo and ran it, and everything is working in my side, so we need to investigate why wouldn't be working for you.

https://twitter.com/alchemist_ubi

@yordis PR the repo with the fix too

@yordis sorry, sure lets discuss it on twitter. I'll ping you the details.

@yordis, I can fix the error handling issue if you want.

@vankop @ScriptedAlchemy I am working on backport this awesome stuff to webpack4 as a Plugin: https://github.com/alibaba/module-federation4

_@ScriptedAlchemy are federated module names capable of having an org / alias prefix like @org/app_name?_

Say @org/app_1 wants to import from @org/app_2: import Stuff from '@org/app_2';. Everywhere you see @org/app_2 below, I've tried replacing with variations of app_2 but it does not resolve:

// app_2 webpack config
new ModuleFederationPlugin({
    name: '@org/app_2',
    library: { type: 'var', '@org/app_2' },
    exposes: {  '@org/app_2': './src/index' },
    filename: 'remoteEntry.js',
})

//  app_1 webpack config
new ModuleFederationPlugin({
    name: '@org/app_1,
    library: { type: 'var', '@org/app_1' },
    remotes: {  '@org/app_2': '@org/app_2' },
    filename: 'remoteEntry.js',
})

Part of the problem seems to be that the name key on the plugin config has to be a valid JS variable declaration, becausename is injected into remoteEntry.js:

var @org/app_2 = // Uncaught SyntaxError: Invalid or unexpected token
/******/ (() => { // webpackBootstrap
...

Yeah it wont work like that. You could go this[@org/app2] or window[‘org/app2’]

I ran into this the other day, streaming code across s3 with module federation

Thanks @ScriptedAlchemy this works: library: { type: "window", name: "@org/app_1" }, 😄

Separate--slightly related question to importing: _have index remotes been considered_?

Something like: import Stuff from '@org/app_1';? In the example repo https://github.com/module-federation/module-federation-examples every import has some suffix after the remote name: import Stuff from '@org/app_1/stuff';.

@ScriptedAlchemy
The need for a fallback feature when writing a static import to resolve dependency on a remote module.

Module Federation is a great solution.
I'm considering using it in my project as well.

I'm writing a static import statement and trying to load a module that is remotely located.

import HeaderComponent from "remoteApp/Header"

However, if a remote module fails to load (due to a failure on the remote side, etc.), the module of the host whose import statement is described will also fail to execute in a chain (and the module that depends on that module...).

This means that a partial failure can destroy the entire application, which is a major reliability issue for the application.
Unless this is resolved, I may be forced to write it all in a dynamic IMPORT statement. However, it is not desirable because it requires a Promise resolution.
Are there any valid options for dealing with this problem?

If not, I have a suggestion for additional specifications.
Fallback Module.
Due to the syntax of the static import statement, it is not possible to handle errors when a module fails to load.
So, I'd like to add a function that allows the host to pre-designate a module to be read as a replacement when the module fails to load.
With this feature, I can, for example, show the React component that I have prepared in case of an error instead of the remote React component.

I hope you'll consider it.

Thanks @ScriptedAlchemy this works: library: { type: "window", name: "@org/app_1" }, 😄

Separate--slightly related question to importing: _have index remotes been considered_?

Something like: import Stuff from '@org/app_1';? In the example repo https://github.com/module-federation/module-federation-examples every import has some suffix after the remote name: import Stuff from '@org/app_1/stuff';.

I think it works, if node resolution already resolves stuff like that. So you could just point it at a folder and it will get the index.js file

@ScriptedAlchemy

The need for a fallback feature when writing a static import to resolve dependency on a remote module.

Module Federation is a great solution.

I'm considering using it in my project as well.

I'm writing a static import statement and trying to load a module that is remotely located.

import HeaderComponent from "remoteApp/Header"

However, if a remote module fails to load (due to a failure on the remote side, etc.), the module of the host whose import statement is described will also fail to execute in a chain (and the module that depends on that module...).

This means that a partial failure can destroy the entire application, which is a major reliability issue for the application.

Unless this is resolved, I may be forced to write it all in a dynamic IMPORT statement. However, it is not desirable because it requires a Promise resolution.

Are there any valid options for dealing with this problem?

If not, I have a suggestion for additional specifications.

Fallback Module.

Due to the syntax of the static import statement, it is not possible to handle errors when a module fails to load.

So, I'd like to add a function that allows the host to pre-designate a module to be read as a replacement when the module fails to load.

With this feature, I can, for example, show the React component that I have prepared in case of an error instead of the remote React component.

I hope you'll consider it.

That’s an implementation detail. You can do this today. If an import fails, catch it and dynamic import the npm installed fallback.

I’ve done this with suspense fallback, or utility functions to get a slightly stale npm installed version of core files. It’s a promise, if it’s rejected import something else.

Additional plugin could be written but likely out of scope of core code. Webpack doesn’t provide fallback options for code splitting.

I agree that an extension should be written to offer a option like that. Technically you could use the shared option and do this today. The remote would override host modules. If the remote fails - the host is defaulting to its own copy and only working remotes can override the host with evergreen versions.

Its important to separate what belongs in the core compiler and whats an implementation detail. Webpack makes it work, but users can break their own builds or apps and webapck, in general, does not mitigate against faulty implementations. The compiler does the basic tasks of making something run, it does not account for infrastructure details or errors - like a missing remote. However, as with suspense callbacks or try-catch on a dynamic import - its left to the users to decide how they should handle a code failure. It could be vanilla code in your app, an additional webpack plugin or loader, or different infrastructure. However its not the responsibility of the core compiler to recover from errors.

The more features we add to the core - the higher the risk of error. Ideally, we keep webpack "small" and use or create additional extensions that can be iterated on without risking stability to webpack, or providing a larger maintenance surface area for the maintainers.

I'm not arguing that it's not a really useful feature to expose in an easier manner - I'm only saying its not necessarily something that belongs in this Github Repo

Around 15 min into this video I go over the concepts when I was looking for a workaround to some other problem. https://youtu.be/-LNcpralkjM

@ScriptedAlchemy if the react version for each app is different, how does this shared: ["react", "react-dom"] works? Does it collides with versioning? e.g. app1 (entry point) react version 16.13.0, app2 react version 16.13.1.

FB7A61FB-DDA4-468E-9D33-3B6CC95D73E1

@vankop @ScriptedAlchemy I am working on backport this awesome stuff to webpack4 as a Plugin: https://github.com/alibaba/module-federation4

I’ve got a proprietary backport. Would be willing to review and possibly collaborate a little on a WP4 backport. Saves me time having to upgrade multiple OSS projects to leverage this capability

Thanks for being so responsive @ScriptedAlchemy I know you have a lot on your plate!

Regarding the exposes key in the plugin, we are trying to expose a common lib of sorts which about 10 apps access over 1000 times.

The problem is many of the apps will import from our common lib differently. Here are just a few simple examples out of 100 variations (_some utils are nested more than four levels deep_) :

import { utils } from '@org/common';
import { camelCaseKeys } from '@org/common/utils';
import { camelCaseKeys } from '@org/common/utils/traverse-keys.js';

Is the best approach for this to expose everything (_over 500 utils & files_) in the exposes key? Are there negative performance ramifications to this approach?

exposes: {
    'common': "./common/src/index",
    'common/utils': "./common/src/utils/index",
    'common/utils/traverse-keys': "./common/src/utils/traverse-keys.js",
    ...
},

If we don't expose everything, we will get an error like: Error: Module common/utils/traverse-keys does not exist in container.

Ahh i understand the ask, yeah ive had to do this for a components folder in a monolith.

a few things you could do.
1) have an index.js file and force people to use the destructure pattern.
2) create a tool that walks the file system and creates all the exposed paths

Problems with this approach - remoteEntry gets a little bigger, more keys, more code.

An alternative solution - use shared and you could make the host override itself with the shared code from a remote. The mechanics are a little strange though - not just build into the plugin by default.

@ScriptedAlchemy I tracked with you up to the last sentence 😄 Could you please expand on that a little?

It's pretty abstract...

shared:{
'common':'./src/fakeCommon.js'
}

The idea being that common is overridden with another copy (the real one)
Depending on where you are trying to expose this stuff. You will need to use an alternative pattern.

Cant see exactly what it would be, but ive had to do things like this before. Safe suggestion is to just create a loop and use exposes. Im not sure end-users should be doing the kind of things im doing with Module Federation. Its very circular to have a remote override its host. Again id refer to the youtube video where i kind of do this, but only against one file.

In my case, Ive got the host using its own remoteEntry, and in doing so ive created the ability to have other remotes override host code via shared. Because the host "thinks" its a remote under the circumstances, I'm able to reverse how everything works and let remotes override hosts instead od hosts overriding remotes. I cant vouch for the stability of such patterns though, Module Federation was not designed to be used in this manner :P

@ScriptedAlchemy nice.

I was playing with routing sample https://github.com/module-federation/module-federation-examples/tree/master/shared-routes2. I've notice that if host2 is not available, the whole app is not working. How will app1 handle the error if remoteEntry script failed to load?

Didn’t really cover fallback mechanics under these conditions. Npm publish pipeline or a code split primitive fallback? Redundancy requires old-school solutions, like install pipelines. You could also go over the network during build time and pull code from a remote as a redundancy- tho that’s still similar to npm installed fallbacks. Though, this still is the same concept as - what do you do if a chunk is missing when you code split an app? It usually breaks unless you’ve specified a pattern for error recovery. Same scenario here. If you’ve got code missing in production, then there’s already problems.

Things to consider when using module Federation.

  • Should I do it
  • If it goes missing. What’s the blast radius
  • Just because I can doesn’t necessarily mean I should

In my builds. Each app has its own router and shell. If there’s a failure. Routes go missing but it recovers from errors.

I either expect the code to always be there, or I’ve designed the platform to cater for missing code.

The apps will handle the error however they have been told to. If they’re not told, just like api data, it’ll break without considerations for missing data.

Under my operating conditions. I have multiple redundancy layers. I can re-route missing requests to other cdns or file systems depending on if I’m on client or server.

Server side Looks like this. Cloudfront||s3||fs||fetch

For that little example. I’m pretty sure a try catch will handle it?

@ScriptedAlchemy @brendonco

Thinking out loud in regards to the last two replies

I'm also exploring options for loading modules on demand - so the shell (core app) being able to load other apps subject to those containers running. core will load app1, app2, app3 and remoteEntry for core will always be there, the rest will be added with docker-compose during the build

@ScriptedAlchemy I'm currently also working out a micro frontend solution for my customer and I'm following this development very close. (but haven't had the chance to play with it). But I have some question that came into my mind:

1) If I'm correct both Federation application (I'll give the application orchestration the microfront-ends this) and the Module Federation applications need to be build using the plugins? It is not possible for the Federation application to consume clients that are just build using webpack, or maybe other bundlers?
2) Since the Federation Plugin adds some runtime code, how sure are we that this will remain compatible between multiple versions of webpack? Currently this is in webpack 5, as mentioned before there are plans to back port this to the version 4. Will those 2 be compatible. I mean if i create my Federation application in webpack 5, but my legacy applications are still on webpack 4 with the federation plugin, will I be able to consume them without issues?

Tnx, in advance to take a little bit of your time :) and keep up the good work!

Google my articles and YouTube playlists.
I haven’t put any effort into consuming code from other bundles. My guess is probably? Either use externals for that or shim the api inside another bundle so it’s got an interface.

I’m supporting the webpack 4 backport - interface layer will operate as a compatibility layer. 4&5 will work together. Today I’m using webpack 4 and 5 with a api shim.

The limitations of WP4 api makes it challenging to overcome some aspects. While we will likely get base level Federation, I will not backport any additional extensions I’ve created. It will work but it’s an unofficial backport. In the hands of the community to progress the tech for WP4 beyond my initial efforts to support it. It’s not yet stable but the intention is that I’ll be able to interchangeably load code between 4&5

However things like streamed federation will likely not be offered to v4 due to extensive use of wp5 only features.

An easy hack (shim) for non webpack. Copy paste the template of remoteEntry and hook it into another bundler. That’s what I’m doing at the moment on my WP4 builds. Require.context and the guts of a webpack 5 runtime. Somewhere in this thread I’m pretty sure i dropped a gist containing the shim

@ScriptedAlchemy @brendonco

Thinking out loud in regards to the last two replies

I'm also exploring options for loading modules on demand - so the shell (core app) being able to load other apps subject to those containers running. core will load app1, app2, app3 and remoteEntry for core will always be there, the rest will be added with docker-compose during the build

Docker containers are ideal for Federation also for server side federation. I run in a fully serverless env so there’s a lot more challenges - hence streaming. Hydration in and out of s3 or redis also works well.

I’ve got blueprints for peer to peer streamed requests in the event of a failure, resolve to current users online. Load directly from their browser - leverages blockchain tech for consensus. Experimental but immutable - the code could never go missing. I tested the concept a few years back. Files lived on for months after they were deleted from origin. The more users connected, the faster the network got. With a service worker attached, clients could host their own dns server and remain fully decentralized. Again this is a highly experimental concept - cannot recommend for enterprise use, but it’s cool

Hello guys,
I have a case to integrate module federation into Angular 7 project. it is using webpack 4 and @angular-devkit/build-angular.

As i guess i have to build angular without any additional tools which overrides webpack config generated inside CLI. So i have to build and serve by pure webpack commands.

Is there any chances to integrate to old angular version ?

@ScriptedAlchemy @brendonco
Thinking out loud in regards to the last two replies
I'm also exploring options for loading modules on demand - so the shell (core app) being able to load other apps subject to those containers running. core will load app1, app2, app3 and remoteEntry for core will always be there, the rest will be added with docker-compose during the build

Docker containers are ideal for Federation also for server side federation. I run in a fully serverless env so there’s a lot more challenges - hence streaming. Hydration in and out of s3 or redis also works well.

I’ve got blueprints for peer to peer streamed requests in the event of a failure, resolve to current users online. Load directly from their browser - leverages blockchain tech for consensus. Experimental but immutable - the code could never go missing. I tested the concept a few years back. Files lived on for months after they were deleted from origin. The more users connected, the faster the network got. With a service worker attached, clients could host their own dns server and remain fully decentralized. Again this is a highly experimental concept - cannot recommend for enterprise use, but it’s cool

nice one

@alecoder the angular team is working on its but are blocked by us. There are workarounds https://www.angulararchitects.io/aktuelles/the-microfrontend-revolution-module-federation-in-webpack-5/

Here a few updates what happened on the dev-1 branch:

  • Overridables (shared) to no longer require wrapping in import(). So no unneccessary import("./bootstrap") in your app code because of container-related plugins. (Note: remote modules still require import(), that won't change.)
  • Persistent caching with container-related plugins is now possible.
  • Container-related plugins are now exposed under require("webpack").container.XXXPlugin and have typings
  • ModuleFederationPlugin doesn't create a container when no exposes are specified
  • Improved schema validation

Tobias, I pulled dev1 yesterday and rebased on my fork - saw lots of these changes.

Very very well done! You’re an absolute champ

Do you have any advice re: caching? I've noticed some cases where browsers will cache remoteEntry.js because it's a static filename. I wonder if I should append a random query parameter to bust the cache or perhaps configure the server response headers to not cache remoteEntry.js

No random query parameter. That's never a good idea. Configure correct caching headers on server. Revalidation and short cache live time.

@sokra are there some cache-control values you would recommend as a safe default?

after testing this out, it's important to note that if you consume exposes inside a remote app, you will need to use a dynamic import somewhere, perhaps not on the root of the app @sokra ?

import LocalButton from "./Button";
import React from "react";

const App = () => (
  <div>
    <h1>Basic Host-Remote</h1>
    <h2>App 2</h2>
    <LocalButton />
  </div>
);

export default App;

with

import React from "react";

const Button = () => <button>App 2 Button</button>;

export default Button;

and a webapck config that has this.

new ModuleFederationPlugin({
      name: "app2",
      library: { type: "var", name: "app2" },
      filename: "remoteEntry.js",
      exposes: {
        Button: "./src/Button",
      },
      shared: ["react", "react-dom"],
    }),

Will require you to either use import(bootstrap.js) in the REMOTE's entrypoint in order for it to work.

OR

you can dynamically import the file itself. Either way, you need a dynamic import on the remote in the execution chain - a promise must exist somewhere to hoist imports up.

import React, {Suspense} from "react";
const LocalButton = React.lazy(()=>import('./Button'));

const App = () => (
  <div>
    <h1>Basic Host-Remote</h1>
    <h2>App 2</h2>
    <Suspense fallback={null}>
      <LocalButton />
    </Suspense>
  </div>
);

export default App;

import(bootstrap.js) will allow you to do this

import LocalButton from "./Button";

Otherwise, if you don't want to have a dynamic import in entrypoint, you can use it like this
const LocalButton = React.lazy(()=>import('./Button'));

@sokra with import bootstrap solved (thank you again) .... do you think we are at a stage where we can release module federation with the next webpack beta <3

Looking for help!

Copied the basic-host-remote example, trying to use remotes in our legacy product as the "host". Made few changes but can seem to load the component, the compiled code on the host side looks like this:

var MyComponent = React.lazy(function () {
  return Promise.resolve().then(function () {
    return __webpack_require__(/*! app2/MyComponent */ "?9bdf");
  }).then(function (res) {
    console.log(res);
    return res;
  })["catch"](function (err) {
    console.log('err', err);
  });
}); 

Error is:

TypeError: Cannot read property 'call' of undefined
    at __webpack_require__ (application.js:25737)

webpack seem to have a module 'container-reference/app2' available and i believe this is because the module is renamed to "?9bdf"/

Is another plugin or splitchunks conflicting with the ModuleFederationPlugin? i have tried to using webpackIgnore comment, but nothing works and i don't know how to debug it any further.

Show me your implementation. Whats the code in legacy.

  • Whats the code in legacy
  • did you load the remoteEntry
  • is legacy running webpack 4

this looks like the chunk/module you're asking for is missing. Ill need more context about your host and remote to help

@bvkimball Judging by very little context. You're either attempting to call this, directly in your host.

var MyComponent = React.lazy(function () {
  return Promise.resolve().then(function () {
    return __webpack_require__(/*! app2/MyComponent */ "?9bdf");
  }).then(function (res) {
    console.log(res);
    return res;
  })["catch"](function (err) {
    console.log('err', err);
  });
}); 

If its not webpack 5, then you need to use window.app2.get('MyComponent').then(factory => factory())

Heres what mine looks like in legacy webpack 4

const DynamicComponent = dynamic(() => window.abtests.get('webpack4FederationComponent').then((factory) => {
    const Module = factory();
    return Module.default;
}), { ssr: false });


<DynamicComponent />

Make Sure you have publicPath set to something absolute in the remote

  output: {
    publicPath: "http://localhost:3002/",
  },

Thank you!
took me a bit to realize that dynamic was a nextjs thing. To get this working for me, I did the following:

const MyComponent = React.lazy(() => window['app2'].get('MyComponent').then((factory: Function) => factory()));
...
   ReactDOM.render(
      <Provider>
        <React.Suspense fallback="Loading...">
          <MyComponent />
        </React.Suspense>
      </Provider>,
      this.el
    );

needed to use window to build my library too

   new ModuleFederationPlugin({
      name: "app2",
      library: { type: "window", name: "app2" },
      filename: "remoteEntry.js",
      exposes: {
        MyComponent: "./src/MyComponent"
      },
      shared: ["react", "react-dom"]
    }),

@sokra with import bootstrap solved (thank you again) .... do you think we are at a stage where we can release module federation with the next webpack beta <3

I'm very excited for next beta version with this feature. Cool stuff.

after testing this out, it's important to note that if you consume exposes inside a remote app, you will need to use a dynamic import somewhere, perhaps not on the root of the app @sokra ?

Sounds like a bug. That's not how it is intended. You only need to use import() when using a remote module in the app part of your build. Own exposed or overridable modules should not need a import().

This may be a long shot but are there any plans to include the webpack-dev-server live reloading / overlay functionality in the exposed chunks? This is quite valuable for developing an app locally against a deployed container. I have also explored the clunky workaround of using https://www.npmjs.com/package/webpack-livereload-plugin but it is not yet compatible with Webpack 5. Ultimately, including the live reloading / overlay functionality in the exposed chunks would be the much preferred solution.

after testing this out, it's important to note that if you consume exposes inside a remote app, you will need to use a dynamic import somewhere, perhaps not on the root of the app @sokra ?

Sounds like a bug. That's not how it is intended. You only need to use import() when using a remote module in the app part of your build. Own exposed or overridable modules should not need a import().

@sokra ill send you my repo in both states

This may be a long shot but are there any plans to include the webpack-dev-server live reloading / overlay functionality in the exposed chunks? This is quite valuable for developing an app locally against a deployed container. I have also explored the clunky workaround of using https://www.npmjs.com/package/webpack-livereload-plugin but it is not yet compatible with Webpack 5. Ultimately, including the live reloading / overlay functionality in the exposed chunks would be the much preferred solution.

I ported livereload to webpack 5. There’s plans to bring hmr capabilities as far as I know. But don’t remember the full details or release plan

This is really awesome and looks like it will solve a lot of problems we've been struggling with for the last year. However, I'm still not clear if this will actually fully solve our problem, so sorry for hijacking this issue to ask about this, and also sorry if I've missed the answer if it's already here in the thread. So here's the question:

Would this support SPAs offering a plugin system? This is the example I'm thinking about:

  1. The main application is always loaded first on page load, so it would always be the host.
  2. It fetches a list of active plugins from the server and would then need to do a fully dynamic import() of each plugin, without the plugins being known at the host application's build time.
  3. The plugins are loaded and can then import shared vendored libraries as well as parts of the plugin framework from the host.

As far as I understand it, I'm positive this merge proposal will solve 1. and 3., but I'm not sure if this proposal would actually support 2., in particular since webpack does not support import() based on fully dynamic expressions as far as I'm aware.

It fetches a list of active plugins from the server and would then need to do a fully dynamic import() of each plugin, without the plugins being known at the host application's build time.

https://github.com/webpack/webpack/issues/10352#issuecomment-612292229

after testing this out, it's important to note that if you consume exposes inside a remote app, you will need to use a dynamic import somewhere, perhaps not on the root of the app @sokra ?

Sounds like a bug. That's not how it is intended. You only need to use import() when using a remote module in the app part of your build. Own exposed or overridable modules should not need a import().

@sokra ill send you my repo in both states

It should already be fixed

@sokra yep it is - just wiped locks and did a fresh install. Its working properly now. Im removing import(bootstrap) from all examples now

@ScriptedAlchemy You should also change require("webpack/lib/container/ModuleFederationPlugin") to require("webpack").container.ModuleFederationPlugin in the examples.

@sokra will do

Also want to discuss hooks with you, especially one added to remoteEntry allowing me to extend the API.

Where should hooks come from, i know webpacks hooks are abstracted heavily through other access points like getCompilationHooks and so on

Off-topic from this merge proposal. But I've created a plugin that extends module federation to accept streams. This is useful for server-side federation. In my case, i am using S3 as the remotes file system - webpack requires code streamed directly from S3. Pretty slick solution for users wondering how to access federated code in serverless environments.

@ScriptedAlchemy do you think you can release module federation in next beta version? Any plan?

@brendonco We are getting close to being able to merge this. That decision is up to @sokra to determine if we have reached a stage at which he is comfortable to merge this into Webpack.

https://twitter.com/wSokra/status/1255927801045286917?s=20

Hello guys,
getting error while trying to build my angular application production mode:

TypeError: Cannot destructure property 'startTime' of 'snapshot' as it is null.
    at FileSystemInfo._checkSnapshotValidNoCache (/home/alecoder/Projects/JS/ap/node_modules/webpack/lib/FileSystemInfo.js:1159:4)
    at FileSystemInfo.checkSnapshotValid (/home/alecoder/Projects/JS/ap/node_modules/webpack/lib/FileSystemInfo.js:1153:8)
    at processCacheResult (/home/alecoder/Projects/JS/ap/node_modules/webpack/lib/cache/ResolverCachePlugin.js:253:26)
    at /home/alecoder/Projects/JS/ap/node_modules/webpack/lib/Cache.js:93:5
    at Hook.eval [as callAsync] (eval at create (/home/alecoder/Projects/JS/ap/node_modules/webpack/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:14:1)
    at Cache.get (/home/alecoder/Projects/JS/ap/node_modules/webpack/lib/Cache.js:75:18)
    at /home/alecoder/Projects/JS/ap/node_modules/webpack/lib/cache/ResolverCachePlugin.js:300:15
    at Hook.eval [as callAsync] (eval at create (/home/alecoder/Projects/JS/ap/node_modules/webpack/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:20:1)

Production mode works. using AngularCompilerPlugin plugin.
there are production and development configurations:

Prod

    new AotPlugin({
      skipCodeGeneration: false,
      tsConfigPath: "./src/tsconfig.app.json",
      directTemplateLoading: true,
      entryModule: path.resolve(
        __dirname,
        "./src/app/app.module#AppModule"
      )
    }),

Development:

    new AotPlugin({
      tsConfigPath: path.resolve(__dirname, './src/tsconfig.app.json'),
      sourceMap: false,
      nameLazyFiles: false,
      skipCodeGeneration: true,
      compilerOptions: {
        enableIvy: false,
      }
    }),

Is this issue somehow linked to new webpack 5 ? It seems error comes from webpack itself.

Whole config:

const AotPlugin = require("@ngtools/webpack").AngularCompilerPlugin;
const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");
const { resolve } = require('path');
const ProgressPlugin = require('webpack/lib/ProgressPlugin');
const CircularDependencyPlugin = require('circular-dependency-plugin');
var IndexHtmlPlugin = require('indexhtml-webpack-plugin');


const shellConfig = {

  devtool: 'eval',
  mode: "development",
  entry: ["./src/polyfills.ts", "./src/main.ts"],
  node: false,



  output: {
    path: resolve('./dist'),
    filename: '[name].js',
  },  



  devServer: {
    host: 'dev.adjarabet.com',
    port: 443,
    historyApiFallback: true,
    disableHostCheck: true,
    watchOptions: {
      ignored: /node_modules/
    },
  },


  resolve: {
    extensions: ['.ts', '.js']
  },


  // performance: {
  //   hints: false,
  // },


  module: {
    rules: [
      { 
        test: /\.ts$/, 
        loader: "@ngtools/webpack"
      },

      // {
      //   test: /\.js$/,
      //   exclude: /(ngfactory|ngstyle).js$/,
      //   enforce: 'pre',
      //   use: 'source-map-loader'
      // },

      // {
      //   test: /\.html$/,
      //   use: 'raw-loader'
      // },

      // {
      //   test: /\.css$/,
      //   use: ['to-string-loader', 'css-loader']
      // },

      // {
      //   test: /\.css$/,
      //   use: ['style-loader', 'css-loader'],
      //   include: [resolve('./src/styles.css')]
      // },  

      // {
      //   test: /\.(eot|svg|cur)$/,
      //   loader: 'file-loader',
      //   options: {
      //     name: `[name].[ext]`,
      //     limit: 10000
      //   }
      // },

      // {
      //   test: /\.(jpg|png|webp|gif|otf|ttf|woff|woff2|ani)$/,
      //   loader: 'url-loader',
      //   options: {
      //     name: `[name].[ext]`,
      //     limit: 10000
      //   }
      // },


      // {
      //   test: /[\/\\]@angular[\/\\]core[\/\\].+\.js$/,
      //   parser: { system: true },
      // }
      {
        test: /\.scss$/,
        use: ['style-loader', 'css-loader', 'sass-loader'],
        //include: [helpers.root('src', 'styles')]
      }      
    ]
  },


  plugins: [

    new HtmlWebpackPlugin({
      template: "./src/index.html"
    }),

    new AotPlugin({
      //mainPath: path.resolve(__dirname, './src/main.ts'),
      tsConfigPath: path.resolve(__dirname, './src/tsconfig.app.json'),
      sourceMap: false,
      nameLazyFiles: false,
      skipCodeGeneration: true,
      compilerOptions: {
        enableIvy: false,
      }
    }),

    //new SuppressExtractedTextChunksWebpackPlugin(),

    new ProgressPlugin(),

    new CircularDependencyPlugin({
      exclude: /[\\\/]node_modules[\\\/]/
    }),
  ]
};


module.exports = [shellConfig];

@alecoder https://www.angulararchitects.io/aktuelles/the-microfrontend-revolution-module-federation-in-webpack-5/ this is for angular apps, written by one of the angular core contributors

How are you guys handling multiple components being exposed in the exposes object? I could be having 100 components that need to be accessed

edit: previous error seems to be unrelated

@vtrikoupis you will have to add each component as a key/value in the exposes object, which I had to do for about 500 util files.

it kind of goes back to my feedback item 2 where I think it would make more sense to have a standard JS file that exports shared components (and webpack looked at that) instead of a config file in the webpack settings itself. Seems more natural (and based on standards) than having to put it in a config.

But I don't know if there's a technical/philosophical reason to not do it that way.

How are you guys handling multiple components being exposed in the exposes object? I could be having 100 components that need to be accessed

edit: previous error seems to be unrelated

Create a loop that walks directories and adds the files to array

@ScriptedAlchemy In upgrading from git://github.com/webpack/webpack.git#dev-1 to 5.0.0-beta.16 we are getting a new error:

Service:2 Uncaught (in promise) TypeError: Cannot read property 'override' of undefined
while loading "ProfilePage" from webpack/container/reference/profile

We have a very similar structure to your shared routing example. The difference is we want the capability to only spin up one micro app + the shell rather than having to spin up all the micro apps. In shell we try to dynamically import each micro app & silently fail if it doesn't load. For example in Shell.js trying to import the profile app without starting its webpack:

import("profile/ProfilePage").catch(e => {})

transpiles away the .catch so the import failure causes the app to crash:

      return __webpack_require__.e(/*! import() */ "webpack_container_remote_some-micro-app_root").then(__webpack_require__.t.bind(__webpack_require__, /*! profile/ProfilePage */ "webpack/container/remote/profile/ProfilePage", 7));

_Interestingly this error doesn't happen with React.lazy, but we are wondering if there are any alternatives?_

Wrap it in a promise. So new Promise(import()).catch

Or try catch

Ahh your override thing is a bug. Add shared:[]

If shared isn’t there then no override object exists in the runtime. This is a temporary workaround that has already been flagged to Tobias.

If you have no shared. The overridables is not added so hosts can’t override anything or attempt to.

Thanks for the quick response @ScriptedAlchemy! Wrapping it:

try {
  import("profile/ProfilePage").catch(e => {})
} catch(e) {}
// OR
Promise(import("profile/ProfilePage").catch(e => {})).catch(e => {})

leads to the same error.

Your shell webpack already has shared in it, and adding "../profile/src/ProfilePage", to shared does not resolve this either.

Note that this error did not occur previously when specifying git://github.com/webpack/webpack.git#dev-1 as the webpack version. However since I re npm installed since the updates to that branch or using the latest beta webpack, this error is now occurring.

Any other options? 😅

You still need shared:[] on remotes or hosts. Even if your not sharing anything. It’s a bug

Show me all your webpack module Federation configs. I’ll spot the problem

how can it be downloaded from S3 many version federation module? I did not find it case in the docks. My cases:

  • I have many apps
  • I have many federation modules with many version and different type build: for client and server

Many apps load config when getting type and version modules.

  • On server - we load on https JS file modules/header/1.20.3/header_server.js, execute and run
  • On client - we load js modules/header/1.20.3/header_client.js, CSS files in CDN

You can see an example in https://www.tinkoff.ru/. Now works on a self solution

We could be helped by a method in which we can add a module ourselves after loading remote.js.

Streamed code is an implementation detail - not webpacks job. I wrote this for lambda federation. Currently streaming 10 separate remotes. Direct from s3 takes about 150ms, from Cloudfront it’s 17ms.

The plugin isn’t open-source. Will likely be licensed for a small amount. I’ll be releasing a trial key that’s good for 2 weeks. And once I’ve finished the ability to hot-reload in production (specifically in node) - I’ll be publishing it.

Multiple versions are already possible in module Federation. So you can deployed versions depending on how you store them on a cdn. Streaming will use similar tactics for versioning as well. Allowing you to lock a remote/host to use a specific version of another remote

You can add modules manually if you tap into the lower level. Dynamic Federation pretty much. But you cannot modify a remotes modules it exposes. You could use shared which will allow you to dynamically override dependencies on the consumer side

@protoEvangelion could you create a repro repo and file an issue?

There is one problem with module federation - shared. Some modules are "expected" to be shared and some __have to__.
The simplest example - react could be presented only once, or some theme stuff, or anything else expected to be a singleton or use a singleton - like every single Redux application.

So

  • would it be beneficial to move remote configuration to a package.json. It already contain information about "imports and exports", and it would be logical to extend it a bit
  • would it be a good idea to mark some packages as a singletons, and force them to be shared
  • how check how many packages could be potentially shared, but were not? (probably some external tool)

@theKashey This could be done with a small update to ModuleFederationPlugin. Would not need to be in the webpack core either.

Ok. So another shared question - is there any versioning around it or not?
For example, I have Button@2, and remote actually needs Button@3, so it could not use my one - would it use own one?
Or the opposite example - I have Button@2 and a few remotes needs Button@3 - would the share Button@3 between each other, or will duplicate it (as it usually happens with node_modules and situation like this, unless you use special plugins)

Hi guys,

I'm trying to lazily import a regular javascript file, is there a standard much like the Suspense for rendering that has worked for you?

related to the above:

I've got a remote defined as
remotes:{ name: "name"}

As expected, the following works fine:
const AppDialog = lazy(() => import("name/AppDialog"));

however when I try importing a regular js module such as:

const getStore = async () => {
  return await import("name/store")
}

I get:

ReferenceError: name is not defined
while loading "store" from webpack/container/reference/name
  • would it be beneficial to move remote configuration to a package.json. It already contain information about "imports and exports", and it would be logical to extend it a bit

We don't have access to the package.json of a remote container. Module Federation is based on the concept of no-compile dependency between host and remote and they are build separately.

If you want to remove configuration somewhere then it can be move to runtime. There are some ideas about adding validation to containers that could ensure proper use of the container. This could be one additional validation.

For example, I have Button@2, and remote actually needs Button@3, so it could not use my one - would it use own one?

Assuming you e. g. Button@2 as shared key, yes.

Or the opposite example - I have Button@2 and a few remotes needs Button@3 - would the share Button@3 between each other, or will duplicate it (as it usually happens with node_modules and situation like this, unless you use special plugins)

No. One design concept was that overrides only happen from host to remote and not between siblings. The goal of this concept is to reduce the factors that can influence the behavior of the containers. With this design only host can influence shared modules of the remote. You only have to check configuration and shared modules of the host. If sibling container could influence each other this would end in chaos since a new container could be deployed which breaks another used container, maybe even depending on which one is included first.

@vtrikoupis this is working for me now to lazy load without Suspense:

const microAppImportMap = {
    someApp: () => import('some-app/root').catch(() => null),
    // ...other micro apps
}

If you are using Suspense, @ScriptedAlchemy has a ton of great examples here: https://github.com/module-federation/module-federation-examples

If you are using Suspense, @ScriptedAlchemy has a ton of great examples here: https://github.com/module-federation/module-federation-examples

Thanks a lot. I'm following the repo yes, using Suspense throughout.. works perfectly. I have a non functional component that needs to access a token in a global store.

I've seen in the same repo a store being shared between the two modules. The project I'm trying to apply this to is with easy-peasy. I wanted to avoid touching that code but it seems I may have to

edit: The desired effect would be my easy-peasy store being available and overriding the remotes. as explained by ScriptedAlchemy at 17:45 https://youtu.be/-LNcpralkjM?t=1065

@vtrikoupis that video was me uncovering another bug that i caused myself. But in the process of doing so - i discovered this possibility. You can make remotes exposes also shared and you could version them as well. hosts would only override remotes if their version matches. So you could have some secondary lookup or npm installed version or s3/fetch which would fall back to fetch the remote directly from a cdn or bucket.

Giving a talk on module federation tomorrow if anyone cares to join. https://twitter.com/sphereit_conf/status/1259874275969970176?s=21

@ScriptedAlchemy I've noticed when I set the remotes with hyphen (-), I got unexpected token.

remotes: {
  "meaningful-name-1": "meaningful-name-1"
}

image

Somewhere in the thread, this was addressed. It’s a variable so it’ll break. Vars cant be hyphenated. Something like “[‘thing-thing’]” should work. Global[thing-thing] for example

@brendonco you fix by adjusting the library key like so: library: { type: 'window', name: 'meaningful-name-1' },. You can also do type: 'this'.

Some new considerations we are evaluating for module federation - I’ll update once they are more finalized.

Outside webpack core. Some areas being actively developed.

  • Federation dashboard, offering graph visualization, code freezing, ability to see who uses what. The idea being you can have a command and control layer that gives you insights. We also will be adding the ability to hot reload server-side federated modules, and version lock modules that remotes exposes, with ability’s to rollback or upgrade what a host is consuming. Module Federation has power with its highly dynamic capabilities - the goal is to enable code-freezes Of ciritical business flows without freezing an entire codebase. Effectively freezing the graph so other teams can continue to deploy remotes that to other non critical hosts (checkout, product display pages during peak for example would be frozen) but other teams can still consume green remotes in production even though a specific host is frozen. Dashboard would not be a service like system. You won’t speak to dashboard. More likely you’ll write something to a db or s3, then remotes read from s3 or whatever (implementation detail) the dashboard going offline should not impact federated apps from functioning correctly. It reads and writes to static sources.

  • streaming is done, but needs some refinements after speaking to Tobias. We also need to verify cache busing specific webpack modules to enable hot reloading on servers over the wire.

  • automatic vendor federation, to version and share all vendor code possible without manually writing it to shared.

  • sandboxing exposed modules in a remote. To prevent global or prototype pollution within a host.

  • reverse overrides patterns to allow remotes to override hosts files - similar to shared.

  • cross remote queries. Allowing two remotes to share vendor code without the host needing a package installed as well

  • federated unit tests to preform bidirectional tests (nobody breaks anyone else depending on a remote)

More to come and again will provide an update on core changes in future webpack releases. All mentioned above is not intended to be part of the core - but solve problems and provide tools valuable at enterprise level where scale is a challenge. As with any technology, tooling can make or break ability to scale reliably. We intend to provide supporting tools to enable well rounded capabilities that will reduce risk and cognitive complexity those of us who control 20-100+ repos. At previous companies - I’ve had big challenges controlling 150 repos where 10-20 MFEs are on a single page at any given time

Speaking about dashboards and rollouts - there is a subset of tests - contract testing (or pact testing in some places) which could shine with MFE.

In contract terms, a consumer of a remote is able to define tests (literally acceptance tests) for it. Existence of this test would be known for remote
As a result - a remote, once it "want to publish a new version" should be able to test itself against known consumers, by the tests defined by those consumers.
That would be a real game changer!

Yeah thats the plan. Internally I’m working on something like that

Pretty much bidirectional tests. Remotes can run tests of remotes to verify they don’t break anything and hosts can run check against remotes to make sure they don’t break.

I am getting __webpack_require__(...)(...).get is not a function error when trying to load the remote module by using:

import('demo/Button')

In the runtime.js:
var promise = __webpack_require__(data[0])(__webpack_require__(data[1])).get(data[2]);

data is ["webpack/container/remote-overrides/empty", "webpack/container/reference/demo", "Button"]

and

the __webpack_require__(data[0])(__webpack_require__(data[1])) results to a constant 1

Upd: the problem disappears if I comment out the optimization:

optimization: {
  runtimeChunk: 'single',
  splitChunks: {
    chunks: 'all',
    maxInitialRequests: Infinity,
    minSize: 50000,
    cacheGroups: {
      vendor: {
        minRemainingSize: 50000,
        test: /[\\/]node_modules[\\/]/,
        name(module) {
          const [, packageName] = module.context.match(
            /[\\/]node_modules[\\/](.*?)([\\/]|$)/
          );

          return `npm.${packageName.replace('@', '')}`;
        },
      },
    },
  },
},

@andrei-zhaleznichenka your splitChunks config means that your container entry is splitted into multiple files (just like your normal entries). This means you need multiple script tags to create a valid remote in the host HTML. Check stats output to get a list of files.

Thanks @sokra! Now it is clear.

I think for my case I can probably skip the optimization for the remotes since it is only required for my host as webpack fails to generate the source-map for the large chunks otherwise.

@ScriptedAlchemy

  1. if I have app1 exposes a side nav
  2. app2 is using the expose side nav from app1
  3. App1 is running, and I didnt load app1 but go directly to route app2. I've notice that if I include app1 remoteEntry.js before loading app2. The side nav will render. But If I didnt load remoteEntry.js of app1, the side nav doesnt work.

Question: The index.html file in app2 will not be used when deployed to an environment e.g dev env, if the entry point is from app1. Is there a way to configure in webpack to load remoteEntry.js of app1? Without using the dynamic script loader. 😀

@brendonco we will soon have a new external type script which will allow

remotes: {
  app1: "app1@https://example.com/app1/remoteEntry.js"
}

This would allow to omit the script tag in the html and webpack will load the script when needed. It still makes sense to preload the script in the HTML, but thats an optional performance optimization.

Theoretically, you could use MF to split chunks. Via shared or exposes. Slightly less control but if you have multiple builds on the page it would add an advantage to what splitchunks does now.

@brendonco we will soon have a new external type script which will allow

remotes: {
  app1: "app1@https://example.com/app1/remoteEntry.js"
}

This would allow to omit the script tag in the html and webpack will load the script when needed. It still makes sense to preload the script in the HTML, but thats an optional performance optimization.

This feature will be helpful.

@ScriptedAlchemy it will only work if you load remoteEntry from App1.

@ScriptedAlchemy We are currently outputting 50+ npm deps in shared: [...]. _Is this an appropriate usage_? All of the examples I've seen have around 3 deps 😬

One optimization we are trying is madge to dynamically figure out the npm deps of each micro app so that the 50+ number is closer to 10+.

However, part of me wonders if there is a better way to do this "auto npm package detection" within a webpack plugin or the module federation plugin itself. It looks like webpack understands the module graph after module federation plugin runs so I'm guessing this isn't possible but wanted to verify 😄

@protoEvangelion probably you looking for something like https://github.com/module-federation/automatic-vendor-federation

@vankop thank you!

I like this approach, however in our situation with a reasonably sized enterprise app there are a few issues with this:

  1. It assumes that all developers will properly add deps to "dependencies": [] & dev deps to "devDependencies": []. That's an assumption we can't make in our case.
  2. We have a mono repo where 99% of the dependencies live in the root package.json and each micro app can have its own package.json with custom deps.
  3. We only want micro apps to build the minimal amount of deps it needs rather than everything in deps. For instance app1 may use lodash so we want to add that to shared but app2 may not so we don't want that in shared.

anyone having a collision of dynamic class generated by styled-components when you shared Navigation. The navigation is using a styled-component.

Example:

app1 nav content style:

import styled from 'styled-components';

export const ContentWrapper = styled.div`
  margin: -1.5em -1.5em 0;
`;

image

app2 using app1 navigation:

import styled from "styled-components";

export const Container = styled.div`
  padding-top: 1rem;
  box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.1);
`;

image

Webpack config (App1):

      exposes: {
        SideNavigation: "./src/common/components/SideNavigation/index",
        Widget: "./src/App",
      },
        shared: [
        "react",
        "react-dom",
        "react-router-dom",
        // workaround to ensure code is provided before booting app
        "./src/common/components/SideNavigation/index"
      ]

Anyway, just a thought. Raise an issue with styled-components here:
https://github.com/styled-components/styled-components/issues/3148

@brendonco I think you need to add styled-components as a shared module. And check your console for styled-components warnings.

Yes I did add before. Same issue. Regardless I added or not as shared module.

Look like the styled-components Babel plugin generates a hash based on package.json name and the path relative to the package.json.

Do both apps have different names in their package.json?

Look like the styled-components Babel plugin generates a hash based on package.json name and the path relative to the package.json.

Do both apps have different names in their package.json?

different name in their package.json

styled generates hash based on style text. Different styles should always generate different hashes as well as same styles leads to the same hashed. Well, that's is a core idea of "hashing".
Something else is here.

yes, quite surprise why there are same hash generated.

Aww man. @brendonco youre having to override the host with itself. I was hoping we wouldn’t encounter the need to do this pattern a whole lot outside the YouTube video 😂 this is almost “reverse overrides” from the looks

Are you able to customize the hashing algorithm? I know in css loader you’re able to provide additional data to prefix

Are you able to customize the hashing algorithm? I know in css loader you’re able to provide additional data to prefix

I tried adding below hashing prefix, and I'm using another table library called ag-grid which got affected :(. In my local is working fine with/without hashing algo. This issue only occured when deployed to a remote server.

      {
        test: /\.css$/,
        loader: 'css-loader',
        options: {
          modules: {
            localIdentName: '[path][name]__[local]--[hash:base64:5]',
            context: path.resolve(__dirname, 'src'),
            hashPrefix: 'custom-hash',
          },
        },
      },

I've been working on getting module federation to work with nrwl and angular, and have used the work done in https://www.angulararchitects.io/aktuelles/the-microfrontend-revolution-part-2-module-federation-with-angular/ as base. I've been having trouble while trying to import multiple federated modules. I have a host(shell) app which has two routes, each lazily loading one of the two federated modules. The first route loads by default on page load, and the other one is navigable by clicking on a tab. The default route always works - regardless of which module it points to. The second route always crashes by throwing an error: TypeError: Cannot read property 'ɵmod' of undefined. When I put a debugger on the route load function, I noticed that the import statement for the second route always resolved with an object containing the module which is already loaded by the default.

I've created a minimal repo that demonstrates the issue. I'm not sure if webpack is going wrong here or I am 😕.

PS: Awesome job on this! I'm really excited by the work done here :)

Are you able to customize the hashing algorithm? I know in css loader you’re able to provide additional data to prefix

I tried adding below hashing prefix, and I'm using another table library called ag-grid which got affected :(. In my local is working fine with/without hashing algo. This issue only occured when deployed to a remote server.

      {
        test: /\.css$/,
        loader: 'css-loader',
        options: {
          modules: {
            localIdentName: '[path][name]__[local]--[hash:base64:5]',
            context: path.resolve(__dirname, 'src'),
            hashPrefix: 'custom-hash',
          },
        },
      },

Nevermind, found the solution :), just use babel-plugin-styled-components to hash the classnames. Not related to MF.

Huge update here: https://github.com/webpack/webpack/pull/10960

This adds versioned shared modules.

@sokra awesome work <3

@sokra @ScriptedAlchemy will the new shared mechanism also work for multiple versions of local files / folders (non npm packages)?

Yes, but you either need to put a package.json there or specify a version in shared config.
npm package are also local files. webpack doesn't do anything special for them. You can do everything also with local files with a package.json.

Hmm, not sure if that's feasible, because the use case is sharing hundreds of utils from a local libs folder which needs to be versioned. Currently we are setting libs up as a remote & exposing all of the files through exposes. And trying to rewrite the internal requests to show something like import {foo} from '@libs-2/utils' when bumping the version. Given that, _would shared be a better solution for this?_

You can. I’m doing this. It comes down to keeping previous artifacts of remotes. I’m able to swap remote versions on a host and working out module my module support. It’s complex tho

You can. I’m doing this. It comes down to keeping previous artifacts of remotes. I’m able to swap remote versions on a host and working out module my module support. It’s complex tho

@sokra @ScriptedAlchemy So I’ve been working with @protoEvangelion on this and we have been keeping the previous remote artifacts. I’m pretty stubborn about asking for help, but this one has me stumped. Here’s a rough overview of our use case and issue:

Remotes:
Shell (imports app1, app2 and libs)
App1 (imports libs)
App2 (imports libs)
Libs - expose a.js

When building these, we pass a deploy version to each. So libs will actually build itself as lib-version (I.e. libs-1, lib-2, etc), and the app will reference the appropriate lib version if it gets rebuilt.

Build scenarios:

  1. Build app1 and lib-1, leveraging “a” from lib-1 (in a, log out “version 1 used”). In app1 Webpack, I re-alias it’s imports to “import a from lib-1”. This piece works as expected, logging out “version 1 used”.
  2. Build app2 and lib-2, leveraging “a” from lib-2 (in a, log out “version 2 used”). In app2 Webpack, I re-alias it’s imports to “import a from lib-2”.

Expected result: app1 logs out “version 1 used” and app2 logs out “version 2 used”.

Actual result: app1 logs out “version 1 used” and app2 logs out “version 1 used”.

Looking at the compiled code, we believe this is the issue.
In compiled lib-1, we see:
"1-a": () => {
return Promise.all([__webpack_require__.e(...stuff, __webpack_require__.e("a_js")]).then(() => () => (__webpack_require__(77346)));
}

In compiled lib-2, we see:
"2-a": () => {
return Promise.all([__webpack_require__.e(...stuff, __webpack_require__.e("a_js")]).then(() => () => (__webpack_require__(77346)));
}

From what we can tell, __webpack_require__ finds a_js in lib-1, and caches it. So a_js never gets leveraged from lib-2.

I realize what we’re doing is probably a hack and maybe there’s a much better way to do this. However I would love to get your thoughts on this @sokra @ScriptedAlchemy as I’ve reached the limits of my Webpack knowledge and could really use the insight from those who understand the internals better than I.

Side note: love the additions, such an amazing addition and much needed.

@jherr we have the whole hot swap versioning thing working? With dashboard write capabilities? Just checking - internally I’ve got a working system but dashboard is more refined At controlling webpack

Huge update here: #10960

This adds versioned shared modules.

@sokra Nice! Will you be bumping the beta version to 17 for this shared versioning feature anytime soon?

@jablonnc make sure to set output.uniqueName and ModuleFederationPlugin.name to unique names like "lib-2" otherwise this causes collisions at runtime.
output.uniqueName default to name from package.json and this is probably equal for lib-1 and lib-2.

Hi guys,

Hi guys, been trying to wrap my head around this.. has anyone incorporated redux for a large-scale app?

I've seen the federated modules example. I'm wondering if app2 can consume the store just as app1 can. To make it more clear:

<RemoteApp store={store} /> is lazily loaded with the dynamicFederation function
This is awesome. My issue is trying to federate the store inside app2.

so store is exposed in the webpack config and I then lazily load the store in my component
const store = React.lazy(() => dynamicFederation('app1', 'store'));

While the store is there, it's missing its functionality.
Does anyone have ideas/advice on the best approach for having a host with multiple remotes, with a single redux store? Perhaps some proof of concept using connect?

Sharing the store is the easy part (you can expose it somewhere, maybe in a separate container for that), the tricky part is combining the reducers from multiple MFEs.

Here is a good idea how to do that: http://nicolasgallagher.com/redux-modules-and-code-splitting/

Reducer registry would also be a exposed module somewhere. Best in the same container as the store.

I just noticed that while the webpack output.libraryTarget config supports 'global' as an option, and output.globalObject lets you change the name of the global object (to provide some sort of namespacing, for example), we don't have a corresponding module federation library.globalObject config option.

Also it is a bit confusing that the module federation config option is library.type instead of, say, library.target or library.libraryTarget. (Edit: as far as I can tell, output.libraryTarget and the module federation library.type should probably match up usually?)

output.libraryTarget is deprecated in favor of output.library.type.
These library options are now grouped in a object because they are used in multiple locations: output.library ModuleFederationPlugin.library entry.xxx.library

Ah, thanks. I didn't see a deprecation warning when compiling nor in the docs, but I understand we're also at the beta stage. It appears library.globalName is not supported? (it complained when I tried to specify it as something like library: {type: 'global', globalName: 'MYNAMESPACE'})

Ah, thanks. I didn't see a deprecation warning when compiling nor in the docs, but I understand we're also at the beta stage.

It's more a soft deprecation. Both options are supported, the new one is recommended. The old one might be deprecated in later version, but we are not in hurry...

In your case you can use type: "assign", name: ["MYNAMESPACE", "lib"] which assigns it to that property. Or type: "var", name: ["MYNAMESPACE", "lib"] which creates this global and assign to it.

@sokra @ScriptedAlchemy from beta-17 i am getting this error from the host mfe, (for remote mfe also i am getting same error)

container-entry:41 Uncaught (in promise) Error: Module "./stores" does not exist in container.
while loading "./stores" from webpack/container/reference/base
    at container-entry:41
    at async Promise.all (/index 0)
    at async Promise.all (/index 3)

all the exposed modules from remote app are resolving with relative path instead of their names,

Here is my Host app configuration

new ModuleFederationPlugin({
      name: "base",
      library: { type: "var", name: "base" },
      filename: `base/external.js`,
      exposes: {
         "stores": "./src/common/libs/stores/index.js",
      },
      remotes: ["base", "accounts"],
      shared: ["react", "react-dom"],
})

Host app, depends on itself for stores (react Zustand store). We did it like this because we want the same instance of store should be used by all MFEs that includes base also.

beta-16 was working fine. once updated with beta-17 i am getting this error. I don't know where i am making mistake.

 exposes: {
-  "stores": "./src/common/libs/stores/index.js",
+  "./stores": "./src/common/libs/stores/index.js",
 },
 exposes: {
-  "stores": "./src/common/libs/stores/index.js",
+  "./stores": "./src/common/libs/stores/index.js",
 },

@sokra Is it the change that version 17 requires us to do.
Should all the exposed module should be treated like localmodule in host mfe. which means, the key of exposes object should always be a relative path.

Currently yes, all keys need to have a ./ prefix. It's a little bit aligned with the exports field syntax. Let's assume the . is replaced with your container name.

In future you might also be able to expose "module" requests. So this might possible in a possible future:

name: "utils",
exposes: {
  "magic/module": "./magic/module"
}
remotes: {
  utils: {
    external: "utils@/utils.js"
    imports: {
      "magic/": "magic/
    }
  }
}

@sokra after upgrading to beta 17, one of the scope module is failing not sure why. No version specified and unable to automatically determine one. Failed to resolve: Error: Can't resolve beta 16 is working fine though.

It will resolve shared modules relative to your project root. And that failed. Where is that shared module on disk?

Note that there is a little problem with the current design I recently noticed. It doesn't work for deep nested node_modules. Like a package is node_modules/abc/node_modules/shared

Deep nested module doesnt work as well. Like what youbhad describe above.

scope module is in this path, node_modules/@prv/abc

Could you create a new issue with a repro?

Currently yes, all keys need to have a ./ prefix. It's a little bit aligned with the exports field syntax. Let's assume the . is replaced with your container name.

@sokra Other than exports, has there been any other thought towards alignment with ECMAScript Modules? Specifically, will Webpack applications ever be able to output ESM outputs that might be loaded by other bundlers.

Since IE11 is legacy as of this year, that could be really useful to help start simplifying outputs.

There has been some work towards this, but that far from finished.

✓ use module script tags
× load chunks via import() and import
× avoid module factories in chunks when possible
✓ load external via import()
× load external via import
× export via export

I have a cool chart that didn't make it into my talk. At least I can share it with you:

Module Federation

Example of eager shared modules. This would stop the error around eager imports if required.

      shared: {
        ...deps,
        react: {
          eager: true,
          singleton: true,
          requiredVersion: deps.react,
        },
        "react-dom": {
          eager: true,
          singleton: true,
          requiredVersion: deps["react-dom"],
        },
      },

I have a cool chart that didn't make it into my talk.

Is your talk recorded and online somewhere? It sounds like it would be interesting to watch!

@jasongrout it's on the 18th

Is there going to be a standard way to exclude libraries from the spreaded ...deps like there was in AutomaticVendorFederation's exclude array?

The next version adds automatical inferrence of the requiredVersion from package.json. So the recommended way of specifying shared modules is now this:

shared: [
  "d3",
  "lodash/",
  "react-dom",
  {
    react: { singleton: true }
  }
]

You could do something like shared: Object.keys(package.dependencies) but I would recommend against that and rather specify the shared modules selectively. Sharing modules has some effects regarding optimization of these packages, so you better validate if sharing modules make sense on case-by-case basis. And some packages need advanced configuration to work correctly.

@brendonco beta.18 should fix the problems with nested node_modules

I'm having issues with the Module Federation plugin, my hosting app is sharing a simple React component that uses React Hooks.
For example this hook:
const [values, setValues] = React.useState([]);

In my remote app I'm consuming the shared component that includes useState and getting the following error:

_react.development.js:1465_
Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:

  1. You might have mismatching versions of React and the renderer (such as React DOM)
  2. You might be breaking the Rules of Hooks
  3. You might have more than one copy of React in the same app
    See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.
    at resolveDispatcher (react.development.js:1465)
    at Object.useState (react.development.js:1496)
    at AudienceInline (AudienceInline.js:12)
    at renderWithHooks (react-dom.development.js:2546)
    at updateFunctionComponent (react-dom.development.js:2745)
    at mountLazyComponent (react-dom.development.js:2811)
    at beginWork (react-dom.development.js:3049)
    at HTMLUnknownElement.callCallback (react-dom.development.js:70)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:90)
    at invokeGuardedCallback (react-dom.development.js:105)

Using React latest version 16.13.1
Does Module Federation support React Hooks?

@elya158 I encountered this issue when react and react-dom weren't shared between the micro sites. For a larger app I'm federating I had to ensure react and react-dom were both singleton in the core/shell app

@sokra

I have a cool chart that didn't make it into my talk. At least I can share it with you:

Any link to the talk's video? :)

@vtrikoupis

@elya158 I encountered this issue when react and react-dom weren't shared between the micro sites. For a larger app I'm federating I had to ensure react and react-dom were both singleton in the core/shell app

I tried sharing like this:
shared: ['react', 'react-dom']
But I'm getting this error from the consuming app:

main.js:898 Uncaught Error: Shared module is not available for eager consumption: webpack/sharing/consume/default/react/react
at Object.__webpack_modules__. (main.js:898)
at __webpack_require__ (main.js:546)
at eval (index.js:1)
at main.js:1062
at main.js:1063
at main.js:1076

How do you configure singleton?

How do you configure singleton?

shared: [
  {
    "react-dom": { singleton: true }
  },
  {
    react: { singleton: true }
  }
]

This article by the author of mf will shed a lot of light

Hey guys, I am having the same issue as @elya158 as my host app is using an component from my "plugin" app. I tried all the proposed stuff from here as shared: ["react", "react-dom", "react-router-dom"] in both apps, I tried add singleton option but nothing works for me as the error stays the same. I am using the beta.17, I tried 18 but didnt work also..If I remove the hook usage everything works perfect.. Is there something else which I miss?

Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.

You get this error when react is not shared. Note that you need to have the shared configuration for both the host and the container. Only react need to be singleton, otherwise you run into problem when host and container use different major versions (so that's probably not your problem currently)

Hey guys, I am having the same issue as @elya158 as my host app is using an component from my "plugin" app. I tried all the proposed stuff from here as shared: ["react", "react-dom", "react-router-dom"] in both apps, I tried add singleton option but nothing works for me as the error stays the same. I am using the beta.17, I tried 18 but didnt work also..If I remove the hook usage everything works perfect.. Is there something else which I miss?

Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.

u used react hooks and load two react instance.

  1. check whether u forget to share react both host and remote app
  2. check whether u need advanced MF api,when u load module at runtime(if u do not load remote.js at html)

Yes I understand that but I already did it without singleton, with singleton, with eager, without nothing ( just specified in a array ) but nothing works.Here are my current webpack files:
Host webpack || Plugin app webpack
This is my hook usage in the component that I expose -> Component using hooks
Is there something else that should be done?

Edited:
I thought it can be from the node version as I saw here https://github.com/webpack/webpack/releases -> I am using the >10.13 version now. I tried with newest or 12.x version but its the same issue
I tried running a few examples from your example pages and non of them works so the issue is probably something globally.. can you tell me which node version is recommended?
++ I checked the versions with the dashboard plugin which is super cool and everything also si matching
Screenshot 2020-06-19 at 16 04 01
Screenshot 2020-06-19 at 16 09 12

Docs on eager imports and troubleshooting common errors. Have a standing PR open to webpack docs as well.

https://link.medium.com/cy2oWOfPs7

If you’re having hooks issues. It’s likely that you are not sharing some other library that uses hooks or you’re async loading some specific hook. Got a repo? I’ve fixed this countless times

You have singleton but try putting a dynamic import at the entrypoint. Check my examples, multiple ways to configure it. https://github.com/module-federation/module-federation-examples

How would applications receive Typescript definition files of used containers?

I imagine there has to be some internal package registry for the type definition files, so the IDE and the build process can access them for autocompletion and type checking.

I only found this example, but it just inlined the d.ts file:
https://github.com/module-federation/module-federation-examples/tree/master/typescript/app1/src

@fabb see this issue

I've run into a weird situation with deferredModules in beta.18 where an additional entry is being added (webpack_sharing_provide_default_react-dom-webpack_sharing_provide_default_react). This causes a blank screen because main.js is waiting on this deferred module to be loaded, but nothing ever loads it. I've prepared a (not-so-minimal, apologies) repro here: https://github.com/NMinhNguyen/webpack-mf-dep-issue. This issue is not present in beta.17 (or older).

This is usually resolved by using require(package.json).dependencies in shared. This does seem a little quirky though. @sokra got some thoughts?

This is usually resolved by using require(package.json).dependencies in shared.

I tried in https://github.com/NMinhNguyen/webpack-mf-dep-issue/commit/7428717c0d45921eb536bc9ffd76ab20525c5231 but it made no difference.

@jherr is this similar to the HMR issue you were seeing sporadically happen?

@NMinhNguyen Try adding a bootstrap.js file as your entry and then dynamically importing your original entry from there

@NMinhNguyen Try adding a bootstrap.js file as your entry and then dynamically importing your original entry from there

How is that different to what’s in my repro currently?

@NMinhNguyen seem to be a bug in the jsonp handling for chunks without js code

@sokra ah thanks. I’ll experiment with removing .css imports and separately I’ll do a git bisect from 17 to 18 to see what commit introduced the bug and report back.

Interesting, this would also explain my conditions. However I’ve been able to circumvent it within my own examples. Wondering if execution order has any impact on this?

@NMinhNguyen seem to be a bug in the jsonp handling for chunks without js code

Ok it seem to be something else. Very weird that it only happens on second build. Enabled Persistent Caching would explain it, but it's not enabled...

Investigating further...

@NMinhNguyen seem to be a bug in the jsonp handling for chunks without js code

Ok it seem to be something else. Very weird that it only happens on second build. Enabled Persistent Caching would explain it, but it's not enabled...

Investigating further...

Ok there seem to be 2 bugs your repro.

  • chunks without js are not handled correctly. This causes the repro to break.
  • providing shared modules looses it's info when modules are unsafe cached by the NormalModuleFactory. This fixes the repro after a rebuild.

@sokra many thanks! From my own git bisect, it seems as though https://github.com/webpack/webpack/commit/3df380e2d788ada96febb012c15fcc5121f0c5f9 introduced the second compilation bug but let me do some more digging to confirm for sure. But actually whilst doing git bisect, I also noticed that sometimes I would get a blank screen on first compilation and it seemed to be due to deferredModules containing a different entry on first compilation (i.e. webpack_sharing_provide_default_material-ui_styles_esm_StylesProvider-webpack_sharing_provide-0ce337 and not webpack_sharing_provide_default_react-dom-webpack_sharing_provide_default_react).

Update

Okay, so https://github.com/webpack/webpack/commit/531f7b47f624250c88851b198795c71c7b300f3c appears to be the last working commit, and then the one straight after (https://github.com/webpack/webpack/commit/3df380e2d788ada96febb012c15fcc5121f0c5f9) is what causes a blank screen on the very first compilation due to webpack_sharing_provide_default_material-ui_styles_esm_StylesProvider-webpack_sharing_provide-0ce337 being added to deferredModules. I'll dig some more to find at what point the second compilation issue arises.

Update 2

Reintroducing chunkCondition code from https://github.com/webpack/webpack/commit/3df380e2d788ada96febb012c15fcc5121f0c5f9#diff-edd6c4e992f08bece638e61e23a9fbbf seems to fix the issue in that commit, however I'm not sure what it means and whether I'm breaking something else by doing that.

No worries I have both bugs already fixed locally. Will push that soon.

11085

11086

@sokra this is amazing work! I've tried master + those 2 PR's and they've fixed my repro. Thank you so much for the quick turnaround.

@jherr is this similar to the HMR issue you were seeing sporadically happen?

Is HMR actually working yet?

@sokra I’ve been having some challenges with advanced remote apis.

remotes: {
app2: { external: "app2@http://somewhere.com/remoteEntry.js" },
},

Does not attach a remote automatically. Can you confirm the correct syntax for self loading remote entries?

@sokra @ScriptedAlchemy

We have a situation where we are trying to import reducers in our host application from our various remotes (they expose the reducers in their Webpack configs) so we can register them all in the store before we attempt to render anything (dealing with legacy code where this is necessary):

remote1Reducers: () => import('remote1/reducers')
remote2Reducers: () => import('remote2/reducers')

In dev mode everything is working okay, but in production we’re running into race conditions where the reducers imported above don’t seem to have access to all the files they need and a result the import fails (happens intermittently).

In all the examples I’ve seen, I see a bootstrap file created for a given remote, where the host imports that file using react.lazy. However in all those examples it assumes it’s a react component being imported and that you can place it in a suspense component.

So how can I import something that is exposed from a remote that is not a react component and ensure that all of its dependencies are loaded first (I.e. that hoisting works appropriately)?

Or do you have to load the entire remote (instead of something it exposes) first before attempting to use it to ensure hoisting works properly?

@jablonnc could you try using the master branch? Sounds like the bug I have already fixed.

@ScriptedAlchemy check if you are using the correct remoteType. It defaults to "script" but only if you haven't specified a library. Best omit library anyway.

@sokra Yeah I will try that out as soon as I have a chance, would love if that were the issue (currently on .17). Thanks for the quick response!

Ok if you are on .17 it's not the issue. The bug I was talking about is only in .18. But try anyway^^

@sokra Thank you!

Also, this is unrelated to Webpack, to some degree. But we have ported part of MF over to rollup, which enables rollup to consume code from Webpack federated systems. (right now rollup only works in host mode)

Still early days, but we have some plans to enable rollup to expose modules back to webpack for consumption. (bi-directional hosts & remote mode)

Conversations can carry on here, for those who are interested. Keeping webpack thread on webpack based discussions. https://github.com/module-federation/rollup-federation

@sokra

@protoEvangelion and I are getting the following error after a page has been loaded for a while, any thoughts on what's causing this?

Screen Shot 2020-06-29 at 5 24 19 PM

Screen Shot 2020-06-29 at 5 23 58 PM

@jablonnc are you on beta 20?

@jablonnc are you on beta 20?

@ScriptedAlchemy We're on .17, I can try .20 if you think that'll help.

(coming here from https://github.com/webpack/webpack/issues/2933)

How is this different from using native ES Modules? The Module Federation website doesn't say anything about this.

What I do know is we write ES Modules _syntax_ in our code, but the runtime is not native ES Modules (but something similar, an alternative?).

Webpack 5's innovative new live code sharing mechanism

Say goodbye to divergent styles and duplicate components throughout parallel teams.

Gone are the days of updating each consuming application after making a change to a shared NPM package.

As you change routes and move through an application, it loads federated modules in the same way you would implement dynamic imports.

I didn't dive deep, but that all sounds exactly like what native ES Modules solves.

Here's an example that imports an "independently deployed" piece of code using native ES Modules:

import {foo} from 'http://foo.com/path/to/foo.js'

Would someone mind to shed light on what extra features Module Federation has that ES Modules don't?

One big reason we can't use ES Modules in our usecase is that it doesn't support bundling multiple modules into a single file request, so it is impractical if you have hundreds of modules to load.

That's what I thought too. I assume Module Federation needs to be backed by a server to do that. If we have a server, we can multiplex ES Modules too right? I imagine an multiplexing ES Module server could also cache results for some time (or until manually told to cache bust).

I guess what I mean is, if we're gonna have a server, why not make a multiplexing ES Module server instead? What is the difference? Would it still be slower than a Module Federation server?

Here's V8's section on multiplexing: https://v8.dev/features/modules#http2

It mentions there a downside:

Unfortunately, HTTP/2 server push is tricky to get right...

Is it possible to get it right? And if so, would that be a viable alternative to Module Federation?

cc @addyosmani and @mathiasbynens (authors of the article). Any thoughts?

@trusktr you might find the comparisons here of interest https://github.com/sokra/slides/blob/master/content/ModuleFederationWebpack5.md

All i know about MF's libraryTarget valid value is var | system, any other ? we need umd actually, but i tried under beta-16.0, it not works.👻

@trusktr There are two major selling points of MF compared to other solutions.

Importing remote modules is not one of them. That's comparable easy with other solutions too, and it has been done in the past.

The first point is the shared modules system. It's a easy and automatic way of sharing modules in the whole system. It understands semver to deduplicate modules, and is aware of singleton modules that must only exist once.

The second point is performance. Even without compile-time knowledge of the other builds it's able to achieve the webpack standard of a single roundtrip per import(). Modules are bundled into chunks for better compression and less requests. Roundtrip-wise this is comparable with a single build solution, except for the initial page load which requires 1 additional roundtrip, resulting in the total of 2 round-trips after the HTML has been loaded (inlining would help, but affects caching negatively). All output files are long term cachable, except the entry to the build that can't be cached as it would prevent new deploys. This could be the small container entry file or a redirect to it.

It's probably also possible to craft a solution on top of ESM. But the ideas I have lack behind this solution:

Most ESM based solutions have an waterfall loading problem which result in too many requests.

Too small modules (not bundled) have an request overhead and bad compression problem. So you need to bundle your esm too.

To handle semver deduplicating you could offer an server side api that redirects, which has an extra roundtrip and is not cachable. Or you could generate a imports map, which either requires whole system knowledge or a server component.

To parallelize the waterfall you need whole system knowledge, so you probably need a runtime which hooks into import(). A Server-Side api would also work.

In general I like that MF doesn't depend on additional server side components and is only static assets. This simplifies the required infrastructure.

I also like that in MF each build can be deployed separately from others without extra step to generate something with whole system knowledge. This might be useful when one build is deployed by a 3rd party and you don't know when it's deployed.

@zhangwilling technically all values for libraries are possible.

Using umd doesn't mean you can use it in node. You also need to use the correct target.

But with umd you should be able to use it with require.js. Not sure why you want this...

Yeah. All supported webpack targets are possible to work as long as webpack is correctly configured

@sokra ive been thinking about when the ESM spec is more stable, we could leverage the loading mechanics, possibly.

Webpack remains in charge of orchestrating chunking and so on. But we have a more native loading system.

Would webpack be able to streamline the use of ESM and reduce the RTT?

As of right now, I don’t consider the specification stable enough. There’s a lot of quirks. Companies have attempted to use it and had to go with MF for Several reasons

How are nested modules meant to work in the latest betas? I used to be able to specify

shared: [
  '@material-ui/styles/esm/StylesProvider',
  '@material-ui/styles/esm/useTheme/ThemeContext',
]

so I thought I'd convert it to

shared: [
  { '@material-ui/styles/esm/StylesProvider': { singleton: true } },
  { '@material-ui/styles/esm/useTheme/ThemeContext': { singleton: true } },
]

but it doesn't seem to be working. I'm essentially trying to share https://unpkg.com/@material-ui/styles@4/esm/StylesProvider and https://unpkg.com/@material-ui/styles@4/esm/useTheme/ThemeContext because they resolve to modules that use React.createContext() and are meant to be singletons.

Are you sharing react as well?

Yup, sorry react and react-dom are successfully shared via

shared: [
  { '@material-ui/styles/esm/StylesProvider': { singleton: true } },
  { '@material-ui/styles/esm/useTheme/ThemeContext': { singleton: true } },
  { react: { singleton: true } },
  { 'react-dom': { singleton: true } },
]

but I didn't think they were necessary to include in the above comment because they do get separate chunks, whereas @material-ui/styles/esm/StylesProvider and @material-ui/styles/esm/useTheme/ThemeContext do not.

Are they referenced this way, like import "@material-ui/styles/esm/StylesProvider"?

Btw I wonder why you are using such a weird syntax? It equal, but this looks nicer:

shared: ​{​
 '@material-ui/styles/esm/StylesProvider'​: ​{​ ​singleton​: ​true​ ​}​,​
 '@material-ui/styles/esm/useTheme/ThemeContext'​: ​{​ ​singleton​: ​true​ ​}​ ,​
  ​react​: ​{​ ​singleton​: ​true​ ​}​,​
  ​'react-dom'​: ​{​ ​singleton​: ​true​ }
​}

Only because I was converting from an array before 😛 But thanks, I'll update it.

Are they referenced this way, like import "@material-ui/styles/esm/StylesProvider"?

No, I think it's actually referenced via ../StylesProvider inside @material-ui/styles: https://github.com/mui-org/material-ui/blob/7e1da61/packages/material-ui-styles/src/makeStyles/makeStyles.js#L6 and the way I'm importing it is via import CssBaseline from '@material-ui/core/CssBaseline'; in bootstrap.js

but let me come up with a repro so you can play around with it.

Do containers store any metadata about the packages included/available? I'm trying to write some custom functionality that could use something like a list of dependencies available in a container, or maybe a list of dependencies which were loaded by the container. If yes, I'd really appreciate a pointer in the right direction, thanks!

but let me come up with a repro so you can play around with it.

https://github.com/NMinhNguyen/webpack-mf-dep-issue/commits/mui

Check the last two commits here: working shared in beta.16 and broken in beta.20. (Actually, there's 3 commits now, but the third one is mostly just cosmetic)

You'll actually notice two issues with beta.20. One of the most obvious ones is probably related to HMR (again): Cannot read property 'push' of undefined. Although sometimes you need to make a code change anywhere and hit save for it to happen. And the second one is that there doesn't appear to be any sharing of that module. If you put a breakpoint in https://github.com/mui-org/material-ui/blob/7e1da61/packages/material-ui-styles/src/makeStyles/makeStyles.js?rgh-link-date=2020-07-01T18%3A10%3A09Z#L6 and compare beta.16 and beta.20 you should see the diff:

// beta.16
/* harmony import */ var _StylesProvider__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../StylesProvider */ "webpack/container/overridable/@material-ui/styles/esm/StylesProvider=../StylesProvider");

// beta.20
/* harmony import */ var _StylesProvider__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ../StylesProvider */ "./node_modules/@material-ui/styles/esm/StylesProvider/StylesProvider.js");

Thanks for explaining you all. This is definitely an interesting neat concept.

MF doesn't depend on additional server side components and is only static assets

If the files are static, how can it get rid of duplicate shared code (dependencies that an app may already have)? I presume a third party code provider has to have all dependency code available for whoever may import it. How can a static asset decide, for example, if React should be included in the payload or not?

Does the build output (the statically served files) consist of many bundles with permutations of needed dependencies from no dependencies to all dependencies? And how semver work with static single-file downloads; more permutations?

It will split the bundle into multiple files (chunks) so it only have to load the files it needs. So if react is a shared modules it will be put into a separate file together with the depenendencies. This happens for each build. In the end each build will have a separate file for react and they may have react in different versions.
During initialization each build will provide the info about which version of react it provides together with a way to load this version.
When a build wants to load react it selects the highest version from the list of provided modules that satisfy the requested version range, and loads it.

@ScriptedAlchemy @sokra, we narrowed down the problem @jablonnc was having with LoadScriptRuntimeModule.js trying to remove a script from the head when it's already been removed.

It was throwing the error cannot ready property 'removeChild' of null here: https://github.com/webpack/webpack/blob/master/lib/runtime/LoadScriptRuntimeModule.js#L134

It is related to the babel runtime. Notice two apps are chunking out the same exact file name:

image

_Should this be a simple fix in webpack like null checking the script before trying to remove it? Or do you think it is a deeper issue?_

How are nested modules meant to work in the latest betas? I used to be able to specify

shared: [
  '@material-ui/styles/esm/StylesProvider',
  '@material-ui/styles/esm/useTheme/ThemeContext',
]

so I thought I'd convert it to

shared: [
  { '@material-ui/styles/esm/StylesProvider': { singleton: true } },
  { '@material-ui/styles/esm/useTheme/ThemeContext': { singleton: true } },
]

but it doesn't seem to be working. I'm essentially trying to share https://unpkg.com/@material-ui/styles@4/esm/StylesProvider and https://unpkg.com/@material-ui/styles@4/esm/useTheme/ThemeContext because they resolve to modules that use React.createContext() and are meant to be singletons.

shared is in object instead of array. try this:

shared: ​{​
 '@material-ui/styles/esm/StylesProvider'​: ​{​ ​singleton​: ​true​ ​}​,​
 '@material-ui/styles/esm/useTheme/ThemeContext'​: ​{​ ​singleton​: ​true​ ​}​ ,​
  ​react​: ​{​ ​singleton​: ​true​ ​}​,​
  ​'react-dom'​: ​{​ ​singleton​: ​true​ }
​}

@brendonco

If you check my repro, it’s already written using the proposed syntax. @sokra proposed it first btw, but said it shouldn’t matter

Instead of manually adding shared dependencies. You can automate the process:

const deps = require("./package.json").dependencies;

const dependencies = Object.keys(deps).reduce((acc, key) => ({
  ...acc,
  [key]: {
    singleton: true,
    requiredVersion: deps[key]
  }
}), {});

shared: {
  ...dependencies,
 "overwrite-your-dep-key": {
   singleton: true,
   requiredVersion: deps["overwrite-your-dep-key"]
}
}

Hello all,

I've been fiddling around for days now but cant get HMR (hot module reloading) to work with my setup. I even had a look at the federation-examples but was unable to translate that to my setup.

My setup is the following:

  • A host app dynamically importing one of the remotes at a time through React.lazy and react-router

  • The remotes are NOT stand alone. I am directly importing he components they expose

The setup itself works fine but as I said I can't get HMR to work with react-hot-loader. I even got to the point where it would correctly load in the changed component but then would not re-render it. There is no error thrown.

Is this even possible in the current stage of implementation? What would be the correct solution?

Regards

I don't think it's a good idea to make all your deps singletons...

@sokra sorry to be bumping my issue, but is there anything else you need from me in order to debug the repro? Was my explanation clear?

I don't think it's a good idea to make all your deps singletons...

Yes, agreed. It's why I'm actually targeting just a handful of modules (using deep imports) with React.createContext() because @material-ui has a lot of components and I wouldn't want to load them all.

@NMinhNguyen the keys in shared specify requests that should be shared. They are only shared if you request them this way. So for shared: ["foo/bar"] you need to import "foo/bar". If you import "foo/index" and that import "./bar" its not shared. And that's good this way. That would be a way to shoot yourself into the foot. Consider package foo changes how bar works (it's an internal module) and publish a patch version. Another container provides this higher version. Now your old foo package would have it's internal bar file replaced with a new version that works different.

That why this is not the default behavior for module requests. Let's say beta.16 was broken here. We are still in a learning process about MF.

We want to provide a system that is pretty safe to use and protects you from problems by default.

Sadly you can't share the complete material-ui package as this disables tree-shaking, which is pretty important here. We don't have a solution for that yet. One idea is to opt-in into per-export sharing, but this still brings problems, like multiple ThemeProviders. Maybe packages need to be designed with sharing in mind to allow solving that in a clean way, e. g. the ThemeProvider could be a separate package which can be shared a singleton.

Anyway for now use an absolute path as key in shared to share that internal module. shared: { [require.resolve("foo/bar"]: { singleton: true, requiredVersion: require("foo/package.json").version } }

@sokra Thank you so much! So essentially, beta.16 was doing the resolution for me, but now I have to do it myself. Not necessarily a bad thing because a) it solves the safety problem you outlined, b) I have a way forward - just gotta be explicit about the module.

Actually, just tried require.resolve and ran into this:

Conflict: Multiple assets emit different content to the same filename static/js/node_modules_material-ui_styles_esm_StylesProvider_index_js.chunk.js.map
Conflict: Multiple assets emit different content to the same filename static/js/node_modules_material-ui_styles_esm_useTheme_ThemeContext_js.chunk.js.map

@ScriptedAlchemy @sokra, we narrowed down the problem @jablonnc was having with LoadScriptRuntimeModule.js trying to remove a script from the head when it's already been removed.

It was throwing the error cannot ready property 'removeChild' of null here: https://github.com/webpack/webpack/blob/master/lib/runtime/LoadScriptRuntimeModule.js#L134

It is related to the babel runtime. Notice two apps are chunking out the same exact file name:

image

_Should this be a simple fix in webpack like null checking the script before trying to remove it? Or do you think it is a deeper issue?_

I am having the same issue.. Is there a workaround or any solution to it? I am using the beta.20 and the dynamic imports used in the examples..

@ScriptedAlchemy @sokra, we narrowed down the problem @jablonnc was having with LoadScriptRuntimeModule.js trying to remove a script from the head when it's already been removed.
It was throwing the error cannot ready property 'removeChild' of null here: https://github.com/webpack/webpack/blob/master/lib/runtime/LoadScriptRuntimeModule.js#L134
It is related to the babel runtime. Notice two apps are chunking out the same exact file name:
image
_Should this be a simple fix in webpack like null checking the script before trying to remove it? Or do you think it is a deeper issue?_

I am having the same issue.. Is there a workaround or any solution to it? I am using the beta.20 and the dynamic imports used in the examples..

@brussev make sure to set a uniquename in your output (referenced here https://webpack.js.org/configuration/output/) for all your remotes and host.

@ScriptedAlchemy @sokra, we narrowed down the problem @jablonnc was having with LoadScriptRuntimeModule.js trying to remove a script from the head when it's already been removed.
It was throwing the error cannot ready property 'removeChild' of null here: https://github.com/webpack/webpack/blob/master/lib/runtime/LoadScriptRuntimeModule.js#L134
It is related to the babel runtime. Notice two apps are chunking out the same exact file name:
image
_Should this be a simple fix in webpack like null checking the script before trying to remove it? Or do you think it is a deeper issue?_

I am having the same issue.. Is there a workaround or any solution to it? I am using the beta.20 and the dynamic imports used in the examples..

@brussev make sure to set a uniquename in your output (referenced here https://webpack.js.org/configuration/output/) for all your remotes and host.

In general when you put multiple webpack build on the same page they need to have different uniqueNames. It defaults to name in package.json, so best put unique names there.

This limitation has its root in the jsonp chunk loading which needs a global in the document for the callback. Once we would use a esm based chunk loading this would no longer be a limitation.

But note that an open PR makes a change in MF so it will also use the uniqueName to resolve conflicts between multiple builds that are providing the same version of a shared module in a deterministic way.

Actually, just tried require.resolve and ran into this:

Conflict: Multiple assets emit different content to the same filename static/js/node_modules_material-ui_styles_esm_StylesProvider_index_js.chunk.js.map

Conflict: Multiple assets emit different content to the same filename static/js/node_modules_material-ui_styles_esm_useTheme_ThemeContext_js.chunk.js.map

@sokra do you have any advice here? It’s from a single webpack build, reproducible in https://github.com/NMinhNguyen/webpack-mf-dep-issue/commits/mui once you apply the require.resolve change.

Separately, it should also be a good reproduction for HMR issues:

  1. add a comment anywhere or add a console.log or whatever
  2. save the file
  3. the browser should auto-refresh the page and display an error

Module federation examples has been updated to beta 22 - standing PR as there's one last app i want to upgrade still. Special thanks to @sokra for resolving the beta 21 but in record time - dynamic remotes are no longer impacted when attaching to share scope at runtime

https://github.com/module-federation/module-federation-examples/pull/180

Is there an explicit reason we can't share the source package?

Use case (for JupyterLab): a package that is both an extension provider and a library (provides something that another extension provider can import).

Example repo: https://github.com/blink1073/module-federation-share-self

I would like to share a named module that is the same as the current package.

However, only react gets a register() call in the resulting remoteEntry.js.

@blink1073 I think it should work this way:

shared: {
  "./index": {
    shareKey: "module-federation-share-self",
    version: require("./package.json").version,
    singleton: true
  },
}

but I'm not sure if this makes sense and how it behaves if a module is shared and exposed.

Thanks @sokra! I'll play with that and report back.

No luck:

Version: webpack 5.0.0-beta.22
Time: 238 ms
Built at: 2020-07-22 06:53:25
                                 Asset       Size
                       bootstrap_js.js   1.58 KiB  [compared for emit]
                            index.html  183 bytes  [compared for emit]
                           index_js.js   1.23 KiB  [compared for emit]
                               main.js   23.9 KiB  [compared for emit]  [name: main]
                        remoteEntry.js   25.2 KiB  [emitted]            [name: module-federation-share-self]
vendors-node_modules_react_index_js.js   73.4 KiB  [compared for emit]  [id hint: vendors]
Entrypoint main = main.js
Entrypoint module-federation-share-self = remoteEntry.js
consume shared module (default) module-federation-share-self@* (singleton) (fallback: ./index.js) 42 bytes [built]
provide shared module (default) [email protected] = ./index 42 bytes [built]
provide shared module (default) [email protected] = ./node_modules/react/index.js 42 bytes [built]
container entry 42 bytes [built]
./index.js 23 bytes [built]
./node_modules/react/index.js 190 bytes [built]
./node_modules/react/cjs/react.development.js 59.2 KiB [built]
./bootstrap.js 48 bytes [built]
./node_modules/object-assign/index.js 2.06 KiB [built]
./node_modules/prop-types/checkPropTypes.js 3.69 KiB [built]
consume shared module (default) react@^16.13.1 (singleton) (fallback: ./node_modules/react/index.js) 42 bytes [built]
./node_modules/prop-types/lib/ReactPropTypesSecret.js 314 bytes [built]
    + 22 hidden modules

ERROR in Conflict: Multiple assets emit different content to the same filename index_js.js

ERROR in Conflict: Multiple assets emit different content to the same filename bootstrap_js.js

You're right in that this might not make sense at all and lead to conflicts. I have a workaround for our use case, since we are auto-generating these bundles for JupyterLab extension authors:

  • Create a new npm package on the fly
  • Use the metadata and dependencies from the source package
  • Have it link to the source package and share its index and also bootstrap its extension entry point
  • Run the webpack build against this new package

@ScriptedAlchemy any implementation sample for Vuejs? I have tried to put together but no luck https://github.com/anish2690/mfe-webpack-demo-vue

I'm running into Uncaught Error: Shared module is not available for eager consumption.

I've read the documentation here, but i don't understand a word. What are _eager consumption_ or _eager execution_? Where are these defined/explained? What are alternatives to eager consumption and/or execution? What are the benefits/drawbacks of each?

Why does setting shared dependencies to eager: true help?
What is an asynchronous boundary and why would i need one? Why would this help in this case?

I'm really struggling to understand the details and would love to have simple diagrams for people who are not knee-deep into either webpack or module federation internals. I love the idea and we're looking to implement this on a microfrontend setup with multiple scrum teams, but i think most of the documentation and articles are very hard to read and understand.

I've even had @ScriptedAlchemy explain parts of this to me in a video call - to no avail.

Edit: The whole documentation page on module federation doesn't use the word "eager" once - not until the troubleshooting section. This is problematic. Also, i couldn't find API docs on the current API that would explain what setting eager to true for a shared dependency will do (and more info about this setting, e.g. default value, which i assume to be false).

@ScriptedAlchemy / @sokra I'm running into a real headscratcher here. I have looked through most of the examples in https://github.com/module-federation/module-federation-examples to see if I can find a solution, and I'm still stumped.

My issue is that from loading the main app, it takes 2 minutes to kick off the bootstrapping.

I have 4 apps in the following configuration:

  • app-container (host app)
  • home (page app)
  • search (page app)
  • nav (library/component app)

app-container uses react-router-dom to display either home or search based on the url. Both home and search use the external export from nav.

If I comment out either route for search or home, OR I remove the React.lazy import of nav from search or home - it loads immediately. The problem seems to be originating from both home and search using the same exposed component "nav".

I ran the performance profiler in my browser, and no processing is taking place during the 2 minutes.
Have you encountered anything like this?

Stuck what I had on git for reference: https://github.com/jandvik/webpack-module-federation-issue

To make it load fast, simply comment out lines 10-12 in packages/search/src/App.tsx
OR comment out line 31 in packages/app-container/src/App.tsx

Thank you for any input!

@jandvik It's a webpack bug.

The script loading attaches a load event but overrides the load event callback from the other remote.

If you want to send a PR see load script runtime module and make sure to keep the old callbacks.

Thank you @sokra - I'll look into and see if I can provide a fix in the next few days 👍

@jandvik Let's open a new issue to avoid to lose the problem

@sokra @evilebottnawi - logged my issue here https://github.com/webpack/webpack/issues/11247
Working on setting up local dev env for webpack to take a crack at it.

@sokra - potential share scope issue: https://github.com/webpack/webpack/issues/11256

Update: not a bug

@sokra - shared library issue. using beta20. Havent tested in beta22 whether this is fixed.

  1. Entry point container with "x library" version 1.0
  2. App2 with "x library" version 1.1.

If I load App2, I get "x library" version 1.0.

Question here regarding the shared property. in the precursor to this (webpack-external-import), the API was 'provideExternals'(in the provider) and 'useExternals'(in the consumer). I couldn't make it to work with webpack's externals, [array syntax].
(https://webpack.js.org/configuration/externals/#combining-syntaxes), like this:

module.exports = {
...
// ❗ : this I can't do with webpack-external-import provideExternals/useExternals
// can we do this with the shared property ?
  externals: [
    {
      // String
      react: 'react',
      // Object
      lodash : {
        commonjs: 'lodash',
        amd: 'lodash',
        root: '_' // indicates global variable
      },
      // [string]
      subtract: ['./math', 'subtract']
    },
    // Function
    function(context, request, callback) {
      if (/^yourregex$/.test(request)){
        return callback(null, 'commonjs ' + request);
      }
      callback();
    },
  ]
};

My usecase:

There are some:

  • libraries that I want the provider to share with the consumer,
  • as well as some modules(@local-packages/**) that are imported from the provider, to not bundle them with the consumer since they are already in the host. I'd like the imports from @local-packages/** to not have them bundled in the consumer, and the provider to expose them for the consumer.

example usecase:

// provider-app.js
// I want this to export a shared library - 'xxx-web-v2'
// I also want to export a ```store``` and some other libraries that are bundled with the provider - e.g. ```redux-requests```


// consumer-app.js
// here i can i use it like this 
import { LitElement } from 'xxx-web-v2'
// and I'd like to consume them from the child 
import { store } from 'provider-app';
// this isn't available in the consumer-app 
// but I can do this since it's exposed by the parent app
import { requests } from 'redux-requests'
// example webpack.config.js
shared: [
            {
              appConfig: '__APP_ENVIRONMENT',
              'xxx-web-v2': 'xxx-web-v2',
            },
            // this is the webpack-node-externals plugin
            nodeExternals({
                 importType: 'umd',
                 additionalModuleDirs: ['../../node_modules'],
            }),
            ❗ This is what I'm curious if it works with the current approach
            function (context, request, callback) {
                if (/^@local-packages/.test(request)) {
                 return callback(null, 'umd ' + request);
               }
               callback();
             },
          ],

From what I read, I can't tell if this is covered/ possible

minimal reproduction

forked a repo to illustrate this.

https://github.com/mihaisavezi/federated-libraries-get-started/tree/issue/use-case-host-remote

@blink1073

https://github.com/blink1073/module-federation-share-self

Is there an explicit reason we can't share the source package?

Use case (for JupyterLab): a package that is both an extension provider and a library (provides something that another extension provider can import).

Example repo: https://github.com/blink1073/module-federation-share-self

I would like to share a named module that is the same as the current package.

However, only react gets a register() call in the resulting remoteEntry.js.

Have you tried just sharing it and not exposing it ?

...
shared: ['xxx-other-package', {
        'self': {
          import: './src/index',
          shareKey: "module-federation-share-self",
        }
      }],

If you have multiple exports in that entry file you'll likely get the error
ERROR in Conflict: Multiple assets emit different content to the same filename but it does work, at least in the poc I made

:warning: In this case from what i see, the relationship possible is only parent -> child; I couldn't get the child-app to start on its own, just when being loaded in the parent.

@brendonco I found an issue with the semver implementation in the runtime builds which was fixed in beta.24 (#11271). I'd be interested to know if this is fixed for you now?

Is there a way to set the webpack publicPath to a dynamic value (like in https://webpack.js.org/guides/public-path/#on-the-fly, i.e., determined in the browser) in the remoteEntry.js file generated by module federation?

@brendonco I found an issue with the semver implementation in the runtime builds which was fixed in beta.24 (#11271). I'd be interested to know if this is fixed for you now?

@mpeyper looks fixed, I'm using beta.25. Will continue to monitor.

Hi,
I don't know if this is the correct place for my question.

I experimented today with webpack 5 and Module federation and stuck at one point.

I have an npm library in my monorepo which hold our shared components, based on MaterialUI. This is mainly used in our ui package.
When I'm loading components with hooks via Module federation I recieve the typical "don't use hooks with two React Versions" error.

Of course the react library is loaded from the components repository.
Is there any way to declare existing dependencies in an pure host - remote environment?

@42tg - I think what you are looking for is "peerDependencies" https://docs.npmjs.com/files/package.json#peerdependencies

If you set React as the peerDependencies in your npm library, it should use the host applications React version and avoid that collision.

If you are having two Module Federations both using React, you can use the "shared" configuration in the ModuleFederationPlugin as shown in this "shared-context" example: https://github.com/module-federation/module-federation-examples/blob/master/shared-context/app1/webpack.config.js

@42tg, to expand on what @jandvik said, I've also been playing with it recently and with react, I found it handy to set the singleton option to true as well as making it shared.

Unrelated to the above. I've been working on using module federation in conjunction with with systemjs and import maps similarly to this example by @joeldenning, and I'm loving the ability to use webpack for the chunking and integration side and systemjs/import maps for the resolution and fetching side and the seperation is proving the be very nice.

My only question mark over the whole thing is actually how they know to work together at all. How is it when the remotes are set to the keys in my import map does webpack know to use systemjs to resolve it? Is it because of the library: { type: "system" } option?

@mpeyper
Currently my SystemJS Implementation which loads the UI (which tries to load the components by Module Federation) blocks me of declaring the shared option in my UI Package, because I never got it to work with Webpack Chunks.
Thanks for the Link to @joeldenning 's repository maybe I find there an solution to bring it all together.

Is there a way to set the webpack publicPath to a dynamic value (like in https://webpack.js.org/guides/public-path/#on-the-fly, i.e., determined in the browser) in the remoteEntry.js file generated by module federation?

To expand on my question:

  1. The documented way to have a dynamic public path is to set the __webpack_public_path__ magic variable in the entry point. However, for remoteEntry.js, there is no one single export, and by default the exports are loaded dynamically, so the public path is needed before the export is even loaded to be able to load the right file.
  2. I found one sort of kludgy workaround, documented very briefly in the context of our project at https://github.com/jupyterlab/jupyterlab/issues/8827#issuecomment-673773549). Basically, if we make an eager shared module, the code is included in the remoteEntry.js, so it has a chance of running before we try to load any other jsonp bundles. If we make this shared module also an export in the module federation config, we can call it directly. But then you have to make sure to load that particular export before requiring any other thing from the remoteEntry module to set the right public path, and it relies on the shared module not getting overridden or discarded when merging with other shared modules that might be loaded.

Is there a way to set the public path in the remoteEntry.js so that it can dynamically calculate the value in the browser? If not, can we make a way?

Edit: clarified that we don't have 'entry points', but rather 'exports' in remoteEntry.js

@mpeyper
Currently my SystemJS Implementation which loads the UI (which tries to load the components by Module Federation) blocks me of declaring the shared option in my UI Package, because I never got it to work with Webpack Chunks.
Thanks for the Link to @joeldenning 's repository maybe I find there an solution to bring it all together.

Finally got SystemJS updated to 6.4.0 and all upcomming issues are resolved...
But nevertheless I don't get it running completely and fail with the following error:

Uncaught ReferenceError: __WEBPACK_EXTERNAL_MODULE_webpack_container_reference_pcweb_gui__ is not defined
while loading "./PrimaryButton" from webpack/container/reference/pcweb_gui

when calling it this way:
const PrimaryButton = React.lazy(() => import('pcweb_gui/PrimaryButton'));

It seems that the remoteEntry is not loaded properly. Had anyone an advice for me ?

  <script type="systemjs-importmap">
  {
    "imports": {
      "multichannel_sdk": "//localhost:8001/remoteEntry.js",
      "multichannel_sdk/": "//localhost:8001/",
      "pcweb_gui": "//localhost:8080/remoteEntry.js",
      "pcweb_gui/": "//localhost:8080/",
      "pcweb_demo_app": "//localhost:8888/remoteEntry.js",
      "pcweb_demo_app/": "//localhost:8888/",   
      "@multichannel/sdk": "/package.js",
      "@pcweb/gui": "//localhost:8080/package.js",
      "@pcweb/demo-app": "//localhost:8888/package.js"
    }
  }
</script>

@jasongrout, regarding public path and systemjs, check out https://github.com/joeldenning/systemjs-webpack-interop which sets the public path by looking up the URL in the systemjs registry. Generally I work with bundles that only have a single entry point, so I'm not as familiar with how public path works when there are multiple. But generally you need to set the public path as early as possible before any dynamic imports or anything else.

@joeldenning - thanks very much for your response.

From my experiments, I think that doesn't help in the case of remoteEntry.js files (i.e., the main file produced by module federation)? Since there is no single export included in the remoteEntry.js file by default, there is no user code that is automatically run in that file before anything else is loaded using jsonp. Am I wrong? Is there a recommended way to make sure some user code is included in the remote_entry.js file and run before it tries to load any other files from the bundle using jsonp?

Is there a recommended way to make sure some user code is included in the remote_entry.js file and run before it tries to load any other files from the bundle using jsonp?

I'm not sure how to do this. @ScriptedAlchemy or @maraisr may be able to answer.

There is a way: create an entry with the same name as name in the ModuleFederationPlugin. These modules will be loaded when the container entry is loaded.

But this isn't that comfortable so I like to have an init option that list modules that are executed on container initialization. These might even have async code in them.

I have the following situation. I'm using the bi-directional hosts example to illustrate what seems to be a bug or an unsupported use-case.

The issue

  • app1 exposes indexExternal.js as ./hostExports which re-exports some modules from exports.js which exports everything from deps.js and effects.js
  • app2 exposes demo-exports.js as ./remoteExports which is just a constant
  • Search for (1) and (2) in the code.

    • (1) - :warning: here we import from app2/remoteExports and it crashes the app with no error. The import is made in a file that is later re-exported. effects.js is re-exported in exports.js to indexExternal.js, where we only pick-up some of the exports. indexExternal.js is the file that is exposed from app1

    • (2) - here we import from app2/remoteExports in app1. This works, the file is not re-exported or anything similar.

other info

I've read in an article something about re-exports of shared modules (tree-shaking capabilities) is not currently supported. This looks to fall under that category.

Relevant links

There is a way: create an entry with the same name as name in the ModuleFederationPlugin. These modules will be loaded when the container entry is loaded.

Thanks!

But this isn't that comfortable so I like to have an init option that list modules that are executed on container initialization. These might even have async code in them.

I agree that would be better.

There is a way: create an entry with the same name as name in the ModuleFederationPlugin. These modules will be loaded when the container entry is loaded.

Thanks!

@jasongrout - have you tried the suggested method, does it work for you? It doesn't work for me in the production app I'm worknig on. But I'll try in a more simple example

There is a way: create an entry with the same name as name in the ModuleFederationPlugin. These modules will be loaded when the container entry is loaded.

But this isn't that comfortable so I like to have an init option that list modules that are executed on container initialization. These might even have async code in them.

This doesn't work with webpack-dev-server.
See https://github.com/webpack/webpack-dev-server/issues/2692

@jasongrout - have you tried the suggested method, does it work for you? It doesn't work for me in the production app I'm worknig on. But I'll try in a more simple example

It worked great for me. Here it is in our beta version:

https://github.com/jupyterlab/jupyterlab/blob/13c77d38cfc6b021fd206d037da836fb9faad93e/builder/src/webpack.config.ext.ts#L216

I haven't used webpack-dev-server, so can't comment on that.

Hi,

I am trying to build a react application with the below mentioned structure and I am facing an issue for which I need your advice.

1) I have a Landing Page (Host) running at http://localhost:3001/
2) I have a Standalone application app1 running at http://localhost:3002/
3) I have a common header running at http://localhost:3003/
4) The common header is consumed by Landing Page and app1
5) app1 is consumed by Landing page and the routing from landing page to app1 happens through react router.

The routing from landing page to app1 happens smoothly when the same header is consumed by Landing page and app1.

But I have a scenario where I need to keep two separate versions of the header (In one version of the header, I just change the text inside the header and rest all the dependencies remains exactly same between the two headers.)
So I have created two separate versions of the header in the dist folder and both running at http://localhost:3003/.

/dist
/dist/v1
/dist/v2

Now my Landing page is consuming the Header version 1 at http://localhost:3003/v1/remoteEntry.js
and app1 is consuming Header version 2 at http://localhost:3003/v2/remoteEntry.js

Now when I navigate from Landing page to app1 my application is crashing with the following error message.

Uncaught TypeError: fn is not a function
while loading "./Header" from webpack/container/reference/commonv2
at handleFunction (remotes loading:30)
at onInitialized (remotes loading:42)
at handleFunction (remotes loading:35)
at onExternal (remotes loading:41)
at handleFunction (remotes loading:35)
at remotes loading:49
at Array.forEach ()
at Object.__webpack_require__.f.remotes (remotes loading:16)
at ensure chunk:6
at Array.reduce ()

I don't get this error if use header version 1 or header version 2 in both landing and app1.

Kindly help me with this issue and guide me with the better approach for versioning the federated common modules.
Greatly appreciate your help!

Below is my complete application code. Please note that I am using "webpack": "5.0.0-beta.29" and "html-webpack-plugin": "^4.4.1".
------------------------------------------------------Common Header-------------------------------------------------------
Common Header:

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
// const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const deps = require("./package.json").dependencies;
const version = 'v2';

module.exports = {
entry: "./src/index",
mode: "development",
devServer: {
contentBase: path.join(__dirname, "dist"),
port: 3003,
},
output: {
filename: '[name].[hash].bundle.js',
path: path.resolve(__dirname, './dist/'+version),
publicPath: 'http://localhost:3003/'+version+'/'
},
devtool: 'inline-source-map',
module: {
rules: [
{
test: /.(png|jpg)$/,
use: [
'file-loader'
]
}, {
test: /.css$/,
use: [
'style-loader', 'css-loader'
]
},
{
test: /.scss$/,
use: [
'style-loader', 'css-loader', 'sass-loader'
]
},
{
test: /.(js|jsx|mjs)$/,
loader: "babel-loader",
exclude: /node_modules/,
options: {
presets: ["@babel/preset-react"],
plugins: ["@babel/plugin-proposal-class-properties"]
},
},
],
},
plugins: [
//new CleanWebpackPlugin(),
new ModuleFederationPlugin({
name: "common"+version,
library: { type: "var", name: "common"+version },
filename: "remoteEntry.js",
exposes: {
"./Header": "./src/components/Header/Header",
},
shared: {
...deps
}
}),
new HtmlWebpackPlugin({
template: "./public/index.html",
}),
],
};

Public/index.html







/src/components/Header/Header.js

import React from 'react';
import AppBar from '@material-ui/core/AppBar';
import Container from '@material-ui/core/Container';
import './Header.css';

const Header = () => {

return (


Header


);
}

export default Header;

src/app.js

import React, { Component } from 'react';
import Header from './components/Header/Header';

class App extends Component {
render() {
return

;
}
}
export default App;

scr/bootstrap.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Route, Switch, Router } from 'react-router-dom';
import { createBrowserHistory } from 'history';
import { StylesProvider } from '@material-ui/styles';
import './App.css';
import App from './App';

const history = createBrowserHistory();

ReactDOM.render(





,
document.getElementById("root")
);

src/index.js:

import("./bootstrap");

-------------------------------------------------------Common Header END-------------------------------------------------

-------------------------------------------------------app1 start---------------------------------------------------------------
app1:

webpack.config.js

const HtmlWebpackPlugin = require("html-webpack-plugin");
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const path = require("path");
const deps = require("./package.json").dependencies;

module.exports = {
entry: "./src/index",
mode: "development",
devServer: {
contentBase: path.join(__dirname, "dist"),
port: 3002,
},
output: {
publicPath: "http://localhost:3002/",
},
devtool: 'source-map',
module: {
rules: [
{
test: /.(js|jsx)$/,
loader: "babel-loader",
exclude: /node_modules/,
options: {
presets: ["@babel/preset-react"],
},
},
],
},
plugins: [
new ModuleFederationPlugin({
name: "app1",
library: { type: "var", name: "app1" },
filename: "remoteEntry.js",
exposes: {
"./App": "./src/App",
},
remotes: {
commonv2: "commonv2"
},
shared: {
...deps
}
}),
new HtmlWebpackPlugin({
template: "./public/index.html",
}),
],
};

public/index.html









src/App.js

import React, { Component } from 'react';
import { Route, Switch, Router, Link } from 'react-router-dom';
const Header = React.lazy(() => import("commonv2/Header"));

class App extends Component {
constructor(props){
super(props);
}
render() {
return (





);
}
}
export default App;

-------------------------------------------------------app1 End---------------------------------------------------------------

-------------------------------------------------------Landing Page start---------------------------------------------------------------
Landing Page:

webpack.config.js

const HtmlWebpackPlugin = require("html-webpack-plugin");
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const path = require("path");
const deps = require("./package.json").dependencies;

module.exports = {
entry: "./src/index",
mode: "development",
devServer: {
contentBase: path.join(__dirname, "dist"),
port: 3001,
},
output: {
publicPath: "http://localhost:3001/",
},
devtool: 'source-map',
module: {
rules: [
{
test: /.(png|jpg)$/,
use: [
'file-loader'
]
},
{
test: /.css$/,
use: [
'style-loader', 'css-loader'
]
},
{
test: /.scss$/,
use: [
'style-loader', 'css-loader', 'sass-loader'
]
},
{
test: /.(js|jsx|mjs)$/,
loader: "babel-loader",
exclude: /node_modules/,
options: {
presets: ["@babel/preset-react"],
plugins: ["@babel/plugin-proposal-class-properties"]
},
},
],
},
plugins: [
new ModuleFederationPlugin({
name: "cockpit",
library: { type: "var", name: "cockpit" },
remotes: {
commonv1: 'commonv1',
app1: 'app1', },
shared: {
...deps
}
}),
new HtmlWebpackPlugin({
template: "./public/index.html",
}),
],
};

public/index.html










src/App.js

import React, { Component } from 'react';
import { Route, Switch, withRouter, Redirect } from 'react-router-dom';
import './App.css';
import Home from './components/Home/Home';
const NewApp = React.lazy(() => import("app1/App"));

class App extends Component {
constructor(props) {
super(props);
this.state = {
userRole : 'Super User'
}
}

render() {
if (!this.state.userRole) {
return 'Loading your application..'
}
return (



} />



)} />


);
}
}
export default App;

Home.js

import React from 'react';
import Container from '@material-ui/core/Container';
import { withRouter } from 'react-router-dom';
const Header = React.lazy(() => import("commonv1/Header"));

const Home = ({ userRole, ...props}) => {
return (

  <Container maxWidth="lg" style={{ marginTop: '100px' }}>
   <React.Suspense fallback="Loading Header">
        <Header />
   </React.Suspense>
  </Container>
</React.Fragment>

);
}
export default withRouter(Home);

-------------------------------------------------------Landing Page End---------------------------------------------------------------

Greatly appreciate your help!

Thank you,
Raj

I can't tell for sure, but a common problem when duplicating repos is that both have the same name in package.json which results in the same value of output.uniqueName, which causes conflicts during chunk loading as they both use the same globals.
Give each of them a unique output.uniqueName.

@sokra How do we test expose component? e.g. snapshot

Host1:
webpack.config.js

 exposes: {
   './Widget': './src/components/Widget.js'
 }

host2:

ShoppingCart

import Widget from 'name/Widget';   --> this will be undefined in unit test

const ShoppingCart = () => <Widget />;

test

import { render, fireEvent } from '@testing-library/react';
import ShoppingCart from './shoppingCart';

test('ShoppingCart', () => {
 const {container} = render(<ShoppingCart />

expect(container).toMatchSnapshot();

});

Mock it in unit tests

how do you mock this import Widget from 'name/Widget'; --> this will be undefined in unit test

@brendonco depends on what unit test system you are using. In Jest, you can do e.g.

jest.mock('name/widget', () => ({
  myExport: 'blabla',
}));

I dont see this is working @csvan still getting undefined.

test

import { render, fireEvent } from '@testing-library/react';
import ShoppingCart from './shoppingCart';

jest.mock('name/Widget', () => content => content);

test('ShoppingCart', () => {
 const {container} = render(<ShoppingCart />

expect(container).toMatchSnapshot();

});

@brendonco you will need to refer to the docs for Jest or whatever you are using for testing. Mocking is not a webpack feature and your problem is not related to it.

I want to make a package that depends on another package (for example,'three'). When I want to publish the package, I don't include three, but let the user download it. Is there any way?

@brendonco you will need to refer to the docs for Jest or whatever you are using for testing. Mocking is not a webpack feature and your problem is not related to it.

That's true, this is not part of webpack feature. But eventually once webpack 5 is out, someone will ask how do you mock and test expose components again.

I want to make a package that depends on another package (for example,'three'). When I want to publish the package, I don't include three, but let the user download it. Is there any way?

I don't think this is relevant to webpack feature.

Well, I can use rollup.js

That's true, this is not part of webpack feature. But eventually once webpack 5 is out, someone will ask how do you mock and test expose components again.

In the code itself (which is what you should be unit testing), exposed components are imported just like other components, so the same mocking patterns apply. MF does not change how modules work, only how they are generated, shared and served. None of these things should affect unit tests.

Understand @csvan. Maybe @ScriptedAlchemy can include sample unit test/mock in this example https://github.com/module-federation/module-federation-examples/tree/master/shared-routes2/app1 ? That would be helpful.

@brendonco for which test library? Different libraries do mocking in different ways, and not everyone uses Jest. Again, exposed modules are just modules as far as the code is concerned. There is literally no difference between mocking an exposed module and a "regular" module since the import syntax is the same for both, so everything you need should already be in the docs of the test library you are using. No need for an explicit example for MF.

(I apologize for taking this thread on a tangent, this will be my last reply regarding mocking)

Is it possible to have a _dynamic_ list of remotes, so that I can add new modules on the fly?

Say for example that my main app has something like:

const myRemotes = await fetchListOfRemotes();
Promise.all(myRemotes.map(remote => import(myRemote.path));

fetchListOfRemotes() may return new remotes as they are added outside of the app overtime, without the main app itself being rebuilt an redeployed in the meantime. Is this doable?

Sorry if this already is answered above, I've been trying to keep up but it seems we are soon over 400 (rather meaty) comments in this thread. A reference would be appreciated in that case.

Answering my own question: https://github.com/webpack/webpack/issues/10352#issuecomment-605841924

@ScriptedAlchemy regarding:

I’m working on an idea for an additional plugin that enables dynamic definition of remotes at runtime. Webpack prefers explicit over implicit. So dynamic remotes at runtime will be a plugin that hooks into RemoteModule and dependency factories

Is there an update regarding this plugin?

Containers have an API to load modules from them at runtime.

const container = getItSomehow();
await __webpack_initialize_sharing__("default");
await container.init(__webpack_share_scopes__.default);
const factory = await container.get("./module");
const exports = factory();

@vankop ill be porting it back to Webpack 4 - but will be as a separate project, not in the core

@ScriptedAlchemy Hi! Do you have some updates here? Very keen to try it with Webpack4 (vue-cli and Vue3 to be precise)

I think we can close this issue. It gets very long. Please create new issues when there are still problems.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Mithras picture Mithras  ·  3Comments

nwhite89 picture nwhite89  ·  3Comments

yannispanousis picture yannispanousis  ·  3Comments

Tjorriemorrie picture Tjorriemorrie  ·  3Comments

karyboy picture karyboy  ·  3Comments