145

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

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

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


Angular 2我想在一个、、、或应用程序Angular 4中实现一个可插入(插件)框架。Angular 5Angular 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基本上可以通过将插件文件夹拖放到系统自动“拾取”的公共模块目录中来添加其他系统中的插件(例如)。在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应用程序并不总是最容易调试的。应用不良插件可能会导致非常不愉快的体验。我目前不知道有一种机制可以优雅地处理行为不端的插件。

4

12 回答 12

25

更新

对于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 并满足以下要求:

  • 奥特
  • 避免重复代码(@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

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

于 2019-04-03T03:53:39.563 回答
17

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

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

于 2018-05-09T06:38:32.567 回答
13

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

关键点:

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

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

于 2018-02-12T21:58:53.613 回答
7

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

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

干杯,尼克拉斯

于 2017-02-06T10:22:23.423 回答
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;
    }
  }
}

最好的...汤姆

于 2017-02-11T22:25:41.323 回答
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
},
于 2017-07-17T18:49:28.103 回答
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),然后我们移动插件的构建内容角 cli 应用程序,到主应用程序“wwwroot”文件夹。完成所有这些之后,我们可以运行“dotnet run”来为 ASP.NET Core 2.0 Web 应用程序提供服务,以托管“ng build”生成的静态文件。

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

于 2017-10-16T02:45:19.987 回答
2

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

存储在database数据库中的组件都是基于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 类似的框架。

于 2017-07-18T09:50:41.453 回答
2

有点离题,但 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

于 2018-03-20T22:04:51.303 回答
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

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

于 2017-08-11T08:41:05.320 回答
2

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

我实际上找到了很好的解决方案,阅读Genius Denys Vuyika的书用 Angular 开发,他实际上在书中解释了一个很好的解决方案,他在书的第 356 页谈到了外部插件并使用Rollup.js来实现解决方案,然后他处理动态加载以前在您的应用程序之外构建的外部插件。

还有其他两个库/项目可以帮助您实现这个结果 ng-packagr和Agnular 的Nx 扩展(来自 Nrwl)我们正在尝试实现后者,我想说它不像我们预期的那么顺利,角度很简单不是为此而构建的,因此我们必须围绕 Angular 的一些核心工作,而 NX ppls 是其中最好的之一。

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

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

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

于 2018-10-05T21:12:27.223 回答
0

我从 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

于 2019-03-20T07:26:19.353 回答