Загружать новые модули динамически во время выполнения с помощью Angular CLI и Angular 5

В настоящее время я работаю над проектом, который размещается на сервере клиентов. Для новых "модулей" нет намерения перекомпилировать все приложение. Тем не менее, клиент хочет обновить модули маршрутизатора/лени, загруженные во время выполнения. Я пробовал несколько вещей, но я не могу заставить его работать. Мне было интересно, знает ли кто-нибудь из вас, что я еще могу попробовать или что я пропустил.

Одна вещь, которую я заметил, большинство ресурсов, которые я пробовал, используя угловые cli, встраивается в отдельные куски по webpack по умолчанию при создании приложения. Это кажется логичным, поскольку он использует расщепление кода webpack. но что, если модуль еще не известен во время компиляции (но скомпилированный модуль хранится где-то на сервере)? Сборка не работает, потому что не может найти модуль для импорта. И использование SystemJS будет загружать модули UMD всякий раз, когда они будут найдены в системе, но также поставляются в отдельном блоке с помощью webpack.

Некоторые ресурсы, которые я уже пробовал;

Некоторый код, который я уже пробовал и реализую, но не работаю в это время;

Расширение маршрутизатора с помощью обычного файла module.ts

     this.router.config.push({
    path: "external",
    loadChildren: () =>
      System.import("./module/external.module").then(
        module => module["ExternalModule"],
        () => {
          throw { loadChunkError: true };
        }
      )
  });

Normal SystemJS Импорт пакета UMD

System.import("./external/bundles/external.umd.js").then(modules => {
  console.log(modules);
  this.compiler.compileModuleAndAllComponentsAsync(modules['External']).then(compiled => {
    const m = compiled.ngModuleFactory.create(this.injector);
    const factory = compiled.componentFactories[0];
    const cmp = factory.create(this.injector, [], null, m);

    });
});

Импортируйте внешний модуль, не работая с webpack (afaik)

const url = 'https://gist.githubusercontent.com/dianadujing/a7bbbf191349182e1d459286dba0282f/raw/c23281f8c5fabb10ab9d144489316919e4233d11/app.module.ts';
const importer = (url:any) => Observable.fromPromise(System.import(url));
console.log('importer:', importer);
importer(url)
  .subscribe((modules) => {
    console.log('modules:', modules, modules['AppModule']);
    this.cfr = this.compiler.compileModuleAndAllComponentsSync(modules['AppModule']);
    console.log(this.cfr,',', this.cfr.componentFactories[0]);
    this.external.createComponent(this.cfr.componentFactories[0], 0);
});

Использовать SystemJsNgModuleLoader

this.loader.load('app/lazy/lazy.module#LazyModule').then((moduleFactory: NgModuleFactory<any>) => {
  console.log(moduleFactory);
  const entryComponent = (<any>moduleFactory.moduleType).entry;
  const moduleRef = moduleFactory.create(this.injector);

  const compFactory = moduleRef.componentFactoryResolver.resolveComponentFactory(entryComponent);
});

Пробовал загрузку модуля, сделанного с помощью накопителя

this.http.get('./myplugin/${metadataFileName}')
  .map(res => res.json())
  .map((metadata: PluginMetadata) => {

    // create the element to load in the module and factories
    const script = document.createElement('script');
    script.src = './myplugin/${factoryFileName}';

    script.onload = () => {
      //rollup builds the bundle so it attached to the window object when loaded in
      const moduleFactory: NgModuleFactory<any> = window[metadata.name][metadata.moduleName + factorySuffix];
      const moduleRef = moduleFactory.create(this.injector);

      //use the entry point token to grab the component type that we should be rendering
      const compType = moduleRef.injector.get(pluginEntryPointToken);
      const compFactory = moduleRef.componentFactoryResolver.resolveComponentFactory(compType); 
// Works perfectly in debug, but when building for production it returns an error 'cannot find name Component of undefined' 
// Not getting it to work with the router module.
    }

    document.head.appendChild(script);

  }).subscribe();

Пример с SystemJsNgModuleLoader работает только тогда, когда модуль уже предоставляется как "ленивый" маршрут в RouterModule приложения (который превращает его в кусок при построении с помощью webpack)

Я нашел много дискуссий по этой теме в StackOverflow здесь и там, и при условии, что решения действительно хороши для загрузки модулей/компонентов динамически, если они известны заранее. но ни один из них не подходит для нашего варианта использования проекта. Пожалуйста, дайте мне знать, что я еще могу попробовать или погрузиться.

Спасибо!

EDIT: Я нашел; https://github.com/kirjs/angular-dynamic-module-loading и попробует это.

UPDATE: я создал репозиторий с примером загрузки модулей динамически с помощью SystemJS (и используя Angular 6); https://github.com/lmeijdam/angular-umd-dynamic-example

Ответ 1

У меня была та же проблема. Насколько я понимаю до сих пор:

Webpack помещает все ресурсы в пакет и заменяет все System.import на __webpack_require__. Поэтому, если вы хотите динамически загружать модуль во время выполнения с помощью SystemJsNgModuleLoader, загрузчик будет искать модуль в комплекте. Если модуль не существует в комплекте, вы получите сообщение об ошибке. Webpack не будет запрашивать сервер для этого модуля. Это проблема для нас, так как мы хотим загрузить модуль, который мы не знаем во время сборки/компиляции. Нам нужен загрузчик, который будет загружать модуль для нас во время выполнения (ленивый и динамический). В моем примере я использую SystemJS и Angular 6/CLI.

  • Установить SystemJS: npm install systemjs -save
  • Добавьте его в angular.json: "scripts": [ "node_modules/systemjs/dist/system.src.js" ]

app.component.ts

import { Compiler, Component, Injector, ViewChild, ViewContainerRef } from '@angular/core';

import * as AngularCommon from '@angular/common';
import * as AngularCore from '@angular/core';

declare var SystemJS;

@Component({
  selector: 'app-root',
  template: '<button (click)="load()">Load</button><ng-container #vc></ng-container>'
})
export class AppComponent {
  @ViewChild('vc', {read: ViewContainerRef}) vc;

  constructor(private compiler: Compiler, 
              private injector: Injector) {
  }

  load() {
    // register the modules that we already loaded so that no HTTP request is made
    // in my case, the modules are already available in my bundle (bundled by webpack)
    SystemJS.set('@angular/core', SystemJS.newModule(AngularCore));
    SystemJS.set('@angular/common', SystemJS.newModule(AngularCommon));

    // now, import the new module
    SystemJS.import('my-dynamic.component.js').then((module) => {
      this.compiler.compileModuleAndAllComponentsAsync(module.default)
            .then((compiled) => {
                let moduleRef = compiled.ngModuleFactory.create(this.injector);
                let factory = compiled.componentFactories[0];
                if (factory) {
                    let component = this.vc.createComponent(factory);
                    let instance = component.instance;
                }
            });
    });
  }
}

мой-dynamic.component.ts

import { NgModule, Component } from '@angular/core';
import { CommonModule } from '@angular/common';

import { Other } from './other';

@Component({
    selector: 'my-dynamic-component',
    template: '<h1>Dynamic component</h1><button (click)="LoadMore()">LoadMore</button>'
})    
export class MyDynamicComponent {
    LoadMore() {
        let other = new Other();
        other.hello();
    }
}
@NgModule({
    declarations: [MyDynamicComponent],
    imports: [CommonModule],
})
export default class MyDynamicModule {}

other.component.ts

export class Other {
    hello() {
        console.log("hello");
    }
}

Как вы можете видеть, мы можем сказать SystemJS, какие модули уже существуют в нашем комплекте. Поэтому нам не нужно снова загружать их (SystemJS.set). Все остальные модули, которые мы импортируем в нашем my-dynamic-component (в этом примере other), будут запрашиваться с сервера во время выполнения.

Ответ 2

Я использовал решение https://github.com/kirjs/angular-dynamic-module-loading с поддержкой библиотеки Angular 6 для создания приложения, которое я использовал в Github. Из-за политики компании ее нужно было отключить. Как только будут обсуждены примеры источника проекта, я поделюсь им на Github!

ОБНОВЛЕНИЕ: можно найти репо; https://github.com/lmeijdam/angular-umd-dynamic-example

Ответ 3

Я считаю, что это возможно, используя SystemJS для загрузки пакета UMD, если вы создаете и запускаете основное приложение с помощью webpack. Я использовал решение, которое использует ng-packagr для создания пакета UMD динамического модуля plugin/addon. Этот github демонстрирует описанную процедуру: https://github.com/nmarra/dynamic-module-loading

Ответ 4

Сделайте это с угловой библиотекой 6 и сделайте свое дело. Я только что поэкспериментировал с этим, и я могу поделиться автономным угловым модулем AOT с основным приложением без перестройки последней.

  1. В угловой библиотеке установите для angularCompilerOptions.skipTemplateCodegen значение false, и после сборки библиотеки вы получите фабрику модулей.
  2. После этого создайте модуль umd с помощью накопительного пакета следующим образом: rollup dist/plugin/esm2015/lib/plugin.module.ngfactory.js --file src/assets/plugin.module.umd.js --format umd --name plugin
  3. Загрузите исходный текстовый пакет umd в основное приложение и оцените его с помощью контекста модуля.
  4. Теперь вы можете получить доступ к ModuleFactory из объекта экспорта.

Здесь https://github.com/iwnow/angular-plugin-example вы можете найти, как разработать плагин с автономной сборкой и AOT

Ответ 5

Да, вы можете использовать ленивые модули загрузки, ссылаясь на них как на модули в маршрутизаторе. Вот пример https://github.com/start-angular/SB-Admin-BS4-Angular-6

  • Сначала добавьте все компоненты, которые вы используете в один модуль.
  • Теперь обратитесь к этому модулю в маршрутизаторе и angular будет ленить загружать ваш модуль в представление.

Ответ 6

Я протестировал в Angular 6, ниже решение работает для динамической загрузки модуля из внешнего пакета или внутреннего модуля.

1. Если вы хотите динамически загрузить модуль из проекта библиотеки или пакета:

У меня есть проект библиотеки "admin" (или вы можете использовать пакет) и проект приложения "app". В моем проекте библиотеки "admin" у меня есть AdminModule и AdminRoutingModule. В моем проекте "app":

а. Внесите изменения в tsconfig.app.json:

  "compilerOptions": {
    "module": "esNext",
  },

б. В app-routing.module.ts:

const routes: Routes = [
    {
        path: 'admin',
        loadChildren: async () => {
            const a = await import('admin')
            return a['AdminModule'];
        }
    },
    {
        path: '',
        redirectTo: '',
        pathMatch: 'full'
    }
];

@NgModule({
    imports: [RouterModule.forRoot(routes)],
    exports: [RouterModule]
})
export class AppRoutingModule {
}

2. если вы хотите загрузить модуль из того же проекта.

Есть 4 разных варианта:

а. В app-routing.module.ts:

const routes: Routes = [
    {
        path: 'example',
        /* Options 1: Use component */
        // component: ExampleComponent,  // Load router from component
        /* Options 2: Use Angular default lazy load syntax */
        loadChildren: './example/example.module#ExampleModule',  // lazy load router from module
        /* Options 3: Use Module */
        // loadChildren: () => ExampleModule, // load router from module
        /* Options 4: Use esNext, you need to change tsconfig.app.json */
        /*
        loadChildren: async () => {
            const a = await import('./example/example.module')
            return a['ExampleModule'];
        }
        */
    },
    {
        path: '',
        redirectTo: '',
        pathMatch: 'full'
    }
];

@NgModule({
    imports: [RouterModule.forRoot(routes)],
    exports: [RouterModule]
})
export class AppRoutingModule {
}
''

Ответ 7

Не работает

System.import('http://lab.azaas.com:52048/my-component-library.umd.js').then(module => {
        console.log(module);
 });

за работой

System.import('./../../assets/umds/my-component-library.umd.js').then(module => {
        console.log(module);
});