Эта статья продолжает развиваться на основе предыдущей статьи Angular5, в которой описывался процесс сборки Angular5 перевода Youdao и решения возникших проблем.
Затем UI был изменен, с bootstrap4 на angular material, я не буду здесь вдаваться в подробности, рендеринг на стороне сервера не имеет ничего общего с модификацией UI.
Те, кто читал предыдущие статьи, обнаружат, что содержание статей смещено в сторону рендеринга на стороне сервера, nuxt of vue и next of react.
До этой ревизии я также пытался найти библиотеки упаковки верхнего уровня, такие как nuxt.js и next.js, которые могут сильно сэкономить время, но безуспешно.
В конце концов решил использовать внешнее и внутреннее изоморфное решение, доступное начиная с Angular2.Angular Universal(Универсальная (изоморфная) поддержка JavaScript для Angular.)
Я не буду подробно представлять здесь содержание документа, в этой статье также делается попытка использовать простой и понятный язык, чтобы представить SSR Angular.
помещение
Написанный ранее проект udao полностью совместим с angular-cli, от сборки до упаковки, что также делает эту статью универсальной для всех проектов angular5, созданных с помощью angular-cli.
процесс строительства
Сначала установите зависимости сервера
yarn add @angular/platform-server express
yarn add -D ts-loader webpack-node-externals npm-run-all
Здесь следует отметить, что
@angular/platform-server
Номер версии лучше всего установить в соответствии с текущей версией angular, например:@angular/platform-server@5.1.0
, чтобы избежать конфликтов версий с другими зависимостями.
Создайте файл: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 { }
Файл обновления: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 { }
Нам нужен основной файл для экспорта модуля сервера
Создайте файл:src/main.server.ts
export { AppServerModule } from './app/app.server.module'
обновить сейчас@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"
}
}
]
// ...
}
над// ...
Представитель опущен, но JSON не имеет комментариев, это выглядит странно ....
Конечно.angular-cli.json
Конфигурация не является фиксированной, вы можете изменить ее в соответствии с вашими потребностями.
Нам нужно создать для сервераtsconfig
файл конфигурации: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"
}
}
Затем обновите:src/tsconfig.app.json
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"baseUrl": "./",
"module": "es2015",
"types": []
},
"exclude": [
"test.ts",
"**/*.spec.ts",
"server.ts"
]
}
Теперь вы можете выполнить следующую команду, чтобы увидеть, работает ли конфигурация.
ng build -prod --build-optimizer --app 0
ng build --aot --app 1
Результат операции должен быть таким, как показано ниже
затем создайтеExpress.js
сервис, создайте файл: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}!`)
})
Конечно, вам нужен файл конфигурации webpack для упаковки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' }
]
}
}
Для удобства упаковки лучше всегоpackage.json
Добавьте к нему несколько строк скрипта, например:
"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"
}
Теперь попробуй запуститьnpm run build
, вы увидите такой вывод:
запуск узла только что упакованnode dist/server.js
документ
Открытьhttp://localhost:4200/
Главная страница проекта будет отображаться нормально
Из приведенных выше инструментов разработчика видно, что html-документ напрямую обрабатывается сервером, а затем пытается запросить данные.
Примечание. Несколько явных инициализаций маршрута (можно щелкнуть меню) в этом проекте не запрашивают данные, но страница сведений объяснения слова будет в
ngOnInit()
способ получения данных, например:http://localhost:4200/detail/add
Странное явление будет происходить при его открытии напрямую.Запрос отправляется один раз на стороне сервера и на стороне клиента соответственно.На стороне сервера выполняется обычный запрос на отрисовку исходных данных первого экрана проекта на стороне сервера. , и второго запроса на стороне клиента не будет!
Обнаружив проблему, наступите на эту яму
Представьте, если тег используется для определения того, получил ли сервер данные, если данные не получены, они будут запрошены на стороне клиента, а если данные получены, запрос не будет отправлен.
Конечно, Angular уже подготовил его первым, т.е.Angular Modules for Transfer State
Так как же на самом деле его использовать? следующим образом
Запросить заполнение
Представляем серверный портал и клиентский порталTransferStateModule
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 { }
Возьмите этот проект в качестве примера вdetail.component.ts
Внутри изменить следующим образом
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)
}
}
}
Код простой и достаточно понятный, соответствует описанному выше принципу
Теперь нам просто нужноmain.ts
файл с небольшими изменениями, чтобыDOMContentLoaded
запустите наш код, чтобыTransferState
нормальная работа:
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))
})
беги сюдаnpm run build && node dist/server.js
затем обновитеhttp://localhost:4200/detail/add
Перейдите в консоль для просмотра сети следующим образом:
Было обнаружено, что в категории XHR не было инициировано никакого запроса, только попадание в кеш сервис-воркера.
На данный момент все ямки пройдены, проект работает нормально, других багов не обнаружено.
Суммировать
Первая статья 2018 года, цель которой — изучить реализацию рендеринга на стороне сервера во всех популярных фреймворках и открыть angular, последний фреймворк, который не пробовали.
Конечно Оранж еще фронтенщик начальных классов.Он знает только реализацию.Не очень понятен принцип,и исходники не очень.Если есть ошибки,посоветуйте.
Окончательный адрес Github такой же, как и в предыдущей статье: https://github.com/OrangeXC/udao.
На Github есть онлайн-ссылка, вот и все.