优化后,Angular应用中的Angular Element会产生无限的重新加载问题

时间:2019-03-28 12:54:52

标签: javascript angular typescript plugins angular-elements

TL; DR :我正在尝试使用 Angular Elements 作为 Angular 应用程序的插件。如果我使用--prod构建元素,则它可以在我的应用程序上与ng serve一起使用(开发设置),但是当我在应用程序中将ng serve --prod与元素一起使用时或{我的应用的{1}}(生产设置)。

但是,如果我构建添加ng build --prod的元素,则可以在我的生产性应用程序中使用,但不能在我的开发设置中使用。

问题是,我期望使用--optimization=false构建 Angular Element 对于这两种情况都可以。

问题是否可以解决此问题?


现在,阅读已久。

在工作中,我们正在尝试在 Angular 网站中使用可配置的插件,在该服务器中,服务器是告诉哪个插件处于活动状态的插件。

我们试图动态加载 Angular 模块,但这是我们完全不同的另一天。

因此,我们接下来要尝试的是 Angular Elements ,而且除非我们按照应有的方式构建一切,否则它还是可以工作的。

首先,我开始遵循本教程https://scotch.io/tutorials/build-a-reusable-component-with-angular-elements/amp,并忽略了有关--prod的所有内容,因为我的功能有所不同。

创建:

我使用下一个命令创建了核心应用程序,它将是托管插件的应用程序:

  • 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",以解决 Angular 如何创建元素的问题。不太确定它是如何工作的,但是 stackoverflow 提出了建议,并且成功了。

我按照本教程中的一些想法将"target": "es2015"编辑为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修复它,那么,我正在尝试调试此问题(事实证明这是不可能的),然后突然加载了它。

我查看了一下时间,对于生产部署来说,我已经迟到了两个小时,所以我添加了这种丑陋的机制来根据环境加载不同的构建。 它在开发和生产中都有效,但是我对此并不感到骄傲。


对不起,如果我的英语不好...不,不,我的英语不好,对不起^ __ ^

1 个答案:

答案 0 :(得分:0)

我找到了解决方法!

事实证明,问题出在同一个上下文中运行多个webpack项目。我对问题的理解本质上是,当您使用webpack构建项目时,它们会在运行时进行一些webpack引导,并依赖于全局上下文(webpackJsonp)中定义的特定功能。当多个webpack配置尝试在同一DOM上下文中进行自举时,会创建此处定义的症状。 (此处有更详细的说明-https://github.com/angular/angular/issues/23732#issuecomment-388670907

此GitHub注释描述了一种解决方案,但没有介绍操作方法-https://github.com/angular/angular/issues/30028#issuecomment-489758172。下面,我将展示我是如何具体解决的。

我们可以使用webpack配置为我们的Web组件重命名webpackJsonp,这样两个Angular项目(或任何使用webpack构建的项目)都不会相互干扰。

解决方案

首先,我们安装@ angular-builders / custom-webpack软件包,以使我们能够在Angular CLI中修改内置的webpack配置。

npm install --save-dev @angular-builders/custom-webpack

接下来,我们更新angular.json文件,以在名为customWebpackConfig的选项中使用新的构建器和新的属性值。这包括我们将要创建的新文件的路径和mergeStrategy。这种合并策略表明,我们要将配置附加到webpack配置的output部分。

angular.json
...
      "architect": {
        "build": {
          "builder": "@angular-builders/custom-webpack:browser",
          "options": {
            "customWebpackConfig": {
              "path": "./extra-webpack.config.js",
              "mergeStrategies": { "output": "append"}
            },
...

最后,我们只需将我们的extra-webpack.config.js文件添加到包含angular.json的目录中。该文件仅包含以下内容:

module.exports = {
    output: {
        jsonpFunction: '<webcomponent-prefix>-webpackJsonp'
    }
}

如果将jsonpFunction的值更改为其他值,它将默认为webpackJsonp。我决定保留函数名称,但为Web组件应用程序添加前缀。从理论上讲,您可以在同一DOM上下文中运行N个Webpack配置,只要每个配置都有唯一的jsonpFunction

再次构建您的Web组件,瞧,它起作用了!