模块联合共享服务
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 之间共享单例。
我正在使用 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
选项
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 之间共享单例。