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

Assets not loaded when deployed in OpenShift, and when mixing with localhost development: 404 Not found #521

Open
alaindeurveilher opened this issue May 28, 2024 · 0 comments

Comments

@alaindeurveilher
Copy link

alaindeurveilher commented May 28, 2024

Question

Environment


Libs:
- @angular/core version: 17.3.9
- single-spa-angular version: 9.1.2


Others:

- Deployment in OpenShift
- local development in localhost

Description

Hello,

I am struggling with making the deployment work in OpenShift, I would like some insight please to help me figuring out the right configuration so that the application and all the MFEs are correctly deployed in OpenShift, and as the same time the different teams can work locally on the development of their own MFEs while getting the rest from the deployed versions.

This project is for a private company so I cannot share repositories of detailed coorporate code, but I will try to provide as much as information as possible.

Architecture

The application is split as followed, each of them in their own gitlab repository:

  • root-config
  • styleguide (library MFE)
  • top-header (application MFE)
  • left-navigation (application MFE)
  • mfe1 (application MFE)
  • mfe2 (application MFE)

All the MFEs listed above are developped with Angular 17 (currently 17.3.9) (standalone components), and use single-spa 6.0.1 with signle-spa-angular 9.1.2.

The styleguide mfe is in charge of the coorporate design system based on primeNG 17.17.

The top header is responsible for the Authenticated user, language selection, and a breadcrumbs navigation

The left navigation is the high level navigation menu allowing to browse into mfe1 or mfe2. It's the only MFE using and importing in it's project "material-icons".

The main MFEs are loaded in the central zone of the page.

I followed the guide to deploy in Docker Images/Container, and adapted it up for my company's OpenShift cluster.

What I am trying to achieve at the moment is to have the following deployment:

each of the deployment is served with NGINX as documented in the guide, and has its own app.importmap file.

So for instance for the navigation MFE, we'll have deployed in the pod something like:

/opt/app-root/src/
    |__ app.importmap
    |__ libs/
          |__ @my-organisation
                |__ navigation
                      |__ 1716885566
                            |__ main.js
                            |__ 239.js
                            |__ material-icons.woff
                            |__ assets/
                                  |__ i18n/
                                        |__ en.json
                                        |__ fr.json
                                        |__ nl.json

And the app.importmap whould then look like this:

{
  "imports": {
    "@my-organisation/navigation": "https://navigation-dev-my-namespace.apps.nonprod-ocp.company.com/libs/@my-organisation/navigation/1716885566/main.js",
    "@my-organisation/navigation/": "https://navigation-dev-my-namespace.apps.nonprod-ocp.company.com/libs/@my-organisation/navigation/1716885566/"
  },
  "scopes": {}
}

The structure is similar for each of the MFEs deployed.

The nginx config file for this deployment looks like this:

worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';


    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /opt/app-root/etc/nginx.d/*.conf;

    server {
        listen       8080 default_server;
        listen       [::]:8080 default_server;
        server_name  _;
        root         /opt/app-root/src;

        # Load configuration files for the default server block.
        include /opt/app-root/etc/nginx.default.d/*.conf;

        location / {
            expires max;
            add_header 'Cache-Control' "public";
            add_header 'Access-Control-Allow-Origin' *;
            add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH';
        }

        location /app.importmap {
            expires 10s;
            add_header 'Cache-Control' "public, must-revalidate";
            add_header 'Access-Control-Allow-Origin' *;
            add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH';
            default_type application/importmap+json;
        }
    }
}

Note: I tried with building the application either a. without deploy-url in the angular.json and in the build command, or b. with ng build --deploy-url=https://navigation-dev-my-namespace.apps.nonprod-ocp.company.com/ => the error it the same.

The root config template file looks like:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Company</title>
  <link rel="icon" type="image/x-icon" href="/favicon.ico">

  <!--
    Remove this if you only support browsers that support async/await.
    This is needed by babel to share largeish helper code for compiling async/await in older
    browsers. More information at https://github.com/single-spa/create-single-spa/issues/112
  -->
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/runtime.min.js"></script>

  <!--
    This CSP allows any SSL-enabled host and for arbitrary eval(), but you should limit these directives further to increase your app's security.
    Learn more about CSP policies at https://content-security-policy.com/#directive
  -->
  <meta http-equiv="Content-Security-Policy"
    content="default-src 'self' https: localhost:*; script-src 'unsafe-inline' 'unsafe-eval' https: localhost:*; connect-src https: localhost:* ws://localhost:* http://*; style-src 'unsafe-inline' https:; object-src 'none';">
  <meta name="importmap-type" content="systemjs-importmap" />
  <!-- If you wish to turn off import-map-overrides for specific environments (prod), uncomment the line below -->
  <!-- More info at https://github.com/joeldenning/import-map-overrides/blob/master/docs/configuration.md#domain-list -->
  <!-- <meta name="import-map-overrides-domains" content="denylist:prod.example.com" /> -->

  <!-- Shared dependencies go into this import map. Your shared dependencies must be of one of the following formats:

    1. System.register (preferred when possible) - https://github.com/systemjs/systemjs/blob/master/docs/system-register.md
    2. UMD - https://github.com/umdjs/umd
    3. Global variable

    More information about shared dependencies can be found at https://single-spa.js.org/docs/recommended-setup#sharing-with-import-maps.
  -->
  <script type="systemjs-importmap">
    {
      "imports": {
        "single-spa": "https://cdn.jsdelivr.net/npm/[email protected]/lib/es2015/system/single-spa.min.js"
      }
    }
  </script>
  <link rel="preload" href="https://cdn.jsdelivr.net/npm/[email protected]/lib/es2015/system/single-spa.min.js"
    as="script">

  <!-- Add your organization's prod import map URL to this script's src  -->
  <script type="systemjs-importmap"
    src="https://import-maps-dev-my-namespace.apps.nonprod-ocp.company.com/app.importmap"></script>

  <!-- TODO: In the end, only one single app.importmap above should remain for deployment  -->
  <!-- TODO: The following are temporary solution while figuring out how to update one single app.importmap above, and should be removed for the final solution -->
  <script type="systemjs-importmap"
    src="https://root-config-dev-my-namespace.apps.nonprod-ocp.company.com/app.importmap"></script>
  <script type="systemjs-importmap"
    src="https://styleguide-dev-my-namespace.apps.nonprod-ocp.company.com/app.importmap"></script>
  <script type="systemjs-importmap"
    src="https://top-header-dev-my-namespace.apps.nonprod-ocp.company.com/app.importmap"></script>
  <script type="systemjs-importmap"
    src="https://navigation-dev-my-namespace.apps.nonprod-ocp.company.com/app.importmap"></script>
  <script type="systemjs-importmap"
    src="https://mfe1-dev-my-namespace.apps.nonprod-ocp.company.com/app.importmap"></script>

  <% if (isLocal) { %>
    <script type="systemjs-importmap">
      {
        "imports": {
          "@my-organisation/root-config": "//localhost:9000/my-organisation-root-config.js",
          "@my-organisation/mfe1": "//localhost:4210/main.js"
        }
      }
    </script>
    <% } %>

      <!--
    If you need to support Angular applications, uncomment the script tag below to ensure only one instance of ZoneJS is loaded
    Learn more about why at https://single-spa.js.org/docs/ecosystem-angular/#zonejs
  -->
      <script src="https://cdn.jsdelivr.net/npm/[email protected]/+esm"></script>

      <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/import-map-overrides.js"></script>
      <% if (isLocal) { %>
        <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/system.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/extras/amd.js"></script>
        <% } else { %>
          <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/system.min.js"></script>
          <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/extras/amd.min.js"></script>
          <% } %>

</head>

<body style="margin: 0; padding: 0; width: 100vw; height: 100vh;">
  <noscript>
    You need to enable JavaScript to run this app.
  </noscript>
  <script>
    System.import('@my-organisation/root-config');
    System.import('@my-organisation/styleguide');
  </script>
  <import-map-overrides-full show-when-local-storage="devtools" dev-libs></import-map-overrides-full>
</body>

</html>

Errors

Type of Error 1: 404

When I navigate to the deployed root app "https://root-config-dev-my-namespace.apps.nonprod-ocp.company.com", in the network tabs of the browser's devtool I see the following 404 error on the files:

Type of Error 2: CORS

CORS error:

"Access to XMLHttpRequest at 'https://navigation-dev-my-namespace.apps.nonprod-ocp.company.com/assets/i18n/en.json' from origin 'https://root-config-dev-my-namespace.apps.nonprod-ocp.company.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource."

Same type of CORS error happens when I serve the root config locally, I get such CORS errors.

"Access to XMLHttpRequest at 'https://navigation-dev-my-namespace.apps.nonprod-ocp.company.com/assets/i18n/en.json' from origin 'http://localhost:9000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource."

Which I do not understand because the the header instruction has been set in the NGNIX config file, even for the OPTIONS requests.

Note The projects use the library "@ngx-translate" for translations, and the translation json files are referenced using the assetUrl utility method, such as:

import {TranslateLoader, TranslateModule} from "@ngx-translate/core";
import {TranslateHttpLoader} from "@ngx-translate/http-loader";
import {assetUrl} from "../single-spa/asset-url";

// AoT requires an exported function for factories
export function HttpLoaderFactory(http: HttpClient) {
  return new TranslateHttpLoader(http, assetUrl("i18n/"));
}

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideAnimationsAsync(),
    importProvidersFrom(HttpClientModule),
    importProvidersFrom(
      TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: HttpLoaderFactory,
        deps: [HttpClient]
      }
    })),
  ]
};

Type of Error 3: material icons

In the navigation MFE which has a dependency with material icons, the icons are not loaded, in instead the name of the icon is displayed

<span class="p-menuitem-icon material-icons">account_tree</span>

the text "account_tree" is displayed on the screen instead of the icon itself, as if it was not loaded from the source:
https://navigation-dev-my-namespace.apps.nonprod-ocp.company.com/libs/@my-organisation/navigation/1716885566/material-icons.woff

Question

Could someone please explain why some of the assets are not correctly resolved, and how to fix it please.

Strangely, the assets of the styleguide are correctly retrived in the libs subfolder, but not the application MFEs.

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

1 participant