在角度2,4,5,6中实现插件架构/插件系统/可插件框架

更新5/24/2018: 我们现在已经从我最初的帖子中 + 3版本的 Angular,但仍然没有最终可行的解决方案。Lars Meijdam (@LarsMeijdam)提出了一个有趣的方法,当然值得一看。(由于专有问题,他不得不暂时删除他最初发布样本的 GitHub 存储库。但是,如果你想要一份拷贝,你可以直接给他发信息。更多信息请参见下面的评论。)

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

如果有任何人从惊人的角团队碰巧遇到这一点,请注意,似乎有很多其他人谁也对这一功能非常感兴趣。对于积压的工作,这可能是值得考虑的。


我想在 Angular 2Angular 4Angular 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 Time(AOT)编译进行预编译。(或许所有人都应该如此。)我不确定插件可以如何添加到(或集成)预编译的应用程序。最好的方案是将插件与主应用程序分开编译。然而,我不确定如何才能做到这一点。后备方法可能是使用所包含的插件重新编译整个应用程序,但是对于仅仅想要(在自己的服务器上)安装应用程序以及所选择的插件的管理用户来说,这会使事情变得有点复杂。

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

34255 次浏览

带有工作插件系统的示例应用程序(感谢 Gijs 创建了 github repo!)基于电子书 掌握角度2分量https://github.com/PacktPublishing/Mastering-Angular-2-Components/tree/master/angular-2-components-chapter-10

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

干杯, 尼克拉斯

您要查找的是延迟模块加载。 下面是一个例子: 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;
}
}
}

最好的... 汤姆

我做了一个黑客加载和编译其他模块在引导时间,但我还没有解决循环依赖性的问题

 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/4的插件系统,用于开发企业应用程序的 RAD 环境。经过一些研究,我决定实现一个数据库存储(但可能在文件系统中)伪角度组件的集合。

数据库数据库中存储的组件基于 < a href = “ https://www.npmjs.com/package/ng-Dynamic”rel = “ nofollow norefrer”> 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类似的框架。

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

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

让我们来看看这个数组包含了什么:

  1. "./dashboard/dashboard.module”-这是我们想要延迟加载的模块的路由 URL,例如: < em > { path: ‘ dashboard’,loadChildren:’。/dashboard/dashboard.module # DashboardModule’}
  2. “ . ./. ./. ./. ./. ./src/app/dashboard/dashboard.module.ts”-这是入口点(构造器)从
  3. “ dashboard.module”-没有 chunk.js 的实际文件名(例如: 仪表板,模块,大块 Js)

因此,在理论上,如果您向 map 属性添加条目来配置您的路由并遵循模式,那么您就可以拥有一个插件系统。现在的挑战是如何在 map 属性中添加或删除条目。显然,这不能从角度代码,它应该做的外部工具。

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

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

更多关于我如何实现这一目标的信息:

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

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

Abp _ plugin _ with _ ui 是一个开发包含后端和 Angular cli 的插件库。对于后端,我使用 aspnetboilerplate 框架,它的前端是使用多个角点应用程序开发的。

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

希望能有所帮助。欢迎任何评论! ^ ^

我刚刚为我的书“ 角度显影”出版了一个新的章节,这个章节涉及到 Angular 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
这些插件需要一个角度包装器:
Https://docs.nativescript.org/plugins/angular-plugin

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

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

我现在和你一样在做同样的任务,尝试制作一个可插拔/可主题的 Angular 版本,这不是一个小问题。

我实际上发现了非常好的解决方案,阅读了天才 丹尼斯 · 维伊卡的书 角度显影,他实际上在书上解释了一个非常好的解决方案,他在书的356页谈到了外部插件,并使用 卷起来来实现解决方案,然后他处理动态加载外部插件,这些外部插件已经在你的应用程序之外构建好了。

还有另外两个库/项目可以帮助您实现这个结果 Ng-packagr Agnular 的 Nx 扩展(来自 Nrwl)我们正在尝试实现后者,我想说它并不像我们预期的那么顺利,棱角很简单,不是为此而设计的,所以我们必须围绕一些核心工作如何棱角,和 NX 的 ppls 是最好的之一。

我们只是在我们的开源项目的开始,我们正在使用 Django + Mongo + Angular,(我们正在调用 WebDjangle和我们的一个可能的方法来回答这个问题,是 Django 将不得不写一些 JSON 配置文件和建立应用程序,每次一个新的插件或主题被安装和激活。

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

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

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

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

他还提到了 github 上的一个应用程序示例: Https://github.com/ionepaul/angular-plugin-architecture

更新

对于 角度11,我强烈建议您看看使用 Webpack 5模块联合会的实现

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

前情提要

Github 演示 角插件架构

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

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

用法很简单:

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

更多相关内容请见我的文章: