javascript - Angular 应用程序中的 Angular 元素在优化时会产生无限重新加载问题

标签 javascript angular typescript plugins angular-elements

TL;博士 :我正在尝试使用 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

  • 然后我使用这个命令创建了一个插件/Angular 元素:
  • 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 的组件。和默认路由去它。

    然后我改了embedded.component.html使用一些 Bootstrap 类变成这样的东西:

    <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 ,页面加载没有问题,注入(inject)插件,将新元素添加到 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 存储库,您可以在其中查看这些代码,并自己尝试解决问题,或者作为您自己想法的示例。
  • https://github.com/daemonraco/angular-as-plugins

  • 如果您想知道我是如何发现使用 --optimization=false 的有点修复它,好吧,我试图调试这个问题(事实证明这是不可能的),突然它加载了。

    我看了看时间,发现生产部署晚了两个小时,所以我添加了这个丑陋的机制,根据环境加载不同的构建。
    它在开发和生产中都有效,但我并不为此感到自豪。

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

    最佳答案

    我找到了解决方案!

    事实证明,问题在于在同一上下文中运行多个 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 的选项中的新属性值。 .这包括我们将要创建的新文件的路径和一个合并策略。此合并策略表示我们要将配置附加到 output webpack 配置部分。

    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 组件,瞧,它可以工作了!

    关于javascript - Angular 应用程序中的 Angular 元素在优化时会产生无限重新加载问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55398196/

    相关文章:

    date - 我如何计算 typescript 中2个日期之间的时间

    javascript - 单击按钮在新选项卡上打开链接

    javascript - Protractor - 无法通过CSS获取嵌套元素

    angular - 如何在 Ionic 2 中配置 Mathjax

    angular - 垫表呈现行但不呈现数据

    angular - 指定的模块不存在 Angular 6

    javascript - ajax数据过滤不起作用

    javascript - 检测cors是否启用?

    angular - 使用 RXJS 继续以 Angular 触发 http 调用,直到满足条件

    javascript - 巴泽尔 - 错误 : missing input file '//server:files/src/index.js'