问题
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"
}
回答1:
With Ivy you need to change the way server.ts
performs bootstrapping.
It looks like you have not migrated your angular ssr files. We had the same problem (but we where not using express-engine at the time), and so we ended up removing our files related to ssr (server.ts
, main.server.ts
,...) and run the following command to let angular modify/create all necessary files
ng add @nguniversal/express-engine
Then, we modified server.ts
to add our customised code back when needed.
If you want to do everything manually without running the command, have a look there
来源:https://stackoverflow.com/questions/60340119/angular-9-you-must-pass-in-a-ngmodule-or-ngmodulefactory-to-be-bootstrapped