【发布时间】:2019-03-28 12:54:52
【问题描述】:
TL;DR:我正在尝试使用 Angular Elements 作为 Angular 应用程序的插件。如果我使用--prod 构建元素,它可以在我的应用程序上与ng serve 一起使用(开发设置),但是当我在我的应用程序上与ng serve --prod 一起使用它或在我的应用程序的ng build --prod 之后使用它时,它会无限重新加载(生产设置)。
不过,如果我构建添加 --optimization=false 的元素,则可以与我的生产应用程序一起使用,但不适用于我的开发设置。
问题是,我期望用--prod 构建一个 Angular 元素 对这两种情况都适用。
问题:有没有办法解决这个问题?
现在,长篇大论。
在工作中,我们试图在我们的 Angular 站点中使用可配置插件,其中服务器是告诉哪个插件处于活动状态的服务器。
我们尝试动态加载 Angular 模块,但这是一个完全不同的令人头疼的问题,我们将其搁置了另一天。
所以,我们接下来想尝试的是Angular Elements,它还挺有效的,除非我们按照应有的方式构建所有东西。
首先,我开始学习本教程 https://scotch.io/tutorials/build-a-reusable-component-with-angular-elements/amp 并忽略了有关 okta 的所有内容,因为我的功能有所不同。
创作:
我使用下一个命令创建了我的核心应用程序,这将是托管插件的应用程序:
ng new core --routing --skip-git --style scss --skip-tests --minimal
然后我使用这个命令创建了一个插件/角度元素:
ng new plugin --skip-git --style scss --skip-tests --minimal
插件:
在完成所有创建后,我进入我的插件并在polyfills.ts 中评论了这一行,我在该站点的某处读到它解决了NgZone 已加载的问题,这是真的:
// import 'zone.js/dist/zone'; // Included with Angular CLI.
在tsconfig.json 中,我将"target": "es5" 更改为"target": "es2015",以解决Angular 如何创建元素的问题。不太确定它是如何工作的,但 stackoverflow 建议它并且它成功了。
我将app.module.ts 编辑成类似这样的内容,遵循教程中的想法以及其他一些我丢失了链接的想法:
import { BrowserModule } from '@angular/platform-browser';
import { CUSTOM_ELEMENTS_SCHEMA, Injector, NgModule } from '@angular/core';
import { createCustomElement } from '@angular/elements';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent,
],
imports: [
BrowserModule,
],
providers: [
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA,
],
entryComponents: [
AppComponent,
],
})
export class AppModule {
constructor(private injector: Injector) {
const elem = createCustomElement(AppComponent, { injector: this.injector });
customElements.define('my-plugin', elem);
}
ngDoBootstrap() {
}
}
注意:我添加了CUSTOM_ELEMENTS_SCHEMA,因为我在某个地方找到了它,但它没有解决它(另外,我不确定它的作用)。
在app.component.ts 中,我这样做是为了在其模板中显示一些属性:
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
})
export class AppComponent {
public content: any = {
a: 10,
b: '20',
}
}
app.component.html 看起来像这样:
Some content:
<pre>{{content | json}}</pre>
在package.json 文件中,我有三个脚本来构建所有文件:
{
"scripts": {
"build": "npm run build:opt && npm run build:noopt",
"build:opt": "ng build --prod --output-hashing none && node build-elements.js",
"build:noopt": "ng build --prod --output-hashing none --optimization=false && node build-elements.noopt.js"
}
}
文件build-elements.js 如下所示(build-elements.noopt.js 相同,但目标名称不同):
'use strict';
const concat = require('concat');
const fs = require('fs-extra');
const path = require('path');
(async function build() {
const files = [
'./dist/plugin/runtime.js',
'./dist/plugin/polyfills.js',
'./dist/plugin/scripts.js',
'./dist/plugin/main.js',
];
const destinationDir = path.join(__dirname, '../core/src/assets');
await fs.ensureDir(destinationDir);
await concat(files, path.join(destinationDir, 'my-plugin.js'));
})();
核心:
对于宿主应用,我添加了一个名为 embedded 的组件,默认路由会转到它。
然后我使用一些 Bootstrap 类将embedded.component.html 更改为类似的内容:
<div id="embedded-container" class="container border border-primary rounded mt-5 p-3" #container>
<pre>Loading...</pre>
</div>
最后,embedded.component.ts 以这样的方式结束,展示了实际的加载机制:
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { Router, ActivatedRoute, Params } from '@angular/router';
import { environment } from '../../environments/environment';
@Component({
selector: 'app-embedded',
templateUrl: './embedded.component.html',
})
export class EmbeddedComponent implements OnInit {
@ViewChild('container') public container: ElementRef;
constructor(protected activatedRoute: ActivatedRoute) {
}
ngOnInit() {
this.activatedRoute.queryParams.subscribe((params: Params) => {
const script = document.createElement('script');
if (params['how-it-should-be'] !== undefined) {
script.src = environment.production ? '/assets/my-plugin.js' : '/assets/my-plugin-no-optimization.js';
} else {
script.src = environment.production ? '/assets/my-plugin-no-optimization.js' : '/assets/my-plugin.js';
}
document.body.appendChild(script);
const div = document.createElement('div');
div.innerHTML = '<my-plugin></my-plugin>';
this.container.nativeElement.innerHTML = '';
this.container.nativeElement.appendChild(div);
});
}
}
跑步:
如果我运行 ng serve 并浏览到 http://localhost:4200,页面加载没有问题,注入插件,将新元素添加到 DOM 并显示来自我的插件的消息。
如果您调试应用程序,您会看到它加载了/assets/my-plugin.js,这是为生产而构建的。除了调试之外,这不是问题。
然后,如果我运行ng serve --prod(或构建它用于生产)它也可以正常工作,但它会加载/assets/my-plugin-no-optimization.js,它是为“调试”而构建的。
这是我最终在我们的实际应用程序中使用的解决方案,但正如您所见,我没有在我的插件中使用优化代码进行生产,这并不好......一点也不。
为了证明我的观点,如果我浏览到http://localhost:4200/?how-it-should-be,它将尝试加载ng serve --prod 的优化插件和ng serve 的调试插件。请注意,这会使您无限重新加载,请打开浏览器开发人员工具来查看它。
我们使用的最终产品要复杂得多,但这些示例的基本逻辑并没有真正起作用。
我还使用它创建了一个 GitHub 存储库,您可以在其中查看这些代码,并自己尝试解决问题,或用作您自己想法的示例。
如果您想知道我是如何发现使用 --optimization=false 可以解决这个问题的,那么,我正试图调试这个问题(这被证明是不可能的)并且突然加载了。
我看了看时间,我已经为生产部署晚了两个小时,所以我添加了那个丑陋的机制来根据环境加载不同的构建。 它在开发和生产中都有效,但我并不为此感到自豪。
对不起,如果我的英语不好......不不不,我的英语不好,对不起^__^
【问题讨论】:
-
我遇到了完全相同的问题 - 您提出的解决方法对我有用。谢谢!想知道您是否在解决原始问题方面取得了任何其他进展。
-
我很高兴能提供帮助。我希望我可以说我有一个更好的解决方案,但恰恰相反,我们的应用程序添加了一个新插件,它使用相同的“解决方法”。我会继续搜索,如果我找到任何东西,我会确保更新这篇文章
-
我遇到了同样的问题!经过多次故障排除后,我决定使用您的代码作为示例向 Angular 提交错误报告。在此处跟踪:github.com/angular/angular/issues/29788
标签: javascript angular typescript plugins angular-elements