Angular 通用:readFile$(...).mergeMap 不是函数

Angular Universal: readFile$(...).mergeMap is not a function

我正在对现有 angular 7 应用程序实施 angular 通用服务器端渲染。我也试图让它与 Firebase 一起工作,但是在我的 firebase 控制台功能日志中,我收到以下错误:

 TypeError: readFile$(...).mergeMap is not a function
    at /user_code/node_modules/angular-universal-express-firebase/node_modules/angular-universal-express/index.js:36:14
    at Layer.handle [as handle_request] (/user_code/node_modules/angular-universal-express-firebase/node_modules/express/lib/router/layer.js:95:5)
    at next (/user_code/node_modules/angular-universal-express-firebase/node_modules/express/lib/router/route.js:137:13)
    at Route.dispatch (/user_code/node_modules/angular-universal-express-firebase/node_modules/express/lib/router/route.js:112:3)
    at Layer.handle [as handle_request] (/user_code/node_modules/angular-universal-express-firebase/node_modules/express/lib/router/layer.js:95:5)
    at /user_code/node_modules/angular-universal-express-firebase/node_modules/express/lib/router/index.js:281:22
    at param (/user_code/node_modules/angular-universal-express-firebase/node_modules/express/lib/router/index.js:354:14)
    at param (/user_code/node_modules/angular-universal-express-firebase/node_modules/express/lib/router/index.js:365:14)
    at Function.process_params (/user_code/node_modules/angular-universal-express-firebase/node_modules/express/lib/router/index.js:410:3)
    at next (/user_code/node_modules/angular-universal-express-firebase/node_modules/express/lib/router/index.js:275:10)

我已经设置 firebase.json 将所有路由重定向到一个名为 ssrApp 的函数,并且当我执行 firebase deploy 时不会抛出任何错误。有人遇到过这个问题吗?

这是我的 firebase.json:

{
  "hosting": {
    "public": "dist",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "**",
        "function": "ssrApp"
      }
    ]
  },
  "functions": {
    "predeploy": "npm --prefix \"$RESOURCE_DIR\" run build"
  }
}

这是我在根目录下的package.json:

{
  "name": "mse",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "build:client-app:prod": "./node_modules/.bin/ng build --prod",
    "build:server-app:prod": "./node_modules/.bin/ng run mse:server",
    "build-all": "./node_modules/.bin/npm-run-all build:client-app:prod build:server-app:prod build:functions",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e",
    "build:functions": "cd functions && npm run build",
    "move-index": "mv ./dist/index.html ./functions/lib/index-server.html",
    "firebase:serve": "firebase serve --only hosting,functions",
    "deploy": "firebase deploy",
    "start:local": "npm-run-all build-all move-index firebase:serve",
    "build-and-deploy:prod": "npm-run-all build-all move-index deploy"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^7.2.4",
    "@angular/cdk": "^7.3.1",
    "@angular/common": "^7.2.4",
    "@angular/compiler": "^7.2.4",
    "@angular/core": "^7.2.4",
    "@angular/forms": "^7.2.4",
    "@angular/http": "^7.2.4",
    "@angular/material": "^7.3.1",
    "@angular/platform-browser": "^7.2.4",
    "@angular/platform-browser-dynamic": "^7.2.4",
    "@angular/platform-server": "^7.2.4",
    "@angular/router": "^7.2.4",
    "@nguniversal/module-map-ngfactory-loader": "^7.1.0",
    "@ngx-translate/core": "^10.0.2",
    "@ngx-translate/http-loader": "^3.0.1",
    "@types/googlemaps": "^3.30.16",
    "animate.css": "^3.7.0",
    "bootstrap": "^4.2.1",
    "compass": "^0.1.1",
    "compass-sass-mixins": "^0.12.7",
    "contentful": "^6.1.3",
    "core-js": "^2.6.4",
    "emailjs-com": "^2.3.2",
    "flag-icon-css": "^3.2.1",
    "hammerjs": "^2.0.8",
    "jquery": "^3.3.1",
    "marked": "^0.4.0",
    "ngx-bootstrap": "^3.2.0",
    "popper.js": "^1.14.7",
    "rxjs": "^6.4.0",
    "ts-loader": "^5.3.3",
    "web-animations-js": "^2.3.1",
    "webpack-cli": "^3.2.3",
    "zone.js": "^0.8.29",
    "@angular/material-moment-adapter": "7.0.0",
    "@nguniversal/express-engine": "^7.0.0",
    "express": "^4.16.2",
    "moment": "^2.22.2",
    "rxjs-compat": "^6.0.0-rc.0"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "^0.6.8",
    "@angular/cli": "~7.3.1",
    "@angular/compiler-cli": "^7.2.4",
    "@angular/language-service": "^7.2.4",
    "@types/jasmine": "^2.8.16",
    "@types/jasminewd2": "^2.0.6",
    "@types/node": "~8.9.4",
    "codelyzer": "~4.2.1",
    "jasmine-core": "~2.99.1",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~1.7.1",
    "karma-chrome-launcher": "~2.2.0",
    "karma-coverage-istanbul-reporter": "^1.4.3",
    "karma-jasmine": "~1.1.1",
    "karma-jasmine-html-reporter": "^0.2.2",
    "protractor": "^5.4.2",
    "ts-node": "~5.0.1",
    "tslint": "~5.9.1",
    "typescript": "~3.2.4",
    "@types/express": "^4.0.39",
    "http-server": "^0.11.1",
    "karma-cli": "~1.0.1",
    "npm-run-all": "^4.1.2"
  }
}

这是我的 angular.json:

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "mse": {
      "root": "",
      "sourceRoot": "src",
      "projectType": "application",
      "prefix": "app",
      "schematics": {
        "@schematics/angular:component": {
          "styleext": "scss"
        }
      },
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "src/tsconfig.app.json",
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ],
            "styles": [
              "node_modules/bootstrap/dist/css/bootstrap.min.css",
              "src/styles/styles.scss",
              "src/styles/mse-theme.scss"
            ],
            "scripts": [
              "node_modules/jquery/dist/jquery.min.js",
              "node_modules/popper.js/dist/umd/popper.min.js",
              "node_modules/bootstrap/dist/js/bootstrap.min.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
            }
          }
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": {
            "browserTarget": "mse:build"
          },
          "configurations": {
            "production": {
              "browserTarget": "mse:build:production"
            }
          }
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "browserTarget": "mse: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": [
              "styles.css"
            ],
            "scripts": [],
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ]
          }
        },
        "lint": {
          "builder": "@angular-devkit/build-angular:tslint",
          "options": {
            "tsConfig": [
              "src/tsconfig.app.json",
              "src/tsconfig.spec.json"
            ],
            "exclude": [
              "**/node_modules/**"
            ]
          }
        },
        "server": {
          "builder": "@angular-devkit/build-angular:server",
          "options": {
            "outputPath": "functions/lib",
            "main": "src/main.server.ts",
            "tsConfig": "src/tsconfig.server.json"
          },
          "configurations": {
            "production": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "sourceMap": false,
              "optimization": {
                "scripts": false,
                "styles": true
              }
            }
          }
        }
      }
    },
    "mse-e2e": {
      "root": "e2e/",
      "projectType": "application",
      "architect": {
        "e2e": {
          "builder": "@angular-devkit/build-angular:protractor",
          "options": {
            "protractorConfig": "e2e/protractor.conf.js",
            "devServerTarget": "mse:serve"
          }
        },
        "lint": {
          "builder": "@angular-devkit/build-angular:tslint",
          "options": {
            "tsConfig": "e2e/tsconfig.e2e.json",
            "exclude": [
              "**/node_modules/**"
            ]
          }
        }
      }
    }
  },
  "defaultProject": "mse"
}

这是我在函数目录中的package.json:

{
  "name": "functions",
  "scripts": {
    "build": "tsc",
    "serve": "npm run build && firebase serve --only functions",
    "shell": "npm run build && firebase functions:shell",
    "start": "npm run shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
  },
  "main": "lib/index.js",
  "dependencies": {
    "angular-universal-express-firebase": "0.0.4",
    "firebase-admin": "~6.0.0",
    "firebase-functions": "^2.1.0",
    "@angular/animations": "^7.2.4",
    "@angular/cdk": "^7.3.1",
    "@angular/common": "^7.2.4",
    "@angular/compiler": "^7.2.4",
    "@angular/core": "^7.2.4",
    "@angular/forms": "^7.2.4",
    "@angular/http": "^7.2.4",
    "@angular/material": "^7.3.1",
    "@angular/platform-browser": "^7.2.4",
    "@angular/platform-browser-dynamic": "^7.2.4",
    "@angular/platform-server": "^7.2.4",
    "@angular/router": "^7.2.4",
    "@nguniversal/module-map-ngfactory-loader": "^7.1.0",
    "@ngx-translate/core": "^10.0.2",
    "@ngx-translate/http-loader": "^3.0.1",
    "@types/googlemaps": "^3.30.16",
    "animate.css": "^3.7.0",
    "bootstrap": "^4.2.1",
    "compass": "^0.1.1",
    "compass-sass-mixins": "^0.12.7",
    "contentful": "^6.1.3",
    "core-js": "^2.6.4",
    "emailjs-com": "^2.3.2",
    "flag-icon-css": "^3.2.1",
    "hammerjs": "^2.0.8",
    "jquery": "^3.3.1",
    "marked": "^0.4.0",
    "ngx-bootstrap": "^3.2.0",
    "popper.js": "^1.14.7",
    "rxjs": "^6.4.0",
    "ts-loader": "^5.3.3",
    "web-animations-js": "^2.3.1",
    "webpack-cli": "^3.2.3",
    "zone.js": "^0.8.29",
    "@angular/material-moment-adapter": "7.0.0",
    "@nguniversal/express-engine": "^7.0.0",
    "express": "^4.16.2",
    "moment": "^2.22.2",
    "rxjs-compat": "^6.0.0-rc.0"
  },
  "devDependencies": {
    "typescript": "^3.2.2",
    "@angular-devkit/build-angular": "^0.6.8",
    "@angular/cli": "~7.3.1",
    "@angular/compiler-cli": "^7.2.4",
    "@angular/language-service": "^7.2.4",
    "@types/jasmine": "^2.8.16",
    "@types/jasminewd2": "^2.0.6",
    "@types/node": "~8.9.4",
    "codelyzer": "~4.2.1",
    "jasmine-core": "~2.99.1",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~1.7.1",
    "karma-chrome-launcher": "~2.2.0",
    "karma-coverage-istanbul-reporter": "^1.4.3",
    "karma-jasmine": "~1.1.1",
    "karma-jasmine-html-reporter": "^0.2.2",
    "protractor": "^5.4.2",
    "ts-node": "~5.0.1",
    "tslint": "~5.9.1",
    "@types/express": "^4.0.39",
    "http-server": "^0.11.1",
    "karma-cli": "~1.0.1",
    "npm-run-all": "^4.1.2"
  },
  "private": true
}

这是我的 index.ts:

import * as angularUniversal from 'angular-universal-express-firebase';


export let ssrApp = angularUniversal.trigger({
  index: __dirname + '/index-server.html',
  main: __dirname + '/main',
  enableProdMode: true,
  cdnCacheExpiry: 600,
  browserCacheExpiry: 300,
  staleWhileRevalidate: 120
});

ng --version:

Angular CLI: 7.3.1
Node: 10.0.0
OS: win32 x64
Angular: 7.2.4
... animations, common, compiler, compiler-cli, core, forms
... http, language-service, platform-browser
... platform-browser-dynamic, platform-server, router

Package                            Version
------------------------------------------------------------
@angular-devkit/architect          0.6.8
@angular-devkit/build-angular      0.6.8
@angular-devkit/build-optimizer    0.6.8
@angular-devkit/core               0.6.8
@angular-devkit/schematics         7.3.1
@angular/cdk                       7.3.1
@angular/cli                       7.3.1
@angular/material                  7.3.1
@angular/material-moment-adapter   7.0.0
@ngtools/webpack                   6.0.8
@schematics/angular                7.3.1
@schematics/update                 0.13.1
rxjs                               6.4.0
typescript                         3.2.4
webpack                            4.8.3

更新的答案:

问题似乎是 angular-universal-express-firebase 在 observable 类型上使用了 .mergeMap() 但对于 rxjs 版本 6 或更高版本 .pipe() 必须使用并且 .mergeMap() 必须从 rxjs/operators.

导入

最好的解决方案是在您的 index.ts 中自己 运行 一个快速应用程序并将其部署到 firebase 云功能(这基本上就是 angular-universal-express-firebase 正在做的)。这使您可以更好地控制服务器上的内容 运行。

这是我的 index.ts:

import * as functions from 'firebase-functions';
import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import {renderModuleFactory} from '@angular/platform-server';
import {provideModuleMap} from '@nguniversal/module-map-ngfactory-loader';
import * as express from 'express';
import {readFileSync} from 'fs';
import {enableProdMode} from '@angular/core';

const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = require('./main');

enableProdMode();

const app = express();

const indexHtml = readFileSync(__dirname + '/index-server.html', 'utf-8').toString();

app.get('*.*', express.static(__dirname + '/dist', {
  maxAge: '1y'
}));

app.route('*').get((req, res) => {

  console.log(req.url);

  renderModuleFactory(AppServerModuleNgFactory, {
    document: indexHtml,
    url: req.url,
    extraProviders: [
      provideModuleMap(LAZY_MODULE_MAP)
    ]
  }).then(html => {
    res.status(200).send(html);
  }).catch(err => {
    console.log(err);
    res.sendStatus(500);
  });

});

exports.ssrApp = functions.https.onRequest(app);

确保安装顶部导入的所有必需包。同样在您的 firebase.json 中确保所有请求都传递给云函数:

"rewrites": [
      {
        "source": "**",
        "function": "ssrApp"
      }
    ]

初始答案:

问题似乎是 angular-universal-express-firebase 在 observable 类型上使用了 .mergeMap() 但对于 rxjs 版本 6 或更高版本 .pipe() 必须使用并且 .mergeMap() 必须从 rxjs/operators.

导入

经过几天的研究和尝试不同的方法,这是对我有用的解决方案:

我没有使用 angular-universal-express-firebase 包,而是使用了 angular7-universal-express-firebase,它基本上更新了代码以适应 .pipe() 方法。

确保卸载 angular-universal-express-firebaseangular-universal-express 包以及 运行 项目函数文件夹中的以下命令:

npm install angular7-universal-express-firebase

npm install angular7-universal-express

希望这对 运行 遇到这个问题的其他人有所帮助!