Maison  >  Article  >  interface Web  >  Utiliser Angular5 pour implémenter la pratique de rendu côté serveur

Utiliser Angular5 pour implémenter la pratique de rendu côté serveur

亚连
亚连original
2018-06-13 17:24:481797parcourir

Cet article présente principalement l'explication détaillée de la pratique de rendu côté serveur Angular5. Maintenant, je la partage avec vous et la donne comme référence.

Cet article poursuit le développement basé sur l'article précédent d'Angular5. L'article ci-dessus parle du processus de création d'Angular5 Youdao Translation et des solutions aux problèmes rencontrés.

Ensuite, l'interface utilisateur est passée de bootstrap4 à un matériau angulaire. Je n'entrerai pas dans les détails ici. Le rendu côté serveur n'a rien à voir avec la modification de l'interface utilisateur.

Ceux qui ont lu les articles précédents constateront que le contenu des articles est orienté vers le rendu côté serveur, le nuxt de vue et le suivant de React.

Avant cette révision, j'ai également essayé de trouver des bibliothèques d'empaquetage de haut niveau comme nuxt.js et next.js, qui peuvent grandement gagner du temps, mais en vain.

J'ai finalement décidé d'utiliser Angular Universal (support JavaScript universel (isomorphe) pour Angular.), une solution isomorphe front-end et back-end disponible depuis Angular2

Je ne le ferai pas. entrez dans les détails du contenu du document ici. Cet article essayez également d'utiliser un langage facile à comprendre pour apporter le SSR d'Angular

Prérequis

Le projet udao écrit ci-dessus est entièrement conforme à angulaire-cli, de la construction à l'emballage, ce qui rend également cet article universellement applicable à tous les projets angulaires5 construits avec angulaire-cli.

Processus de construction

Installez d'abord les dépendances du serveur

yarn add @angular/platform-server express
yarn add -D ts-loader webpack-node-externals npm-run-all

Il convient de noter ici que le numéro de version de @angular/platform-server est la meilleure installation en fonction de la version angulaire actuelle, telle que : @angular/platform-server@5.1.0 pour éviter les conflits de version avec d'autres dépendances.

Créer le fichier : src/app/app.server.module.ts

import { NgModule } from '@angular/core'
import { ServerModule } from '@angular/platform-server'

import { AppModule } from './app.module'
import { AppComponent } from './app.component'

@NgModule({
 imports: [
  AppModule,
  ServerModule
 ],
 bootstrap: [AppComponent],
})
export class AppServerModule { }

Mettre à jour le fichier : src/app/app.module.ts

import { BrowserModule } from '@angular/platform-browser'
import { NgModule } from '@angular/core'
// ...

import { AppComponent } from './app.component'
// ...

@NgModule({
 declarations: [
  AppComponent
  // ...
 ],
 imports: [
  BrowserModule.withServerTransition({ appId: 'udao' })
  // ...
 ],
 providers: [],
 bootstrap: [AppComponent]
})
export class AppModule { }

Nous avons besoin d'un fichier principal pour exporter le module serveur

Créez le fichier : src/main.server.ts

export { AppServerModule } from './app/app.server.module'

Mettons maintenant à jour le fichier de configuration de @angular/cli.angular- cli.json

{
 "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
 "project": {
  "name": "udao"
 },
 "apps": [
  {
   "root": "src",
   "outDir": "dist/browser",
   "assets": [
    "assets",
    "favicon.ico"
   ]
   // ...
  },
  {
   "platform": "server",
   "root": "src",
   "outDir": "dist/server",
   "assets": [],
   "index": "index.html",
   "main": "main.server.ts",
   "test": "test.ts",
   "tsconfig": "tsconfig.server.json",
   "testTsconfig": "tsconfig.spec.json",
   "prefix": "app",
   "scripts": [],
   "environmentSource": "environments/environment.ts",
   "environments": {
    "dev": "environments/environment.ts",
    "prod": "environments/environment.prod.ts"
   }
  }
 ]
 // ...
}

Le // ... ci-dessus signifie omis, mais il n'y a pas de commentaire dans json, ce qui a l'air bizarre....

Bien sûr.angular-cli. json La configuration n'est pas figée, vous pouvez la modifier selon vos besoins

Nous devons créer un fichier de configuration tsconfig pour le serveur : src/tsconfig.server.json

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

Ensuite mise à jour : src/tsconfig.app .json

{
 "extends": "../tsconfig.json",
 "compilerOptions": {
  "outDir": "../out-tsc/app",
  "baseUrl": "./",
  "module": "es2015",
  "types": []
 },
 "exclude": [
  "test.ts",
  "**/*.spec.ts",
  "server.ts"
 ]
}

Vous pouvez maintenant exécuter la commande suivante pour voir si la configuration est valide

ng build -prod --build-optimizer --app 0
ng build --aot --app 1

Le résultat de l'exécution doit être celui indiqué ci-dessous

Créez ensuite le service Express.js, créez le fichier : src/server.ts

import 'reflect-metadata'
import 'zone.js/dist/zone-node'
import { renderModuleFactory } from '@angular/platform-server'
import { enableProdMode } from '@angular/core'
import * as express from 'express'
import { join } from 'path'
import { readFileSync } from 'fs'

enableProdMode();

const PORT = process.env.PORT || 4200
const DIST_FOLDER = join(process.cwd(), 'dist')

const app = express()

const template = readFileSync(join(DIST_FOLDER, 'browser', 'index.html')).toString()
const { AppServerModuleNgFactory } = require('main.server')

app.engine('html', (_, options, callback) => {
 const opts = { document: template, url: options.req.url }

 renderModuleFactory(AppServerModuleNgFactory, opts)
  .then(html => callback(null, html))
});

app.set('view engine', 'html')
app.set('views', 'src')

app.get('*.*', express.static(join(DIST_FOLDER, 'browser')))

app.get('*', (req, res) => {
 res.render('index', { req })
})

app.listen(PORT, () => {
 console.log(`listening on http://localhost:${PORT}!`)
})

Bien sûr, vous avez besoin d'un fichier de configuration webpack pour empaqueter le fichier server.ts : webpack.config.js

const path = require('path');
var nodeExternals = require('webpack-node-externals');

module.exports = {
 entry: {
  server: './src/server.ts'
 },
 resolve: {
  extensions: ['.ts', '.js'],
  alias: {
   'main.server': path.join(__dirname, 'dist', 'server', 'main.bundle.js')
  }
 },
 target: 'node',
 externals: [nodeExternals()],
 output: {
  path: path.join(__dirname, 'dist'),
  filename: '[name].js'
 },
 module: {
  rules: [
   { test: /\.ts$/, loader: 'ts-loader' }
  ]
 }
}

Pour faciliter le packaging, il est préférable d'ajouter quelques lignes de script à package.json, comme suit :

"scripts": {
 "ng": "ng",
 "start": "ng serve",
 "build": "run-s build:client build:aot build:server",
 "build:client": "ng build -prod --build-optimizer --app 0",
 "build:aot": "ng build --aot --app 1",
 "build:server": "webpack -p",
 "test": "ng test",
 "lint": "ng lint",
 "e2e": "ng e2e"
}

Essayez maintenant d'exécuter npm run build et vous verrez le résultat suivant :

node exécute le fichier node dist/server.js qui vient d'être emballé

Ouvrir http://localhost:4200/ et la page principale du projet s'affichera normalement

Depuis les outils de développement ci-dessus, vous pouvez voir que le document html est rendu directement par le serveur. Ensuite, essayez de demander des données.

Remarque : Aucune des initialisations de route explicites (menu cliquable) de ce projet ne demande de données, mais la page de détails de l'explication du mot obtiendra des données dans la méthode ngOnInit(), par exemple : http:// Un étrange Le phénomène se produira lorsque localhost:4200/detail/add est ouvert directement. La demande est envoyée une fois au serveur et au client respectivement. La demande des données d'initialisation du premier écran du projet de rendu normal côté serveur est exécutée sur le serveur. côté serveur, mais pas deux fois côté client.

Après avoir découvert le problème, éliminons le gouffre

Imaginez si une marque est utilisée pour distinguer si le serveur a reçu les données. Si les données n'ont pas été obtenues, le client les demandera, n'envoyez pas de demande si les données ont été obtenues

Bien sûr, Angular les a déjà préparées, c'est-à-dire les modules angulaires pour l'état de transfert

Alors comment l’utiliser concrètement ? Voir ci-dessous

Demande de remplissage de la fosse

Introduire TransferStateModule respectivement à l'entrée du serveur et à l'entrée du client

import { ServerModule, ServerTransferStateModule } from '@angular/platform-server';
// ...

@NgModule({
 imports: [
  // ...
  ServerModule,
  ServerTransferStateModule
 ]
 // ...
})
export class AppServerModule { }
import { BrowserModule, BrowserTransferStateModule } from '@angular/platform-browser';
// ...

@NgModule({
 declarations: [
  AppComponent
  // ...
 ],
 imports: [
  BrowserModule.withServerTransition({ appId: 'udao' }),
  BrowserTransferStateModule
  // ...
 ]
 // ...
})
export class AppModule { }

Prendre ce projet comme exemple en détail.composant. ts, Modifier comme suit

import { Component, OnInit } from '@angular/core'
import { HttpClient } from '@angular/common/http'
import { Router, ActivatedRoute, NavigationEnd } from '@angular/router'
import { TransferState, makeStateKey } from '@angular/platform-browser'

const DETAIL_KEY = makeStateKey('detail')

// ...

export class DetailComponent implements OnInit {
 details: any

 // some variable

 constructor(
  private http: HttpClient,
  private state: TransferState,
  private route: ActivatedRoute,
  private router: Router
 ) {}

 transData (res) {
  // translate res data
 }

 ngOnInit () {
  this.details = this.state.get(DETAIL_KEY, null as any)

  if (!this.details) {
   this.route.params.subscribe((params) => {
    this.loading = true

    const apiURL = `https://dict.youdao.com/jsonapi?q=${params['word']}`

    this.http.get(`/?url=${encodeURIComponent(apiURL)}`)
    .subscribe(res => {
     this.transData(res)
     this.state.set(DETAIL_KEY, res as any)
     this.loading = false
    })
   })
  } else {
   this.transData(this.details)
  }
 }
}

Le code est assez simple et clair, conforme au principe décrit ci-dessus

Il ne nous reste plus qu'à apporter de petits ajustements au fichier main.ts pour exécuter notre code when DOMContentLoaded pour que TransferState fonctionne correctement :

import { enableProdMode } from '@angular/core'
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'

import { AppModule } from './app/app.module'
import { environment } from './environments/environment'

if (environment.production) {
 enableProdMode()
}

document.addEventListener('DOMContentLoaded', () => {
 platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.log(err))
})

Allez ici pour exécuter npm run build && node dist/server.js puis actualisez http://localhost:4200/detail/add à la console pour afficher le réseau comme suit :

Il a été constaté qu'aucune demande n'a été initiée dans la catégorie XHR, seul le cache du service-worker a été touché.

Tous les pièges ont été surmontés jusqu'à présent. Le projet fonctionne normalement et aucun autre bug n'a été trouvé.

Résumé

Le premier article en 2018, le but est d'explorer l'implémentation du rendu côté serveur dans tous les frameworks populaires, et d'ouvrir angulaire, un framework qui n'a finalement pas été essayé.

Bien sûr, Orange est encore un élève du primaire, je sais seulement comment le mettre en œuvre. Les principes ne sont pas très clairs, et le code source n'est pas très clair. J'espère que vous pourrez m'éclairer.

L'adresse finale de Github est la même que celle de l'article précédent : https://github.com/OrangeXC/udao

Ce qui précède est ce que j'ai compilé pour tout le monde, j'espère qu'il sera utile. à tout le monde à l'avenir.

Articles associés :

Explication détaillée de la façon d'implémenter vuex (tutoriel détaillé)

Implémentation du paiement WeChat via vue.js

Implémentation du contrôle des autorisations des utilisateurs dans Vue2.0

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn