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


133

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

Angular 6最近的架构更改确实使我们更接近解决方案。此外,Angular Elements(https://angular.io/guide/elements)提供了一些组件功能-尽管与我在本文中最初描述的功能不尽相同。

如果来自惊人的Angular团队的任何人碰巧遇到此问题,请注意,似乎还有很多其他人对此功能也非常感兴趣。积压的订单可能值得考虑。


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

(开发这个可插入框架的特定用例是,我需要开发一个小型的内容管理系统。由于此处未详细说明的许多原因,Angular 2/4/5/6它非常适合该系统的大多数需求。)

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

(关于“ 没有直接访问或不知道应用程序的源代码或内部工作原理的措辞”是一个核心目标。)

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

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

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

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

通常,Angular 2/4/5/6非常需要使用,因为:

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

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

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

  • 通常,提供可插拔体系结构的好处之一是,第三方开发人员很容易扩展系统功能。显然,这些开发人员不会熟悉他们所插入的应用程序的所有代码复杂性。开发插件后,其他技术程度较低的用户都可以简单地安装应用程序和任何选定的插件。但是,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 TimeAOT)编译进行了预编译。(大概应该是所有。)我不确定如何将插件添加到预编译的应用程序(或与之集成)。最好的情况是将插件与主应用程序分开编译。但是,我不确定如何使这项工作。一个后备方法可能是使用任何包含的插件重新编译整个应用程序,但这对于只想将应用程序(在其自己的服务器上)以及任何选定的插件一起安装的管理用户来说,会使事情变得有些复杂。

  • 在一个Angular 2/4/5/6应用程序中,尤其是预编译的应用程序中,一条错误或冲突的代码可能会破坏整个应用程序。Angular 2/4/5/6应用程序并非总是最容易调试的。行为不当的插件的应用可能会导致非常不愉快的体验。我目前不了解正常处理不良行为插件的机制。


1
在我看来,angular 2模块是一个插件。@ angular / router,@ angular / forms,@ angular / http,@ angular / material,这些都是来自angular的“插件”,我们可以检查一下它们是如何制作“插件”的。
Timathon '17

8
@Timathon,不幸的是,它们不一样。插件系统允许在不修改核心应用程序代码的情况下扩展应用程序。使用@ angular / router,@ angular / forms等要求用户修改应用程序。这些实际上是库,而不是插件。我真的很想让非开发人员的管理用户选择并使用他们最感兴趣的插件,而无需了解应用程序的内部细节。
Anthony Gatlin

1
你有什么办法吗?我有兴趣尝试类似的东西。方式2角是构建(约模块)我想一个插件式架构将适合它真的很好,但它似乎并没有任何实例等不

2
@Joe,对于这个问题,我仍然没有好的解决方案。我也和你一样
安东尼·加特林

2
我在github上创建了一个可能有帮助的解决方案的存储库。它使用Angular 6库和1个基本应用程序,这些应用程序延迟加载了UMD捆绑的库。github.com/lmeijdam/angular-umd-dynamic-example 如果您有任何建议,请随时添加!
拉尔斯·梅丹(Rars Meijdam)'18年

Answers:


17

我在github上创建了一个可能有帮助的解决方案的存储库。它使用Angular 6库和1个基本应用程序,这些应用程序延迟加载了UMD捆绑的库。https://github.com/lmeijdam/angular-umd-dynamic-example

如果您有任何建议,请随时添加!


2
如另一条评论中所述,由于公司政策的缘故,我不得不将存储库设为私有。.随着讨论的进行,我将稍后将其放回网上
Lars Meijdam

希望看到您的解决方案。
Subhan Ahmed

@Subhan,我目前正忙于重写存储库,然后再次将其放回GH。给我一点时间。否则,您也可以直接与我联系!:D
拉尔斯·梅伊丹

@ lars-meijdam:我们仍然会等待很多:D ...我也很感兴趣。在此先感谢您
aguetat

17

🛠️Github演示angular-plugin-architecture

也许Ivy可以更改某些内容,但是暂时我使用的是使用Angular CLI自定义生成器并满足以下要求的解决方案:

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

用法很简单:

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

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


很好的例子!一个问题。如何OAuthToken在运行时从主应用程序传递给图书馆服务?
yogen darji

@yurzui如果我们有多个角度应用程序,并且使用它们的整个分布,而不是像插件1和插件2那样制作模块,可以使用相同的方法吗?
Momin Shahzad

您能否在不久的将来重新审视此示例并使之与Ivy兼容?如果愿意,请添加一个共享服务示例,该共享服务InjectionToken将在AppModule中提供并注入其他插件中。谢谢!
Jyrkka

11

我刚刚为我的《用Angular开发》一书出版了一个新的章节,该书解决了Angular 2+中插件的主题,对于试图构建外部插件的人们应该引起极大的兴趣。

关键点:

  • 外挂程式
  • 根据字符串名称构建组件
  • 从外部来源加载配置
  • 动态更改应用程序路线
  • 外部插件
  • 创建插件库
  • 将插件加载到应用程序中
  • 具有插件内容的动态路由

这本书是免费获得的,并且具有“按需付费”的模式。随时获取副本,希望对您有所帮助。


如何用书中说明的插件架构替换子组件?我将替换模板,或者可能将添加输入属性ecc ...同时,我需要知道是否存在一种覆盖/扩展所提供服务的方法。
MatteoCalò18年

1
@Denis Vuyka,这本书看起来不错,但缺少关键部分-支持AoT编译。
谢尔盖·索科洛夫

7

有一个工作的插件系统应用实例(感谢的Gijs的创始GitHub库!)https://github.com/PacktPublishing/Mastering-Angular-2-Components/tree/master/angular-2-components-chapter-10基于在eBook Mastering Angular 2组件上

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

干杯,尼古拉斯


2
无法从该链接中看到示例代码,可以张贴代码笔或JSFiddle吗?
肖恩·蔡斯


4
我读过这本书,但插件部分已过时。它使用普通的JS和SystemJS,而Angular则针对Typescript和Webpack。使用Webpack和Typescript似乎无法实现,我提出了一个问题,以防您找到任何解决方案。这是链接
Luigi Dallavalle


有人可以确认以上方法是否有效?如果我们不想使用systemjs怎么办
django

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;
    }
  }
}

最好...汤姆


17
感谢您抽出宝贵的时间回答我的问题。我熟悉延迟加载的模块,但是这些并不是我想要的。延迟加载的模块在设计时仍必须是已知的。我希望能够添加构建原始应用程序时不了解或未想到的实际模块和功能。(我正在寻找的是更具动态性的东西。)当然,这些组件将使用(某种形式的)延迟加载,但这只是难题的一小部分。再次感谢您分享此答案。
安东尼·加特林

1
我同意这不能回答问题。延迟加载对插件体系结构没有帮助,因为在设计时需要它们。除非需要,否则它不会下载数据或将数据传输到客户端。

如果您的应用在编译时会知道所有可用的插件模块,该怎么办。目前,您将一个新模块添加到平台中,必须使用该模块重新编译它。只是一个主意...不确定我是否会大大增加JS文件的大小,不确定是否延迟加载功能会将这样的模块放到一个单独的文件中然后再延迟加载,我只是分享我的想法...
Vladimir Prudnikov

@VladimirPrudnikov,如果应用程序可以在编译时知道所有插件,那就太好了。但是,这个想法是能够在应用程序编译后添加插件。这将允许真正动态地插入模块。但是,这将要求模块在部署时也进行预编译-我不确定这将如何工作。我也不确定如何使插件模块版本与Angular兼容。
安东尼·加特林

2

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

 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
},

2

我也在寻找角度为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代码类似于角度组件:

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中的框架相似。


2

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

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

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

  1. “ ./dashboard/dashboard.module”-这是我们要延迟加载的模块的路由URL,例如:{path:'dashboard',loadChildren:'./dashboard/dashboard.module#DashboardModule'}
  2. “ ../../../../../src/app/dashboard/dashboard.module.ts”-这是入口点(构造函数)从
  3. “ dashboard.module” -不带有chunk.js的实际文件名(例如:dashboard.module.chunk.js

因此,从理论上讲,如果将条目添加到map属性中以配置路由并遵循该模式,则可以拥有一个插件系统。现在的挑战是如何在该map属性中添加或删除条目。显然,它不能通过角度代码来完成,而应该使用外部工具来完成。


2

我试图通过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”生成的静态文件。

希望它会有所帮助。欢迎任何意见!^^


我正在尝试关注您的插件文档,但是我认为文档正在跳过一些步骤。如果我看错了,我深表歉意。我不清楚整个“添加插件”的内容。我一步一步地遵循了这个步骤,但是我并没有真正看到结果。运行电源外壳后,在端口4200上应该看到什么?我在/aspnet-core/src/Todo.MainProject.Web.Host/中看不到Plugins文件夹。我运行了Powershell,但未创建该文件夹。任何帮助表示赞赏。我认为您的方法是我所需要的,但是我对它的工作方式还有些模糊。
布莱恩·基特

好。在问问题之前,我应该这样做。我花了一些时间进行调试,并找出了答案。#1)Powershell不会将.zip放在需要放置的地方,我需要创建plugin文件夹并移动zip。#2)当有角度的应用程序启动时,它会动态调用加载程序,并将插件复制到webroot。那是很清楚的,但我现在明白了。#3)我必须使用url来调用插件,但它不会在任何地方显示。我原以为它会出现在仪表板上。感谢您的工作,这是大量的代码。
布赖恩·基特


2

我目前正与您在同一个任务中,尝试制作Angular的可插入/可移植版本,这不是一个小问题。

我实际上找到了不错的解决方案,正在读书 Genius Denys Vuyika用角度开发,他实际上在书中解释了一个不错的解决方案,他在书的第356页上谈到了外部插件,并使用Rollup.js实现了解决方案后,他将动态加载以前在应用程序外部构建的外部插件。

还有两个其他库/项目可帮助您实现此结果 Agnular的ng-packagrNx扩展(来自Nrwl),我们想实现后者,我想说它不像我们预期的那么平滑,角度很简单Angular和NX ppls是其中最好的之一,因此我们必须围绕一些核心问题进行工作。

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

我们已经完成的工作是,可以从数据库中为插件使用组件的标签,组件将显示在屏幕上!同样,该项目处于非常早期的阶段,我们在WordPress上稍稍增加了我们的体系结构,为实现我们的梦想,我们还有更多的测试要做:

我希望本书能为您提供帮助,并且使用Rollup.js我知道您将能够解决这个不重要的问题。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.