模块联合共享服务

Module Federation Shared Services

我正在使用 Angular 11 和 Webpack 5 开发一个新项目。我的工作基于 Manfred Steyer 的 Module Federation Plugin Example 存储库,它使用 Angular CLI。 我不知道如何在我的两个应用程序之间共享本地 Angular 库中的单例服务

我会尽力解释我的设置。真的很抱歉这会持续多久。

简化的文件结构

root
  package.json
  projects/
    shell/
      src/app/
        app.module.ts
        app.component.ts
      webpack.config.ts <-- partial config
    mfe1/
      src/app/
        app.module.ts
        app.component.ts
      webpack.config.ts <-- partial config
    shared/
      src/lib/
        global.service.ts
      package.json <-- package.json for lib

Angular

两个 app.component.ts 文件相同

import {GlobalService} from 'shared';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent {
  constructor(shared: SharedService) {}
}

global.service.ts

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class GlobalService {

  constructor() {
    console.log('constructed SharedService');
  }
}

Webpack

shell/webpack.config.ts

const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");

module.exports = {
  output: {
    publicPath: "http://localhost:5000/",
    uniqueName: "shell"
  },
  optimization: {
    // Only needed to bypass a temporary bug
    runtimeChunk: false
  },
  plugins: [
    new ModuleFederationPlugin({
      remotes: {
        'mfe1': "mfe1@http://localhost:3000/remoteEntry.js"
      },
      shared: ["@angular/core", "@angular/common", "@angular/router"]
    })
  ],
};

mfe1/webpack.config.ts

const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");

module.exports = {
  output: {
    publicPath: "http://localhost:3000/",
    uniqueName: "mfe1"
  },
  optimization: {
    // Only needed to bypass a temporary bug
    runtimeChunk: false
  },
  plugins: [
    new ModuleFederationPlugin({

      // For remotes (please adjust)
      name: "mfe1",
      library: {type: "var", name: "mfe1"},
      filename: "remoteEntry.js",
      exposes: {
        './Module': './projects/mfe1/src/app/app.module.ts',
      },
      shared: ["@angular/core", "@angular/common", "@angular/router"]
    })
  ],
};

共享库package.json

{
  "name": "shared",
  "version": "0.0.1",
  "main": "src/public-api.ts",
  "peerDependencies": {
    "@angular/common": "^11.0.0-next.5",
    "@angular/core": "^11.0.0-next.5"
  },
  "dependencies": {
    "tslib": "^2.0.0"
  }
}

此配置编译并运行,但 mfe1 实例化一个新的 GlobalService。我在应用程序加载时看到“constructed SharedService”登录,然后在加载远程模块后再次登录。我试图通过 ScriptedAlchemy 关注 another example,但我不知道如何让它工作。它要么编译并运行并创建两个实例,要么编译失败引用丢失的模块,具体取决于我如何弄乱我的配置我确定。

ScriptedAlchemy 示例使我似乎需要在 webpack.config.ts 文件的 shared 库数组中引用我的共享库。这完全有道理,但我无法让它工作

shell/webpack.config.ts and mfe1/webpack.config.ts
  ...
  shared: ["@angular/core", "@angular/common", "@angular/router", "shared"]

如果我以这种方式引用本地库,我不可避免地会在构建过程中出错

Error: Module not found: Error: Can't resolve 'shared' in '/Path/to/module-federation-plugin-example/projects/shell/src/app'

我发布的例子是简化的。希望不要太过分,但这里有一个 link to a repo 显示了问题

TL;DR

  • 确保您的共享服务具有的任何模块依赖项也被共享。
  • 确保您已在 webpack.config.ts
  • 中正确构建共享配置

我要指出,我的项目是一个使用 Angular CLI 的 NX Monorepo。我正在使用(目前)Angular 11.0.0-next 和 Webpack 5,在撰写本文时,它仅作为 opt-in 与 ng11 一起使用。

如果你在 tsconfig 中使用路径别名,你习惯于导入本地库,如“@common/my-lib”,但你不能在你的 webpack 配置中通过别名共享模块。此外,如果你在 NX 上,如果你从绝对或相对库路径导入,你的 linting 会报错,所以 Webpack 想要的和 nx/tslint 想要的之间存在脱节。

在我的项目中,我有一些库别名如下

tsconfig.base.json
...
"paths": {
    "@common/facades": [
        "libs/common/facades/src/index.ts"
    ],
    "@common/data-access": [
        "libs/common/data-access/src/index.ts"
    ]
}

为了在我的 webpack 配置中完成这项工作。我们必须使用这些别名,但通过使用 import 选项

告诉 webpack 在哪里可以找到这些库
apps/shell/webpack.config.js
...
plugins: [
    new ModuleFederationPlugin({
        remotes: {
            'dashboard': 'dashboard@http://localhost:8010/remoteEntry.js',
            'reputation': 'reputation@http://localhost:8020/remoteEntry.js'
        },
        shared:         {
            "@angular/core": {
                "singleton": true
            },
            "@angular/common": {
                "singleton": true
            },
            "@angular/router": {
                "singleton": true
            },
            "@angular/material/icon": {
                "singleton": true
            },
            "@common/data-access": {
                "singleton": true,
                "import": "libs/common/data-access/src/index"
            },
            "@common/facades": {
                "singleton": true,
                "import": "libs/common/facades/src/index"
            },
            "rxjs": {
                "singleton": true
            },
            "ramda": {
                "singleton": true
            }
        }
    })
]

这解决了我遇到的 Webpack 在编译时无法找到模块的问题。

我的第二个问题是我试图共享依赖于公共数据访问服务的公共外观。我没想到共享数据访问服务,因为它们是无状态的,但这导致我的 MFE 实例化新的单例。这一定是因为共享服务具有 'unshared' 依赖性。共享我的数据访问层和我的外观层导致在我的应用程序中共享单例。

依赖关系问题是这里最重要的事情,因为它更难调试。没有任何错误或任何东西——只是没有像您预期的那样工作。一开始您甚至可能没有意识到您有一个共享服务的两个实例。因此,如果您 运行 遇到此问题,请查看您的依赖项并确保您共享所需的一切。这可能是在您的应用程序中保持最小共享 state/dependencies 的一个理由。

  • 关于@angular/material/icon 共享配置的快速说明。在我的应用程序中,我使用每个应用程序都需要的图标填充 MatIconRegistry。我必须专门共享 @angular/material/icon 以在每个 MFE 之间共享单例。