javascript - Angular 9 - 您必须传入 NgModule 或 NgModuleFactory 才能引导

标签 javascript angular angular-universal

我将 Angular 应用程序从 v6 更新到了 v9,测试时确实没问题,但是当我将其部署到临时服务器时,出现此错误:

You must pass in a NgModule or NgModuleFactory to be bootstrapped

我搜索了很多相关内容,但我不知道在这里可以做什么。

./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"
}

最佳答案

使用 Ivy,您需要更改 server.ts 执行引导的方式。

您似乎尚未迁移 Angular ssr 文件。我们遇到了同样的问题(但我们当时没有使用express-engine),因此我们最终删除了与 ssr 相关的文件(server.tsmain.server.ts ,...) 并运行以下命令让 Angular 修改/创建所有必需的文件

ng add @nguniversal/express-engine

然后,我们修改了 server.ts 以在需要时添加我们的自定义代码。

如果您想手动执行所有操作而不运行命令,请查看 there

关于javascript - Angular 9 - 您必须传入 NgModule 或 NgModuleFactory 才能引导,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60340119/

相关文章:

javascript - 新手问题: connect two functions int two files

javascript - 如何在 Osclass 分类脚本中使用 javascript/jquery 检测用户是否在主页上

javascript - 构造函数内声明和构造函数外部声明的区别

Angular:有没有办法在单元测试中模拟 PLATFORM_ID 的值?

node.js - 将 Angular 2/3/4/5/6 项目转换为 Angular Universal

javascript - Chrome 不允许过期时间少于 4 小时的 cookie

JavaScript 静态变量

Angular 7 组件测试 - 如何验证输入元素值

angular - 使用对象数组中的 Angular Material 树时超出最大调用堆栈大小

angular - 是否可以使用 Angular 拦截器拦截像 .html 这样的静态文件?我想改变 lang 属性