Angular2(CLI)摇树删除动态创建的 NgModule

Posted

技术标签:

【中文标题】Angular2(CLI)摇树删除动态创建的 NgModule【英文标题】:Angular2 (CLI) tree shaking removing dynamically created NgModule 【发布时间】:2017-07-21 02:23:32 【问题描述】:

我认为Angular-cli tree-shaking exclude component from removal 的问题非常相似,但我似乎无法从中得到什么。

基本上我有一个动态组件工厂,如How can I use/create dynamic template to compile dynamic Component with Angular 2.0? 中所述。

当我使用带有非生产设置的最新 Angular CLI 构建它时,一切正常。但是,一旦我使用生产设置,当我尝试加载具有动态创建内容的页面时,我会立即在浏览器中看到以下错误跟踪:

异常:未找到“e”的 NgModule 元数据。 原始堆栈跟踪: main.dc05ae9….bundle.js:formatted:4731 错误:没有 NgModule 元数据 为“e”找到。 在 f (vendor.c18e6df….bundle.js:formatted:76051) 在 t.resolve (vendor.c18e6df….bundle.js:formatted:20624) 在 t.getNgModuleMetadata (vendor.c18e6df….bundle.js:formatted:20169) 在 t._loadModules (vendor.c18e6df….bundle.js:formatted:40474) 在 t._compileModuleAndAllComponents (vendor.c18e6df….bundle.js:formatted:40462) 在 t.compileModuleAndAllComponentsSync (vendor.c18e6df….bundle.js:formatted:40436) 在 e.createComponentFactory (main.dc05ae9….bundle.js:formatted:4789)

这是我的组件工厂类:

@Injectable()
export class DynamicTypeBuilder     
  constructor() 
  

  private _cacheOfFactories: [templateKey: string]: ComponentFactory<any> = ;
  private compiler: Compiler = new JitCompilerFactory([useDebug: false, useJit: true]).createCompiler();

  public createComponentFactory<COMPONENT_TYPE>(type: any, template: string, additionalModules: any[] = []): Observable<ComponentFactory<COMPONENT_TYPE>> 

    let factory = this._cacheOfFactories[template];
    if (factory) 
      return Observable.of(factory);
    

    // unknown template ... let's create a Type for it
    let module = this.createComponentModule(type, additionalModules);

    // compiles and adds the created factory to the cache
    return Observable.of(this.compiler.compileModuleAndAllComponentsSync(module))
                     .map((moduleWithFactories: ModuleWithComponentFactories<COMPONENT_TYPE>) => 
                       factory = moduleWithFactories.componentFactories.find(value => value.componentType == type);
                       this._cacheOfFactories[template] = factory;                           
                       return factory;
                     );
  

  protected createComponentModule(componentType: any, additionalModules: any[]): Type<any> 
    @NgModule(
      imports: [
        FormsModule,
        ReactiveFormsModule,
        BrowserModule,
        PipesModule,
        ...additionalModules
      ],
      declarations: [
        componentType
      ],
      schemas:[CUSTOM_ELEMENTS_SCHEMA]
    )
    class RuntimeComponentModule 
    

    return RuntimeComponentModule;
  

正在被转译为

var _ = function() 
    function e() 
        this._cacheOfFactories = ,
        this.compiler = new i.a([
            useDebug: !1,
            useJit: !0
        ]).createCompiler()
    
    return e.prototype.createComponentFactory = function(e, t, n) 
        var i = this;
        var _ = this._cacheOfFactories[t];
        if (_)
            r.Observable.of(_);
        var a = this.createComponentModule(e, n);
        return r.Observable.of(this.compiler.compileModuleAndAllComponentsSync(a)).map(function(n) 
            return _ = n.componentFactories.find(function(t) 
                return t.componentType == e
            ),
            i._cacheOfFactories[t] = _,
            _
        )
    
    ,
    e.prototype.createComponentModule = function(e, t) 
        var n = function() 
            function e() 
            return e
        ();
        return n
    
    ,
    e.ctorParameters = function() 
        return []
    
    ,
    e
()

错误消息中的“e”是来自createComponentModule 的函数e(),正如您所见,它是空的,即使它应该包含@NgModule 内容。

如何动态创建一个新的 NgModule 并且仍然使用 Angular CLI 的生产模式?

版本: Angular2:2.4.8 Angular CLI:1.0.0-beta.32.3 打字稿:2.1.6

【问题讨论】:

请更新您的问题,因为它需要一些清晰度 【参考方案1】:

我有同样的错误信息。我发现的解决方法是不要对运行时模块使用装饰器。

protected createComponentModule(componentType: any, additionalModules: any[]): Type<any> 
  return NgModule(
    imports: [
      FormsModule,
      ReactiveFormsModule,
      BrowserModule,
      PipesModule,
      ...additionalModules
    ],
    declarations: [
      componentType
    ],
    schemas:[CUSTOM_ELEMENTS_SCHEMA]
  )(class RuntimeComponentModule );

好的,我没有完全理解为什么会发生错误。错误消息基本上说模块e 没有元数据。 Angular 中模块的元数据通常被声明为装饰器。

ES7 中的装饰器等价于 curry 函数。这意味着

@NgModule()
class A 

等于

NgModule()(class A )

我个人认为咖喱的方式要好得多...

更新了 22 场比赛: 来自官方repo的答案https://github.com/angular/angular-cli/issues/5359#issuecomment-287500703 根本不使用AOT。 请使用ng build --prod --no-aot 构建代码 就我而言,一切都解决了。

【讨论】:

恐怕不能正常工作。代码编译良好,并包含在转译的 JS 中,但是,现在有一个新问题。 Angular 在新的 NgModule 中找不到任何自定义导入的模块。所以在上面的例子中FormsModuleReactiveFormsModuleBrowserModule可以找到,但是PipesModule不能。错误在CompileMetadataResolver.getNgModuleMetadata 其实我也有同样的问题。这似乎在 JIT 编译中根本没有编译装饰器。不仅RuntimeComponentModule。如果您阅读编译后的main.js,它不包含PipesModule 的元数据。我将继续这个tmr。谢谢你告诉我角度模块很好。【参考方案2】:

不幸的是,无论是 Angular 2.x 还是 Angular 4 beta,目前这似乎都是不可能的(我会尽量保持最新的答案)。 问题在于动态组件定义包含文件引用(模板、样式表),在运行时无法再通过之前运行的 AOT 编译器解决这些引用。 但是,如果组件或模块不包含文件引用,当前的 Angular 代码也不允许真正动态地创建组件。它只是找不到在运行时创建的元数据。

总结问题,动态组件创建分为3个层次:

    静态定义一个组件并将其包含在一个 NgModule 中,AOT 编译器可以在 AOT 编译时找到它。这样的组件可以在任何时候实例化而不会出现问题。 (参见 ComponentFactoryResolver 等) 静态定义组件主体(代码等),但允许具有动态模板和/或样式(即,仅在需要时在代码中创建模板)。这也需要在运行时编译 NgModule。目前只有在不使用 AOT 编译器时才有可能,这代表了我在此处发布的问题。 动态定义完整的组件,包括代码和模板。这不是这里的意图,甚至可能会走得很远。但可能有人也有这个问题。

在我看来,第二个问题是可以解决的。 Angular 团队说,因为它是 AOT,它只能编译那些在 AOT 编译时静态已知的东西,但我不同意。 我可以考虑 AOT 编译此类组件的“存根”的可能性,然后在需要时使用动态模板或样式表对其进行实例化。可能需要为 @Component 注释使用新属性或像 @DynamicComponent 这样的全新注释,但对我来说似乎可行。我不知道@NgModule 声明是否需要进行相同的更改,但我认为他们会这样做。

【讨论】:

在 v4 中我正在使用它。一次 jit + aot :) 是的,在 github 上看到了你的帖子!现在我们需要让它在 CLI 中运行。 ciao 塞巴斯蒂安! @PatrikLaszlo 你能指导我们吗? pages.corifeus.com/github/corifeus-builder-angular/artifacts/…

以上是关于Angular2(CLI)摇树删除动态创建的 NgModule的主要内容,如果未能解决你的问题,请参考以下文章

使用 ng-content 动态创建 angular2 组件

Angular2基础

ng-cli

安装特定版本的 ng cli

使用 webpack 和 typescript 摇树

Angular2:使用NG-ZORRO的tabs结合路由复用策略实现动态tab