简体   繁体   English

关于 .NET Core 3.1 和 Angular SSR spa.UseSpaPrerendering 替代方案的问题

[英]Questions on .NET Core 3.1 & Angular SSR spa.UseSpaPrerendering alternative

I created this post to get some insights from the community.我创建这篇文章是为了从社区中获得一些见解。 A little while ago with the release of .NET Core 3.0 the usage of the well-known and widely used spa.UseSpaPrerendering has been marked as Obsolete.不久前,随着 .NET Core 3.0 的发布,众所周知且广泛使用的spa.UseSpaPrerendering已被标记为过时。

Around early-2019 I implemented SSR using .NET Core in a project that uses Angular but needed SEO and better loading perf.大约在 2019 年初,我在一个使用 Angular 但需要 SEO 和更好的加载性能的项目中使用 .NET Core 实现了 SSR。

1 year later (now, beginning of 2020) they want the same for a different project. 1 年后(现在,2020 年初),他们希望在不同的项目中也有同样的情况。 But it already uses Core 3.1.但它已经使用了 Core 3.1。 immediately we noticed the Depricated flag, so I went searching for a way to do it ourselves.我们立即注意到了 Depricated 标志,所以我开始寻找自己做的方法。

From past experience the SSR problem had 2 parts, the first being getting your Angular app to actually be able to run in Server-side.根据过去的经验,SSR 问题有两部分,第一部分是让您的 Angular 应用程序能够在服务器端实际运行。 So getting rid of or working around all the stuff that is unable to be executed in Server-side (working around usage of window API's, by using isPlatform stuff in Angular).因此,摆脱或解决所有无法在服务器端执行的东西(解决窗口 API 的使用,通过在 Angular 中使用 isPlatform 东西)。 Second part was to actually get .NET Core to spin up the Angular CLI to start the actual pre-rendering.第二部分是实际让 .NET Core 启动 Angular CLI 以开始实际的预渲染。 This was done using the UseSpaPrerendering .这是使用UseSpaPrerendering完成的。

Analysing the documentation that told us to figure it out ourselves and checking out my code from the past, things actually started to make sense.分析告诉我们自己解决问题的文档并检查我过去的代码,事情实际上开始变得有意义了。

I looked at the commands in my package.json file the 2 main commands executed were build:ssr to actually pre-compile the whole server/main.js next to the browser/... files.我查看了 package.json 文件中的命令,执行的 2 个主要命令是build:ssr来实际预编译浏览器/...文件旁边的整个 server/main.js。 The second command was the serve:ssr , which was going to be executed by the UseSpaPrerendering code (at least that is what I assume).第二个命令是serve:ssr ,它将由UseSpaPrerendering代码执行(至少这是我的假设)。

In practice, our CI/CD would go and execute the build:ssr and publish all the files to the App server running .NET Core runtime.在实践中,我们的 CI/CD 将执行build:ssr并将所有文件发布到运行 .NET Core 运行时的应用服务器。 and using the UseSpaPrerendering code it would then execute the serve:ssr .然后使用UseSpaPrerendering代码执行serve:ssr

Now jumping forward towards the present where I need to find a solution.现在跳到我需要找到解决方案的现在。 I figured that I could also just run the necessary commands myself.我想我也可以自己运行必要的命令。 So after excluding some of the non-SSR compatible code in my Angular I ran the build:ssr command myself, followed by the serve:ssr command.所以在我的 Angular 中排除了一些非 SSR 兼容的代码后,我自己运行了build:ssr命令,然后是serve:ssr命令。 which worked, my Angular app was SSR rendered, by served by node itself rather than by .NET Core.效果很好,我的 Angular 应用程序是 SSR 渲染的,由节点本身而不是 .NET Core 提供。

Next step was that I tried to that in my .NET Core Startup file.下一步是我在我的 .NET Core 启动文件中尝试过。 for now I did the build:ssr myself (because in production it would be done by CI/CD) and I re-wrote the start script in my package.json to run the command npm run serve:ssr .现在我自己做了build:ssr (因为在生产中它会由 CI/CD 完成)并且我在我的 package.json 中重写了start脚本来运行命令npm run serve:ssr I started that command using the spa.UseAngularCliServer(npmScript: "start");我使用spa.UseAngularCliServer(npmScript: "start");启动了该命令spa.UseAngularCliServer(npmScript: "start"); code in my startup.cs and there I had it, my .NET Core runtime starts up both my API and my SSR Angular app.代码在我的 startup.cs 中,我有它,我的 .NET Core 运行时启动我的 API 和我的 SSR Angular 应用程序。

So far so good, but only 1 problem now.到目前为止一切顺利,但现在只有 1 个问题。 my SSR Angular is hosted on port 4000 default and also listens to that port (I can see that in my output) and my API listens on port 5000(http) and 5001(https).我的 SSR Angular 默认托管在端口 4000 上,并且还侦听该端口(我可以在输出中看到),而我的 API 侦听端口 5000(http) 和 5001(https)。

So now I have a couple of questions about this:所以现在我有几个关于这个的问题:

  1. Is this the correct way of doing this now?这是现在这样做的正确方法吗?
  2. How can I make sure that in production, when one goes to my app, that the node listener will kick in?我如何确保在生产中,当人们进入我的应用程序时,节点侦听器将启动?
  3. Would it be better if I just completely separated my .NET Core API and Angular SSR app completely ?如果我将 .NET Core API 和 Angular SSR 应用程序完全分开会更好吗? And Thus also deploy them separately?因此也分别部署它们?

To anyone facing this issues, I've just solved it and here is our solutions but there are few facts:对于任何面临这个问题的人,我刚刚解决了它,这是我们的解决方案,但事实很少:

  • [Web.config] Node Context, I mean the Process Working Directory, works different in iisnode, PWD is the target file path, this means that if your main.js is within dist/server/main.js then the paths relative to browser won't be dist/browser/ but ../browser/ [Web.config] Node Context,我的意思是进程工作目录,在iisnode中工作不同,PWD是目标文件路径,这意味着如果你的main.js在dist/server/main.js中,那么路径相对于浏览器不会是 dist/browser/ 而是 ../browser/
  • Consider that during deployment you will have to generate Web.config according to this new structure考虑到在部署期间,您必须根据这个新结构生成 Web.config

    -Handler iisnode -NodeStartFile dist/server/main.js -appType node -Handler iisnode -NodeStartFile dist/server/main.js -appType 节点

  • [server.ts] - Having that in mind consider also to set the browser path according to your runtime environment so that if you are in production it should be ../browser [server.ts] - 考虑到这一点,还要考虑根据您的运行时环境设置浏览器路径,这样如果您在生产环境中,它应该是 ../browser

  • [server.ts] - Order matters in server.ts. [server.ts] - server.ts 中的顺序很重要。 IF YOU FACE BROWSER API ISSUES it is because "import { AppServerModule } from './ main.server ';"如果您遇到浏览器 API 问题,那是因为“从 './ main.server ' 导入 { AppServerModule };” MUST be placed AFTER domino declarations.必须被放置AFTER多米诺声明。

Here is a working example on a server.ts that is also using i18n redirections according to url requests with a locale string (now that I solved this i18n issues too it I can tell you that it worth to read the docs).这是 server.ts 上的一个工作示例,它也根据带有区域设置字符串的 url 请求使用 i18n 重定向(现在我也解决了这个 i18n 问题,我可以告诉你值得阅读文档)。

/***************************************************************************************************
 * Load `$localize` onto the global scope - used if i18n tags appear in Angular templates.
 */
import { APP_BASE_HREF } from '@angular/common';
import '@angular/localize/init';
import { ngExpressEngine } from '@nguniversal/express-engine';
import * as express from 'express';
import { existsSync } from 'fs';
import { join } from 'path';
import 'zone.js/dist/zone-node';
import { environment } from './environments/environment';

// THIS FIX MOST OF THE COMMON ISSUES WITH SSR:
// FIRST SET THE BROWSER PATH ACCORDING TO RUNTIME ENVIRONMENT
let browserPath;
if (environment.production) {
  browserPath = '../browser';
} else {
  browserPath = 'dist/browser';
}
const enDistFolder = join(process.cwd(), browserPath + '/en');

// Emulate browser APIs
const domino = require('domino');
const fs = require('fs');
const templateA = fs.readFileSync(join(enDistFolder, 'index.html')).toString();

const win = domino.createWindow(templateA);
console.log('win');
win.Object = Object;
console.log('Object');
win.Math = Math;
console.log('Math');

global['window'] = win;
global['document'] = win.document;
global['Event'] = win.Event;
console.log('declared Global Vars....');

/****************************************************/   
/** NOTE THIS: I need to avoid sorting this line */
// USE CTRL+P -> SAVE WITHOUT FORMATTING
import { AppServerModule } from './main.server';
/****************************************************/

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

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

  server.set('view engine', 'html');
  server.set('views', browserPath);

  // Example Express Rest API endpoints
  // server.get('/api/**', (req, res) => { });
  // Serve static files from /browser
  server.get('*.*', express.static(browserPath, {
    maxAge: '1y'
  }));

  server.use('/robots.txt', express.static('/en/robots.txt'));
  server.use('/ads.txt', express.static('/en/ads.txt'));

  // THE ORIGINAL Universal Requests handler
  // // // All regular routes use the Universal engine
  // // server.get('*', (req, res) => {
  // //   res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
  // // });

  // OUR i18n REQUESTS HANDLER
  // All regular routes use the Universal engine
  server.get('*', (req, res) => {
    // this is for i18n
    const supportedLocales = ['en', 'es'];
    const defaultLocale = 'es';
    const matches = req.url.match(/^\/([a-z]{2}(?:-[A-Z]{2})?)\//);

    // check if the requested url has a correct format '/locale' and matches any of the supportedLocales
    const locale = (matches && supportedLocales.indexOf(matches[1]) !== -1) ? matches[1] : defaultLocale;

    res.render(`${locale}/index.html`, { req });
  });

  return server;
}

function run() {
  const port = process.env.PORT || 4000;

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

// Webpack will replace 'require' with '__webpack_require__'
// '__non_webpack_require__' is a proxy to Node 'require'
// The below code is to ensure that the server is run only when not requiring the bundle.
declare const __non_webpack_require__: NodeRequire;
const mainModule = __non_webpack_require__.main;
const moduleFilename = mainModule && mainModule.filename || '';
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
  run();
}

export * from './main.server';

I still need to work a bit on this code and in our app (SSR and oauth issues, another funny topic) but I want to share it because it took us almost 20 deployments to fix these issues.我仍然需要在这段代码和我们的应用程序中做一些工作(SSR 和 oauth 问题,另一个有趣的话题),但我想分享它,因为我们花了近 20 次部署来解决这些问题。

Final words: if you come here after an angular 8 migration I'll be glad to help you and give you nice hints but, honestly, follow the guide and read carefully the docs.最后的话:如果你在 angular 8 迁移之后来到这里,我很乐意帮助你并给你很好的提示,但老实说,请遵循指南并仔细阅读文档。 Also, if you are using Azure DevOps pipelines, you should consider using an npm cache.此外,如果您使用 Azure DevOps 管道,则应考虑使用 npm 缓存。 Our as is large and we are now saving more than 12 minutes on each build process (That is a huge amount of time, isn't it?) Feel free to get in touch with me.我们的 as 很大,我们现在在每个构建过程中节省了超过 12 分钟(这是一个巨大的时间,不是吗?)请随时与我联系。

Juan胡安

I'm struggling with netcore 3.1 and angular too when it comes to deploy the project on azure or anything.. did you find anything?在将项目部署到 azure 或其他任何东西时,我也在为 netcore 3.1 和 angular 苦苦挣扎..你有什么发现吗? Could you provide your startup file?你能提供你的启动文件吗? when I use dotnet publish, package.json is not copied to publish/ClientApp directory so the command used by spa.UseAngularCliServer() fails or it just doesn't find /index.html.当我使用 dotnet publish 时,package.json 不会复制到 publish/ClientApp 目录,因此spa.UseAngularCliServer()使用的命令失败或找不到 /index.html。

for now, I run my project locally like this:现在,我像这样在本地运行我的项目:

app.UseSpa(spa =>
  {
    spa.Options.SourcePath = "ClientApp";
    if (env.IsDevelopment())
    {
      spa.UseAngularCliServer(npmScript: "dev:ssr");
    }
  });

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 UseSpaPrerendering 从 .net 核心 3.0 起被弃用,有什么替代方案? - UseSpaPrerendering being deprecated from .net core 3.0 onwards, what is the alternative? .Net Core 3.1 SPA 授权失败 - .Net Core 3.1 SPA authorize failed ASP.NET 核心 SPA SSR:使用 AntiForgery - ASP.NET core SPA SSR: Use AntiForgery .Net Core 3.0 Angular SSR 项目未运行 - .Net Core 3.0 Angular SSR Project not running 在.Net Core 3.1中动态设置SPA源路径 - Dynamically setting the SPA source path in .Net Core 3.1 带有角度模板的 asp.net core 3.1 无法再响应 404 并始终重定向到 spa index.html 页面 - asp.net core 3.1 with angular template can no longer respond with 404 and always redirect to spa index.html page Angular 9 组件和 .NET Core 应用程序未作为 SPA 运行 - Angular 9 component and .NET Core application not running as SPA .net core 3.1 中 RequestCookieCollection 和 ResponseCookies 的替代方案是什么? - what's the alternative of RequestCookieCollection and ResponseCookies in .net core 3.1? .NET Core 3.0 Web API 连接问题上的 Angular SPA - Angular SPA on .NET Core 3.0 Web API Connection issues .net Core:Angular SPA模板更新导致身份验证问题 - .net Core : Angular SPA template update causing authentication issue
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM