Angular 9 - You must pass in a NgModule or NgModuleFactory to be bootstrapped

主宰稳场 提交于 2020-04-18 07:16:09

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!