Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Specification of mf-manifest.json #2496

Closed
2 tasks done
MadaraUchiha-314 opened this issue May 15, 2024 · 7 comments
Closed
2 tasks done

Specification of mf-manifest.json #2496

MadaraUchiha-314 opened this issue May 15, 2024 · 7 comments

Comments

@MadaraUchiha-314
Copy link
Contributor

MadaraUchiha-314 commented May 15, 2024

Clear and concise description of the problem

For a build tool plugin which is implementing module federation (for e.g. rollup-plugin-module-federation), if it wants to generate the manifest file, what's the specification it should adhere to ?

The lack of specification for manifest protocol

Suggested solution

A specification for mf-manifest.json should be published so that all bundler plugins can adhere to that.

Or a utility module/function should be exposed via the SDK or runtime or appropriate package so that build tools can use that to generate and emit the file as part of the build process.

Alternative

No response

Additional context

No response

Validations

  • Read the Contributing Guidelines.
  • Check that there isn't already an issue that request the same feature to avoid creating a duplicate.
@ScriptedAlchemy
Copy link
Member

Yessss. I'm busy implementing federation into esbuild and the manifest is one remaining part. You can check my pr that open if you want to see my esbuild implementation.

Also can you dm me on twitter? I added a new api called create container which lets you dynamically create my container interfaces at runtime.

@ScriptedAlchemy
Copy link
Member

I just realized you're from intuit and AppFabric team. I think Zack C is working with you guys

@ScriptedAlchemy
Copy link
Member

ScriptedAlchemy commented May 15, 2024

Here is the specification:

  • INSTANCE_NAME: The name from mfConfig.name.
  • APP_IDENTIFIER: The sanitized name from package.json, usually in the format comprehensive-demo-react18_app-01.
  • ENV_TYPE: "local" when running on localhost, otherwise the version listed in the hosts package.json
  • REMOTE_FILENAME: The filename of the remote entry, typically remoteEntry.js (without the public path).
  • REMOTE_PATH: The path to the remote entry, such as /js/ (without the public path).
  • REMOTE_TYPE: The type of the remote library from mfConfig.library.type. It can be "global" (for var, assign, window, etc.) or "esm" (for ES module output).
  • types: TypeScript type information.
  • pluginVersion: The version of the federation runtime from package.json.version.
  • publicPath: The public path URL, e.g., "http://localhost:3001/" (absolute URL) or "auto".
  • PACKAGE: The package name, such as "react".
  • REMOTE_CONTAINER: The remote container syntax parsed from MF options, e.g., REMOTE_CONTAINER@http://.
  • REMOTE_CONTAINER_ALIAS: The key of the remote in MF options. For example, in {myApp: app2@http}, REMOTE_CONTAINER_ALIAS is myApp, while the container name is app2.
  • CONSUMED_MODULE: The module being imported from a remote, e.g., import(REMOTE_CONTAINER_ALIAS/CONSUMED_MODULE).
  • REMOTE_ENTRY: The entry URL for the remote, which can be a .js file or a .json manifest pointer, e.g., REMOTE_CONTAINER@REMOTE_ENTRY.
  • EXPOSED_MODULE: The name of an exposed module without the './' prefix, for example, SideNav.
{
  "id": "INSTANCE_NAME",
  "name": "INSTANCE_NAME",
  "metaData": {
    "name": "INSTANCE_NAME",
    "type": "app",
    "buildInfo": {
      "buildVersion": "ENV_TYPE",
      "buildName": "APP_IDENTIFIER"
    },
    "remoteEntry": {
      "name": "REMOTE_FILENAME",
      "path": "REMOTE_PATH",
      "type": "REMOTE_TYPE"
    },
   "types": {
      "path": "",
      "name": "",
      "zip": "@mf-types.zip",
      "api": "@mf-types.d.ts"
    },
    "globalName": "INSTANCE_NAME",
    "pluginVersion": "0.1.9",
    "publicPath": PUBLICPATH
  },
  "shared": [
    {
      "id": "INSTANCE_NAME:PACKAGE",
      "name": "PACKAGE",
      "version": "18.2.0",
      "singleton": true,
      "requiredVersion": "^18.2.0",
      "assets": {
        "js": {
          "async": [], the dynamic imported chunks, non initial
          "sync": [
            "894.js", the chunks it needs
          ]
        },
        "css": {
          "async": [],
          "sync": []
        }
      }
    },
  ],
  "remotes": [
    {
      "federationContainerName": "REMOTE_CONTAINER",
      "moduleName": "CONSUMED_MODULE",
      "alias": "REMOTE_CONTAINER_ALIAS",
      "entry": REMOTE_ENTRY
    },
    {
      "federationContainerName": "app_02",
      "moduleName": "Dialog",
      "alias": "app_02",
      "entry": "http://localhost:3002/mf-manifest.json"
    },
    {
      "federationContainerName": "app_03",
      "moduleName": "Button",
      "alias": "app_03",
      "entry": "http://localhost:3003/mf-manifest.json"
    },
    {
      "federationContainerName": "app_04",
      "moduleName": "loadApp",
      "alias": "app_04",
      "entry": "http://localhost:3004/mf-manifest.json"
    },
    {
      "federationContainerName": "app_05",
      "moduleName": "components",
      "alias": "app_05",
      "entry": "http://localhost:3005/mf-manifest.json"
    }
  ],
  "exposes": [
    {
      "id": "INSTANCE_NAME:EXPOSED_MODULE",
      "name": "EXPOSED_MODULE",
      "assets": {
        "js": {
          "sync": [
            "__federation_expose_SideNav.js"
          ],
          "async": [
            "729.js",
            "569.js",
            "457.js",
            "710.js",
            "894.js",
            "210.js"
          ]
        },
        "css": {
          "sync": [],
          "async": []
        }
      },
      "path": "./SideNav"
    },
    {
      "id": "INSTANCE_NAME:Page",
      "name": "Page",
      "assets": {
        "js": {
          "sync": [
            "__federation_expose_Page.js"
          ],
          "async": [
            "729.js",
            "569.js",
            "457.js",
            "710.js",
            "894.js"
          ]
        },
        "css": {
          "sync": [],
          "async": []
        }
      },
      "path": "./Page"
    }
  ]
}

@MadaraUchiha-314
Copy link
Contributor Author

This is fantastic. Are all of these fields used by the federation runtime ? Is there a subset of the fields which are strictly required ?

@ScriptedAlchemy
Copy link
Member

I believe all fileds are used. some by options like preloadRemote, others are used for loadRemote etc. I dont think we pack any additional data in here other than whats directly needed for all features of the runtime to function

@MadaraUchiha-314
Copy link
Contributor Author

@ScriptedAlchemy If we are consuming multiple exposed modules from a remote, then each of those gets listed separately on the remotes array in the manifest. Why is that so ???

Screenshot 2024-06-18 at 7 48 11 PM

There can only be a single version of a remote that one consumer can use, right ??

When a container does this, there's no way for a bundler to unambiguously resolve this to different remotes.

import Button, { someThingElse } from 'project-b/button';
import Link, { someThingDifferent } from 'project-b/link'; 

Hence, why can't we simplify the remotes array to be:

"remotes": [
    {
      "federationContainerName": "http://localhost:8080/packages/examples/project-b/dist/rspack/esm/my-remote-entry.js",
      "moduleNames": ["button", "link"],
      "alias": "project-b",
      "entry": "*"
    },
],

@MadaraUchiha-314
Copy link
Contributor Author

I had a quick look at the esbuild implementation which also generates the manifest here

It only uses the module federation config provided by the user which doesn't have sufficient information about the "exposed" modules which are used from remotes.

For eg. in my case I have the following federation config:

remotes: {
    'project-b': 'https://localhost:8080/remoteEntry.js'
}

and I consume 2 exposed modules from the remote:

import Button, { someThingElse } from 'project-b/button';
import Link, { someThingDifferent } from 'project-b/link'; 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants