node.js - 服务器端渲染无法在实时服务器上使用延迟加载 Angular 12 模块

标签 node.js server-side-rendering angular-universal angular12

在 localhost 模块上,延迟加载工作正常,在 <app-root><app-root/> 中显示元标记和 HTML 内容在查看页面源代码中,但它没有显示在实时服务器上。在实时服务器上,我只能看到 AppModule 直接子组件的元标记和 HTML 内容。但延迟加载的模块组件不显示元标记和 HTML。这种奇怪的行为仅出现在实时服务器上。

app.server.module.ts

 import { NgModule } from '@angular/core';
    import { ServerModule } from '@angular/platform-server';
    import { AppComponent } from './app.component';
    import { AppModule } from './app.module';
    
    @NgModule({
        imports: [AppModule, ServerModule],
        bootstrap: [AppComponent]
    })
    export class AppServerModule { }

ma​​in.server.ts

import '@angular/localize/init'
import '@angular/platform-server/init';

import { enableProdMode } from '@angular/core';

import { environment } from './environments/environment';

if (environment.production) {
  enableProdMode();
}

export { AppServerModule } from './app/app.server.module';
export { renderModule } from '@angular/platform-server';

server.ts

import '@angular/localize/init';
import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import { ngExpressEngine } from '@nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';

import { AppServerModule } from './src/main.server';
import { APP_BASE_HREF } from '@angular/common';
import { existsSync, readFileSync } from 'fs';
import { createWindow } from 'domino';
import 'localstorage-polyfill'
import 'localstorage-polyfill'
const scripts = readFileSync('dist/clientWeb/browser/index.html').toString();
const window = createWindow(scripts) as any;
(global as any).window = window;
(global as any).document = window.document;
(global as any).Event = window.Event;
(global as any).KeyboardEvent = window.KeyboardEvent;
(global as any).MouseEvent = window.MouseEvent;

// The Express app is exported so that it can be used by serverless Functions.
export function app(): express.Express {
  const server = express();
  const distFolder = join(process.cwd(), 'dist/clientWeb/browser');
  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,
  }));

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

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

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

  return server;
}

function run(): void {
  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}`);
  });
}
const MockBrowser = require('mock-browser').mocks.MockBrowser;
const mock = new MockBrowser();
(global as any).localStorage = localStorage;

// 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 './src/main.server';

app.modules.ts

import { BrowserModule, HammerGestureConfig, HAMMER_GESTURE_CONFIG, Title } from '@angular/platform-browser';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { AppLayoutComponent } from './shared/layout/app-layout/app-layout.component';
import { NgModule, CUSTOM_ELEMENTS_SCHEMA, ModuleWithProviders } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AppRoutingModule } from './app-routing.module';
import { HttpClientModule } from '@angular/common/http';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { Tradesmanapplayout2Component } from './shared/layout/tradesmanapplayout2/tradesmanapplayout2.component';
import { Supplierapplayout2Component } from './shared/layout/supplierapplayout2/supplierapplayout2.component';
import { Userapplayout2Component } from './shared/layout/userapplayout2/userapplayout2.component';
import { AppDasboardHeader2Component } from './shared/layout/app-dasboard-header2/app-dasboard-header2.component';
import { AppDasboardFooter2Component } from './shared/layout/app-dasboard-footer2/app-dasboard-footer2.component';

const routes: Routes = [

  {
    path: 'resetpassword',
    component: AppHeaderLayoutComponent,
    loadChildren: () => import('./common/resetPassword/resetpassword.module').then(m => m.ResetpasswordModule)
  },
  {
    path: 'Supplier',
    component: Userapplayout2Component,
    loadChildren: () => import('./supplier/supplier.module').then(m => m.SupplierModule),
    
  },
  {
    path: 'User',
    component: Userapplayout2Component,
    loadChildren: () => import('./user/user.module').then(m => m.UserModule),
  
  },
  {
    path: 'MarketPlace',
    //component: UsersApplayoutComponent,
    //component: AppHeaderLayoutComponent,
    component: Userapplayout2Component,
    loadChildren: () => import('./marketplace/marketplace.module').then(m => m.MarketplaceModule),
  },
  {
    path: 'User/Agrements',
    //component: AppLayoutComponent,
    component: AppHeaderLayoutComponent,

    loadChildren: () => import('./agrements/agrements.module').then(m => m.AgrementsModule)
  },
  {
    path: 'HWUser',
    //component: AppLayoutComponent,
    component: AppHeaderLayoutComponent,
    loadChildren: () => import('./HelpAndFAQ/helpQuestion.module').then(m => m.HelpQuestionModule)
  },
  {
    path: 'Tradesman',
    component: Userapplayout2Component,
    loadChildren: () => import('./trademan/trademan.module').then(m => m.TrademanModule),
    canActivate: [AuthGuardTradesmanService],
    canActivateChild: [AuthGuardTradesmanService]
  },
  {
    path: 'ContactUs',
    //component: AppLayoutComponent,
    component: AppHeaderLayoutComponent,
    loadChildren: () => import('./contactUs/contactUs.module').then(m => m.ContactUsModule)
  },
  {
    path: 'landing-page/liveleads',
    //component: AppLayoutComponent,
    component: AppHeaderLayoutComponent,
    loadChildren: () => import('./landing-page/liveleads/liveleads.module').then(m => m.LiveleadsModule)
  },
  {
    path: 'landing-page',
    component: AppHeaderLayoutComponent,
    loadChildren: () => import('./landing-page/landing-page.module').then(m => m.LandingPageModule)
  }
]

@NgModule({
  declarations: [
    AppComponent,
    AppLayoutComponent,
    AppDasboardHeaderComponent,
    AppDasboardFooterComponent,
    AppLeftmenuComponent,
    AppcommonfooterComponent,
    SupplierLayoutComponent,
    TrademanLayoutComponent,
    TrademenuLeftComponent,
    SupplierLeftmenuComponent,
  ],
  imports: [
    BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),
    NgbModule,
    RouterModule.forRoot(routes, { enableTracing: false }),
    ModalModule.forRoot(),
    HttpModule,
    BrowserAnimationsModule,
    NgxImageCompressService,
    Events,
    Title,
    metaTagsService,
    ShareService,

  ],
  schemas: [
    CUSTOM_ELEMENTS_SCHEMA
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

在 IIS 上部署 Web.config

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <location path="." inheritInChildApplications="false">
    <system.webServer>
      <handlers>
    <add name="iisnode" path="main.js" verb="*" modules="iisnode"/>
      </handlers>
      <aspNetCore processPath="dotnet" arguments=".\HW.Web2.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" />

<rewrite>
   <rules>
        <rule name="LogFile" patternSyntax="ECMAScript" stopProcessing="true">
             <match url="iisnode"/>
        </rule>
       
        <rule name="StaticContent">
             <action type="Rewrite" url="{{REQUEST_URI}}"/>
        </rule>
        <rule name="DynamicContent">
             <conditions>
                  <add input="{{REQUEST_FILENAME}}" matchType="IsFile" negate="True"/>
             </conditions>
             <action type="Rewrite" url="main.js"/>
        </rule>
   </rules>
 </rewrite>
 <iisnode devErrorsEnabled="false"
  debuggingEnabled="false"
   loggingEnabled="false" 
   nodeProcessCommandLine="C:\Program Files\nodejs\node.exe" />

    </system.webServer>
  </location>
</configuration>

最佳答案

有多种原因会导致 Angular 应用程序的 body 标记无法在服务器端呈现。这是一个 list :

  • 首先确保您的实时环境支持 NodeJS。如果服务器上没有 NodeJS,则无法使用服务器端渲染
  • 在 Visual Studio 中,尝试更改 ASPNETCORE_ENVIRONMENT环境变量来自DEVELOPMENTPRODUCTION并运行您的应用程序。在某些情况下,应用程序在任一配置中的行为都不同(当 main.js 时,在另一个位置查找 PRODUCTION 文件)。启动调试器并尝试预呈现 View 后,您可能会在 Visual Studio 输出窗口 中看到一些异常。
    • 就我而言,main.js文件必须以 ClientApp/dist/main.js 结尾。所以我不得不修改angular.json改变 projects:ClientApp:architect:build:options:outputPathdist (see also)
  • 如果您在使用 Visual Studio 时遇到此问题,请务必查看输出窗口是否有错误,这将为您指明正确的方向。
  • 如果您正在托管 PWA(例如通过 @angular/pwa ),那么在浏览器中查看源代码时出现空白页面是完全正常的。如果您随后按 ctrl + F5,您将绕过向您显示预渲染的 html 源的 Angular Service Worker。这是你不应该担心的事情。 Google、Bing...实际上会获取页面的服务器端呈现版本并为其编制索引。
  • 如果您使用的是 ASP.NET Core 3.1 或更早版本,则无法拥有 async lambda 为您 SupplyData代表。 SupplyData不是Func<Task>not being awaited in the source code of ASP.NET CoreI changed this in my port to .NET 6
options.SupplyData = async (context, data) => { ... };
  • 某些实时环境会阻止您的 Angular 应用程序在 SSR 期间发送回服务器的 Web 请求。在这种情况下,您将收到内部服务器错误 (500)。您需要使用OnSupplyData来解决这个问题.

关于node.js - 服务器端渲染无法在实时服务器上使用延迟加载 Angular 12 模块,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71158747/

相关文章:

node.js - Angular 6 ssr页面负载太大

node.js - mongoDB promise 返回得太早

javascript - 在我的代码中收到以下错误 "Invalid left-hand side in assignment"

javascript - Node.js:通过电子邮件发送数据作为附件而不保存它

reactjs - 有没有办法使用 React 执行 SVG 图形的服务器端渲染?

javascript - GatsbyJS 和 Contentful 与 SSR

javascript - 使用 Material UI 中的 withWidth HOC

angular - Firebase firestore 不能与 Angular Universal 一起使用 : package. json 不存在

node.js - 将 Angular 2/3/4/5/6 项目转换为 Angular Universal

multithreading - V8 内存限制可能会在 Node.js 应用程序中导致哪些不好的事情?