웹 프론트엔드 JS 튜토리얼 Angular 서버사이드 렌더링 방법에 대한 자세한 설명

Angular 서버사이드 렌더링 방법에 대한 자세한 설명

May 02, 2018 pm 05:32 PM
angular 방법 상해

이번에는 Angular 서버사이드 렌더링 방법에 대한 자세한 설명을 가져왔습니다. Angular 서버사이드 렌더링의 주의사항은 무엇인가요? 실제 사례를 살펴보겠습니다.

Angular Universal

Angular는 서버 측 렌더링을 위한 프런트엔드 및 백엔드 동형 솔루션을 제공하는 서버 측에서 Angular 애플리케이션을 실행하는 기술인 Angular Universal(통합 플랫폼)입니다.

표준 Angular 애플리케이션은 브라우저에서 실행되며 사용자 작업에 응답하여 DOM의 페이지를 렌더링합니다.

Angular Universal은 서버 측 렌더링(SSR)이라는 프로세스를 통해 서버에 정적 애플리케이션 페이지를 생성합니다.

이러한 페이지를 생성하고 브라우저에서 요청할 때 직접 응답할 수 있습니다. 또한 페이지를 HTML 파일로 미리 생성한 다음 이를 서버의 정적 파일로 제공할 수도 있습니다.

작동 방식

유니버설 애플리케이션을 만들려면 platform-server 패키지를 설치해야 합니다. 플랫폼 서버 패키지는 서버측 DOM 구현, XMLHttpRequest 및 기타 하위 수준 기능을 제공하지만 더 이상 브라우저에 의존하지 않습니다. platform-server 包。 platform-server 包提供了服务端的 DOM 实现、XMLHttpRequest 和其它底层特性,但不再依赖浏览器。

你要使用 platform-server 模块而不是 platform-browser 模块来编译这个客户端应用,并且在一个 Web 服务器上运行这个 Universal 应用。

服务器(下面的示例中使用的是 Node Express 服务器)会把客户端对应用页面的请求传给 renderModuleFactory 函数。

renderModuleFactory 函数接受一个模板 HTML 页面(通常是 index.html)、一个包含组件的 Angular 模块和一个用于决定该显示哪些组件的路由作为输入。

该路由从客户端的请求中传给服务器。 每次请求都会给出所请求路由的一个适当的视图。

renderModuleFactory 在模板中的 <app>

클라이언트 애플리케이션을 컴파일하고 웹 서버에서 유니버설 애플리케이션을 실행하려면 platform-browser 모듈 대신 platform-server 모듈을 사용해야 합니다.

서버(아래 예에서는 Node Express 서버가 사용됨)는 애플리케이션 페이지에 대한 클라이언트의 요청을 renderModuleFactory 함수에 전달합니다.

renderModuleFactory 함수는 템플릿 HTML 페이지(일반적으로 index.html), 구성 요소가 포함된 Angular 모듈, 표시할 구성 요소를 결정하는 경로를 입력으로 받아들입니다.

이 경로는 클라이언트의 요청에 따라 서버로 전달됩니다. 각 요청은 요청된 경로에 대한 적절한 보기를 제공합니다.

renderModuleFactory는 템플릿의 <app> 태그에 있는 뷰를 렌더링하고 클라이언트를 위한 완성된 HTML 페이지를 생성합니다.
  1. 마지막으로 서버는 렌더링된 페이지를 클라이언트에 반환합니다.

  2. 서버 측 렌더링이 필요한 이유
  3. 세 가지 주요 이유:
  4. 웹 크롤러(SEO)에 도움이 됩니다.

휴대폰 및 저전력 장치의 성능 향상

첫 페이지에 빠르게 표시

웹 크롤러(SEO)에 도움이 됩니다.

Google, Bing, Baidu, Facebook, Twitter 및 기타 검색 엔진이나 소셜 미디어 사이트는 웹 크롤러를 사용하여 앱 콘텐츠를 색인화하고 해당 콘텐츠를 인터넷을 통해 검색할 수 있도록 합니다.

이러한 웹 크롤러는 인간처럼 대화형 Angular 앱을 탐색하고 색인을 생성하지 못할 수 있습니다.

Angular Universal은 JavaScript 없이도 검색, 연결 및 탐색이 가능한 정적 버전의 애플리케이션을 생성할 수 있습니다. 또한 각 URL이 완전히 렌더링된 페이지를 반환하므로 사이트를 미리 볼 수 있습니다.

웹 크롤러를 활성화하는 것을 흔히 검색 엔진 최적화(SEO)라고 합니다.

휴대폰 및 저전력 장치의 성능 향상

일부 장치에서는 JavaScript를 지원하지 않거나 JavaScript가 제대로 구현되지 않아 허용할 수 없는 사용자 경험이 발생합니다. 이러한 경우에는 서버에서 렌더링되고 JavaScript가 없는 앱 버전이 필요할 수 있습니다. 몇 가지 제한 사항이 있지만 앱을 전혀 사용할 수 없는 사람들에게는 이 버전이 유일한 옵션일 수 있습니다.

🎜홈페이지를 빠르게 표시🎜🎜🎜홈페이지를 빠르게 표시하는 것은 사용자의 관심을 끄는 데 중요합니다. 🎜🎜페이지를 로드하는 데 3초 이상 걸리면 모바일 웹사이트의 53%가 이탈됩니다. 사용자가 다른 작업을 하기로 결정하기 전에 사용자의 관심을 끌 수 있도록 앱을 더 빠르게 실행해야 합니다. 🎜🎜Angular Universal을 사용하면 전체 애플리케이션처럼 보이는 애플리케이션의 "랜딩 페이지"를 생성할 수 있습니다. 이러한 랜딩 페이지는 순수 HTML이며 JavaScript가 비활성화된 경우에도 표시됩니다. 이러한 페이지는 브라우저 이벤트를 처리하지 않지만 routerLink를 사용하여 사이트 내에서 탐색할 수 있습니다. 🎜

실제로는 사용자의 관심을 끌기 위해 랜딩 페이지의 정적 버전을 사용할 수 있습니다. 동시에 무대 뒤에서 완전한 Angular 애플리케이션도 로드하게 됩니다. 사용자는 랜딩 페이지가 거의 즉시 나타날 것으로 기대하며, 전체 앱이 로드되면 완전한 대화형 경험을 갖게 됩니다.

샘플 분석

다음은 GitHub의 샘플 프로젝트 angle-universal-starter를 기반으로 설명됩니다.

이 프로젝트는 첫 번째 기사의 예제 프로젝트와 마찬가지로 Angular CLI를 기반으로 개발 및 구축되었으므로 두 프로젝트 간의 유일한 차이점은 서버 측 렌더링에 필요한 구성입니다.

설치 도구

시작하기 전에 다음 패키지를 설치해야 합니다(샘플 프로젝트는 npm install으로 구성됨). npm install 即可):

  1. @angular/platform-server - Universal 的服务端元件。

  2. @nguniversal/module-map-ngfactory-loader - 用于处理服务端渲染环境下的惰性加载。

  3. @nguniversal/express-engine - Universal 应用的 Express 引擎。

  4. ts-loader - 用于对服务端应用进行转译。

  5. express - Node Express 服务器

使用下列命令安装它们:

npm install --save @angular/platform-server @nguniversal/module-map-ngfactory-loader ts-loader @nguniversal/express-engine express
로그인 후 복사

项目配置

配置工作有:

  1. 创建服务端应用模块:src/app/app.server.module.ts

  2. 修改客户端应用模块:src/app/app.module.ts

  3. 创建服务端应用的引导程序文件:src/main.server.ts

  4. 修改客户端应用的引导程序文件:src/main.ts

  5. 创建 TypeScript 的服务端配置:src/tsconfig.server.json

  6. 修改 @angular/cli 的配置文件:.angular-cli.json

  7. 创建 Node Express 的服务程序:server.ts

  8. 创建服务端预渲染的程序:prerender.ts

  9. 创建 Webpack 的服务端配置:webpack.server.config.js

1、创建服务端应用模块:src/app/app.server.module.ts

import { NgModule } from '@angular/core';
import { ServerModule, ServerTransferStateModule } from '@angular/platform-server';
import { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';
import { AppBrowserModule } from './app.module';
import { AppComponent } from './app.component';
// 可以注册那些在 Universal 环境下运行应用时特有的服务提供商
@NgModule({
  imports: [
    AppBrowserModule, // 客户端应用的 AppModule
    ServerModule, // 服务端的 Angular 模块
    ModuleMapLoaderModule, // 用于实现服务端的路由的惰性加载
    ServerTransferStateModule, // 在服务端导入,用于实现将状态从服务器传输到客户端
  ],
  bootstrap: [AppComponent],
})
export class AppServerModule {
}
로그인 후 복사

服务端应用模块(习惯上叫作 AppServerModule)是一个 Angular 模块,它包装了应用的根模块 AppModule,以便 Universal 可以在你的应用和服务器之间进行协调。 AppServerModule 还会告诉 Angular 再把你的应用以 Universal 方式运行时,该如何引导它。

2、修改客户端应用模块:src/app/app.module.ts

@NgModule({
  imports: [
    AppRoutingModule,
    BrowserModule.withServerTransition({appId: 'my-app'}),
    TransferHttpCacheModule, // 用于实现服务器到客户端的请求传输缓存,防止客户端重复请求服务端已完成的请求
    BrowserTransferStateModule, // 在客户端导入,用于实现将状态从服务器传输到客户端
    HttpClientModule
  ],
  declarations: [
    AppComponent,
    HomeComponent
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppBrowserModule {
  constructor(@Inject(PLATFORM_ID) private platformId: Object,
        @Inject(APP_ID) private appId: string) {
    
    // 判断运行环境为客户端还是服务端
    const platform = isPlatformBrowser(platformId) ? 'in the browser' : 'on the server';
    console.log(`Running ${platform} with appId=${appId}`);
  }
}
로그인 후 복사

NgModule 的元数据中 BrowserModule 的导入改成 BrowserModule.withServerTransition({appId: 'my-app'}),Angular 会把 appId 值(它可以是任何字符串)添加到服务端渲染页面的样式名中,以便它们在客户端应用启动时可以被找到并移除。

此时,我们可以通过依赖注入@Inject(PLATFORM_ID)@Inject(APP_ID))取得关于当前平台和 appId 的运行时信息:

constructor(@Inject(PLATFORM_ID) private platformId: Object,
      @Inject(APP_ID) private appId: string) {
  
  // 判断运行环境为客户端还是服务端
  const platform = isPlatformBrowser(platformId) ? 'in the browser' : 'on the server';
  console.log(`Running ${platform} with appId=${appId}`);
}
로그인 후 복사

3、创建服务端应用的引导程序文件:src/main.server.ts

该文件导出服务端模块:

export { AppServerModule } from './app/app.server.module';
로그인 후 복사

4、修改客户端应用的引导程序文件:src/main.ts

监听 DOMContentLoaded 事件,在发生 DOMContentLoaded 事件时运行我们的代码,以使 TransferState 正常工作

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppBrowserModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
  enableProdMode();
}
// 在 DOMContentLoaded 时运行我们的代码,以使 TransferState 正常工作
document.addEventListener('DOMContentLoaded', () => {
  platformBrowserDynamic().bootstrapModule(AppBrowserModule);
});
로그인 후 복사

5、创建 TypeScript 的服务端配置:src/tsconfig.server.json

{
 "extends": "../tsconfig.json",
 "compilerOptions": {
  "outDir": "../out-tsc/app",
  "baseUrl": "./",
  "module": "commonjs",
  "types": [
   "node"
  ]
 },
 "exclude": [
  "test.ts",
  "**/*.spec.ts"
 ],
 "angularCompilerOptions": {
  "entryModule": "app/app.server.module#AppServerModule"
 }
}
로그인 후 복사

tsconfig.app.json

@angular /platform-server - Universal용 서버 구성요소입니다.

    @nguniversal/module-map-ngfactory-loader - 서버 측 렌더링 환경에서 지연 로딩을 처리하는 데 사용됩니다.
  1. @nguniversal/express-engine - Universal 애플리케이션용 Express 엔진. 🎜🎜🎜ts-loader - 서버측 애플리케이션을 번역하는 데 사용됩니다. 🎜🎜🎜express - Node Express Server 🎜
🎜다음 명령을 사용하여 설치합니다. 🎜
{
  "platform": "server",
  "root": "src",
  "outDir": "dist/server",
  "assets": [
   "assets",
   "favicon.ico"
  ],
  "index": "index.html",
  "main": "main.server.ts",
  "test": "test.ts",
  "tsconfig": "tsconfig.server.json",
  "testTsconfig": "tsconfig.spec.json",
  "prefix": "",
  "styles": [
   "styles.scss"
  ],
  "scripts": [],
  "environmentSource": "environments/environment.ts",
  "environments": {
   "dev": "environments/environment.ts",
   "prod": "environments/environment.prod.ts"
  }
}
로그인 후 복사
로그인 후 복사
🎜프로젝트 구성 🎜🎜구성 작업은 다음과 같습니다. 🎜🎜 🎜🎜 서버 애플리케이션 모듈 생성: src/app/app.server.module.ts🎜🎜🎜클라이언트 애플리케이션 모듈 수정: src/app/app.module.ts🎜🎜🎜서버 애플리케이션 생성 Bootstrap 파일: src/main.server.ts🎜🎜🎜클라이언트 애플리케이션의 부트로더 파일 수정: src/main.ts🎜🎜🎜TypeScript 서버 구성 생성: src/tsconfig .server.json🎜 🎜🎜@angular/cli의 구성 파일 :.angular-cli를 수정하세요. json🎜🎜🎜Node Express 서비스 프로그램 생성: server.ts🎜🎜🎜서버 측 사전 렌더링 프로그램 생성: prerender.ts🎜🎜🎜Webpack 서버 구성 생성: webpack. server.config.js🎜🎜1. 서버 애플리케이션 모듈을 생성합니다: src/app/app.server.module.ts🎜
import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import { enableProdMode } from '@angular/core';
import * as express from 'express';
import { join } from 'path';
import { readFileSync } from 'fs';
// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();
// Express server
const app = express();
const PORT = process.env.PORT || 4000;
const DIST_FOLDER = join(process.cwd(), 'dist');
// Our index.html we'll use as our template
const template = readFileSync(join(DIST_FOLDER, 'browser', 'index.html')).toString();
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = require('./dist/server/main.bundle');
// Express Engine
import { ngExpressEngine } from '@nguniversal/express-engine';
// Import module map for lazy loading
import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
app.engine('html', ngExpressEngine({
  bootstrap: AppServerModuleNgFactory,
  providers: [
    provideModuleMap(LAZY_MODULE_MAP)
  ]
}));
app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser'));
/* - Example Express Rest API endpoints -
 app.get('/api/**', (req, res) => { });
*/
// Server static files from /browser
app.get('*.*', express.static(join(DIST_FOLDER, 'browser'), {
  maxAge: '1y'
}));
// ALl regular routes use the Universal engine
app.get('*', (req, res) => {
  res.render('index', {req});
});
// Start up the Node server
app.listen(PORT, () => {
  console.log(`Node Express server listening on http://localhost:${PORT}`);
});
로그인 후 복사
로그인 후 복사
🎜서버 측 애플리케이션 모듈 (일반적으로 AppServerModule이라고 함)는 Universal이 애플리케이션과 서버 사이를 조정할 수 있도록 애플리케이션의 루트 모듈 AppModule을 래핑하는 Angular 모듈입니다. AppServerModule은 또한 Universal로 실행될 때 애플리케이션을 부트스트랩하는 방법을 Angular에 알려줍니다. 🎜🎜2. 클라이언트 애플리케이션 모듈 수정: src/app/app.module.ts🎜
// Load zone.js for the server.
import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
import { join } from 'path';
import { enableProdMode } from '@angular/core';
// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();
// Import module map for lazy loading
import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';
import { renderModuleFactory } from '@angular/platform-server';
import { ROUTES } from './static.paths';
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = require('./dist/server/main.bundle');
const BROWSER_FOLDER = join(process.cwd(), 'browser');
// Load the index.html file containing referances to your application bundle.
const index = readFileSync(join('browser', 'index.html'), 'utf8');
let previousRender = Promise.resolve();
// Iterate each route path
ROUTES.forEach(route => {
  const fullPath = join(BROWSER_FOLDER, route);
  // Make sure the directory structure is there
  if (!existsSync(fullPath)) {
    mkdirSync(fullPath);
  }
  // Writes rendered HTML to index.html, replacing the file if it already exists.
  previousRender = previousRender.then(_ => renderModuleFactory(AppServerModuleNgFactory, {
    document: index,
    url: route,
    extraProviders: [
      provideModuleMap(LAZY_MODULE_MAP)
    ]
  })).then(html => writeFileSync(join(fullPath, 'index.html'), html));
});
로그인 후 복사
로그인 후 복사
🎜 NgModule의 메타데이터에 있는 BrowserModule 가져오기를 BrowserModule.withServerTransition으로 변경합니다. ({appId: 'my-app'}), Angular는 클라이언트 애플리케이션이 시작될 때 찾아서 제거할 수 있도록 서버 렌더링 페이지의 스타일 이름에 appId 값(문자열일 수 있음)을 추가합니다. 🎜🎜이 시점에서 종속성 주입()을 통과할 수 있습니다. @Inject(PLATFORM_ID)@Inject(APP_ID))를 사용하여 현재 플랫폼 및 appId에 대한 런타임 정보를 얻습니다. 🎜
// Work around for https://github.com/angular/angular-cli/issues/7200
const path = require('path');
const webpack = require('webpack');
module.exports = {
  entry: {
    server: './server.ts', // This is our Express server for Dynamic universal
    prerender: './prerender.ts' // This is an example of Static prerendering (generative)
  },
  target: 'node',
  resolve: {extensions: ['.ts', '.js']},
  externals: [/(node_modules|main\..*\.js)/,], // Make sure we include all node_modules etc
  output: {
    path: path.join(dirname, 'dist'), // Puts the output at the root of the dist folder
    filename: '[name].js'
  },
  module: {
    rules: [
      {test: /\.ts$/, loader: 'ts-loader'}
    ]
  },
  plugins: [
    new webpack.ContextReplacementPlugin(
      /(.+)?angular(\|\/)core(.+)?/, // fixes WARNING Critical dependency: the request of a dependency is an expression
      path.join(dirname, 'src'), // location of your src
      {} // a map of your routes
    ),
    new webpack.ContextReplacementPlugin(
      /(.+)?express(\|\/)(.+)?/, // fixes WARNING Critical dependency: the request of a dependency is an expression
      path.join(dirname, 'src'),
      {}
    )
  ]
};
로그인 후 복사
🎜3 서버 애플리케이션의 부트로더 파일을 생성합니다. > src/main.server.ts🎜🎜이 파일은 서버 모듈을 내보냅니다: 🎜
{
  "scripts": {
    "ng": "ng",
    "start": "ng serve -o",
    "ssr": "npm run build:ssr && npm run serve:ssr",
    "prerender": "npm run build:prerender && npm run serve:prerender",
    "build": "ng build",
    "build:client-and-server-bundles": "ng build --prod && ng build --prod --app 1 --output-hashing=false",
    "build:prerender": "npm run build:client-and-server-bundles && npm run webpack:server && npm run generate:prerender",
    "build:ssr": "npm run build:client-and-server-bundles && npm run webpack:server",
    "generate:prerender": "cd dist && node prerender",
    "webpack:server": "webpack --config webpack.server.config.js --progress --colors",
    "serve:prerender": "cd dist/browser && http-server",
    "serve:ssr": "node dist/server"
  }
}
로그인 후 복사
로그인 후 복사
🎜4. 클라이언트 애플리케이션의 부트스트랩 파일을 수정합니다: src/main.ts🎜🎜Listen DOMContentLoaded 이벤트에 대해 TransferState가 제대로 작동하도록 DOMContentLoaded 이벤트가 발생할 때 코드를 실행하세요. code> tsconfig.app.json의 차이점은 다음과 같습니다. 🎜🎜모듈 속성은 require() 메소드를 통해 서버 애플리케이션으로 가져올 수 있도록 commonjs여야 합니다. 🎜🎜angularCompilerOptions 섹션에는 AOT 컴파일러에 대한 몇 가지 옵션이 있습니다. 🎜🎜🎜🎜entryModule - 서버 애플리케이션의 루트 모듈이며 형식은 path/to/file#ClassName입니다. 🎜

6、修改 @angular/cli 的配置文件:.angular-cli.json

apps 下添加:

{
  "platform": "server",
  "root": "src",
  "outDir": "dist/server",
  "assets": [
   "assets",
   "favicon.ico"
  ],
  "index": "index.html",
  "main": "main.server.ts",
  "test": "test.ts",
  "tsconfig": "tsconfig.server.json",
  "testTsconfig": "tsconfig.spec.json",
  "prefix": "",
  "styles": [
   "styles.scss"
  ],
  "scripts": [],
  "environmentSource": "environments/environment.ts",
  "environments": {
   "dev": "environments/environment.ts",
   "prod": "environments/environment.prod.ts"
  }
}
로그인 후 복사
로그인 후 복사

7、创建 Node Express 的服务程序:server.ts

import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import { enableProdMode } from '@angular/core';
import * as express from 'express';
import { join } from 'path';
import { readFileSync } from 'fs';
// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();
// Express server
const app = express();
const PORT = process.env.PORT || 4000;
const DIST_FOLDER = join(process.cwd(), 'dist');
// Our index.html we'll use as our template
const template = readFileSync(join(DIST_FOLDER, 'browser', 'index.html')).toString();
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = require('./dist/server/main.bundle');
// Express Engine
import { ngExpressEngine } from '@nguniversal/express-engine';
// Import module map for lazy loading
import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
app.engine('html', ngExpressEngine({
  bootstrap: AppServerModuleNgFactory,
  providers: [
    provideModuleMap(LAZY_MODULE_MAP)
  ]
}));
app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser'));
/* - Example Express Rest API endpoints -
 app.get('/api/**', (req, res) => { });
*/
// Server static files from /browser
app.get('*.*', express.static(join(DIST_FOLDER, 'browser'), {
  maxAge: '1y'
}));
// ALl regular routes use the Universal engine
app.get('*', (req, res) => {
  res.render('index', {req});
});
// Start up the Node server
app.listen(PORT, () => {
  console.log(`Node Express server listening on http://localhost:${PORT}`);
});
로그인 후 복사
로그인 후 복사

8、创建服务端预渲染的程序:prerender.ts

// Load zone.js for the server.
import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
import { join } from 'path';
import { enableProdMode } from '@angular/core';
// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();
// Import module map for lazy loading
import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';
import { renderModuleFactory } from '@angular/platform-server';
import { ROUTES } from './static.paths';
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = require('./dist/server/main.bundle');
const BROWSER_FOLDER = join(process.cwd(), 'browser');
// Load the index.html file containing referances to your application bundle.
const index = readFileSync(join('browser', 'index.html'), 'utf8');
let previousRender = Promise.resolve();
// Iterate each route path
ROUTES.forEach(route => {
  const fullPath = join(BROWSER_FOLDER, route);
  // Make sure the directory structure is there
  if (!existsSync(fullPath)) {
    mkdirSync(fullPath);
  }
  // Writes rendered HTML to index.html, replacing the file if it already exists.
  previousRender = previousRender.then(_ => renderModuleFactory(AppServerModuleNgFactory, {
    document: index,
    url: route,
    extraProviders: [
      provideModuleMap(LAZY_MODULE_MAP)
    ]
  })).then(html => writeFileSync(join(fullPath, 'index.html'), html));
});
로그인 후 복사
로그인 후 복사

9、创建 Webpack 的服务端配置:webpack.server.config.js

Universal 应用不需要任何额外的 Webpack 配置,Angular CLI 会帮我们处理它们。但是由于本例子的 Node Express 的服务程序是 TypeScript 应用(server.ts及prerender.ts),所以要使用 Webpack 来转译它。这里不讨论 Webpack 的配置,需要了解的移步 Webpack官网

// Work around for https://github.com/angular/angular-cli/issues/7200
const path = require('path');
const webpack = require('webpack');
module.exports = {
  entry: {
    server: './server.ts', // This is our Express server for Dynamic universal
    prerender: './prerender.ts' // This is an example of Static prerendering (generative)
  },
  target: 'node',
  resolve: {extensions: ['.ts', '.js']},
  externals: [/(node_modules|main\..*\.js)/,], // Make sure we include all node_modules etc
  output: {
    path: path.join(dirname, 'dist'), // Puts the output at the root of the dist folder
    filename: '[name].js'
  },
  module: {
    rules: [
      {test: /\.ts$/, loader: 'ts-loader'}
    ]
  },
  plugins: [
    new webpack.ContextReplacementPlugin(
      /(.+)?angular(\\|\/)core(.+)?/, // fixes WARNING Critical dependency: the request of a dependency is an expression
      path.join(dirname, 'src'), // location of your src
      {} // a map of your routes
    ),
    new webpack.ContextReplacementPlugin(
      /(.+)?express(\\|\/)(.+)?/, // fixes WARNING Critical dependency: the request of a dependency is an expression
      path.join(dirname, 'src'),
      {}
    )
  ]
};
로그인 후 복사

测试配置

通过上面的配置,我们就制作完成一个可在服务端渲染的 Angular Universal 应用。

在 package.json 的 scripts 区配置 build 和 serve 有关的命令:

{
  "scripts": {
    "ng": "ng",
    "start": "ng serve -o",
    "ssr": "npm run build:ssr && npm run serve:ssr",
    "prerender": "npm run build:prerender && npm run serve:prerender",
    "build": "ng build",
    "build:client-and-server-bundles": "ng build --prod && ng build --prod --app 1 --output-hashing=false",
    "build:prerender": "npm run build:client-and-server-bundles && npm run webpack:server && npm run generate:prerender",
    "build:ssr": "npm run build:client-and-server-bundles && npm run webpack:server",
    "generate:prerender": "cd dist && node prerender",
    "webpack:server": "webpack --config webpack.server.config.js --progress --colors",
    "serve:prerender": "cd dist/browser && http-server",
    "serve:ssr": "node dist/server"
  }
}
로그인 후 복사
로그인 후 복사

开发只需运行 npm run start执行 npm run ssr 编译应用程序,并启动一个Node Express来为应用程序提供服务 http://localhost:4000

dist目录:

执行npm run prerender - 编译应用程序并预渲染应用程序文件,启动一个演示http服务器,以便您可以查看它 http://localhost:8080

注意: 要将静态网站部署到静态托管平台,您必须部署dist/browser文件夹, 而不是dist文件夹

dist目录:

根据项目实际的路由信息并在根目录的 static.paths.ts 中配置,提供给 prerender.ts 解析使用。

export const ROUTES = [
  '/',
  '/lazy'
];
로그인 후 복사

因此,从dist目录可以看到,服务端预渲染会根据配置好的路由在 browser 生成对应的静态index.html。如 / 对应 /index.html/lazy 对应 /lazy/index.html

服务器到客户端的状态传输

在前面的介绍中,我们在 app.server.module.ts 中导入了 ServerTransferStateModule,在 app.module.ts 中导入了 BrowserTransferStateModuleTransferHttpCacheModule

这三个模块都与服务器到客户端的状态传输有关:

  1. ServerTransferStateModule:在服务端导入,用于实现将状态从服务器传输到客户端

  2. BrowserTransferStateModule:在客户端导入,用于实现将状态从服务器传输到客户端

  3. TransferHttpCacheModule:用于实现服务器到客户端的请求传输缓存,防止客户端重复请求服务端已完成的请求

使用这几个模块,可以解决 http请求在服务端和客户端分别请求一次 的问题。

比如在 home.component.ts 中有如下代码:

import { Component, OnDestroy, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit, OnDestroy {
  constructor(public http: HttpClient) {
  }
  
  ngOnInit() {
    this.poiSearch(this.keyword, '北京市').subscribe((data: any) => {
      console.log(data);
    });
  }
  
  ngOnDestroy() {
  }
  
  poiSearch(text: string, city?: string): Observable<any> {
    return this.http.get(encodeURI(`http://restapi.amap.com/v3/place/text?keywords=${text}&city=${city}&offset=20&key=55f909211b9950837fba2c71d0488db9&extensions=all`));
  }
}
로그인 후 복사

代码运行之后,

服务端请求并打印:

客户端再一次请求并打印:

方法1:使用 TransferHttpCacheModule

使用 TransferHttpCacheModule 很简单,代码不需要改动。在 app.module.ts 中导入之后,Angular自动会将服务端请求缓存到客户端,换句话说就是服务端请求到数据会自动传输到客户端,客户端接收到数据之后就不会再发送请求了。

方法2:使用 BrowserTransferStateModule

该方法稍微复杂一些,需要改动一些代码。

调整 home.component.ts 代码如下:

import { Component, OnDestroy, OnInit } from '@angular/core';
import { makeStateKey, TransferState } from '@angular/platform-browser';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
const KFCLIST_KEY = makeStateKey('kfcList');
@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit, OnDestroy {
  constructor(public http: HttpClient,
        private state: TransferState) {
  }
  ngOnInit() {
    // 采用一个标记来区分服务端是否已经拿到了数据,如果没拿到数据就在客户端请求,如果已经拿到数据就不发请求
    const kfcList:any[] = this.state.get(KFCLIST_KEY, null as any);
    if (!this.kfcList) {
      this.poiSearch(this.keyword, '北京市').subscribe((data: any) => {
        console.log(data);
        this.state.set(KFCLIST_KEY, data as any); // 存储数据
      });
    }
  }
  ngOnDestroy() {
    if (typeof window === 'object') {
      this.state.set(KFCLIST_KEY, null as any); // 删除数据
    }
  }
  poiSearch(text: string, city?: string): Observable<any> {
    return this.http.get(encodeURI(`http://restapi.amap.com/v3/place/text?keywords=${text}&city=${city}&offset=20&key=55f909211b9950837fba2c71d0488db9&extensions=all`));
  }
}
로그인 후 복사
  1. 使用 const KFCLIST_KEY = makeStateKey('kfcList') 创建储存传输数据的 StateKey

  2. HomeComponent构造函数中注入 TransferState

  3. ngOnInit 中根据 this.state.get(KFCLIST_KEY, null as any) 判断数据是否存在(不管是服务端还是客户端),存在就不再请求,不存在则请求数据并通过 this.state.set(KFCLIST_KEY, data as any) 存储传输数据

  4. ngOnDestroy 中根据当前是否客户端来决定是否将存储的数据进行删除

相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!

推荐阅读:

全局搜索时结果高亮如何实现

Angular4的router属性使用案例详解

위 내용은 Angular 서버사이드 렌더링 방법에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

AI Hentai Generator

AI Hentai Generator

AI Hentai를 무료로 생성하십시오.

인기 기사

R.E.P.O. 에너지 결정과 그들이하는 일 (노란색 크리스탈)
3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 최고의 그래픽 설정
3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 아무도들을 수없는 경우 오디오를 수정하는 방법
3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌

뜨거운 도구

메모장++7.3.1

메모장++7.3.1

사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전

SublimeText3 중국어 버전

중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

SublimeText3 Mac 버전

SublimeText3 Mac 버전

신 수준의 코드 편집 소프트웨어(SublimeText3)

Tomato Free Novel 앱에서 소설 쓰는 방법 Tomato Novel에서 소설 쓰는 방법에 대한 튜토리얼을 공유하세요. Tomato Free Novel 앱에서 소설 쓰는 방법 Tomato Novel에서 소설 쓰는 방법에 대한 튜토리얼을 공유하세요. Mar 28, 2024 pm 12:50 PM

Tomato Novel은 매우 인기 있는 소설 읽기 소프트웨어입니다. 우리는 종종 Tomato Novel에서 읽을 새로운 소설과 만화를 가지고 있습니다. 많은 친구들도 용돈을 벌고 소설의 내용을 편집하고 싶어합니다. 글로 쓰고 싶은데, 그 안에 소설을 어떻게 쓰는지 친구들도 모르니까, 소설 쓰는 방법에 대한 소개를 함께 살펴보는 시간을 가져보겠습니다. 토마토 소설을 사용하여 소설을 쓰는 방법에 대한 튜토리얼을 공유하세요. 1. 먼저 휴대폰에서 토마토 무료 소설 앱을 열고 개인 센터 - 작가 센터를 클릭하세요. 2. 토마토 작가 도우미 페이지로 이동하여 새로 만들기를 클릭하세요. 소설의 끝 부분에 예약하십시오.

컬러풀 마더보드에서 바이오스로 진입하는 방법은 무엇입니까? 두 가지 방법을 가르쳐주세요. 컬러풀 마더보드에서 바이오스로 진입하는 방법은 무엇입니까? 두 가지 방법을 가르쳐주세요. Mar 13, 2024 pm 06:01 PM

컬러풀한 마더보드는 중국 국내 시장에서 높은 인기와 시장 점유율을 누리고 있지만 일부 컬러풀한 마더보드 사용자는 아직도 설정을 위해 BIOS에 진입하는 방법을 모르시나요? 이러한 상황에 대응하여 편집자는 다채로운 마더보드 BIOS에 들어갈 수 있는 두 가지 방법을 특별히 가져왔습니다. 방법 1: U 디스크 시작 단축키를 사용하여 U 디스크 설치 시스템에 직접 들어갑니다. 한 번의 클릭으로 U 디스크를 시작하는 Colour 마더보드의 단축키는 ESC 또는 F11입니다. 먼저 Black Shark 설치 마스터를 사용하여 Black을 만듭니다. Shark U 디스크 부팅 디스크를 켠 후 컴퓨터를 켜면 시작 화면이 나타나면 키보드의 ESC 또는 F11 키를 계속 눌러 시작 항목을 순차적으로 선택할 수 있는 창으로 커서를 "USB. "가 표시된 후

WeChat에서 삭제된 연락처를 복구하는 방법(삭제된 연락처를 복구하는 방법을 알려주는 간단한 튜토리얼) WeChat에서 삭제된 연락처를 복구하는 방법(삭제된 연락처를 복구하는 방법을 알려주는 간단한 튜토리얼) May 01, 2024 pm 12:01 PM

불행하게도 사람들은 어떤 이유로든 실수로 특정 연락처를 삭제하는 경우가 많습니다. WeChat은 널리 사용되는 소셜 소프트웨어입니다. 사용자가 이 문제를 해결할 수 있도록 이 문서에서는 삭제된 연락처를 간단한 방법으로 검색하는 방법을 소개합니다. 1. WeChat 연락처 삭제 메커니즘을 이해하면 삭제된 연락처를 검색할 수 있습니다. WeChat의 연락처 삭제 메커니즘은 연락처를 주소록에서 제거하지만 완전히 삭제하지는 않습니다. 2. WeChat에 내장된 "연락처 복구" 기능을 사용하세요. WeChat은 "연락처 복구"를 제공하여 시간과 에너지를 절약합니다. 사용자는 이 기능을 통해 이전에 삭제한 연락처를 빠르게 검색할 수 있습니다. 3. WeChat 설정 페이지에 들어가서 오른쪽 하단을 클릭하고 WeChat 애플리케이션 "나"를 열고 오른쪽 상단에 있는 설정 아이콘을 클릭하여 설정 페이지로 들어갑니다.

Win11에서 관리자 권한을 얻는 방법 요약 Win11에서 관리자 권한을 얻는 방법 요약 Mar 09, 2024 am 08:45 AM

Win11 관리자 권한을 얻는 방법에 대한 요약 Windows 11 운영 체제에서 관리자 권한은 사용자가 시스템에서 다양한 작업을 수행할 수 있도록 하는 매우 중요한 권한 중 하나입니다. 때로는 소프트웨어 설치, 시스템 설정 수정 등과 같은 일부 작업을 완료하기 위해 관리자 권한을 얻어야 할 수도 있습니다. 다음은 Win11 관리자 권한을 얻는 몇 가지 방법을 요약한 것입니다. 도움이 되기를 바랍니다. 1. 단축키를 사용하세요. Windows 11 시스템에서는 단축키를 통해 명령 프롬프트를 빠르게 열 수 있습니다.

Ubuntu 24.04에 Angular를 설치하는 방법 Ubuntu 24.04에 Angular를 설치하는 방법 Mar 23, 2024 pm 12:20 PM

Angular.js는 동적 애플리케이션을 만들기 위해 자유롭게 액세스할 수 있는 JavaScript 플랫폼입니다. HTML 구문을 템플릿 언어로 확장하여 애플리케이션의 다양한 측면을 빠르고 명확하게 표현할 수 있습니다. Angular.js는 코드를 작성, 업데이트 및 테스트하는 데 도움이 되는 다양한 도구를 제공합니다. 또한 라우팅 및 양식 관리와 같은 많은 기능을 제공합니다. 이 가이드에서는 Ubuntu24에 Angular를 설치하는 방법에 대해 설명합니다. 먼저 Node.js를 설치해야 합니다. Node.js는 서버 측에서 JavaScript 코드를 실행할 수 있게 해주는 ChromeV8 엔진 기반의 JavaScript 실행 환경입니다. Ub에 있으려면

모바일 드래곤 알 부화의 비밀이 공개됩니다(모바일 드래곤 알을 성공적으로 부화하는 방법을 단계별로 알려드립니다) 모바일 드래곤 알 부화의 비밀이 공개됩니다(모바일 드래곤 알을 성공적으로 부화하는 방법을 단계별로 알려드립니다) May 04, 2024 pm 06:01 PM

모바일 게임은 기술의 발전과 함께 사람들의 삶에 없어서는 안될 부분이 되었습니다. 귀여운 드래곤 알 이미지와 흥미로운 부화 과정으로 많은 플레이어들의 관심을 끌었으며, 특히 주목을 받은 게임 중 하나가 드래곤 알 모바일 버전이다. 플레이어가 게임에서 자신만의 드래곤을 더 잘 육성하고 성장시킬 수 있도록 이 글에서는 모바일 버전에서 드래곤 알을 부화시키는 방법을 소개합니다. 1. 적절한 유형의 드래곤 알을 선택하십시오. 플레이어는 게임에서 제공되는 다양한 유형의 드래곤 알 속성과 능력을 기반으로 자신이 좋아하고 적합한 드래곤 알 유형을 신중하게 선택해야 합니다. 2. 부화기의 레벨을 업그레이드하세요. 플레이어는 작업을 완료하고 소품을 수집하여 부화기의 레벨을 향상시켜야 합니다. 부화기의 레벨에 따라 부화 속도와 부화 성공률이 결정됩니다. 3. 플레이어가 게임에 참여하는데 필요한 자원을 수집하세요.

빨리 익히세요: Huawei 휴대폰에서 두 개의 WeChat 계정을 여는 방법 공개! 빨리 익히세요: Huawei 휴대폰에서 두 개의 WeChat 계정을 여는 방법 공개! Mar 23, 2024 am 10:42 AM

현대 사회에서 휴대폰은 우리 삶에 없어서는 안 될 필수품이 되었습니다. 일상적인 의사소통, 업무, 생활을 위한 중요한 도구로 WeChat이 자주 사용됩니다. 그러나 서로 다른 거래를 처리할 때 두 개의 WeChat 계정을 분리해야 할 수도 있습니다. 이를 위해서는 휴대폰이 동시에 두 개의 WeChat 계정에 로그인하는 기능을 지원해야 합니다. 국내 유명 브랜드인 화웨이 휴대폰은 많은 사람들이 사용하고 있습니다. 그렇다면 화웨이 휴대폰에서 위챗 계정을 2개 개설하는 방법은 무엇일까요? 이 방법의 비밀을 공개해보겠습니다. 우선, Huawei 휴대폰에서 두 개의 WeChat 계정을 동시에 사용해야 합니다.

휴대폰에서 글꼴 크기를 설정하는 방법(휴대폰에서 글꼴 크기를 쉽게 조정) 휴대폰에서 글꼴 크기를 설정하는 방법(휴대폰에서 글꼴 크기를 쉽게 조정) May 07, 2024 pm 03:34 PM

휴대폰이 사람들의 일상 생활에서 중요한 도구가 되면서 글꼴 크기 설정은 중요한 개인화 요구 사항이 되었습니다. 다양한 사용자의 요구를 충족하기 위해 이 기사에서는 간단한 조작을 통해 휴대폰 사용 경험을 개선하고 휴대폰의 글꼴 크기를 조정하는 방법을 소개합니다. 휴대폰의 글꼴 크기를 조정해야 하는 이유 - 글꼴 크기를 조정하면 텍스트가 더 명확하고 읽기 쉬워집니다. - 다양한 연령대의 사용자의 읽기 요구에 적합 - 시력이 좋지 않은 사용자가 글꼴 크기를 사용하는 것이 편리합니다. 휴대폰 시스템의 설정 기능 - 시스템 설정 인터페이스에 들어가는 방법 - 찾기에서 설정 인터페이스의 "디스플레이" 옵션을 입력합니다. - "글꼴 크기" 옵션을 찾아 타사를 통해 글꼴 크기를 조정합니다. 애플리케이션 - 글꼴 크기 조정을 지원하는 애플리케이션 다운로드 및 설치 - 애플리케이션을 열고 관련 설정 인터페이스로 진입 - 개인에 따라

See all articles