在 Angular 2、4、5、6 中实现插件架构/插件系统/可插拔框架

Posted

技术标签:

【中文标题】在 Angular 2、4、5、6 中实现插件架构/插件系统/可插拔框架【英文标题】:Implementing a plugin architecture / plugin system / pluggable framework in Angular 2, 4, 5, 6 【发布时间】:2017-05-17 05:33:38 【问题描述】:

2018 年 5 月 24 日更新:我们现在是我原来帖子中 Angular 的 +3 版本,但仍然没有最终可行的解决方案。 Lars Meijdam (@LarsMeijdam) 提出了一种有趣的方法,当然值得一看。 (由于专有问题,他不得不暂时删除他最初发布示例的 GitHub 存储库。但是,如果您想要副本,可以直接给他发消息。请参阅下面的 cmets 了解更多信息。)

Angular 6 最近的架构变化确实让我们更接近解决方案。此外,Angular Elements (https://angular.io/guide/elements) 提供了一些组件功能——尽管与我最初在这篇文章中描述的不太一样。

如果出色的 Angular 团队中的任何人碰巧遇到了这个问题,请注意似乎还有很多其他人也对这个功能非常感兴趣。积压工作可能值得考虑。


我想在Angular 2Angular 4Angular 5Angular 6 应用程序中实现一个可插拔(插件)框架。

(我开发这个可插拔框架的具体用例是我需要开发一个微型内容管理系统。由于这里未必详细说明的一些原因,Angular 2/4/5/6 几乎完美地满足了大多数需求系统。)

我所说的可插入框架(或插件架构)是指允许第三方开发人员通过使用可插入组件来创建或扩展主要应用程序功能的系统而无需直接访问或了解主要应用程序的源代码或内部工作原理

(关于“无法直接访问或了解应用程序的源代码或内部工作原理”的措辞是核心目标。)

可插入框架的示例包括常见的内容管理系统,例如 WordPressDrupal

理想的情况(与 Drupal 一样)是能够简单地将这些可插入组件(或插件)放入文件夹中,让应用程序自动检测或发现它们,并让它们神奇地“工作”。 "以某种可热插拔的方式发生这种情况,这意味着在应用程序运行时,将是最佳的。

我目前正在尝试确定以下五个问题的答案(在您的帮助下)。

    实用性:Angular 2/4/5/6 应用程序的插件框架是否实用? (到目前为止,我还没有找到任何实用的方法来使用 Angular2/4/5/6 创建一个真正可插拔的框架。) 预期挑战:在为Angular 2/4/5/6 应用程序实施插件框架时可能会遇到哪些挑战? 实施策略:可以采用哪些特定技术或策略来实施Angular 2/4/5/6 应用程序的插件框架? 最佳实践:Angular 2/4/5/6 应用程序实施插件系统的最佳实践是什么? 替代技术: 如果插件框架在Angular 2/4/5/6 应用程序中实用,那么相对等效技术(例如React) 可能适用于现代高反应性 Web 应用程序

一般来说,使用Angular 2/4/5/6 是非常可取的,因为:

它自然是极快的——快得惊人。 它消耗的带宽非常少(在初始加载之后) 它的占用空间相对较小(在 AOTtree shaking 之后)--并且占用空间继续缩小 它功能强大,Angular 团队和社区正在继续快速发展其生态系统 它与许多最佳和最新的 Web 技术(例如 TypeScriptObservables)配合得很好 Angular 5 现在支持服务工作者 (https://medium.com/@webmaxru/a-new-angular-service-worker-creating-automatic-progressive-web-apps-part-1-theory-37d7d7647cc7) 得到Google 的支持,未来很可能会得到支持和增强

我非常想在我当前的项目中使用Angular 2/4/5/6。如果我能够使用Angular 2/4/5/6,我还将使用Angular-CLI 并且可能使用Angular Universal(用于服务器端渲染。)

到目前为止,关于上述问题,这是我的想法。 请查看并提供您的反馈和启发。

Angular 2/4/5/6 应用程序使用包——但这不一定与允许应用程序中的插件相同。其他系统中的插件(例如Drupal)基本上可以通过将插件文件夹拖放到系统自动“拾取”的公共模块目录中来添加。在Angular 2/4/5/6 中,包(可能是插件)通常通过npm 安装,添加到package.json,然后手动导入到应用程序中——如app.module。这比Drupal 删除文件夹并让系统自动检测包的方法复杂得多。 安装插件越复杂,人们使用它们的可能性就越小。如果Angular 2/4/5/6 有办法自动检测和安装插件会更好。我非常有兴趣找到一种方法,它允许非开发人员安装 Angular 2/4/5/6 应用程序并安装任何选定的插件,而无需了解所有应用程序的架构。

一般来说,提供可插拔架构的好处之一是第 3 方开发人员可以很容易地扩展系统的功能。显然,这些开发人员不会熟悉他们正在插入的应用程序的所有复杂代码。一旦开发了插件,其他技术含量更低的用户可以简单地安装应用程序和任何选定的插件。但是,Angular 2/4/5/6 相对复杂,学习曲线非常漫长。更复杂的是,大多数生产Angular 2/4/5/6 应用程序还使用Angular-CLIAngular UniversalWebPack。正在实现插件的人可能至少需要了解所有这些如何组合在一起的基本知识——以及对TypeScript 的强大工作知识和对NodeJS 的合理熟悉。知识要求是否如此极端以至于没有第三方愿意开发插件?

大多数插件可能会有一些服务器端组件(例如,用于存储/检索插件相关数据)以及一些客户端输出。 Angular 2/4/5 特别(并且强烈)不鼓励开发人员在运行时注入他们自己的模板——因为这会带来严重的安全风险。为了处理插件可能适应的多种类型的输出(例如图形的显示),允许用户创建以另一种形式注入响应流的内容似乎是必要的。我想知道如何在不破坏Angular 2/4/5/6 的安全机制的情况下满足这种需求。

大多数生产 Angular 2/4/5/6 应用程序是使用 Ahead of Time (AOT) 编译进行预编译的。 (可能全部都应该。)我不确定如何将插件添加到(或集成到)预编译的应用程序中。最好的方案是与主应用程序分开编译插件。但是,我不确定如何使这项工作。回退可能是使用任何包含的插件重新编译整个应用程序,但这对于只想安装应用程序(在他自己的服务器上)以及任何选定的插件的管理用户来说有点复杂。

Angular 2/4/5/6 应用程序中,尤其是预编译的应用程序中,单个错误或冲突的代码可能会破坏整个应用程序。 Angular 2/4/5/6 应用程序并不总是最容易调试的。应用不良插件可能会导致非常不愉快的体验。我目前不知道有一种机制可以优雅地处理行为不端的插件。

【问题讨论】:

在我看来,angular 2 模块是一个插件。 @angular/router、@angular/forms、@angular/http、@angular/material,这些都是 Angular 的“插件”,我们可以看看它们是如何制作“插件”的。 @Timathon,不幸的是,它们不一样。插件系统允许在不修改核心应用程序代码的情况下扩展应用程序。使用@angular/router、@angular/forms 等需要用户修改应用程序。这些是真正的库而不是插件。我真的更感兴趣的是允许非开发人员管理用户选择和使用他们最感兴趣的插件,而不必了解应用程序的内部细节。 你有没有得到这个?我有兴趣尝试类似的东西。 Angular 2 的构建方式(围绕模块)我认为插件类型的架构非常适合它,但它似乎没有任何示例等。 @Joe,我仍然没有解决这个问题的好方法。我和你的想法一样。 我在 github 上创建了一个存储库,其解决方案可能会有所帮助。它使用 Angular 6 个库和 1 个基础应用程序,懒惰地加载 UMD 捆绑库; github.com/lmeijdam/angular-umd-dynamic-example如果有什么建议,欢迎补充! 【参考方案1】:

更新

对于Angular 11,我强烈建议您查看Webpack 5 Module Federation的实现

?https://github.com/alexzuza/angular-plugin-architecture-with-module-federation

以前的版本

?️ Github 演示 angular-plugin-architecture

也许 Ivy 可以改变一些东西,但目前我使用的解决方案是使用 Angular CLI Custom Builder 并满足以下要求:

AOT 避免重复代码(包如@angular/corecommon,forms,router,rxjs,tslib) 在所有插件中使用共享库,但不要在每个插件中使用该共享库生成工厂,而是重用库代码和工厂 与 Angular CLI 提供的优化级别相同 对于导入外部模块,我们只需要知道一件事:它们的包文件路径 我们的代码应该能够识别模块并将插件放入页面中 支持服务器端渲染 仅在需要时加载模块

用法很简单:

ng build --project plugins --prod --modulePath=./plugin1/plugin1.module#Plugin1Module 
         --pluginName=plugin1 --sharedLibs=shared --outputPath=./src/assets/plugins

在我的文章中对此有更多的了解:

Building extensible Dynamic Pluggable Enterprise Application with Angular

【讨论】:

很好的例子!一个问题。如何在运行时将 OAuthToken 从我们的主应用程序传递到库服务? @yurzui 如果我们有多个 Angular 应用程序并且我们使用它们的整个分布而不是像插件 1 和插件 2 那样制作模块,我们可以使用相同的方法吗? 您能否在不久的将来重新审视这个示例并使其与 Ivy 兼容?如果您愿意,请添加一个共享服务和共享InjectionToken 的示例,它们将在 AppModule 中提供并注入其他插件。谢谢!【参考方案2】:

我在 github 上创建了一个存储库,其中提供了一个可能会有所帮助的解决方案。它使用 Angular 6 个库和 1 个基础应用程序,懒惰地加载 UMD 捆绑库; https://github.com/lmeijdam/angular-umd-dynamic-example

如果您有任何建议,请随时补充!

【讨论】:

正如另一条评论中提到的,由于公司政策,我不得不将存储库设为私有。。稍后我会将其重新上线,因为讨论正在进行中 很想看看你的解决方案。 @Subhan,我目前正忙于重写存储库,然后再将其放回 GH。再给我一点时间。否则你也可以直接联系我! :D @lars-meijdam :我们仍然会等待很多 :D ...我也很感兴趣。提前谢谢你【参考方案3】:

我刚刚为我的书“Developing with Angular”发布了一个新章节,该章节讨论了 Angular 2+ 中的插件主题,并且应该对尝试构建外部插件的人非常感兴趣。

关键点:

插件 基于字符串名称构建组件 从外部来源加载配置 动态更改应用程序路径 外部插件 创建插件库 将插件加载到应用程序中 带有插件内容的动态路由

这本书是免费的,并且有“支付你想要的”模式。 随意获取一份副本,希望对您有所帮助。

【讨论】:

如何用书中说明的插件架构替换子组件?我将替换模板,或者我可能会添加一个输入属性 ecc... 同时我需要知道是否存在覆盖/扩展所提供服务的方法。 @Denis Vuyka,这本书看起来不错,但缺少关键部分——对 AoT 编译的支持。【参考方案4】:

带有工作插件系统的示例应用程序(感谢 Gijs 建立 github 存储库!)https://github.com/PacktPublishing/Mastering-Angular-2-Components/tree/master/angular-2-components-chapter-10 基于电子书 Mastering Angular 2 Components

扩展核心应用组件的插件架构 文件插件系统(用于简单地添加插件目录/文件而无需编辑任何核心配置文件或需要重新编译您的应用程序!) 加载和动态使用插件 构建基本插件管理器以即时激活/停用插件

干杯, 尼克拉斯

【讨论】:

看不到该链接中的示例代码,您可以发布代码笔或 JSFiddle 吗? github.com/PacktPublishing/Mastering-Angular-2-Components/tree/… 我看过书,但是插件部分已经过时了。它使用普通的 JS 和 SystemJS,而 Angular 的目标是 Typescript 和 Webpack。使用 Webpack 和 Typescript 似乎无法实现,我发布了一个问题以防您找到任何解决方案。这是link 这应该会有所帮助:github.com/mgechev/angular-seed/issues/… 有人可以确认上述方法是否有效吗?如果我们不想使用 systemjs 怎么办【参考方案5】:

您正在寻找的是延迟模块加载。 这是一个例子: http://plnkr.co/edit/FDaiDvklexT68BTaNqvE?p=preview

import Component from '@angular/core';
import Router from '@angular/router';

@Component(
  selector: 'my-app',
  template: `
    <a [routerLink]="['/']">Home</a> | 
    <a [routerLink]="['/app/home']">App Home</a> |
    <a [routerLink]="['/app/lazy']">App Lazy</a>

    <hr>
    <button (click)="addRoutes()">Add Routes</button>

    <hr>
    <router-outlet></router-outlet>
  `
)
export class App 
  loaded: boolean = false;
  constructor(private router: Router) 

  addRoutes() 
    let routerConfig = this.router.config;

    if (!this.loaded) 
      routerConfig[1].children.push(
        path: `lazy`,
        loadChildren: 'app/lazy.module#LazyModule'
      );

      this.router.resetConfig(routerConfig);
      this.loaded = true;
    
  

最好的...汤姆

【讨论】:

感谢您抽出宝贵时间回答我的问题。我熟悉延迟加载的模块,但这些并不是我想要的。延迟加载的模块在设计时仍然必须是已知的。我希望能够添加构建原始应用程序时未知或未设想的实际模块和功能。 (我正在寻找的是更动态的东西。)当然,这些组件会使用(某种形式的)延迟加载,但这只是难题的一小部分。再次感谢您分享这个答案。 我同意这不能回答问题。延迟加载对插件架构没有帮助,因为它们在设计时是必需的。它只是在需要时才将数据下载/传输到客户端。 如果您的应用程序在编译时知道所有可用的插件模块怎么办。在您向平台添加新模块时,必须使用该模块重新编译它。只是一个想法...不确定我这是否会显着增加 JS 文件的大小,不确定延迟加载功能是否会将此类模块放入单独的文件中然后延迟加载,我只是分享我的想法...跨度> @VladimirPrudnikov,如果应用程序可以在编译时知道所有插件,那就太好了。然而,这个想法是能够在编译应用程序之后添加插件。这将允许真正动态地插入模块。但是,这将要求模块在部署时也进行预编译——我不确定这将如何工作。我也不确定如何保持插件模块版本与 Angular 兼容。【参考方案6】:

我在引导时对加载和编译其他模块进行了破解,但我还没有解决循环依赖的问题

 const moduleFile: any = require(`./$app/$app.module`),
                    module = moduleFile[Object.keys(moduleFile)[0]];

 route.children.push(
     path: app,
     loadChildren: (): Promise<any> => module
 );
 promises.push(this.compiler.compileModuleAndAllComponentsAsync(module));

然后在 AppModule 中添加:


        provide: APP_INITIALIZER,
        useFactory: AppsLoaderFactory,
        deps: [AppsLoader],
        multi: true
,

【讨论】:

【参考方案7】:

我也在寻找 angular 2/4 的插件系统,用于为工作中的企业应用程序开发 RAD 环境。经过一番研究,我决定实现一个数据库存储(但可能在文件系统中)伪 Angular 组件的集合。

存储在数据库数据库中的组件基于ng-dynamic 主要组件实现类似这样:

declare var ctx: any;

@Component(
    selector: 'my-template',
    template: `
<div>
    <div *dynamicComponent="template; context:  ctx: ctx ;"></div>
</div>
  `,
    providers: [EmitterService],

)

export class MyTemplateComponent implements OnMount, AfterViewInit, OnChanges 


    // name
    private _name: string;
    get name(): string 
        return this._name;
    
    @Input()
    set name(name: string) 
        this._name = name;        
        this.initTemplate();
    

    template: string;
    ctx: any = null;

    private initTemplate() 

        this.templateSvc.getTemplate(this.name).subscribe(res => 
            // Load external JS with ctx implementation
            let promise1 = injectScript(res.pathJs);
            // Load external CCS
            let promise2 = injectScript(res.pathCss);

            Promise.all([promise1, promise2]).then(() => 

                // assign external component code
                this.ctx = ctx; //

                // sets the template
                this.template = res.template;

                this.injectServices();

                if (this.ctx && this.ctx.onInit) 
                    this.ctx.onInit();
                

            );

        );

    

外部javascript代码类似于angular组件:

var ctx = 

// injected    
_httpService: ,
_emitterService: null,

// properies
model: 
    "title": "hello world!",
,


// events
onInit() 
    console.log('onInit');
,

onDestroy() 
    console.log('onDestroy');
,

onChanges(changes) 
    console.log('changes', changes);
,

customFunction1() 
    console.log('customFunction1');
,

childTemplateName: string = 'other-component'; 

;

并且组件的模板就像角模板:

<a (click)="customFunction1()"> ctx.model.title </a>
<input [(ngModel)]="ctx.model.title" type="text" />

也可以嵌套:

<a (click)="customFunction1()"> ctx.model.title </a>
<my-template [name]="childTemplateName"></my-template>

虽然并不完美,但自定义组件的开发者拥有与 angular2/4 类似的框架。

【讨论】:

【参考方案8】:

可以“手动”完成。由于 webpack 对外部(插件)模块一无所知,因此他不能将它们包含在包中。所以我所做的就是查看 webpack 生成的代码,我在 main.bundle.js 中找到了这段代码:

var map = 
"./dashboard/dashboard.module": ["../../../../../src/app/dashboard/dashboard.module.ts","dashboard.module"]; 

让我们检查该数组包含的内容:

    "./dashboard/dashboard.module" - 这是我们想要延迟加载的模块的路由 URL,例如: path: 'dashboard', loadChildren: './dashboard/仪表板.module#DashboardModule' "../../../../../src/app/dashboard/dashboard.module.ts" - 这是入口点(构造器)取自 "dashboard.module" - 没有 chunk.js 的实际文件名(例如:dashboard.module.chunk.js

所以理论上,如果你在地图属性中添加入口配置你的路由并遵循模式,你就可以拥有一个插件系统。现在的挑战是如何从该地图属性中添加或删除条目。显然它不能从角度代码完成,应该为外部工具完成。

【讨论】:

【参考方案9】:

我尝试使用 ABP、Angular 和 ASP.NET Core 实现插件架构:https://github.com/chanjunweimy/abp_plugin_with_ui

基本上,我使用不同的角度应用程序开发了角度插件,然后我将它们动态地添加在一起。

关于我如何实现它的更多信息:

我有 2 个 angular-cli 应用程序,1 个是主要的 angular cli 应用程序,另一个是插件 angular cli 应用程序。我们在 Angular-cli 插件架构方法中面临的问题是我们如何集成它们。

现在,我所做的是,我在两个应用程序上运行 ng-build,并将它们放入“wwwroot”文件夹中,然后托管在 ASP.NET core 2.0 服务器中。一个展示这个想法的更简单的存储库是 Angular Multiple App:https://github.com/chanjunweimy/angular-multiple-app

abp_plugin_with_ui 是一个用于开发包含后端和 Angular cli 的插件的存储库。对于后端,我使用了 aspnetboilerplate 框架,前端是使用多个 angular-cli 应用程序开发的。

要使主应用程序与插件应用程序集成,我们必须在两个应用程序上运行“ng-build”(注意我们也必须更改为插件应用程序的href),然后我们移动构建插件 Angular cli 应用程序的内容,到主应用程序“wwwroot”文件夹。完成这一切之后,我们就可以运行“dotnet run”来服务于 ASP.NET Core 2.0 Web 应用程序来托管“ng build”生成的静态文件。

希望它有所帮助。欢迎任何cmets! ^^

【讨论】:

我正在尝试关注您的插件文档,但我认为文档跳过了几个步骤。如果我误读了它,我深表歉意。我不清楚整个“添加插件”部分。我一步一步地遵循了这一点,但我并没有真正看到结果。运行 power shell 后,我应该在端口 4200 上看到什么?我在 /aspnet-core/src/Todo.MainProject.Web.Host/ 中看不到插件文件夹。我运行了powershell,但没有创建该文件夹。任何帮助表示赞赏。我认为你的方法是我需要的,但我对它的工作原理有点模糊。 好的。在问这个问题之前我应该​​这样做。我花时间调试,并想出了我的答案。 #1)powershell 没有把 .zip 放在需要的地方,我需要创建插件文件夹,然后移动 zip。 #2) 当 Angular 应用启动时,它会动态调用加载器,并将插件复制到 webroot。那是一种拼写,但我现在明白了。 #3) 我必须使用 url 来调用插件,它不会出现在任何地方。我期待它出现在仪表板上。感谢您的工作,这是一大段代码。【参考方案10】:

有点离题,但 UI 组件库可能会引起一些搜索插件的读者的兴趣:https://medium.com/@nikolasleblanc/building-an-angular-4-component-library-with-the-angular-cli-and-ng-packagr-53b2ade0701e

NativeScript 有内置的 UI 插件:https://docs.nativescript.org/plugins/building-plugins 这些插件需要一个 Angular Wrapper:https://docs.nativescript.org/plugins/angular-plugin

【讨论】:

【参考方案11】:

我目前和你有同样的追求,试图制作一个可插入/可主题化的 Angular 版本,这不是一个小问题。

我实际上找到了很好的解决方案,阅读了天才Denys Vuyika的书Developing with Angular,他实际上在书中解释了一个很好的解决方案,他在书的第356页谈到了外部插件和使用@987654323 @ 实现解决方案,然后他处理动态加载以前在您的应用程序之外构建的外部插件。

还有其他两个库/项目可以帮助您实现此结果 ng-packagr 和 Nx extensions for Agnular (from Nrwl) 我们正在尝试实现后者,我想说它不像我们预期的那么顺利,Angular 很简单,不是为此而构建的,所以我们必须围绕 Angular 如何解决一些核心问题,而 NX ppls 是其中最好的之一。

我们只是在我们的开源项目的开始,我们正在使用 Django+Mongo+Angular,(我们正在调用WebDjangular,我们可能的解决方法之一是 Django 必须编写一些 JSON每次安装和激活新插件或主题时构建应用程序。

我们已经完成的是,从数据库中我们可以使用插件上的组件标签,组件将打印在屏幕上!同样,该项目处于非常早期的阶段,我们的架构有点基于 Wordpress,我们还有很多测试要做以实现我们的梦想:D

我希望这本书可以帮助你,并且使用 Rollup.js 我知道你将能够解决这个不平凡的问题。

【讨论】:

【参考方案12】:

我从 Paul Ionescu 那里找到了一篇关于如何在 Angular 中构建插件可扩展应用程序的好文章。

https://itnext.io/how-to-build-a-plugin-extensible-application-architecture-in-angular5-736890278f3f

他还引用了 github 上的示例应用程序: https://github.com/ionepaul/angular-plugin-architecture

【讨论】:

在 Angular 5 中... Angular 9, 10 with Ivy 是否提供其他方式? Angular 5 上的示例是否仍然适用于 Angular 9 或 10?

以上是关于在 Angular 2、4、5、6 中实现插件架构/插件系统/可插拔框架的主要内容,如果未能解决你的问题,请参考以下文章

使用 @ngtools/webpack 在 Angular 11 + Webpack 4 中实现 AOT

是否可以使用 $rootScope 作为存储在 Angular 1 中实现类似 Redux 的架构?

如何在 Angular 6 中实现 Keycloak?

如何使用 angular2 在 Office 加载项中实现对话框 API?

在C#程序中实现插件架构

如何在 Angular 4 中实现 google auth 登录