在 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 { }
main.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
环境变量来自DEVELOPMENT
至PRODUCTION
并运行您的应用程序。在某些情况下,应用程序在任一配置中的行为都不同(当main.js
时,在另一个位置查找PRODUCTION
文件)。启动调试器并尝试预呈现 View 后,您可能会在 Visual Studio 输出窗口 中看到一些异常。- 就我而言,
main.js
文件必须以ClientApp/dist/main.js
结尾。所以我不得不修改angular.json
改变projects:ClientApp:architect:build:options:outputPath
至dist
(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 Core 。 I 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/