1

I have a large project that was working well under webpack, angular 4 and recently moved to angular 5, and an improved webpack config. The 'development' build loads just fine.

But the 'production' build loads and dies with a 'Please add a @NgModule annotation' - yuck since so many many have crashed on this shoal.

So I turned off mangle to see what component was causing an issue. And I found that whatever was listed in the beginning of the imports: [ ... ] array section in @NgModule was listed. So WHY is this? and what can be causing this lovely error?

Why would the first module listed at the beginning of imports always not be found?

In the version listed below, the error is:

Uncaught Error: Unexpected value 'FormsModule' imported by the module 'AppModule'. Please add a @NgModule annotation.

Here is the section of the project that I hope might shed some light:

@NgModule({
    bootstrap: [ AppComponent ],
    declarations: COMPONENTS,
    imports: [ // import Angular's modules
      FormsModule,
      ReactiveFormsModule,
      HttpModule,
      BrowserModule,
      RouterModule.forRoot(ROUTES, { useHash: true, preloadingStrategy: PreloadAllModules }),
      BsDropdownModule.forRoot(),
      AlertModule.forRoot(),
      DatepickerModule.forRoot(),
      TooltipModule.forRoot(),
      ModalModule.forRoot(),
      LocalStorageModule.withConfig({
        prefix: 'tracker',
        storageType: 'localStorage'
      }),
      A2Edatetimepicker,
      DndModule.forRoot(),
      NgPipesModule,
      ColorPickerModule
    ],
    providers: [ // expose our Services and Providers into Angular's dependency injection
      ENV_PROVIDERS,
      APP_PROVIDERS,
      {
        provide: AuthHttp,
        useFactory: (http: Http, localStorage: LocalStorageService) => {
          let conf: AuthConfig;
          conf = new AuthConfig({
            tokenName: 'token',
            tokenGetter: (() => {
              const t = localStorage.get<any>('id_token');
              // console.log(t);
              return t;
            }),
            globalHeaders: [{'Content-Type': 'application/json'}],
          });
          return new AuthHttp(conf, http);
        },
        deps: [Http, LocalStorageService]
      },
      {
        provide: SocketService,
        useFactory: (broadcast: BroadCaster,
                     authHttp: AuthHttp) => {
          return new SocketService(broadcast, authHttp);
        },
        deps: [BroadCaster, AuthHttp]
      },
      {
        provide: ImportUserData,
        useFactory: (authHttp: AuthHttp,
                     localStorage: LocalStorageService) => {
          return new ImportUserData(authHttp, localStorage);
        },
        deps: [AuthHttp, LocalStorageService]
      },
      {
        provide: AuthService,
        useFactory: (http: Http,
                     authHttp: AuthHttp,
                     localStorage: LocalStorageService) => {
          const as = new AuthService(http, authHttp, localStorage);
          return as;
        },
        deps: [Http, AuthHttp, LocalStorageService]
      },
      {
        provide: UserCondition,
        useFactory: (authHttp: AuthHttp,
                     broadcast: BroadCaster,
                     localStorage: LocalStorageService,
                     sanitizer: DomSanitizer,
                     zone: NgZone) => {
          return new UserCondition(authHttp, broadcast, localStorage, sanitizer, zone);
        },
        deps: [AuthHttp, BroadCaster, LocalStorageService, DomSanitizer, NgZone]
      },
      {
        provide: ProfileComponent,
        useFactory: (userCondtion: UserCondition,
                     sanitizer: DomSanitizer,
                     auth: AuthService) => {
          return new ProfileComponent(userCondtion, sanitizer, auth);
        },
        deps: [UserCondition, DomSanitizer, AuthService]
      },
      {
        provide: LogoutWarningComponent,
        useFactory: (userCondtion: UserCondition,
                     broadcaster: BroadCaster,
                     versionS: Version,
                     auth: AuthService) => {
          return new LogoutWarningComponent(userCondtion, broadcaster, versionS, auth);
        },
        deps: [UserCondition, BroadCaster, Version, AuthService]
      },
    ]
  })
  export class AppModule implements OnDestroy {
    private mVersion: string;
    ...
    ...
  } 

Update to this problem:

After several days, I made quite a bit of progress.
Also I learned several things that may help others - who run into this sort of problem.

  1. comment out nearly all of the plugins in the production version of the webpack build.
  2. turn off name mangle.

Once I had done this, I learned that the issue was coming from the 'production' setting the config section from the project: angular-starter. The place where the issue surfaced was in the 'build-utils.js' file:

    const ts = require('typescript');
    const path = require('path');
    const fs = require('fs');
    const helpers = require('./helpers');

    const DEFAULT_METADATA = {
      title: 'Tracker',
      baseUrl: '/',
      isDevServer: helpers.isWebpackDevServer(),
      HMR: helpers.hasProcessFlag('hot'),
      AOT: process.env.BUILD_AOT || helpers.hasNpmFlag('aot'),
      E2E: !!process.env.BUILD_E2E,
      WATCH: helpers.hasProcessFlag('watch'),
      tsConfigPath: 'tsconfig.webpack.json',

      /**
       * This suffix is added to the environment.ts file, if not set the default environment file is loaded (development)
       * To disable environment files set this to null
       */
      envFileSuffix: ''
    };

    function supportES2015(tsConfigPath) {
      if (!supportES2015.hasOwnProperty('supportES2015')) {
        const tsTarget = readTsConfig(tsConfigPath).options.target;
        supportES2015['supportES2015'] = tsTarget !== ts.ScriptTarget.ES3 && tsTarget !== ts.ScriptTarget.ES5;
      }
      return supportES2015['supportES2015'];
    }

    function readTsConfig(tsConfigPath) {
      const configResult = ts.readConfigFile(tsConfigPath, ts.sys.readFile);
      return ts.parseJsonConfigFileContent(configResult.config, ts.sys,
        path.dirname(tsConfigPath), undefined, tsConfigPath);
    }

    function getEnvFile(suffix) {
      if (suffix && suffix[0] !== '.') {
        suffix = '.' + suffix;
      }

      if (suffix === null) {
        return;
      }

      let fileName = helpers.root(`src/environments/environment${suffix}.ts`);
      if (fs.existsSync(fileName)) {
        return fileName;
      } else if (fs.existsSync(fileName = helpers.root('src/environments/environment.ts'))) {
        console.warn(`Could not find environment file with suffix ${suffix}, loading default environment file`);
        return fileName;
      } else {
        throw new Error('Environment file not found.')
      }
    }

    /**
     * Read the tsconfig to determine if we should prefer ES2015 modules.
     * Load rxjs path aliases.
     * https://github.com/ReactiveX/rxjs/blob/master/doc/lettable-operators.md#build-and-treeshaking
     * @param supportES2015 Set to true when the output of typescript is >= ES6
     */
    function rxjsAlias(supportES2015) {
      try {
        const rxjsPathMappingImport = supportES2015 ? 'rxjs/_esm2015/path-mapping' : 'rxjs/_esm5/path-mapping';
        const rxPaths = require(rxjsPathMappingImport);
        return rxPaths(helpers.root('node_modules'));
      } catch (e) {
        return {};
      }
    }

    function ngcWebpackSetup(prod, metadata) {
      if (!metadata) {
        metadata = DEFAULT_METADATA;
      }

      // TURNED OFF THE buildOptimizer here:
      // const buildOptimizer = prod;
      const buildOptimizer = false;
      const sourceMap = true; // TODO: apply based on tsconfig value?
      const ngcWebpackPluginOptions = {
        skipCodeGeneration: !metadata.AOT,
        sourceMap
      };

      const environment = getEnvFile(metadata.envFileSuffix);
      // console.log('environment:', environment);
      if (environment) {
        ngcWebpackPluginOptions.hostReplacementPaths = {
          [helpers.root('src/environments/environment.ts')]: environment
        }
      }

      if (!prod && metadata.WATCH) {
        // Force commonjs module format for TS on dev watch builds.
        ngcWebpackPluginOptions.compilerOptions = {
          module: 'commonjs'
        };
      }

      const buildOptimizerLoader = {
        loader: '@angular-devkit/build-optimizer/webpack-loader',
        options: {
          sourceMap
        }
      };

      const loaders = [
        {
          test: /(?:\.ngfactory\.js|\.ngstyle\.js|\.ts)$/,
          use: metadata.AOT && buildOptimizer ? [ buildOptimizerLoader, '@ngtools/webpack' ] : [ '@ngtools/webpack' ]
        },
        ...buildOptimizer
          ? [ { test: /\.js$/, use: [ buildOptimizerLoader ] } ]
          : []
      ];
      console.log('loaders:', loaders);
      // console.log('ngcWebpackPluginOptions:', ngcWebpackPluginOptions);

      return {
        loaders,
        plugin: ngcWebpackPluginOptions
      };
    }


    exports.DEFAULT_METADATA = DEFAULT_METADATA;
    exports.supportES2015 = supportES2015;
    exports.readTsConfig = readTsConfig;
    exports.getEnvFile = getEnvFile;
    exports.rxjsAlias = rxjsAlias;
    exports.ngcWebpackSetup = ngcWebpackSetup;

Here I needed to set: 'buildOptimizer' to false inside the ngcWebpackSetup() function above. The downside is that the minimized code grew from 3.59 MB to 4.04 MB. BUT the code now loads and runs properly in the browser!

I will hopefully learn what the issue is here and report back, and post a correction to the 'angular-starter' project possibly.

JoelParke
  • 2,676
  • 2
  • 24
  • 38
  • At the top of your module file do you have `import { NgModule } from '@angular/core';`? – Z. Bagley Dec 08 '17 at 00:43
  • 1
    I'm going to take a wild guess here, but I suspect you have old intermediate files still cached from the last time you compiled with Angular 4. These are not compatible with Angular 5, but the compiler has no way of knowing they are from a previous version. Clear your caches and dist folder and build again. – Reactgular Dec 08 '17 at 00:45
  • Yes, Z. Bagley, I do have NgModule imported from '@angular/core'. – JoelParke Dec 08 '17 at 04:57
  • cgTag - when I do a clean build, I do: 1. npm run clean, which does: "clean": "npm cache clean --force && npm run rimraf -- node_modules doc coverage dist compiled dll. 2. yarn - to reinstall all the npm packages. Is there anything else that I need to do to fully clear any other caches I am not aware of? – JoelParke Dec 08 '17 at 05:15
  • I have tried everything I can think of at the moment. AND I still have the SAME error. --- reading and looking, I have seen some comments adding some packages to the "paths": { "@angular/*": ["node_modules/@angular/*"] }, But I am unclear why. – JoelParke Dec 08 '17 at 05:48
  • It seems to me, that there should be a better way of learning what is wrong with the 'production' build, while the 'development' build is fine! – JoelParke Dec 08 '17 at 05:50
  • As I looked high and low, I discovered that one of the packages that made it's way into this project, was causing a warning: WARNING in ./node_modules/app-root-path/browser-shim.js 10:8-46 Critical dependency: the request of a dependency is an expression @ ./node_modules/app-root-path/browser-shim.js @ ./node_modules/codelyzer/angular/config.js @ ./node_modules/codelyzer/util/logger.js @ ./src/app/tracker/east/tinymce/taskComment.component.ts @ ./src/app/tracker/east/index.ts .... So I removed that package, and now no warnings, but still the build fails in the browser... – JoelParke Dec 10 '17 at 17:19

0 Answers0