0

我拿起了https://www.angulararchitects.io/en/aktuelles/multi-framework-and-version-micro-frontends-with-module-federation-the-good-the-bad-the-ugly的 git 示例/进一步引用为Ex。1 和https://www.angulararchitects.io/en/aktuelles/dynamic-module-federation-with-angular/进一步引用为 Ex。2.

前任。1 中有多个角度项目。为了简化起见,只需使用 shell 和 mfe1(微前端 1)。该示例对每个项目使用package.jsonand angular.json,我想使用它是因为在现实世界中,我们可能有很多微前端,并且不想一次更新所有依赖项或管理所有依赖项。在这个例子中,罪魁祸首是它使用了 aWrapperComponent和 a registry.ts。当一个路由被激活时,它会通过这个函数延迟加载:

    ngAfterContentInit(): void {
    
        const elementName = this.route.snapshot.data['elementName'];
        const importName = this.route.snapshot.data['importName'];
    
        const importFn = registry[importName];
        importFn()
          .then(_ => console.debug(`element ${elementName} loaded!`))
          .catch(err => console.error(`error loading ${elementName}:`, err));
    
        const element = document.createElement(elementName);
        this.vc.nativeElement.appendChild(element);
    
    }

registry包含一个映射,如

export const registry = {
    mfe1: () => import('mfe1/web-components')
};

AFAIK 发生了一些“webpack 魔术”来进行导入,并mfe1通过webpack.config.js. 这意味着我需要在编译时知道微前端的数量和名称。

前任。2 使用动态方法,通过使用LookupService

export class LookupService {
    lookup(): Promise<Microfrontend[]> {
        return Promise.resolve([
            {
                // For Loading
                remoteEntry: 'http://localhost:3000/remoteEntry.js',
                remoteName: 'mfe1',
                exposedModule: './Module',

                // For Routing
                displayName: 'Flights',
                routePath: 'flights',
                ngModuleName: 'FlightsModule'
            }
        ] as Microfrontend[]);
    }
}

以及构建路线的功能

export function buildRoutes(options: Microfrontend[]): Routes {

    const lazyRoutes: Routes = options.map(o => ({
        path: o.routePath,
        loadChildren: () => loadRemoteModule(o).then(m => m[o.ngModuleName])
    }));

    return [...APP_ROUTES, ...lazyRoutes];
}

现在,我想结合这两种方法。所以,我复制了 Ex 的方法。2 进入前。1,现在正在努力解决以下问题。

当简单地公开AppModuleofmfe1并动态加载它时,我有多个RouterModule.forRoot()调用,这是无法避免的,因为mfe1并且shell也应该独立工作。这会导致错误。所以,我只是做了一个“虚拟” AppModule,调用RouterModule.forRoot()并有一条指向另一个模块的路径,该模块仅使用RouterModule.forChild()FlightsModule在示例中称为)。这个另一个FlightsModule是动态加载的shell,因此不再多次调用RouterModule.forRoot(). 这是必要的还是有其他选择?

因为我只能加载 的一个“子模块” mfe1,所以我需要转移几个常用文件的内容。mfe1/src/styles.css对于全局样式,需要将其移动到始终由路由激活的组件,encapsulation: ViewEncapsulation.None以使其全局可访问,例如在ngx-toastr需要使用时。微前端不能使用中的styles选项,因为它在加载到. 通常导入和声明的所有内容都需要转移到子模块。等等...angular.jsonshellAppModule

最后,我认为这种方法绝对不是最好的,但我尝试了很多不同的东西,除了这个,没有一个是有效的。

4

1 回答 1

0

当我研究这个时,链接的方式对我不起作用,我不记得它是否与您显示的错误相同。

这对我有用:

-------- app-shell --------
app-shell 根文件夹中的 Webpack.config.js 文件:

const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');

module.exports = {
  resolve: {
    plugins: [
      new TsconfigPathsPlugin({
        configFile: './projects/app-shell/tsconfig.app.json',
      })
    ]
  },
  output: {
    uniqueName: "appShell"
  },
  optimization: {
    runtimeChunk: false
  },
  plugins: [
    new ModuleFederationPlugin({
        remotes: { },
        shared: {
          "@angular/core": { singleton: true, strictVersion: true }, 
          "@angular/common": { singleton: true, strictVersion: true }, 
          "@angular/router": { singleton: true, strictVersion: true }
        }
    })
  ],
};

它指向 app-shell tsconfig.app.json。
我的 app-shell app-routing.module.ts 文件:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AppComponent } from './app.component';

import { loadRemoteModule } from '@angular-architects/module-federation';
import { environment } from '../environments/environment';

const routes: Routes = [
  {
    path: 'envio',
    loadChildren: () =>
      loadRemoteModule({
        remoteEntry: environment.remotes.emktConfig,
        remoteName: 'emktConfig',
        exposedModule: './Send',
      }).then((m) => m.SendModule),
  },
  {
    path: 'formularios',
    loadChildren: () =>
      loadRemoteModule({
        remoteEntry: environment.remotes.emktConfig,
        remoteName: 'emktConfig',
        exposedModule: './ConfigForms',
      }).then((m) => m.ConfigFormsModule),
  },
  {
    path: 'integracoes',
    loadChildren: () =>
      loadRemoteModule({
        remoteEntry: environment.remotes.emktConfig,
        remoteName: 'emktConfig',
        exposedModule: './Integrations',
      }).then((m) => m.IntegrationsModule),
  },
  {
    path: 'dominios',
    loadChildren: () =>
      loadRemoteModule({
        remoteEntry: environment.remotes.emktConfig,
        remoteName: 'emktConfig',
        exposedModule: './Domains',
      }).then((m) => m.DomainsModule),
  },
  {
    path: 'integracao',
    loadChildren: () =>
      loadRemoteModule({
        remoteEntry: environment.remotes.emktConfig,
        remoteName: 'emktConfig',
        exposedModule: './Integrations',
      }).then((m) => m.IntegrationsModule),
  },
  {
    path: 'conteudo',
    loadChildren: () =>
      loadRemoteModule({
        remoteEntry: environment.remotes.emktConfig,
        remoteName: 'emktConfig',
        exposedModule: './Content',
      }).then((m) => m.ContentModule),
  },
  // {
  //   path: '**',
  //   redirectTo:"/dominios"
  // },
];

@NgModule({
  imports: [RouterModule.forRoot(routes, { initialNavigation: 'enabled' })],
  exports: [RouterModule]
})
export class AppRoutingModule { }

varenvironment.remotes.emktConfig是一个 url,它指向remoteEntry.js另一个项目中的文件(在我的情况下,它在同一个文件夹中),在开发时,它指向端口,例如: localhost://4201/remoteEntry.js

-------- 其他项目 -------- 来自其他项目的 Webpack.config.js 文件:

const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');

module.exports = {
  resolve:{
    plugins:[
      new TsconfigPathsPlugin({
        configFile: './projects/emkt-config/tsconfig.app.json'
      })
    ]
  },
  output: {
    uniqueName: "emktConfig",
  },
  optimization: {
    runtimeChunk: false
  },
  plugins: [
    new ModuleFederationPlugin({
      name: "emktConfig",
      filename: "remoteEntry.js",
      exposes: {
        "./Domains":"./projects/emkt-config/src/app/domains/domains.module.ts",
        './Content': './projects/emkt-config/src/app/content/content.module.ts',
        './Send': './projects/emkt-config/src/app/send/send.module.ts',
        './ConfigForms': './projects/emkt-config/src/app/config-forms/config-forms.module.ts',
        './Integrations': './projects/emkt-config/src/app/integrations/integrations.module.ts'
      },
      shared: {
        "@angular/core": { singleton: true, strictVersion: true }, 
        "@angular/common": { singleton: true, strictVersion: true }, 
        "@angular/router": { singleton: true, strictVersion: true }
      }
    })
  ],
};

所以,这里是我从另一个项目中公开模块并在 app-shell 路由器中访问它的地方。

应用程序外壳:
在此处输入图像描述

其他项目:
在此处输入图像描述

而在项目中,是一个普通的Angular项目:
在此处输入图像描述

--------- 一些其他问题 ---------

1 - 对于 ngx-toast,您应该将其添加到所有 webpack.config.js 文件中的共享对象中,包括 shell 和其他项目。

1.1 - 关于在应用程序之间共享导入,据我所知,你不能这样做,因为它是不同的项目,所以,每个项目应该有你自己的 ngx-toast 导入,也许你可以创建一个服务并在之间共享该服务项目,但我不知道它是否有效。

2 - 关于css,我有这个文件结构......: 在此处输入图像描述

在 web 文件夹中,我有在所有项目中加载的主要样式,它在 angular.json 中设置为所有新项目。 在此处输入图像描述

2.1 - 思维差异,我只能想象一个 CDN 来加载该文件,而不是与项目一起构建

于 2021-09-14T17:54:28.267 回答