Angular 9:使用 angular i18n 和服务器端渲染(angular 通用)

Angular 9: Using angular i18n along with server side rendering (angular universal)

我正在将 angular 7 应用程序迁移到 angular 9,它使用服务器端呈现(angular 通用)和 angular i18n 2 种语言(法语和英语)。

在旧的 angular 7 流程中,由于我使用的是 AOT,因此我必须为生产环境进行 5 次构建:

然后,在 server.ts 中,我动态加载了正确的服务器包

旧server.ts

app.engine('html', (_, options: any, callback) => {

  const isFR= options.req.url.indexOf('site-fr') >= 0 ;
  const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = isFR ? require('./dist/server/fr/main') : require('./dist/server/en/main');

// Our index.html we'll use as our template
  const template = readFileSync(join(DIST_FOLDER, 'browser', isFR ? 'fr' : 'en', 'index.html')).toString();
  renderModuleFactory(AppServerModuleNgFactory, {
    // Our index.html
    document: template,

我将应用程序迁移到 angular 9,现在根据我在 documentation 中的了解,只需要一个客户端构建。

You can also provide the --localize option to the ng build command with your existing production configuration. In this case, the CLI builds all locales defined under i18n in the project configuration.

这似乎适用于客户端构建,因为生成了 2 个文件夹(生成了 fren)。

然而,没有任何地方提到在 i18n 中使用 SSR。所以我最终得到了一个 server.ts.

这是我用来构建的脚本和运行项目

angular.json

"serve:ssr": "node dist/myproject/server/main.js",
"build:ssr": "ng build -c production --localize && ng run myproject:server:production"

新server.ts

// The Express app is exported so that it can be used by serverless Functions.
export function app(port) {
  const server = express();
  const distFolder = join(process.cwd(), 'dist/myproject/browser/fr');
  const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';

  // Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
  server.engine('html', ngExpressEngine({
    bootstrap: AppServerModule,
  }));

Dist文件夹结构

- dist
  - myproject
    - browser
        - fr
          - index.html
          - *.js
        - en
          - index.html
          - *.js      
    - server
        - main.js

注意:我确实看到一个封闭的 github issue 描述了这个问题,但解决方案基本上是回到以前的方式,即每种语言有 2 个构建,[=17= 也有 2 个构建].

肯定还有其他方法吗?

我找到了一个只涉及 2 个构建的解决方案。但是 运行 现在需要 2 个服务器进程实例。

第 1 步:angular.json

确保在 angular.json 中正确定义了语言环境,并在 my-project:server 选项中添加新的 allLocales 目标。

我创建了一个新的 allLocales 目标,因为我不知道如何将 production 目标与 enfr 配置结合起来。这样做的目的是只构建一个服务器,并生成所有语言服务器。

对于前面的包,这在 ng build 本身是可能的(即 ng build --configuration=production,fr,en or ng build --configuration=production --localize

angular.json

 "projects": {
    "my-project": {
      "i18n": {
        "locales": {
          "en": {
            "translation": "src/locale/messages.en.xlf",
            "baseHref": ""
          },
          "fr": {
            "translation": "src/locale/messages.fr.xlf",
            "baseHref": ""
          }
        }
      },
      "root": "",
      "sourceRoot": "src",
      "projectType": "application",
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            //...
          },
          "configurations": {
            "production": {
             //...
            },

            "en": {
              "localize": [
                "en"
              ]
            },


            "fr": {
              "localize": [
                "fr"
              ]
            }

          }
        },

        "server": {
          "builder": "@angular-devkit/build-angular:server",
          "options": {
            "outputPath": "dist/my-project/server",
            "main": "server.ts",
            "tsConfig": "tsconfig.server.json"
          },

          "configurations": {
            "production": {
              //...
            },

            "allLocales": {
              "outputHashing": "none",
              "optimization": false,
              "sourceMap": false,
              "namedChunks": false,
              "extractLicenses": true,
              "vendorChunk": false,
              "localize": [
                "en", "fr"
              ],
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ]
            }
          }
        },

第 2 步:server.ts

修改server.ts 文件以接受语言参数。每个 运行 生成的服务器包 main.js 的替代品将有自己的端口和语言。

server.ts

//...
export function app(language) { //add language here
  const server = express();
  const distFolder = join(process.cwd(), 'dist/my-project/browser', language); //Use language here


//...

function run() {
  const port = process.env.PORT || 5006;
  const language = process.env.LANGUAGE || 'fr';

  // Start up the Node server
  const server = app(language); //Use the language here
  server.listen(port, () => {
    console.log(`Node Express server listening on http://localhost:${port} for language ${language}`);
    });

第 3 步:修改 package.json

package.json

"build:ssr": "ng build -c production --localize && ng run my-project:server:allLocales"
"serve:ssr-en": "env PORT=5006 LANGUAGE=en node dist/my-project/server/en/main.js",
"serve:ssr-fr": "env PORT=5007 LANGUAGE=fr node dist/my-project/server/fr/main.js",

build:ssr 将为所有语言构建客户端包,然后为所有语言构建服务器包 server:ssr-XX 将为与语言 XX

关联的端口和语言启动 nodejs 服务器

这是结构

- dist
  - myproject
    - browser
        - fr
          - index.html
          - *.js
        - en
          - index.html
          - *.js      
    - server
        - fr
            - main.js
        - en
            - main.js

第 4 步:反向代理

如果您使用反向代理,请不要忘记将所有请求重定向到正确的 main.js 实例

注意 angular 9 的构建过程现在快得多,因为只有 2 个构建。

这就是我们设法解决 Angular 9(通用和国际化)中的问题的方法: 我们使用旧的 webpack 配置和本地化来构建 ssr。 Build 在单个构建中为每种语言配置浏览器和服务器。

注意 我们在 Angular 8 中有通用和 i18n,因此您可能需要检查 Webpack 服务器配置的文档。

package.json:

  • 我们使用了仍在 Angular 8 中的旧 webpack 服务器配置 通用文档。
  • 使用本地化标志构建生产环境。
  • 构建很好,但是 docker 内存堆大小有问题,因此增加了节点 space

    ...
    "webpack:server": "webpack --config webpack.server.config.js --progress --colors",
    "build:prod": "ng build --configuration=production --localize",
    "build:server:prod": "node --max_old_space_size=8048 ./node_modules/@angular/cli/bin/ng run APP-NAME:server:production",
    "build:client-and-server-bundles": "npm run build:prod && npm run build:server:prod",
    "build:ssr": "npm run build:client-and-server-bundles && npm run webpack:server",
    "serve:ssr": "node dist/server.js"
    

angular.json(在项目设置中声明 i18n 语言环境):

服务器选项:

    ...
      "options": {
        "main": "src/main.server.ts",
        "tsConfig": "src/tsconfig.server.json",
        "localize": ["fi", "en", "sv"]
      }

server.ts

在服务器配置中,我们声明了所有包的路由

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

然后是一个 hacky 解决方法: 为每个区域声明 require 方法,使用 AppServerModule、Lazy 模块映射、快速引擎和模块映射提供程序构建服务器配置

const {AppServerModule: AppServerModuleFi, LAZY_MODULE_MAP: LAZY_MODULE_MAP_FI, ngExpressEngine: ngExpressEngineFi, provideModuleMap: provideModuleMapFi} = require('./dist/server/fi/main');
const {AppServerModule: AppServerModuleEn, LAZY_MODULE_MAP: LAZY_MODULE_MAP_EN, ngExpressEngine: ngExpressEngineEn, provideModuleMap: provideModuleMapEn} = require('./dist/server/en/main');
const {AppServerModule: AppServerModuleSv, LAZY_MODULE_MAP: LAZY_MODULE_MAP_SV, ngExpressEngine: ngExpressEngineSv, provideModuleMap: provideModuleMapSv} = require('./dist/server/sv/main');

然后对于每个路由,我们将使用之前声明的服务器配置文件,其中 "dedicated" 需要配置。 英语构建示例:

routes.forEach((route) => {
  if (route.path.startsWith('/en')) { // Check against path
  // EN routes
  app.get(route.path, (req, res) => {

    app.engine('html', ngExpressEngineEn({
      bootstrap: AppServerModuleEn,
      providers: [
        provideModuleMapEn(LAZY_MODULE_MAP_EN)
      ]
    }));
    app.set('view engine', 'html');
    app.set('views', join(DIST_FOLDER, 'browser'));

    res.render(route.view, {
      req,
      res,
      engine: ngExpressEngineEn({
        bootstrap: AppServerModuleEn,
        providers: [provideModuleMapEn(LAZY_MODULE_MAP_EN),
        { req, res }]
      })
    });
  });