首页 > 解决方案 > 模块联合共享服务

问题描述

我正在使用 Angular 11 和 Webpack 5 开发一个新项目。我的工作基于 Manfred Steyer 的Module Federation Plugin Example repo,它使用 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

两个 app.component.ts 文件是相同的

import {GlobalService} from 'shared';

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

全球服务.ts

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

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

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

网页包

外壳/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. 我在应用程序加载时看到“构造的 SharedService”,然后在加载远程模块后再次看到。我试图遵循ScriptedAlchemy 的另一个示例,但我不知道如何使其工作。它要么编译并运行并创建两个实例,要么无法编译引用缺少的模块,这取决于我确定我的配置是如何搞砸的。

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

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'

我发布的示例已简化。希望不要过分如此,但这里是一个显示问题的回购链接

标签: angularwebpackangular-clinrwl-nxwebpack-5

解决方案


TL;博士

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

我要注意我的项目是一个使用 Angular CLI 的 NX Monorepo。我正在使用(目前)Angular 11.0.0-next 和 Webpack 5,在撰写本文时,它只能作为 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 实例化新的单例。这一定是因为共享服务具有“非共享”依赖关系。与我的外观层共享我的数据访问层导致在我的整个应用程序中共享单例。

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

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

推荐阅读