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

Local module federation implementation #782

Open
AbrahamBaby opened this issue Nov 4, 2024 · 5 comments
Open

Local module federation implementation #782

AbrahamBaby opened this issue Nov 4, 2024 · 5 comments
Labels
status:new New issue, not reviewed by the team yet. status:stale Missing feedback or response for prolonged period of time. type:bug A bug report.

Comments

@AbrahamBaby
Copy link

AbrahamBaby commented Nov 4, 2024

Describe the bug

Hi devs,
I was doing a POC on the repack Module Federation implementation with local MFE and could see in the superapp showcase regarding the poc-local-module-federation and could see there are some modifications needed for the mfe to work locally without any server, so i was able to see what changes needs to be done so that it will work on ios, but there were no references on how to implement it android, it would be helpful if anyone can comment on this.

Thank you

System Info

React native 0.74.4

Re.Pack Version

4.4.0

Reproduction

.

Steps to reproduce

.

@AbrahamBaby AbrahamBaby added status:new New issue, not reviewed by the team yet. type:bug A bug report. labels Nov 4, 2024
@zmzlois
Copy link
Contributor

zmzlois commented Nov 4, 2024

can you provide a minimal reproduction so we can look into this? thank you

@AbrahamBaby
Copy link
Author

AbrahamBaby commented Nov 5, 2024

Hi, @zmzlois,
I have created 3 MFE’s and one of them i want to load from local not from server, i saw one example in the super-app-showcase where it was briefly explained how we can load a mfe using local module federation, but in that example it was mentioned how to load in ios only , there were no info regarding how to load it in android, it would be helpful if you can provide any reference for loading the local mfe in android also

Reference of super app showcase local mfe setup: https://github.com/callstack/super-app-showcase/tree/local-module-federation-poc

@AbrahamBaby
Copy link
Author

Hi @jbroma / @thymikee / @zmzlois
It would be helpful if you can provide some assistance in the above mentioned issue, was running the local-module-federation-poc from super-app-showcase and was stuck when running it for android. Please provide a response as i am unable to move forward with the implementation.

Thanks in advance

@slubal
Copy link

slubal commented Nov 8, 2024

For what its worth, I added my own scrappy script cache by having script load interceptors like following
This enabled following functionality in our app,

  1. Download the script from whatever URL
  2. intercept the loadScript call to update the URL from http.... to file:///
  3. If the http or anything is down for whatever reason fallback to last synced file
  4. If the fallback is not present then fallback to baked in split bundles ( this will require you to have a map that you can fallback to but it works by simply providing location of your split bundle)

In your main app have following

  • index.ts
ScriptManager.shared.executeInterceptors = async function (script) {
  console.log('Interceptor called for:', script);
  console.log('Before interceptor:', script);
  await ScriptInterceptor(script, false);
  console.log("After interceptor", script)
};
ScriptManager.shared.addResolver(async (scriptId, caller) => {
.... 
}
  • ScriptInterceptor.ts
import RNFS from 'react-native-fs';
import {Script} from '@callstack/repack/client';

function makeSafeFilename(url: string): string {
    // Extract the part of the URL after the domain name (strip the protocol and domain)
    const urlPath = url.replace(/^https?:\/\/[^/]+/, '');

    // Replace slashes (/) with dashes (-) and append a .js extension
    return urlPath.replace(/\//g, '-') + '.js';
}


export const ScriptInterceptor = async function (script: Script, shouldUseCache = true) {
    const cacheDir = `${RNFS.DocumentDirectoryPath}/script_cache`;

    // Construct the full URL with query parameters
    const url = script.locator.query
        ? `${script.locator.url}?${script.locator.query}`
        : script.locator.url;

    // Use the full URL as part of the cache key to differentiate scripts with different queries
    const cacheKey = script.scriptId;//makeSafeFilename(url);
    const cacheFilePath = `${cacheDir}/${cacheKey}.js`;

    // Ensure cache directory exists
    try {
        if (!(await RNFS.exists(cacheDir))) {
            await RNFS.mkdir(cacheDir);
        }
    } catch (dirError: any) {
        console.error(`Failed to create cache directory: ${dirError.message}`);
        throw dirError;
    }

    const assignLocatorUrlFromLocalFileSystem = (fileLocation: string, isAbsolutePath = true) => {
        // assign script to have absolute path
        script.locator.absolute = true;
        script.locator.url = fileLocation;
    }

    // Check if the script is already cached
    if (await RNFS.exists(cacheFilePath) && shouldUseCache) {
        console.log(`Cache hit for script: ${cacheFilePath}`);
        assignLocatorUrlFromLocalFileSystem(`file://${cacheFilePath}`);
        console.log('updated path', script.locator.url)
        return;
    }

    console.log(`Downloading script: ${url}`);
    try {
        const response = await fetch(url, {
            method: script.locator.method,
            headers: script.locator.headers,
            body: script.locator.body,
        });

        if (!response.ok) {
            console.log('error downloading script', response.status, 'for script id:', script.scriptId);
            return;
        }

        const scriptContent = await response.text();

        // Cache the script to the filesystem
        await RNFS.writeFile(cacheFilePath, scriptContent, 'utf8');
        console.log(`Cached script at: ${cacheFilePath}`);

        // Update locator to use the file URL
        assignLocatorUrlFromLocalFileSystem(`file://${cacheFilePath}`);
    } catch (error: any) {
        console.error(`Error in interceptor while downloading script: ${error.message}`);
        return;
        //throw error;
    }
};

You do need to patch the ScriptManager.js directly or use patch-package library

diff --git a/node_modules/@callstack/repack/dist/modules/ScriptManager/ScriptManager.js b/node_modules/@callstack/repack/dist/modules/ScriptManager/ScriptManager.js
index ff3dc81..824b607 100644
--- a/node_modules/@callstack/repack/dist/modules/ScriptManager/ScriptManager.js
+++ b/node_modules/@callstack/repack/dist/modules/ScriptManager/ScriptManager.js
@@ -256,7 +256,6 @@ export class ScriptManager extends EventEmitter {
         }
 
         this.emit('resolved', script.toObject()); // if it returns false, we don't need to fetch the script
-
         return script;
       } // If no custom shouldUpdateScript function was provided, we use the default behaviour
 
@@ -309,6 +308,10 @@ export class ScriptManager extends EventEmitter {
         try {
           this.emit('loading', script.toObject());
           this.on('__loaded__', onLoaded);
+          console.log(this.executeInterceptors)
+          if (this.executeInterceptors) {
+            await this.executeInterceptors(script);
+          }
           await this.nativeScriptManager.loadScript(scriptId, script.locator);
         } catch (error) {
           const {
diff --git a/node_modules/@callstack/repack/dist/modules/ScriptManager/federated.js b/node_modules/@callstack/repack/dist/modules/ScriptManager/federated.js
index 63897a0..54c95d1 100644
--- a/node_modules/@callstack/repack/dist/modules/ScriptManager/federated.js
+++ b/node_modules/@callstack/repack/dist/modules/ScriptManager/federated.js
@@ -57,7 +57,8 @@ export let Federated;
     // `container` reference is not updated, so `container.__isInitialized`
     // will crash the application, because of reading property from `undefined`.
 
-
+    console.log('in import module')
+    console.log('in import module')
     if (!self[containerName]) {
       // Download and execute container
       await ScriptManager.shared.loadScript(containerName);

Copy link

github-actions bot commented Dec 9, 2024

This issue has been marked as stale because it has been inactive for 30 days. Please update this issue or it will be automatically closed in 14 days.

@github-actions github-actions bot added the status:stale Missing feedback or response for prolonged period of time. label Dec 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status:new New issue, not reviewed by the team yet. status:stale Missing feedback or response for prolonged period of time. type:bug A bug report.
Projects
None yet
Development

No branches or pull requests

3 participants