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.
- comment out nearly all of the plugins in the production version of the webpack build.
- 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.