概览
在高的层次上,Angular Universal有两个主要方面:
- 服务端渲染意味着生成指定路由页面的全部HTML
- 在浏览器将服务器视图转换为客户端视图
服务端渲染有两种不同的处理方式。第一种选择是预渲染你的应用,也就是说你可以使用Universal构建工具(例如 gulp,grunt,broccoli,webpack等)来在构建期间为所有路由生成静态HTML。然后你可以把静态HTML部署到CDN。这个方法的好处是高伸缩性和高性能。缺点是灵活性不如第二种方法。
第二种方法是在web服务器对每一个请求动态重渲染你的应用。也有一些缓存选项来提高伸缩性和性能,但是你需要对每个请求在Angular Universal上下文中运行你的应用代码。
Angular Universal最初被构建用来和node.js后端协同工作。对大多数流行的node.js服务器端框架例如Express或Hapi.js都有适配器。处node.js之外,Angular Universal具有ASP.NET核心支持。我们希望在不久的未来提供对Java,PHP和Python的支持。
服务端渲染的步骤
预渲染的步骤:
- 通过构建工具生成静态HTML
- 部署HTML到CDN
- CDN提供服务器视图
- 服务器视图到客户端视图的转换(后面说)
重渲染的步骤
- HTTP GET请求发送到服务器
- 服务器为预加载(Preboot)生成包含已渲染的HTML和内联JavaScript的页面。(并且出于缓存的目的,你也可以选择性的添加序列化的数据)
- 服务器视图到客户端视图的转换(后面说)
服务器视图到客户端视图的转换
- 浏览器从服务器收到初始化数据(负载,payload)
- 用户看到服务器视图
- 预加载创建隐藏的div,被用来进行客户端启动和记录事件
- 浏览器对其他的资源进行异步请求(例如图片,JS,CSS等)
- 一旦外部资源加载完毕,Angular客户端再次启动
- 客户端视图被渲染到预加载时创建的div中
- 启动完成,Angular客户端调用preboot.done()
- 预加载事件重新执行,为了反应用户在Angular启动之前造成的改变而对应用状态进行调整(例如在文本框中输入,点击按钮等)
- 预加载将可见的服务器视图div转换为隐藏的客户端视图div
- 最后,预加载在可见的客户端视图执行一些善后,包括设置焦点。
注意这些工作中的大多数时可以配置的并且可以对你的情况进行调整。例如,你可以精确地控制预加载怎样监听事件、重新执行他们和服务器视图到客户端视图的转换。也有一些性能优化选项。
入门
初始配置
有很多可能的对于Angular Universal的配置。为了快速入门,我们把注意力放在大多数共同的步骤上:
- Angular 2
- Node.js后端
- Webpack
1.Angular 2 应用
我们不会回顾怎样创建Angular 2 应用。如果你没有创建过一个Angular 2 应用,请参阅相关文档。
2.安装依赖
在你的应用根目录执行
> npm install body-parser angular2-universal preboot express --save
> typings install node express body-parser serve-static express-serve-static-core mime --ambient
3.添加一个服务器
创建一个文件叫做server.js在你的应用根目录,包含下面的代码。读其中的注释,根据你的Angular 2 应用做一些适当的调整。
//polyfills必须是node import的第一个东西
import 'angular2-universal/polyfills';
import * as path from 'path';
import * as express from 'express';
// Angular 2 Universal
import {provideRouter} from '@angular/router';
import {enableProdMode} from '@angular/core';
import {
expressEngine,
BASE_URL,
REQUEST_URL,
ORIGIN_URL,
NODE_LOCATION_PROVIDERS,
NODE_HTTP_PROVIDERS,
ExpressEngineConfig
} from 'angular2-universal';
// 把这行替换为你Angular 2 的根组件
import {App, routes} from './app';
const app = express();
const ROOT = path.join(path.resolve(__dirname, '..'));
enableProdMode();
// Express 视图
app.engine('.html', expressEngine);
app.set('views', __dirname);
app.set('view engine', 'html');
function ngApp(req, res) {
let baseUrl = '/';
let url = req.originalUrl || '/';
let config: ExpressEngineConfig = {
directives: [ App ],
// 在到达服务器的所用请求之间共享的依赖
platformProviders: [
{provide: ORIGIN_URL, useValue: 'http://localhost:3000'},
{provide: BASE_URL, useValue: baseUrl},
],
// 为每个请求重复创建的依赖
providers: [
{provide: REQUEST_URL, useValue: url},
provideRouter(routes),
NODE_LOCATION_PROVIDERS,
NODE_HTTP_PROVIDERS,
],
// 如果为true,服务器会在所有异步完成后返回响应
async: true,
// 如果你想要预加载,你需要为app根设置选择器
// 你可以在这里包含一些预加载选项(在其他文档中解释)
preboot: false // { appRoot: 'app' }
};
res.render('index', config);
}
// 服务静态文件
app.use(express.static(ROOT, {index: false}));
// 将所有请求发送到Angular Universal
// 如果你想要Express处理某些路由(例如一个API),要调整这里
app.get('/', ngApp);
app.get('/home', ngApp);
app.get('/about', ngApp);
// 服务
app.listen(3000, () => {
console.log('Listening on: http://localhost:3000');
});
4.开启服务
node server.js