Angular 8:通用和 i18n - 无法在本地提供应用程序

Angular 8: Universal and i18n - not able to serve app locally

我已将 Angular Universal 添加到具有 i18n 的现有项目中。我可以构建它,但我无法提供服务。

目前我收到这个错误:

Cannot find module '/home/my-user/my-app/dist/da/server'

"dist" 文件夹中文件的结构是:

dist
 |-da
   |-browser
   |-server
     |-main.js
 |-en
   |-browser
   |-server
     |-main.js
 |-server.js

我在 package.json 中的服务脚本:

"serve-da:ssr": "node dist/da/server",
"serve-en:ssr": "node dist/en/server",

这向我表明 server.js 应该位于 "da" 和 "en" 文件夹中,而不是 dist 文件夹的根目录中?如果是,我应该在哪里更正这个?我不确定 server.js 是在哪里以及如何生成的。

我什至不确定错误是关于 server.js 文件的。我只是假设...我希望有人能指出我正确的方向...

这些是我的配置文件:

angular.json

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "edApp": {
      "root": "",
      "sourceRoot": "src",
      "projectType": "application",
      "prefix": "ed",
      "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",
              "src/apple-touch-icon.png",
              "src/browserconfig.xml",
              "src/favicon-16x16.png",
              "src/favicon-32x32.png",
              "src/mstile-150x150.png",
              "src/safari-pinned-tab.svg"
            ],
            "scripts": [],
            "styles": [
              {
                "input": "./node_modules/bootstrap/dist/css/bootstrap.css"
              },
              "./node_modules/ngx-bootstrap/datepicker/bs-datepicker.css",
              "src/styles.scss"
            ]
          },
          "configurations": {
            "da": {
              "aot": true,
              "outputPath": "dist/da/browser",
              "baseHref": "/da/",
              "i18nFile": "src/i18n/messages.da.xlf",
              "i18nLocale": "da",
              "i18nFormat": "xlf",
              "i18nMissingTranslation": "[Fejl]"
            },
            "en": {
              "aot": true,
              "outputPath": "dist/en/browser",
              "baseHref": "/en/",
              "i18nFile": "src/i18n/messages.en.xlf",
              "i18nLocale": "en",
              "i18nFormat": "xlf",
              "i18nMissingTranslation": "[Error]"
            },
            "production-da": {
              "index": {
                "input": "src/index.prod.da.html",
                "output": "index.html"
              },
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "outputPath": "dist/da/browser",
              "baseHref": "/da/",
              "i18nFile": "src/i18n/messages.da.xlf",
              "i18nLocale": "da",
              "i18nFormat": "xlf",
              "i18nMissingTranslation": "[Fejl]",
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "aot": true,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "serviceWorker": true
            },
            "production-en": {
              "index": {
                "input": "src/index.prod.en.html",
                "output": "index.html"
              },
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "outputPath": "dist/en/browser",
              "baseHref": "/en/",
              "i18nFile": "src/i18n/messages.en.xlf",
              "i18nLocale": "en",
              "i18nFormat": "xlf",
              "i18nMissingTranslation": "[Error]",
              "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": "edApp:build",
            "proxyConfig": "proxy.conf.json",
            "disableHostCheck": true
          },
          "configurations": {
            "production-da": {
              "browserTarget": "edApp:build:production-da"
            },
            "production-en": {
              "browserTarget": "edApp:build:production-en"
            },
            "en": {
              "browserTarget": "edApp:build:en"
            },
            "da": {
              "browserTarget": "edApp:build:da"
            }
          }
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "browserTarget": "edApp: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/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/**"
            ]
          }
        },
        "server": {
          "builder": "@angular-devkit/build-angular:server",
          "options": {
            "outputPath": "dist/server",
            "main": "src/main.server.ts",
            "tsConfig": "src/tsconfig.server.json"
          },
          "configurations": {
            "production-da": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "outputPath": "dist/da/server",
              "i18nFile": "src/i18n/messages.da.xlf",
              "i18nFormat": "xlf",
              "i18nLocale": "da",
              "sourceMap": false,
              "optimization": {
                "scripts": false,
                "styles": true
              }
            },
            "production-en": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "outputPath": "dist/en/server",
              "i18nFile": "src/i18n/messages.en.xlf",
              "i18nFormat": "xlf",
              "i18nLocale": "en",
              "sourceMap": false,
              "optimization": {
                "scripts": false,
                "styles": true
              }
            }
          }
        }
      }
    },
    "edApp-e2e": {
      "root": "e2e/",
      "projectType": "application",
      "architect": {
        "e2e": {
          "builder": "@angular-devkit/build-angular:protractor",
          "options": {
            "protractorConfig": "e2e/protractor.conf.js",
            "devServerTarget": "edApp:serve"
          },
          "configurations": {
            "production": {
              "devServerTarget": "edApp:serve:production"
            }
          }
        },
        "lint": {
          "builder": "@angular-devkit/build-angular:tslint",
          "options": {
            "tsConfig": "e2e/tsconfig.e2e.json",
            "exclude": [
              "**/node_modules/**"
            ]
          }
        }
      }
    }
  },
  "defaultProject": "edApp"
}

package.json 脚本部分

我正在使用 build:ssr 和 serve-da:ssr

"scripts": {
  "ng": "ng",
  "start": "ng serve",
  "build": "ng build",
  "test": "ng test",
  "lint": "ng lint",
  "e2e": "ng e2e",
  "before-build": "node prebuild.js",
  "extract-i18n": "ng xi18n --output-path i18n --i18n-locale en && xliffmerge --profile xliffmerge.json",
  "serve-da": "ng serve -o --configuration=da",
  "serve-en": "ng serve -o --configuration=en",
  "serve-da-home": "ng serve -o --configuration=da --host 0.0.0.0",
  "build-da": "ng build --configuration=da",
  "build-en": "ng build --configuration=en",
  "prebuild-prod-da": "npm run before-build",
  "build-prod-da": "ng build --prod --configuration=production-da",
  "build-prod-en": "ng build --prod --configuration=production-en",
  "build-all": "for lang in da en; do npm run build-$lang; done",
  "build-prod-all": "for lang in da en; do npm run build-prod-$lang; done",
  "compile:server": "webpack --config webpack.server.config.js --progress --colors",
  "serve-da:ssr": "node dist/da/server",
  "serve-en:ssr": "node dist/en/server",
  "build:ssr": "npm run build:client-and-server-bundles && npm run compile:server",
  "build:client-and-server-bundles": "npm run build-prod-all && ng run edApp:server:production-da --bundleDependencies all && ng run edApp:server:production-en --bundleDependencies all"

server.ts

import 'zone.js/dist/zone-node';

import * as express from 'express';
import {join} from 'path';

//Allow access the window or document globals without causing any errors.
//Especially useful when using 3rd party modules
const domino = require('domino');
const fs = require('fs');
const path = require('path');

// Use the browser index.html as template for the mock window
const template = fs.readFileSync(path.join(__dirname, '.', 'dist', 'index.html')).toString();

// Shim for the global window and document objects.
const window = domino.createWindow(template);
global['window'] = window;
global['document'] = window.document;

// Express server
const app = express();

const PORT = process.env.PORT || 4000;
//const DIST_FOLDER = join(process.cwd(), 'dist/browser');
const DIST_FOLDER = join(process.cwd(), 'dist');
const routes = [
  {path: '/da/*', view: 'da/index', bundle: require('./dist/da/server/main')},
  {path: '/en/*', view: 'en/index', bundle: require('./dist/en/server/main')}
];

// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const {AppServerModuleNgFactory, LAZY_MODULE_MAP, ngExpressEngine, provideModuleMap} = require('./dist/server/main');

// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
app.engine('html', ngExpressEngine({
  bootstrap: AppServerModuleNgFactory,
  providers: [
    provideModuleMap(LAZY_MODULE_MAP)
  ]
}));

app.set('view engine', 'html');
//app.set('views', DIST_FOLDER);
app.set('views', join(DIST_FOLDER, 'browser'));

app.get('*.*', express.static(join(DIST_FOLDER, 'browser')));
routes.forEach((route) => {
  app.get(route.path, (req, res) => {
    res.render(route.view, {
      req, res, engine: ngExpressEngine({
        bootstrap: route.bundle.AppServerModuleNgFactory,
        providers: [provideModuleMap(route.bundle.LAZY_MODULE_MAP)]
      })
    });
  });
});

// Start up the Node server
app.listen(PORT, () => {
  console.log(`Node Express server listening on http://localhost:${PORT}`);
});

webpack.server.config.js

// Work around for https://github.com/angular/angular-cli/issues/7200

const path = require('path');
const webpack = require('webpack');

module.exports = {
  mode: 'none',
  entry: {
    // This is our Express server for Dynamic universal
    server: './server.ts'
  },
  externals: {
    './dist/server/main': 'require("./server/main")'
  },
  target: 'node',
  resolve: { extensions: ['.ts', '.js'] },
  optimization: {
    minimize: false
  },
  output: {
    // Puts the output at the root of the dist folder
    path: path.join(__dirname, 'dist'),
    filename: '[name].js'
  },
  module: {
    noParse: /polyfills-.*\.js/,
    rules: [
      { test: /\.ts$/, loader: 'ts-loader' },
      {
        // Mark files inside `@angular/core` as using SystemJS style dynamic imports.
        // Removing this will cause deprecation warnings to appear.
        test: /(\|\/)@angular(\|\/)core(\|\/).+\.js$/,
        parser: { system: true },
      },
    ]
  },
  plugins: [
    new webpack.ContextReplacementPlugin(
      // fixes WARNING Critical dependency: the request of a dependency is an expression
      /(.+)?angular(\|\/)core(.+)?/,
      path.join(__dirname, 'src'), // location of your src
      {} // a map of your routes
    ),
    new webpack.ContextReplacementPlugin(
      // fixes WARNING Critical dependency: the request of a dependency is an expression
      /(.+)?express(\|\/)(.+)?/,
      path.join(__dirname, 'src'),
      {}
    )
  ]
};

您真的要 运行 server.js 脚本的两个实例吗?

只生成了一个 server.js 脚本实例。您可以 运行 只有一个服务器脚本实例并动态加载主包,就像您已经做的那样。这种情况只要修改运行脚本即可:

"serve:ssr": "node dist/server",

如果你真的想要 运行 2 个实例,你需要修改服务脚本来更改端口,如果你运行将它们都安装在同一台机器上(但你可以保留同样生成 server.js 文件)

"serve-da:ssr": "env PORT=4000 node dist/server",
"serve-en:ssr": "env PORT=4001 node dist/server",

@David 说的完全正确...不需要两台服务器。

我已经能够通过以下更改克服此错误:

选择默认语言,然后在 angular.json.[=15 中将该版本的 outputPath 设置为 dist/browser =]

server.ts 中将相同的语言设置为路由中的默认语言:

const routes = [
  {path: '/en/*', view: 'en/index', bundle: require('./dist/en/server/main')},
  {path: '/*', view: 'da/index', bundle: require('./dist/da/server/main')}
];

server.ts中我也更正了这一行(__dirname删除):

const template = fs.readFileSync(path.join('.', 'dist','browser','index.html')).toString();

我现在正在处理一组全新的错误 - 我需要以某种方式模拟浏览器特定的东西。幸运的我;-)