【问题标题】:Angular 9: Using angular i18n along with server side rendering (angular universal)Angular 9:使用 Angular i18n 和服务器端渲染(角度通用)
【发布时间】:2020-03-11 16:25:35
【问题描述】:

我正在将 Angular 7 应用程序迁移到 Angular 9,它使用服务器端渲染(角度通用)和 Angular i18n 来支持 2 种语言(法语和英语)。

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

  • 2 个客户端构建(1 个用于法语,1 个用于英语)
  • 2 x 服务器构建(1 个用于法语,1 个用于英语)
  • server.ts 的构建

然后,在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 中的理解,只需要一个客户端构建。

您还可以使用现有的生产配置为 ng build 命令提供 --localize 选项。在这种情况下,CLI 会构建项目配置中 i18n 下定义的所有语言环境。

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

但是,没有任何地方提到将 SSR 与 i18n 一起使用。所以我最终得到了一个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 个构建,还有 server.ts 的 2 个构建。

肯定还有别的办法吗?

【问题讨论】:

    标签: angular server-side-rendering angular-universal angular9 angular-i18n


    【解决方案1】:

    我找到了一个只涉及 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 个构建。

    【讨论】:

    • 如何让它在没有反向代理的情况下在单端口上运行两种语言?
    • 理论上,您需要编写另一个服务器脚本,手动“需要”与语言对应的main.js 文件。或查看其他建议的答案
    【解决方案2】:

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

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

    package.json

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

      ...
      "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');
    

    然后,对于每个路由,我们将使用先前声明的服务器配置文件和“专用”需求配置。 英语构建示例:

    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 }]
          })
        });
      });
    

    【讨论】:

    • 很好的答案,感谢您的分享,您可能希望将其展示给开发团队以指导他们,他们有一项未完成的任务,但自 2 月以来他们还没有推进。跨度>
    • 这不会为每个语言环境创建 3 个服务器吗?运行构建时,所有 main.js 都将包含 server.ts 代码?
    • @Spock,仔细检查一下,每个区域都有一台服务器。还检查了代码不会在 main.js 文件中相乘。
    猜你喜欢
    • 1970-01-01
    • 2017-04-27
    • 1970-01-01
    • 2020-01-24
    • 1970-01-01
    • 2022-01-13
    • 2023-04-01
    • 2017-06-01
    • 2018-03-21
    相关资源
    最近更新 更多