Skip to content

Tree shaking removes dynamically created NgModule #5275

Closed
@SebastianFree

Description

@SebastianFree

I am taking the below information from the following StackOverflow question: http://stackoverflow.com/questions/42537138/angular2-cli-tree-shaking-removing-dynamically-created-ngmodule (I am the author)

OS?

any

Versions.

@angular/cli: 1.0.0-rc.1
node: 6.10.0
os: linux x64
@angular/common: 2.4.9
@angular/compiler: 2.4.9
@angular/core: 2.4.9
@angular/forms: 2.4.9
@angular/http: 2.4.9
@angular/platform-browser: 2.4.9
@angular/platform-browser-dynamic: 2.4.9
@angular/router: 3.4.9
@angular/upgrade: 2.4.9
@angular/cli: 1.0.0-rc.1
@angular/compiler-cli: 2.4.9
typescript: 2.1.6

Repro steps.

Compiling my app with a non-production environment works just fine. However, when I run ng build --prod -e prod the transpiling/compiling process removes all dynamically created modules.

The log given by the failure.

Immediately when trying to load a dynamically created component:

EXCEPTION: No NgModule metadata found for 'e'.
ORIGINAL STACKTRACE:
main.dc05ae9….bundle.js:formatted:4731
Error: No NgModule metadata found for 'e'.
at f (vendor.c18e6df….bundle.js:formatted:76051)
at t.resolve (vendor.c18e6df….bundle.js:formatted:20624)
at t.getNgModuleMetadata (vendor.c18e6df….bundle.js:formatted:20169)
at t._loadModules (vendor.c18e6df….bundle.js:formatted:40474)
at t._compileModuleAndAllComponents (vendor.c18e6df….bundle.js:formatted:40462)
at t.compileModuleAndAllComponentsSync (vendor.c18e6df….bundle.js:formatted:40436)
at e.createComponentFactory (main.dc05ae9….bundle.js:formatted:4789)

Mention any other details that might be useful.

My dynamic component builder (the problem is in the createComponentModule function which dynamically creates a new module):

@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;
  }
}

And its transpiled/compiled result:

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
}()

The 'e' in the error message is the function e() from createComponentModule which, as you can see, is empty even though is should contain the @NgModule content.

This is the transpiled/compiled content with a non-production setting, still containing all the information about the removed NgModule:

var DynamicTypeBuilder = (function () {
    function DynamicTypeBuilder() {
        // since this object is a singleton, we can have the cache and the compiler here
        this._cacheOfFactories = {};
        this.compiler = new __WEBPACK_IMPORTED_MODULE_1__angular_compiler__["a" /* JitCompilerFactory */]([{ useDebug: false, useJit: true }]).createCompiler();
    }
    DynamicTypeBuilder.prototype.createComponentFactory = function (type, template, additionalModules) {
        var _this = this;
        if (additionalModules === void 0) { additionalModules = []; }
        var factory = this._cacheOfFactories[template];
        if (factory) {
            return __WEBPACK_IMPORTED_MODULE_2_rxjs__["Observable"].of(factory);
        }
        var module = this.createComponentModule(type, additionalModules);
        return __WEBPACK_IMPORTED_MODULE_2_rxjs__["Observable"].of(this.compiler.compileModuleAndAllComponentsSync(module))
            .map(function (moduleWithFactories) {
            factory = moduleWithFactories.componentFactories.find(function (value) { return value.componentType == type; });
            _this._cacheOfFactories[template] = factory;
            return factory;
        });
    };
    DynamicTypeBuilder.prototype.createComponentModule = function (componentType, additionalModules) {
        var RuntimeComponentModule = (function () {
            function RuntimeComponentModule() {
            }
            return RuntimeComponentModule;
        }());
        RuntimeComponentModule = __decorate([
            __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__angular_core__["NgModule"])({
                imports: [
                    __WEBPACK_IMPORTED_MODULE_3__angular_forms__["FormsModule"],
                    __WEBPACK_IMPORTED_MODULE_3__angular_forms__["ReactiveFormsModule"],
                    __WEBPACK_IMPORTED_MODULE_4__angular_platform_browser__["BrowserModule"],
                    __WEBPACK_IMPORTED_MODULE_5_app_modules_pipes_module__["a" /* PipesModule */]
                ].concat(additionalModules),
                declarations: [
                    componentType
                ],
                schemas: [__WEBPACK_IMPORTED_MODULE_0__angular_core__["CUSTOM_ELEMENTS_SCHEMA"]]
            })
        ], RuntimeComponentModule);
        // a module for just this Type
        return RuntimeComponentModule;
    };
    return DynamicTypeBuilder;
}());
DynamicTypeBuilder = __decorate([
    __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__angular_core__["Injectable"])(),
    __metadata("design:paramtypes", [])
], DynamicTypeBuilder);

I assume tree shaking removes this dynamically created NgModule.
How can I create a new NgModule like this and still use the production mode of Angular CLI?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions