I updated my Angular app from v6 to v9, and when testing it is really ok, but when I deploy it to my staging server, I have this error:
You must pass in a NgModule or NgModuleFactory to be bootstrapped
I have searched a lot about it, but I have no idea what could be done here.
./server.ts:
const environment = process.env.NODE_ENV || 'local';
const docker = process.env.DOCKER || false;
// These are important and needed before anything else
import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import { enableProdMode } from '@angular/core';
// Express Engine
import { ngExpressEngine } from '@nguniversal/express-engine';
import { MODULE_MAP } from '@nguniversal/module-map-ngfactory-loader';
import * as express from 'express';
import * as forceSSL from 'express-force-ssl';
import * as morgan from 'morgan';
import * as bodyParser from 'body-parser';
import { join } from 'path';
import * as fs from 'fs';
import * as compression from 'compression';
import * as Redis from 'redis';
// Import http and https servers
import * as http from 'http';
import * as https from 'https';
// Routes
import XRouter from './backend/routes/x.router';
//This is not the real route name, I'm omitting it.
// MOTD
import motd from './motd';
// Import cache config
const CACHE_CONFIG = require('./cache.conf');
// Faster server renders w/ Prod mode (dev mode never needed)
if(environment !== 'local') enableProdMode();
// Express server
const app = express();
let appRedirect;
if(!docker) appRedirect = express();
const PORT = process.env.PORT || 4000;
let PORT_SSL;
if(!docker) PORT_SSL = process.env.PORT || 443;
const URL_REDIS = process.env.URL_REDIS || 'redis://127.0.0.1:6379';
const DIST_FOLDER = join(process.cwd(), 'dist');
const template = fs.readFileSync(join(DIST_FOLDER, 'browser', 'index.html')).toString();
const domino = require('domino');
const win = domino.createWindow(template);
global['window'] = win;
global['document'] = win.document;
global['DOMTokenList'] = win.DOMTokenList;
global['Node'] = win.Node;
global['Text'] = win.Text;
global['HTMLElement'] = win.HTMLElement;
global['navigator'] = win.navigator;
global['MutationObserver'] = getMockMutationObserver();
global['Event'] = win.Event;
function getMockMutationObserver() {
return class {
observe(node, options) {
}
disconnect() {
}
takeRecords() {
return [];
}
};
}
// Create a cache instance
const redisCache = Redis.createClient(URL_REDIS);
// Init routers
const xRouter = XRouter(redisCache);
/*******************************************************
************** Function to handle cache ****************
********************************************************/
function canBeCached(req) {
let output = false;
if(req.method != 'GET') return false;
output = CACHE_CONFIG.availableCache.some(url => {
return (req.originalUrl.indexOf(url) >= 0)
});
return output;
}
/******************************************************
************** Enable GZIP Compression ****************
*******************************************************/
app.use(compression({
level: 9
}));
/******************************************************
*********** Add morgan on DEV environment *************
*******************************************************/
if(environment === 'local')
app.use(morgan('dev'));
/******************************************************
************** Enable CORS in DEV mode ****************
*******************************************************/
app.use(function(req, res, next) {
res.header(Access-Control-Allow-Origin, *);
res.header(Access-Control-Allow-Headers, Origin, X-Requested-With, Content-Type, Accept);
next();
});
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())
/******************************************************
***************** Config SSR Angular ******************
*******************************************************/
if(environment !== 'local') {
try {
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./dist/server/main');
const { MODULE_MAP } = require('@nguniversal/module-map-ngfactory-loader');
app.engine('html', ngExpressEngine({
bootstrap: AppServerModuleNgFactory,
providers: [
{
provide: MODULE_MAP,
useValue: 'lazy'
}
]
}));
app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser'));
} catch(err) {
console.log(err);
}
}
server.on('listening', () => {
console.log(`Server listening...`);
});
./webpack.server.config.js
/**
* Package dist/server on server.ts script to render on server-side
*/
const path = require('path');
const webpack = require('webpack');
module.exports = {
mode: 'development', // # Temporary fix: https://github.com/angular/angular-cli/issues/8616
entry: { server: './server.ts' },
resolve: {
extensions: ['.js', '.ts'],
alias: {
'hiredis': path.join(__dirname, 'aliases/hiredis.js')
}
},
target: 'node',
// this makes sure we include node_modules and other 3rd party libraries
externals: [/(node_modules|main..*.js)/],
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js'
},
module: {
rules: [
{ test: /.ts$/, loader: 'ts-loader' }
]
},
plugins: [
// Temporary Fix for issue: https://github.com/angular/angular/issues/11580
// for WARNING Critical dependency: the request of a dependency is an expression
new webpack.ContextReplacementPlugin(
/(.+)?angular(\|/)core(.+)?/,
path.join(__dirname, 'src'), // location of your src
{} // a map of your routes
),
new webpack.ContextReplacementPlugin(
/(.+)?express(\|/)(.+)?/,
path.join(__dirname, 'src'),
{}
)
]
}
./src/tsconfig.server.json
{
extends: ../tsconfig.json,
compilerOptions: {
outDir: ../out-tsc/app,
baseUrl: ./,
module: commonjs,
types: []
},
exclude: [
test.ts,
**/*.spec.ts
],
angularCompilerOptions: {
entryModule: app/app.server.module#AppServerModule
}
}
./src/main.server.ts:
export { AppServerModule } from './app/app.server.module';
./src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { registerLocaleData, isPlatformBrowser } from '@angular/common';
import { NgModule, PLATFORM_ID, Inject, APP_ID, } from '@angular/core';
import { NgxMaskModule } from 'ngx-mask';
// configure language
import ptBr from '@angular/common/locales/pt';
registerLocaleData(ptBr);
import { AppComponent } from './app.component';
import { CoreModule } from './core/core.module';
import { LayoutModule } from './layout/layout.module';
import { PagesModule } from './pages/pages.module';
import { SharedModule } from './shared/shared.module';
import { AgmCoreModule } from '@agm/core';
import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';
import moment = require('moment');
moment.locale('pt-BR');
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule.withServerTransition({appId: 'site'}),
BrowserAnimationsModule,
CoreModule,
LayoutModule,
PagesModule,
SharedModule,
NgxMaskModule.forRoot(),
AgmCoreModule.forRoot({
apiKey: ''
}),
ServiceWorkerModule.register('./ngsw-worker.js', { enabled: environment.production })
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {
constructor(
@Inject(PLATFORM_ID) private platformId: Object,
@Inject(APP_ID) private appId: string) {
const platform = isPlatformBrowser(platformId) ?
'in the browser' : 'on the server';
console.log(`Running ${platform} with appId=${appId}`);
}
}
./src/app/app.server.module.ts:
/**
This is the App loaded for the server render
express-js will load this file instead of AppModule directly.
**/
import { NgModule } from '@angular/core';
import { ServerModule } from '@angular/platform-server';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';
import { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';
// We get the zone properties passed from express application
// @see server.ts
export function getRequest() {
return Zone.current.get('req') || {};
}
export function getResponse() {
return Zone.current.get('res') || {};
}
@NgModule({
imports: [
AppModule,
ServerModule,
ModuleMapLoaderModule,
],
bootstrap: [AppComponent],
providers: [
{ provide: 'req', useFactory: getRequest },
{ provide: 'res', useFactory: getResponse }
]
})
export class AppServerModule { }
angular.json
{
$schema: ./node_modules/@angular/cli/lib/config/schema.json,
version: 1,
newProjectRoot: projects,
projects: {
site: {
root: ,
sourceRoot: src,
projectType: application,
prefix: app,
schematics: {
@schematics/angular:component: {
styleext: scss
}
},
architect: {
build: {
builder: @angular-devkit/build-angular:browser,
options: {
outputPath: dist/browser,
index: src/index.html,
main: src/main.ts,
polyfills: src/polyfills.ts,
tsConfig: src/tsconfig.app.json,
assets: [
src/favicon.ico,
src/assets,
src/manifest.json
],
styles: [
node_modules/ngx-owl-carousel-o/lib/styles/prebuilt-themes/owl.carousel.min.css,
node_modules/ngx-owl-carousel-o/lib/styles/prebuilt-themes/owl.theme.default.min.css,
src/styles.scss
],
scripts: [
src/assets/scripts/modernizr/modernizr.js,
src/assets/scripts/g.js
]
},
configurations: {
production: {
fileReplacements: [
{
replace: src/environments/environment.ts,
with: src/environments/environment.prod.ts
}
],
optimization: true,
outputHashing: all,
sourceMap: false,
extractCss: true,
namedChunks: false,
aot: true,
extractLicenses: true,
vendorChunk: false,
buildOptimizer: true,
serviceWorker: true
},
development: {
fileReplacements: [
{
replace: src/environments/environment.ts,
with: src/environments/environment.dev.ts
}
],
optimization: true,
outputHashing: all,
sourceMap: true,
extractCss: true,
namedChunks: false,
aot: true,
extractLicenses: true,
vendorChunk: false,
buildOptimizer: true,
serviceWorker: true
},
site-dev: {
fileReplacements: [
{
replace: src/environments/environment.ts,
with: src/environments/environment.site-dev.ts
}
],
optimization: true,
outputHashing: all,
sourceMap: false,
extractCss: true,
namedChunks: false,
aot: true,
extractLicenses: true,
vendorChunk: false,
buildOptimizer: true,
serviceWorker: true
}
}
},
serve: {
builder: @angular-devkit/build-angular:dev-server,
options: {
browserTarget: site:build
},
configurations: {
production: {
browserTarget: site:build:production
}
}
},
server: {
builder: @angular-devkit/build-angular:server,
options: {
outputPath: dist/server,
main: src/main.server.ts,
tsConfig: src/tsconfig.server.json
},
configurations: {
site-dev: {
fileReplacements: [{
replace: src/environments/environment.ts,
with: src/environments/environment.site-dev.ts
}]
},
development: {
fileReplacements: [{
replace: src/environments/environment.ts,
with: src/environments/environment.dev.ts
}]
},
production: {
fileReplacements: [{
replace: src/environments/environment.ts,
with: src/environments/environment.prod.ts
}]
}
}
},
extract-i18n: {
builder: @angular-devkit/build-angular:extract-i18n,
options: {
browserTarget: site:build
}
},
test: {
builder: @angular-devkit/build-angular:karma,
options: {
main: src/test.ts,
polyfills: src/polyfills.ts,
tsConfig: src/tsconfig.spec.json,
karmaConfig: src/karma.conf.js,
styles: [
src/_variables.scss,
src/_shared.scss,
src/styles.scss
],
scripts: [],
assets: [
src/favicon.ico,
src/assets,
src/manifest.json
]
}
},
lint: {
builder: @angular-devkit/build-angular:tslint,
options: {
tsConfig: [
src/tsconfig.app.json,
src/tsconfig.spec.json
],
exclude: [
**/node_modules/**
]
}
}
}
},
site-e2e: {
root: e2e/,
projectType: application,
architect: {
e2e: {
builder: @angular-devkit/build-angular:protractor,
options: {
protractorConfig: e2e/protractor.conf.js,
devServerTarget: site:serve
},
configurations: {
production: {
devServerTarget: site:serve:production
}
}
},
lint: {
builder: @angular-devkit/build-angular:tslint,
options: {
tsConfig: e2e/tsconfig.e2e.json,
exclude: [
**/node_modules/**
]
}
}
}
}
},
defaultProject: site
}