首页 > 解决方案 > 如何在 Angular Universal 中配置缓存

问题描述

我按照教程将 Universal 集成到我们的 Angular 9 应用程序中。现在我们已经配置了 server.ts。

在每个教程中,我都会看到:

// Example Express Rest API endpoints
// app.get('/api/**', (req, res) => { });

接着:

server.get(
        '*.*',
        express.static(distFolder, {
            maxAge: '1y',
        })
    );

如果我们保持这样,我想每个请求都会被缓存,/api 也是如此。这是正确的吗?我们的应用程序有很多“产品页面”,其中库存和价格可能会经常变化。所以我们需要始终保持新鲜的库存和价格。出于这个原因,我不会缓存从我们的 API 调用返回的服务器数据,这些 API 调用位于“/api/”路径(或https://api.my-app.com “如果需要绝对路径)。留下一切如果我没记错的话,上面写的应该缓存所有东西。我做了一个测试,直接在数据库中更改价格,我希望看到旧的,但不是这样,我总是看到新的。所以我怀疑是缓存机制不起作用。而且当我浏览组件时,我继续看到每个 api 调用(未缓存)

有人可以更好地解释一下这个机制吗?

关于这个的另一个问题是:如果缓存有效,如何在节点中清除它?节点将所有内容缓存在内存中。我想当我们停止节点时缓存会被清除。这是正确的吗?

这是我的包 json:

"scripts": {
    "ng": "node --max_old_space_size=8192 node_modules/@angular/cli/bin/ng",
    "start": "npm run ng -- serve",
    "serve:server": "node ./dist-server/main.js",
    "serve:server:debug": "node --inspect ./dist-server/main.js",
    "start:server": "npm run build:server && node ./dist-server/main.js",
    "start:server:debug": "npm run build:server && node ./dist-server/main.js --inspect",
    "build": "npm run ng -- build",
    "build:server": "ng run my-app:server:production",
    "ssr:watch": "ng run my-app:serve-ssr:production",
    "demo:ssr:watch": "ng run universal-demo:serve-ssr:dev",
    "build-all": "npm-run-all build-production build:server-app:prod",
    "start:express-server": "ts-node -P ./src/tsconfig.server.json ./server.ts",
    "prerender": "ts-node -P ./server.tsconfig.json ./prerender.ts",
    "prerender:debug": "ts-node -P ./server.tsconfig.json --inspect ./prerender.ts",
    "test": "npm run ng -- test",
    "lint": "npm run ng -- lint",
    "e2e": "npm run ng -- e2e",
    "analyze": "webpack-bundle-analyzer dist/stats.json",
    "compodoc": "npx compodoc -p src/tsconfig.app.json",
    "build:stats": "ng build --stats-json --prod",
    "build-preprod": "ng build --configuration preprod --index=/src/index/preprod/index.html",
    "build-production": "ng build --configuration production --index=/src/index/production/index.html",
    "build-staging": "ng build --configuration staging --index=/src/index/staging/index.html"
  }

这是我的 angular.json:

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "my-app": {
      "root": "",
      "sourceRoot": "src",
      "projectType": "application",
      "prefix": "app",
      "schematics": {
        "@schematics/angular:component": {
          "style": "scss"
        }
      },
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "aot": true,
            "outputPath": "dist",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "src/tsconfig.app.json",
            "stylePreprocessorOptions": {
              "includePaths": ["src/assets/sass"]
            },
            "assets": [
              "src/assets",
              "src/favicon.ico",
              "src/manifest.json",
              "src/firebase-messaging-sw.js",
              "src/main-sw.js",
              "src/assets/js/intersection-observer.js"
            ],
            "styles": [
              "src/styles/styles.scss",
              "src/styles/my-app.scss",
              "node_modules/font-awesome/css/font-awesome.css"
            ],
            "scripts": [],
            "es5BrowserSupport": true
          },
          "configurations": {
            "staging": {
              "budgets": [
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "6kb"
                }
              ],
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "aot": true,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "ngswConfigPath": "src/app/config/ngsw-config.json",
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.staging.ts"
                }
              ],
              "serviceWorker": true
            },
            "preprod": {
              "budgets": [
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "6kb"
                }
              ],
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "aot": true,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "ngswConfigPath": "src/app/config/ngsw-config.json",
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.preprod.ts"
                }
              ],
              "serviceWorker": true
            },
            "production": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": true,
              "extractCss": true,
              "namedChunks": true,
              "aot": true,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "ngswConfigPath": "src/app/config/ngsw-config.json",
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "2mb",
                  "maximumError": "5mb"
                },
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "6kb"
                }
              ],
              "serviceWorker": true
            }
          }
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": {
            "browserTarget": "my-app:build"
          },
          "configurations": {
            "production": {
              "browserTarget": "my-app:build:production"
            }
          }
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "browserTarget": "my-app: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"]
          }
        },
        "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": "server.ts",
            "tsConfig": "src/tsconfig.server.json",
            "stylePreprocessorOptions": {
              "includePaths": ["src/assets/sass"]
            }
          },
          "configurations": {
            "dev": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "optimization": true
            },
            "production": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "optimization": true
            }
          }
        },
        "serve-ssr": {
          "builder": "@nguniversal/builders:ssr-dev-server",
          "options": {
            "browserTarget": "my-app:build",
            "serverTarget": "my-app:server"
          },
          "configurations": {
            "production": {
              "browserTarget": "my-app:build:production",
              "serverTarget": "my-app:server:production"
            }
          }
        },
        "prerender": {
          "builder": "@nguniversal/builders:prerender",
          "options": {
            "browserTarget": "my-app:build:production",
            "serverTarget": "my-app:server:production",
            "routes": ["/"]
          },
          "configurations": {
            "production": {}
          }
        }
      }
    },
    "my-app-e2e": {
      "root": "e2e/",
      "projectType": "application",
      "prefix": "",
      "architect": {
        "e2e": {
          "builder": "@angular-devkit/build-angular:protractor",
          "options": {
            "protractorConfig": "e2e/protractor.conf.js",
            "devServerTarget": "my-app:serve"
          },
          "configurations": {
            "production": {
              "devServerTarget": "my-app:serve:production"
            }
          }
        },
        "lint": {
          "builder": "@angular-devkit/build-angular:tslint",
          "options": {
            "tsConfig": "e2e/tsconfig.e2e.json",
            "exclude": ["**/node_modules/**"]
          }
        }
      }
    }
  },
  "defaultProject": "my-app"
}

谢谢

标签: node.jsangularcachingangular-universal

解决方案


dist/browser以下代码适用于构建项目后位于目录中的静态文件。这些主要是 JavaScript、CSS 和图像文件。这maxAge将设置 Cache-Control 标头,该标头指示文件将被视为新鲜的最长时间。同样,这适用于提供dist/browser永远不会更改的文件。

server.get(
  '*.*',
  express.static(distFolder, {
    maxAge: '1y',
  })
);

以下代码仅作为如何将其他端点添加到 Express 服务器的示例:https ://expressjs.com/en/guide/routing.html 。它与缓存没有任何关系。

// Example Express Rest API endpoints
// app.get('/api/**', (req, res) => { });

以下代码是使用 Angular 应用程序呈现 HTML 响应的内容。默认实现中没有为此提供缓存,因为它会根据应用程序中内容更改的频率而有所不同。

server.get('*', (req, res) => {
  res.render(
    indexHtml, 
    { 
      req, 
      providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] 
    });
});

这是一些如何实现缓存的伪代码。该cache变量可以是内存缓存,如Redis或其他一些实现。

server.get('*',
  // Middleware to check if cached response exists
  (req, res, next) => {
    const cachedHtml = cache.get(req.url);
    if (cachedHtml) {
      // Cache exists. Send it.
      res.send(cachedHtml);
    } else {
      // Cache does not exist. Render a response using the Angular app
      next();
    }
  },
  // Angular SSR rendering
  (req, res) => {
    res.render(
      indexHtml,
      {
        req,
        providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }]
      },
      (err: Error, html: string) => {
        // Cache the rendered `html` for this request url to use for subsequent requests
        cache.set(req.url, html);

        res.send(html);
      }
    );
  }
);

推荐阅读