jacob-ebey / webpack-federated-stats-plugin

Write out relevant federation stats to a file for further consumption.
17 stars 6 forks source link

Add remote modules to stats #8

Closed ScriptedAlchemy closed 2 years ago

ScriptedAlchemy commented 2 years ago

Adding Remote modules to stats plugin. Needed to find and correlate nested remotes during SSR

jacob-ebey commented 2 years ago

Mind updating the README with the new output format?

ScriptedAlchemy commented 2 years ago

@jacob-ebey done!

jacob-ebey commented 2 years ago

Oh interesting. It's just the given "name" mapped to a chunk ID? How is this going to be utilized?

ScriptedAlchemy commented 2 years ago

In next.js for federated SSR, I can grab any federated imports in the project without a problem and sideload them into the loadable manifest.

However when I have nested federated imports, in production mode, the only thing i can gain access to is a proxy object where loadable looks for the import key (checkout/title) and the module id it needs is the module id in the remote container. I need to be able to correlate name to module id in order to push it into next.js at runtime so when the client hydrates its got a full list of all async import ids otherwise ill get flicker and hydration issues as even tho the chunk is SSR'd on the page, the promise wasn't pre-resolved before render starts.

This is an interesting scenario because usually id be looking for IDs elsewhere, but in this case i need to search for the module ID in the remote, not the ID of the import in the host. Like a reverse-reverse lookup

jacob-ebey commented 2 years ago

Do you have another plugin or something that adds the external module ID to the stats? As far as I'm aware this external ID isn't grabbed by webpack at compile time, so isn't this ID the internal representation of the 3rd party module just for this compilations resolution graph?

ScriptedAlchemy commented 2 years ago

Its a long story. Im using your plugin instead of forking it - I take what it emits and embed it into webpack federation API, then recreate a mini module graph as each remote attaches itself to a host. So while they are all only have their own information. When combined at runtime I can determine the external module id since I now know what remote imports the current remote has inside it.

Id already made these edits internally in node modules and it does what I needed it to :P

Excuse the mess. My loops are pretty rough but you can see the remoteModule line toward the bottom where I do the "reverse reverse" lookup inside the host.


const extractChunkCorrelation = (remoteContainer, lookup, request) => {
  if (
    remoteContainer &&
    remoteContainer.chunkMap &&
    remoteContainer.chunkMap.federatedModules
  ) {
    const path = remoteContainer.path.split("@")[1];
    const [baseurl] = path.split("static/ssr");
    remoteContainer.chunkMap.federatedModules.map((federatedRemote) => {
      federatedRemote.exposes[request].forEach((remoteChunks) => {
        remoteChunks.chunks.map((chunk) => {
          if (!lookup.files.includes(new URL(chunk, baseurl).href)) {
            lookup.files.push(new URL(chunk, baseurl).href);
          }
        });
      });
    });
  } else {
    console.warn(
      "Module Federation:",
      "no federated modules in chunk map OR experiments.flushChunks is disabled"
    );
  }
};
const requireMethod =
  typeof __non_webpack_require__ !== "undefined"
    ? __non_webpack_require__
    : require;
const requestPath = path.join(
  process.cwd(),
  ".next",
  "server/pages",
  "../../react-loadable-manifest.json"
);
let remotes = {};
const loadableManifest = requireMethod(requestPath);
requireMethod.cache[requestPath].exports = new Proxy(loadableManifest, {
  get(target, prop, receiver) {
    if (!target[prop]) {
      let remoteImport = prop.split("->")[1]

      if (remoteImport) {
        remoteImport = remoteImport.trim();
        const [remote, module] = remoteImport.split("/");

        if (!remotes[remote]) {
          Object.assign(remotes,generateDynamicRemoteScript(global.loadedRemotes[remote]))
        }

        const dynamicLoadableManifestItem = {
          id: prop,
          files: [],
        };
        // TODO: figure out who is requesting module
        let remoteModuleContainerId
          Object.values(global.loadedRemotes).find((remote)=> {
          if(remote.chunkMap && remote.chunkMap.federatedModules[0] && remote.chunkMap.federatedModules[0].remoteModules) {
            if(remote.chunkMap.federatedModules[0].remoteModules[remoteImport]) {
              remoteModuleContainerId = remote.chunkMap.federatedModules[0].remoteModules[remoteImport]
              return true
            }
          }
        })
        if(remoteModuleContainerId) {
          dynamicLoadableManifestItem.id = remoteModuleContainerId
        }
        extractChunkCorrelation(
          global.loadedRemotes[remote],
          dynamicLoadableManifestItem,
          `./${module}`
        );
        return dynamicLoadableManifestItem;
      }
    }
    return target[prop];
  },
});
jacob-ebey commented 2 years ago

Gotcha.

ScriptedAlchemy commented 2 years ago

Much love <3 ill watch npm for a version bump