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

Images used by remotes not found in the shell #21

Open
marco24690 opened this issue Nov 22, 2021 · 21 comments
Open

Images used by remotes not found in the shell #21

marco24690 opened this issue Nov 22, 2021 · 21 comments

Comments

@marco24690
Copy link

Hi Manfred,
in your example I can't load images belonging to the remote app from the shell.
Your specific case works because the angular.png image exist also in the shell app and is that one that is downloaded and used by the app.
I've tried to substitute the image with anoter one that exist in the remote app only and the result is this one below.
It try to reach the image on the host app, but it doesn't exist there. If the remote app it' s run as standalone everything it's ok

image

How can be fixed? (without using absolute path for all the images used in the remotes)
Thank you!

@h4de5
Copy link

h4de5 commented Feb 9, 2022

having the same issue - tried this fix suggested here: module-federation/module-federation-examples#697

but we do not have a shared assets library - we would like to have a solution just for displaying static images from the remote app within the remote app.

@ofirrifo
Copy link

ofirrifo commented Feb 16, 2022

I having the same issue any solution @manfredsteyer

@scottwalter-nice
Copy link

How about this. document.currentScript?.getAttribute('src') which return something like: http://localhost:5001/src_bootstrap_ts.js. You would need to parse the results to only get the host and port. Then prefix your images with the result.

@gleisonkz
Copy link

I'm also having this same issue, but in my case, the image's URL is defined in CSS using background: url("..."), how can be fixed this case?

@jure123
Copy link

jure123 commented May 21, 2022

I found a few solutions for this problem. First we need to understand the main issue: let's say the shell app is running on localhost:3000, and the remote app is running on localhost:3001. In the remote app you can use images in two ways:

  • in html code: <img src="/assets/image.png">
  • in CSS stylesheet: .img { background-image: url(/assets/image.png) }

When you deploy the remote app and run it directly (on localhost:3001) it will correctly resolve both links to image to url localhost:3001/assets/image.png, and image will show.

But when you load the remote app in the shell app (on localhost:3000), the shell app will inject the remote html and css code inside the host DOM, and all links will be resolved relatively to the shell app url, which means on localhost:3000/assets/image.png (where the image does not exist.

The solution is we have to modify the remote app code to output absolute url to images:

  • in html code: <img src="http://localhost:3001/assets/image.png">
  • in CSS stylesheet: .img { background-image: url(http://localhost:3001/assets/image.png) }

If you know URL where the remote app will be deployed in advance, the easiest solution is to set deployUrl option in angular.json file (if remote app is Angular):

"build": {
  "options": {
    "deployUrl": "http://localhost:3001"
    ...
   },
   "configurations": {
     "production": {
       "deployUrl": "https://production.server.com"
       ...
    }
}

With this option angular compiler will automatically convert all relative links to absolute (=all links to images in compiled HTML and CSS code will start with http://localhost:3001). When the remote app is loaded inside the shell app the images will be loaded from the correct locations.

This is not a good solution when we need to build a project that can be deployable on any server, because this compiled code has absolute URLs hard coded and can only be deployed on the particular server. Another dissadvantage is if our remote app uses a lot of images, because hard-coded absolute links will make html/css files larger. Maybe in that case we can optimize the deployment process and deploy all the remote app assets to the shell server, so relative links will still work.

I think currently the most elegant solution to make the remote app deployable anywhere (not having to set the deployment url in advance) is to use dynamic link resolvement in our HTML and CSS code. For that a special global javascript variable __webpack_public_path__ comes handy. This variable is automatically defined by webpack in the project compilation process, and contains absolute URL to deployed server (that we can configure statically in advance or can be resolved dynamicall at run-time based on browser URL address from where we loaded the remote app (=I think it gets resolved in the remote app remoteEntry.js, where the client-side JS code is aware of the absolute URL where it got downloaded from).

So, we can dynamically resolve absolute links to images in our remote app like this:

Angular component TS file:

@Component({
  templateUrl: './page1.component.html',
})
export class Page1Component {
  assetUrl = __webpack_public_path__;
}

Angular component HTML template:

<img src="{{assetUrl}}image.png" />

If we have images inside our CSS files the solution is quite more tricky. Angular is possible to dynamically inject variables in CSS at run-time using @HostBinding decorator, but is not very convenient solution especially for injecting URL's for images:

Angular component TS file that will dynamicall handle it's CSS file:

@Component({
  styleUrls: ['./page1.component.scss'],
})
export class Page1Component {

  // CSS variable --imageUrl will be passed to CSS style declared in page1.component.scss.
  @HostBinding("style.--imageUrl") imageUrl = `url('${__webpack_public_path__}image.png')`;
 
}

CSS file:

.img {
  background-image: var(--imageUrl);
}

Disadvantage of this solution is that we must declare CSS variable for every different image we want to inject in the CSS. It would be much easier if we could just declare a base url path variable in TS file, and in CSS to append it to our image filename, like: background-image: url(var(--imageBaseUrl)/image.png);. This would throw compilation error because angular compiler (or mroe specifically css-loader in webpack, used by angular compiler) wants to resolve every url() function it finds in CSS files, and it won't understand how to resolve url(var(--imageBaseUrl)/image.png) (it will throw syntax error). I could not find a way we could tell the compiler to ignore this cases, css-loader is always used and we cannot configure it properly.

If you use many images in CSS files there is also a more elegant solution: put all styles that use images in your "global-remote" CSS file, where you can use relative links to images. When you deploy your remote app, browser will load the globa-remote CSS from http://localhost:3001/global-remote.css, and browser will automatically resolve all relative links relatively to localhost:3001. But for this to work you cannot use "scoped" CSS files from angular (=CSS files that are attached to angular component), because scoped CSS styles are compiled in JS code together with the component and are dynamically injected at-runtime to html DOM, so relative links to image will be resolved relatively to the shell app localhost:3000, not to the remote app). You have to compile this kind of CSS file separately, and include in your remote app separately:

To compile "remote-global" CSS separately, configure it in angular.json:

"build": {
          "options": {
            "styles": [
              {
                "input": "src/app/remote-global.scss",
                "bundleName": "remote-global" // Tell compiler to compile our style to remote-global.css
              }
            ],

Include this CSS in our remote app main App component (app.component.html):

<link [rel]="'stylesheet'" [href]="{{assetUrl}}remote-global.css">

Make sure also [rel] attribute is in brackets otherwise angular compiler will try to resolve href in compile-time and will throw syntax error. Compiler must ignore this tag that must be resolved at run-time.

In app.component.ts define assetUrl variable that will resolve to absolute url of deployed remote app, like mentioned before:

@Component({
 templateUrl: './app.component.html',
})
export class AppComponent {
 assetUrl = __webpack_public_path__;
}

@gleisonkz
Copy link

@jure123 I tried all of the options you suggested, but unfortunately, nothing worked for me.

@jure123
Copy link

jure123 commented Jun 2, 2022

@jure123 I tried all of the options you suggested, but unfortunately, nothing worked for me.

@gleisonkz, I'm using all those options in my project, and they are working well. Unfortunately I cannot share the project. If you wrote more details what and how is not working I may be able to help you.

@h4de5
Copy link

h4de5 commented Jun 10, 2022

jure123's soltion did work for me. but I had to do something more:

as webpack_public_path was not defined in typescript I had to do first install webpack types:

npm install --save-dev @types/webpack-env 

and add types to the tsconfig.app.json:

"compilerOptions": {
  "types": ["webpack-env"]
},

after that I could reference images like this in the remotes html: <img src="{{ assetUrl }}/assets/img.png" /> . it does get displayed during serve and when accessing a production build - something which did not work previously as well.

@Totot0
Copy link

Totot0 commented Jul 9, 2022

What is the best solution. I am a same problem

@artgoce
Copy link

artgoce commented Dec 29, 2022

I'm running into the same issue but using React. Did somebody have had this same issue?

@maurotamm
Copy link

maurotamm commented Jan 27, 2023

Since I use proxies to eliminate the need for cors + relative remote path you can prefix all the relative URLs.

But that is quite tedious and will be hard to maintain/communicate to all the developers.

/mfe/mfremote/assets/normal.svg

Where <shell>/mfe/mfremote/ is a proxy (angular for serve, deployments are separate).

But autoprefixing all relative URLs would be even better, but can't find anything that would work for http or CSS calls with both prod builds and ng serve.

@AnandKatta
Copy link

Hi Team,
Facing the same issue and the css files are not loading to shell from mfe.

@adrianbegi
Copy link

Same issue here, can't seem to find a good solution for non-monorepo implementations.

@Ketec
Copy link

Ketec commented Feb 14, 2023

It works fine - you just have to have a proxy on the shell that points to remote asset dir.
And remotes have to include their relative path to the shell in the CSS URLs.

A proxy on your shell would catch any requests to /mfe/mfremote/ and forward them internally to the remote "deployment" URL.
So you can use /mfe/mfremote/assets/normal.svg in CSS/html, the proxy detects the path and redirects it to remote (use URL rewrite so it redirects it to <remoteurl>/assets/normal.svg).

The annoying part is that you do need to hardcode that prefix everywhere in css/html. No good way to autoprefix there.
For httpClient, you can at least add an interceptor to detect/handle prefixing.

@adrianbegi
Copy link

@Ketec Yup, that is the solution I went with. I just moved my assets to a specific folder in the remote and referenced them through that folder then any call to that folder in the host I added a proxy for to point to the remote.

@arkonsolutions
Copy link

arkonsolutions commented Mar 8, 2023

I came up with this solution. We take a proxy (nginx+devServer):

  1. Configure nginx, add rules redirecting from the path /assets/%mfename%/ (for each mfe a separate rule (location)) to the web server address of the microfrontend, where all artifacts are located, including the assets of the microfrontend.
#assets proxy: begin
    location /assets/my-awesome-mfe/ {
        root /ftp/www/my-awesome-mfe/;
        try_files $uri $uri/;
    }
#assets proxy: end
  1. In the MFE project, move all the assets from the folder ./assets to the folder ./assets/%mfename%/. And accordingly in the project references to the assets must include a new folder named mfe. This folder is required to set up a proxy for redirecting to the assets from the required web server.

  2. For development time. In the shell project, in the development-config webpack we add a proxy like we did for nginx, but in devServer:

devServer: {
        historyApiFallback: true,
        proxy: {
            "/assets/my-awesome-mfe": {
                target: "http://localhost:8083"
            },
            "/assets/my-awesome-mfe2": {
                target: "http://localhost:8084"
            }
        }
}
  1. In the MFE-project add a key: "resourcesOutputPath": "assets/my-awesome-mfe" to the angular.json file. All assets from css when building the MFT will be stored here.

As a result, any MFE will receive its assemblies from the shell on the relative path, for example '/assets/my-awesome-mfe/logo.png' will receive the file http://my-shell-app.com/assets/my-awesome-mfe/logo.png. In this case, the file logo.png will actually be in a folder with its own project (my-awesome-mfe), you do not need to copy them anywhere else. In addition, you do not need to make any framework-specific settings, you can copy the assemblies to where you need them by webpack as well.

@Alireza-Samadi73
Copy link

I came up with this solution. We take a proxy (nginx+devServer):

  1. Configure nginx, add rules redirecting from the path /assets/%mfename%/ (for each mfe a separate rule (location)) to the web server address of the microfrontend, where all artifacts are located, including the assets of the microfrontend.
#assets proxy: begin
    location /assets/my-awesome-mfe/ {
        root /ftp/www/my-awesome-mfe/;
        try_files $uri $uri/;
    }
#assets proxy: end
  1. In the MFE project, move all the assets from the folder ./assets to the folder ./assets/%mfename%/. And accordingly in the project references to the assets must include a new folder named mfe. This folder is required to set up a proxy for redirecting to the assets from the required web server.
  2. For development time. In the shell project, in the development-config webpack we add a proxy like we did for nginx, but in devServer:
devServer: {
        historyApiFallback: true,
        proxy: {
            "/assets/my-awesome-mfe": {
                target: "http://localhost:8083"
            },
            "/assets/my-awesome-mfe2": {
                target: "http://localhost:8084"
            }
        }
}
  1. In the MFE-project add a key: "resourcesOutputPath": "assets/my-awesome-mfe" to the angular.json file. All assets from css when building the MFT will be stored here.

As a result, any MFE will receive its assemblies from the shell on the relative path, for example '/assets/my-awesome-mfe/logo.png' will receive the file http://my-shell-app.com/assets/my-awesome-mfe/logo.png. In this case, the file logo.png will actually be in a folder with its own project (my-awesome-mfe), you do not need to copy them anywhere else. In addition, you do not need to make any framework-specific settings, you can copy the assemblies to where you need them by webpack as well.

I have done your solution. but it work only on developMode when i want to publish my project on server let say IIS proxy config does not work. do you have any solution for product mode ?

@arkonsolutions
Copy link

I have done your solution. but it work only on developMode when i want to publish my project on server let say IIS proxy config does not work. do you have any solution for product mode ?

This solution also works on a remote server. I don't know about IIS, but it works with nginx. See point 1, I gave an example of a config there.

@downquark
Copy link

This worked for me: https://stackoverflow.com/a/34583854/1623533

@MouzamSadiq
Copy link

As a workaround, I fixed this issue by moving the asset images to the public folder in both the Remote and Shell apps. Now, both apps have the same assets under the public folder, which resolves the issue both in the cluster and locally (for independently running apps).

@dantecarlo
Copy link

As a workaround, I fixed this issue by moving the asset images to the public folder in both the Remote and Shell apps. Now, both apps have the same assets under the public folder, which resolves the issue both in the cluster and locally (for independently running apps).

I got the same problem with vite + react-ts. For now, I also had to solve it this way, but it is not optimal because the host should have the images of all the clients.

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