Главная » Статьи » Angular 5 рендеринг на стороне сервера с помощью Firebase – пошаговое руководство

Angular 5 рендеринг на стороне сервера с помощью Firebase – пошаговое руководство

Angular 5 рендеринг на стороне сервера с помощью Firebase – пошаговое руководство

От автора: AngularFire Lite – самая первая и на данный момент единственная библиотека, поддерживающая серверный рендеринг по умолчанию как для Firestore, так и Realtime Database. В библиотеке Angularfire2 есть и другие функции, такие как Storage, Observable based Transactions, Batched Writes и Cloud Messaging.

Чтобы использовать в Angular Firebase для рендеринга на стороне сервера, вам понадобится:

Angular 5

AngularFire Lite (легкая оболочка Angular Firebase API)

История про название: библиотеку назвали AngularFire Lite просто потому, что она на 50% легче Angularfire2. В отличие от цифры 2 в официальной библиотеке, здесь решили подбирать версии Angular, поэтому на данный момент это версия 5. Можете ознакомиться с библиотекой в ее репозитории.

Руководство

Шаг 1: установка зависимостей и генерация нового проекта Angular CLI:

Напоминание: установите nodejs отсюда и Angular CLI с помощью команды:

npm i @angluar/cli

далее сгенерируйте новый проект:

ng new AngularFireLiteSSR

установите следующие зависимости, которые нужны нам для серверного рендера:

npm i @angular/platform-server @nguniversal/module-map-ngfactory-loader ts-loader

Шаг 2: установка AngularFire Lite и Firebase

Смените директорию на папку проекта и установите оба пакета: Angularfire Lite и Firebase. Замечание: на момент написания поста в firebase sdk 4.8.1 есть проблемы, поэтому устанавливайте 4.8.0:

cd AngularFireLiteSSR
npm i angularfire-lite [email protected]

Шаг 3: создание нового проекта Firebase и получение учетных данных

Откройте в браузере https://console.firebase.google.com. Кликните на Add Project, укажите название проекта и далее Create Project:

Angular 5 рендеринг на стороне сервера с помощью Firebase – пошаговое руководство

Angular 5 рендеринг на стороне сервера с помощью Firebase – пошаговое руководство

Для получения учетных данных кликните на Add Firebase to your web app

Angular 5 рендеринг на стороне сервера с помощью Firebase – пошаговое руководство

Скопируйте только объект config

Angular 5 рендеринг на стороне сервера с помощью Firebase – пошаговое руководство

Шаг 4: сохранение учетных данных Firebase в проекте Angular

Откройте файл environment.prod.ts, расположенный в папке src/environments/environment.ts в редакторе и вставьте объект config, полученный из консоли Firebase внутрь своего объекта environment. Повторите то же самое для файла enviroment.ts:

export const environment = { production: true, // production: false => in enviroment.ts config : { apiKey: 'your api key goes here', authDomain: 'your authDomain goes here', databaseURL: 'your databaseUrl goes here', projectId: 'your projectId goes here', storageBucket: 'your storageBucket goes here', messagingSenderId: 'your messagingSenderId goes here' }
}; 

Шаг 5: настройка AngularFire Lite и совместимость с Universal

Откройте корневой модуль app.module.ts и импортируйте AngularFireLite, передайте объект config, который мы храним в файле environment.

Чтобы AppModule был совместим с Universal, необходимо вызвать BrowserModule.withServerTransition и передать appId. Ваш модуль должен выглядеть так:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core'; import { AppComponent } from './app.component';
import { AngularFireLite } from 'angularfire-lite';
import { environment } from '../environments/environment'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, AngularFireLite.forRoot(environment.config), BrowserModule.withServerTransition({appId: 'angularfire-lite-project'}), ], providers: [ ], bootstrap: [AppComponent]
})
export class AppModule { } 

Шаг 6: создание серверного модуля и его экспорт

Создайте новый модуль в папке src/app и назовите его app.server.module.ts. Модуль должен импортировать ваш модуль ядра, за которым следует ServerModule из пакета @angular/platform-server (порядок важен).

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

Для поддержки модуля ленивой загрузки на сервере мы добавили Map Loader Module.

Очень важно импортировать ServerTransferStateModule, он необходим для AngularFire Lite, чтобы создавать переходы без мерцаний при первичной загрузке приложения на Angular. Офигеть!!

Чтобы экспортировать серверный модуль, создайте новый файл main.server.ts в папке /src и скопируйте в него следующую строку:

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

Шаг 7: создание tsconfig для пакета Universal

Просто скопируйте файл tsconfig, который Angular CLI генерирует (tsconfig.app.json в папке /src) и назовите новый файл tsconfig.server.json

Нужно сделать всего пару вещей

Сменить назначение модуля: с es2015 на commonjs, чей узел мы используем

Добавить angularcompilerOptions, чтобы компилятор знал о его модуле ввода AppServerModule. Мы используем хэш-символ в пути, чтобы достичь реального класса.

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

Шаг 8: настройка Angular CLI для создания серверного приложения

Откройте файл .angular-cli.json в корне проекта – это файл настроек Angular CLI, там описаны способы создания приложения.

Взгляните на массив apps – в нем хранятся записи, которые сообщают Angular CLI, как собрать клиентское приложение с помощью простой команды ng build.

В массив apps необходимо добавить новый объект, чтобы Angular CLI знал, как компилировать наше серверное приложение. Ваш массив apps должен выглядеть так:

"apps": [ { "root": "src", "outDir": "dist/browser", "assets": [ "assets", "favicon.ico" ], "index": "index.html", "main": "main.ts", "polyfills": "polyfills.ts", "test": "test.ts", "tsconfig": "tsconfig.app.json", "testTsconfig": "tsconfig.spec.json", "prefix": "app", "styles": [ "styles.css" ], "scripts": [], "environmentSource": "environments/environment.ts", "environments": { "dev": "environments/environment.ts", "prod": "environments/environment.prod.ts" } }, { "name": "ssr", "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": "app", "styles": [ "styles.css" ], "scripts": [], "environmentSource": "environments/environment.ts", "environments": { "dev": "environments/environment.ts", "prod": "environments/environment.prod.ts" } }
], 

В первой записи необходимо лишь изменить outDir на dist/browser, чтобы в этой папке хранился пакет клиентского приложения.

Во второй записи ssr мы говорим CLI, что необходимая нам платформа – это сервер, а наш пакет Universal должен быть в dist/server. Также это необходимо указать в tsconfig, который мы создали для сервера и главного файла сервера.

Шаг 9: подъем Express Server и использование Webpack

Не буду вдаваться в детали, но здесь мы создаем экспресс сервер для локального использования нашего серверного приложения.

Создайте server.ts в корне файла проекта и файл webpack.server.config.js для упаковки файлы ts с помощью ts-loader, который мы установили ранее

Файл server.ts:

// These are important and needed before anything else
import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import { renderModuleFactory } from '@angular/platform-server';
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 = 5000;
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'); const {provideModuleMap} = require('@nguniversal/module-map-ngfactory-loader'); app.engine('html', (_, options, callback) => { renderModuleFactory(AppServerModuleNgFactory, { // Our index.html document: template, url: options.req.url, // DI so that we can get lazy-loading to work differently (since we need it to just instantly render it) extraProviders: [ provideModuleMap(LAZY_MODULE_MAP) ] }).then(html => { callback(null, html); });
}); app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser')); // Server static files from /browser
app.get('*.*', express.static(join(DIST_FOLDER, 'browser'))); // All regular routes use the Universal engine
app.get('*', (req, res) => { res.render(join(DIST_FOLDER, 'browser', 'index.html'), {req});
}); // Start up the Node server
app.listen(PORT, () => { console.log(`Node server listening on http://localhost:${PORT}`);
}); 

Файл webpack.server.config.js:

const path = require('path');
const webpack = require('webpack'); module.exports = { entry: { server: './server.ts' }, resolve: { extensions: ['.js', '.ts'] }, target: 'node', // this makes sure we include node_modules and other 3rd party libraries externals: [/(node_modules|main\..*\.js)/], output: { path: path.join(__dirname, 'dist'), filename: '[name].js' }, module: { rules: [ { test: /\.ts$/, loader: 'ts-loader' } ] }, plugins: [ // Temporary Fix for issue: https://github.com/angular/angular/issues/11580 // for "WARNING Critical dependency: the request of a dependency is an expression" new webpack.ContextReplacementPlugin( /(.+)?angular(\\|\/)core(.+)?/, path.join(__dirname, 'src'), // location of your src {} // a map of your routes ), new webpack.ContextReplacementPlugin( /(.+)?express(\\|\/)(.+)?/, path.join(__dirname, 'src'), {} ) ]
} 

Шаг 10: скрипт NPM для пакета и использования приложения

Теперь нам осталось лишь добавить скрипт npm, чтобы сэкономить время написания длинной команды для создания приложения через CLI, запаковать в файл server.ts с помощью Webpack и запустить с помощью узла.

Скопируйте следующую команду в объект scripts в файл package.json:

"universal": "ng build --prod && ng build --prod --app ssr --output-hashing=false && webpack --config webpack.server.config.js --progress --colors && node dist/server.js"

Использование AngularFire Lite

Здесь мы не будем делать ничего сложного, просто распечатаем кое-какие данные из Firestore в тег header, но не уходите, скоро я выпущу новое пошаговое руководство по создания красивых приложений на AngularFire Lite, готовых к выкату в продакшн.

Если добавить документ и назвать его hello с полем firestore и значением Hello Firestore from AngularFire Lite, то его можно легко вытащить в app.component.ts следующим образом:

Импортируете AngularFireLiteFirestore и вставляете его в конструктор

Добавляете ссылку в хук жизненного цикла ngOnInt и вызываете метод read

Подписываетесь на компонент или шаблон с помощью async пайпа для получения данных

Ниже показан пример использования нескольких функций AngularFire Lite, среди которых Realtime Database, Firestore (читает и запаковывает записи) и authentication (не забудьте установить права на чтение и запись в панели управления Firebase в true чисто для теста).

import {Component, OnInit} from '@angular/core'; import {AngularFireLiteAuth, AngularFireLiteDatabase, AngularFireLiteFirestore} from 'angularfire-lite'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit { constructor(public db: AngularFireLiteDatabase, public auth: AngularFireLiteAuth, public firestore: AngularFireLiteFirestore) { } databaseData; databaseList; databaseQuery; firestoreData; firestoreList; firestoreQuery; authState; ngOnInit() { // Realtime Database this.db.read('hello/hello').subscribe((data) => { this.databaseData = data; }); // Realtime Database list retrieval this.databaseList = this.db.read('hello'); // Realtime Database query this.db.query('hello').limitToLast(1).orderByKey().on('value').subscribe((data) => { this.databaseQuery = data; }); // Firestore this.firestore.read('hello/hellodoc').subscribe((data) => { this.firestoreData = data; }); // Firestore list retrieval this.firestoreList = this.firestore.read('hello'); // Firestore Query this.firestore.query('query').limit(1).on().subscribe((data) => { this.firestoreQuery = data; }); // Authentication this.auth.isAuthenticated().subscribe((isAuth) => { this.authState = isAuth; }); } // Login Button Clicked login() { this.auth.signin('[email protected]', '123456'); } } 

И шаблон:

<br>
<h1>AngularFire Lite Realtime Database</h1>
<br> <span style="color:crimson">realtime database </span><h4>{{databaseData}}</h4>
<span style="color:crimson">realtime database list of data</span>
<ul> <li *ngFor="let item of databaseList | async">{{item}}</li>
</ul> <span style="color:crimson">realtime database query</span><h4>{{databaseQuery}}</h4> <br>
<h1>AngularFire Lite Firestore</h1>
<br> <span style="color:crimson">firestore database</span><h4>{{firestoreData?.hellofield }}</h4>
<span style="color:crimson">firebase database list of data</span>
<ul> <li *ngFor="let doc of firestoreList | async">{{doc | json}}</li>
</ul>
<span style="color:crimson">firestore query</span><h4 *ngFor="let query of firestoreQuery">{{query?.data1}}</h4>
<br>
<br> <span>I am logged in?</span><h4>{{ authState }}</h4> <button (click)="login()">login</button> 

Наслаждайтесь серверным великолепием! Осталось лишь запустить

npm run universal

Откройте http://localhost:5000/, там должны появиться ваши данных, так как вы храните их в Realtime Database или Firestore. Если открыть исходный код, можно посмотреть реальный HTML.

Angular 5 рендеринг на стороне сервера с помощью Firebase – пошаговое руководство

Вы получаете преимущества в:

SEO и иконки социальных сетей на общем контенте

Меньше время загрузки (первый рендер/первая отрисовка)

Вам лень? Клонируйте демо репозиторий AngularFire Lite

Если вам немного лень, клонируйте демо репозиторий AngularFire Lite с GitHub по ссылке.

Что по поводу рендера с помощью Firebase Cloud Functions и firebase хостинга?

Эта статья входит в серию, и я обязательно расскажу про эти темы. Я хотел начать с базовых принципов серверного рендера и продемонстрировать, что AngularFire Lite действительно позволяет Angular Universal рендерить HTML, используя данные, хранящиеся в Firebase realtime database или firestore, в отличие от Angularfire2, который выбрасывает ошибку.

Если не хотите пропустить будущие руководства, подпишитесь. Вы будете получать уведомления при выходе новых статей. Увидимся!

Автор: Hamed Baatour

Источник: https://medium.com/

Редакция: Команда webformyself.