manfredsteyer / module-federation-plugin-example

258 stars 177 forks source link

Images used by remotes not found in the shell #21

Open marco24690 opened 2 years ago

marco24690 commented 2 years ago

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 commented 2 years ago

having the same issue - tried this fix suggested here: https://github.com/module-federation/module-federation-examples/issues/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 commented 2 years ago

I having the same issue any solution @manfredsteyer

scottwalter-nice commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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:

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:

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 commented 2 years ago

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

jure123 commented 2 years ago

@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 commented 2 years ago

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 commented 2 years ago

What is the best solution. I am a same problem

artgoce commented 1 year ago

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

maurotamm commented 1 year ago

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 commented 1 year ago

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

adrianbegi commented 1 year ago

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

Ketec commented 1 year ago

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 commented 1 year ago

@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 commented 1 year ago

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

2) 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.

3) 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"
            }
        }
}

4) 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 commented 5 months ago

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 commented 4 months ago

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 commented 1 month ago

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