Angular Element в приложении Angular создает бесконечную проблему перезагрузки при оптимизации - PullRequest
4 голосов
/ 28 марта 2019

TL; DR : я пытаюсь использовать Angular Elements в качестве плагинов для Angular приложения. Если я создаю элемент с --prod, он работает с ng serve в моем приложении (настройка разработки), но переходит в бесконечную перезагрузку, когда я использую его с ng serve --prod в моем приложении или после ng build --prod моего приложения ( настройка производства).

Хотя, если я создаю элемент с добавлением --optimization=false, он работает с моим продуктивным приложением, но не с настройками разработки.

Дело в том, что я ожидал, что построение Углового элемента с --prod будет хорошо для обоих случаев.

Вопрос : Есть ли способ решить эту проблему?


Теперь длинное чтение.

На работе мы пытаемся использовать настраиваемые плагины на нашем 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-element с помощью этой команды:

  • ng new plugin --skip-git --style scss --skip-tests --minimal

Plugin:

После всего создания я зашел в свой плагин и прокомментировал эту строку в 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:
{{content | json}}

В файле 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'));
})();

Core:

Для хост-приложения я добавил компонент с именем embedded и к нему идет маршрут по умолчанию.

Затем я изменил embedded.component.html на что-то вроде этого, используя несколько Bootstrap классов:


    Loading...

Наконец, 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 голосов
/ 10 июня 2019

Я нашел решение!

Оказывается, проблема заключается в том, что несколько проектов веб-пакетов работают в одном контексте. Я понимаю эту проблему по существу, когда вы создаете проекты с помощью веб-пакета, они выполняют некоторую загрузку веб-пакета во время выполнения и полагаются на определенную функцию, определенную в глобальном контексте (webpackJsonp). Когда более чем одна конфигурация веб-пакета пытается выполнить эту загрузку в одном и том же контексте DOM, это создает симптомы, определенные здесь. (Более подробное объяснение можно найти здесь - https://github.com/angular/angular/issues/23732#issuecomment-388670907)

Этот комментарий GitHub описывает решение, но не с практическими рекомендациями - https://github.com/angular/angular/issues/30028#issuecomment-489758172. Ниже я покажу, как я его конкретно решил.

Мы можем использовать конфигурацию веб-пакета для переименования webpackJsonp для нашего веб-компонента, чтобы оба проекта Angular (или что-либо созданное с помощью веб-пакета) не мешали друг другу.

Решение

Сначала мы устанавливаем пакет @ angular-builders / custom-webpack, чтобы мы могли изменить встроенную конфигурацию веб-пакета в Angular CLI.

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

Затем мы обновляем наш файл angular.json, чтобы он использовал наш новый компоновщик и новое значение свойства в опциях customWebpackConfig. Это включает в себя путь к новому файлу, который мы собираемся создать, и mergeStrategy. Эта стратегия слияния говорит, что мы хотим добавить конфигурацию в раздел 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, если вы измените его на что-то еще, кроме того, что оно будет работать. Я решил сохранить имя функции, но добавить префикс для моего приложения веб-компонента. Теоретически, вы можете иметь N конфигураций веб-пакетов, работающих в одном контексте DOM, если каждая из них имеет уникальный jsonpFunction

Снова создайте свой веб-компонент и вуаля, он работает!

...