Общие службы федерации модуля

Я работаю над новым проектом с использованием Angular 11 и Webpack 5. Я основываю свою работу на Manfred Steyer's Репозиторий" 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

Угловой

оба файла 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. Я вижу, как построенный 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'

Примеры, которые я опубликовал, упрощены. Надеюсь, не слишком, но вот ссылка на репо, который показывает проблему


person Mike    schedule 23.10.2020    source источник


Ответы (1)


TL;DR

  • Убедитесь, что все зависимости модулей ваших общих служб также являются общими.
  • Убедитесь, что вы правильно структурировали общую конфигурацию в webpack.config.ts

Замечу, что мой проект - это NX Monorepo, использующий Angular CLI. Я использую (на данный момент) Angular 11.0.0-next и Webpack 5, которые на момент написания доступны только по подписке с ng11.

Если вы используете псевдонимы путей в своем tsconfig, вы привыкли импортировать локальные библиотеки, такие как @ common / my-lib, но вы не можете делиться модулями по псевдонимам в конфигурации вашего веб-пакета. Кроме того, если вы используете NX, ваш линтинг будет жаловаться, если вы импортируете из абсолютных или относительных путей библиотеки, поэтому существует разрыв между тем, что хочет 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"
    ]
}

Чтобы это работало в моем файле config. Мы должны использовать эти псевдонимы, но указать веб-пакету, где найти библиотеки, с помощью параметра import

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 share. В моем приложении я заполняю MatIconRegistry значками, которые нужны каждому приложению. Мне пришлось поделиться значком @ angular / material / специально, чтобы использовать синглтон во всех MFE.
person Mike    schedule 28.10.2020
comment
У вас есть полный пример, которым вы можете поделиться? Будет здорово! - person mah.cr; 13.11.2020
comment
@Mike, для чего используется singleton: true? - person TahaOUARRAK; 01.12.2020
comment
singleton: true использует один и тот же экземпляр общего модуля во всех ваших интегрированных модулях. Это особенно важно в angular для таких вещей, как глобальные службы аутентификации. - person Mike; 01.12.2020