湯桂川
@GF SECURITIES
Coder/Speaker/Writer
@ Tencent - Front End Engineer
@ Gf Securities - Front End Expert
① What
What 's universal
what's the purpose
② How
How to start
How it works
③ More
More Universal
In FinTech
client
Get Page
Static HTML
server
(CDN
/CGI)
Get CSS Files (or inline)
CSS (page parse)
Get JavaScript Files
JS (events, xhr)
Request Data
Data
Render
Template
* Picture by Wassim Chegham
Get Page
Dynamic HTML(with data in tag...)
server
(CDN
/CGI)
Data fetch by server
Render Template
Async Get Assets(css, js)
Assets(parse, events)
client
- DNS, TCP,RTT
Get Page
Dynamic HTML(with rendered templ)
server
(CDN
/CGI)
Async get assets(css, js)
Assets(parse, events)
Data fetch by server
Interaction
or re-render
Render Template
client
< 1s
Without server-side rendering
With server-side rendering
renderToString
Backbone
FastBoot
not the same
but only look the same
廣發証券原創書籍
《Angular 2》
Github:
lightningtgc/ awesome-ng2
Get Page
HTML(pre-render+preboot.js(inline))
server
(CDN)
Async Get Assets(css, js)
PrebootJS records events
Angular 2 Bootstraping
clientView rendered to hidden div
PrebootJS replays events
PrebootJS switches view context to previously hidden div (clientView)
client
< 1s
interaction
(Why?)
Legacy Browers
YES
YES
YES
NO
NO
NO
Angular 2
Universal
Angular 2
YES
NO
HTML Mail
or non-js
FB, Twitter
Link Preview
Lazy Loding
Performance
universal-starter
universal
preboot
angular 2
parse5
Express(Hapi)
Engine
gulp(webpack)
Prerender
modules
Universal Starter Kit (Express.js + Webpack)
git clone https://github.com/angular/universal-starter.git
npm install
npm start
Node
npm start
server.ts
main.node.ts
index.html
client.ts
main.browser.ts
app.component.ts
Express
engine
Browser
send
①Server Line
②Browser Line
Show me the code?
前方高能。。。
{
"name": "universal-starter",
"version": "2.0.0",
"scripts": {
"start": "npm run server",
"prestart": "npm run build",
"prebuild": "rimraf dist",
"build": "webpack",
"server": "nodemon dist/server/index.js"
}
}
package.json (mini version)
> npm start
①Server Line
import * as express from 'express';
const app = express();
// Angular 2 Universal
import { expressEngine } from 'angular2-universal';
// Express View
app.engine('.html', expressEngine);
app.set('views', __dirname);
app.set('view engine', 'html');
import { ngApp } from './main.node';
// ensure routes match client-side-app
app.get('/', ngApp);
app.get('/home', ngApp);
// Server
let server = app.listen(process.env.PORT || 3000, () => {
console.log(`See: localhost:${server.address().port}`);
});
server.ts (mini version)
import {
REQUEST_URL,ORIGIN_URL,NODE_LOCATION_PROVIDERS,
NODE_HTTP_PROVIDERS,ExpressEngineConfig
} from 'angular2-universal';
import { provideRouter } from '@angular/router';
import { APP_BASE_HREF } from '@angular/common';
// Application
import {App} from './app/app.component';
import {routes} from './app/app.routes';
export function ngApp(req, res) {
let baseUrl = '/';
let url = req.originalUrl || '/';
let config: ExpressEngineConfig = {
directives: [ App ],
platformProviders: [
{provide: ORIGIN_URL, useValue: 'http://localhost:3000'},
{provide: APP_BASE_HREF, useValue: baseUrl},
],
providers: [
{provide: REQUEST_URL, useValue: url},
NODE_HTTP_PROVIDERS,NODE_LOCATION_PROVIDERS
provideRouter(routes)
],
async: true,
preboot: true
};
res.render('index', config);
}
main.node.ts
Async
Gap Event
①
②
@Component({
selector: 'app', // <app></app>
directives: [ ...ROUTER_DIRECTIVES, XLarge ],
styles: [` * { padding:0; margin:0; }`],
template: `<span x-large>Hello {{ title }}!</span>`
})
export class App {
title: string = 'Tang Guichuan';
data = {};
server: string;
constructor(public http: Http) { }
ngOnInit() {
setTimeout(() => {
this.server = 'This was rendered from the server!';
}, 10);
// use services for http calls
this.http.get('/data.json')
.subscribe(res => {
this.data = res.json();
});
}
}
app.component.ts ( just Angular 2 )
Preboot.js
1.preboot.complete()
& replayEvent
3.getInlineCode to insert into the server view.
2.prebootstrap.toString() & recordEvent
export function createPrebootHTML(code: string, config?: any): string {
let html = '';
html += `
<style>
${ getPrebootCSS(config && config.uglify) }
</style>
`;
html += `
<script>
${ code }
</script>
`;
if (config && config.start === true) {
html += '<script>\n preboot.start(); \n</script>';
}
return html;
}
universal/src/node/ng_preboot.ts
<!doctype html>
<html lang="en">
<head>
<title>Angular 2 Universal Starter</title>
<meta charset="UTF-8">
<meta name="description" content="Angular 2 Universal">
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
<base href="/">
</head>
<body>
<app>
Loading Universal ...
</app>
<!-- In server.ts:
app.use(express.static(
path.join(ROOT,'dist/client'),
{index: false}
)); -->
<script src="/index.js"></script>
</body>
</html>
index.html
<!DOCTYPE html><html lang="en"><head>
<title>Angular 2 Universal Starter</title>
<meta charset="UTF-8">
<meta name="description" content="Angular 2 Universal">
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
<base href="/">
<style>*[_ngcontent-yyj-1] { padding:0; margin:0; }
#universal[_ngcontent-yyj-1] { text-align:center; font-weight:bold; padding:15px 0; }
}</style>
</head><body>
<app _nghost-yyj-1="">
<h3 _ngcontent-yyj-1="" id="universal">Angular2 Universal</h3>
</app>
<style>
.preboot-overlay{background:grey;opacity:.27}@keyframes spin{to{transform:rotate(1turn)}}.preboot-spinner{position:relative;display:inline-block;width:5em;height:5em;margin:0 .5em;font-size:12px;text-indent:999em;overflow:hidden;animation:spin 1s infinite steps(8)}
</style>
<script>
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var ]});
</script>
<script>
preboot.start();
</script>
<script src="/index.js"></script>
</body></html>
index.html (with prebootJS)
import {prebootComplete} from 'angular2-universal';
import {ngApp} from './main.browser';
// on document ready bootstrap Angular 2
document.addEventListener('DOMContentLoaded', () => {
ngApp()
.then(prebootComplete);
});
client.ts
② Browser Line
①
②
import {bootstrap} from '@angular/platform-browser-dynamic';
import { provideRouter } from '@angular/router';
import { HTTP_PROVIDERS } from '@angular/http';
// Application
import {App} from './app/app.component';
import {routes} from './app/app.routes';
// you must return bootstrap for client.ts
export function ngApp() {
return bootstrap(App, [
...HTTP_PROVIDERS,
provideRouter(routes)
]);
}
main.browser.ts
export function prebootComplete(value?: any) {
if ('preboot' in window && !prebootCompleted) {
(<any>window).preboot.complete();
}
return value;
}
universal/src/browser/bootstrap.ts
return {
complete: complete,
completeApp: completeApp,
replayEvent: replayEvent,
switchBuffer: switchBuffer,
cleanup: cleanup,
setFocus: setFocus,
findClientNode: findClientNode,
getNodeKey: getNodeKey
};
preboot/src/browser/preboot_browser.ts
< 1s
interaction
Angular 1.x SSR in Github:
...
Application(Components...)
Renderer(createElement, Event handlers..)
DomAdapter(Abstract DOM API)
Dom
Renderer
Native、
Web Worker...
ServerDom
Renderer
Browser
Node.js (Server View)
Parse5DomAdapter
BrowserDomAdapter
Document
pase5
extend
implement
implement
use
use
Bootloader
(bootstrap)
Rendering
Engine
(express...)
configure
①
②
③
① Abstract UI Layer ② Platform Layer ③ Runtime Layer
white: application
yellow: angular2
purple: execution environment
blue: angular-universal
Cross platform
import * as gulp from 'gulp';
import {gulpPrerender} from '@angular/universal';
import {App} from './app';
gulp.task("prerender", () => {
return gulp.src(['index.html'])
.pipe(gulpPrerender({
directives: [App]
}));
});
Killer feature
DEPENDENCY INJECTION
Use localStorage?
Same Interface,
Different implement,
Environmental Identity
Universal Support :
Plan:
( Without localstorage)
Call HTTP request twice?
Others?
/////////////////////////
// ** Example Directive
// Notice we don't touch the Element directly
@Directive({
selector: '[x-large]'
})
export class XLarge {
constructor(element: ElementRef, renderer: Renderer) {
// ** IMPORTANT **
// we must interact with the dom through -Renderer-
// for webworker/server to see the changes
renderer.setElementStyle(element.nativeElement, 'fontSize', 'x-large');
// ^^
}
}
app.component.ts
DO NOT USE Global Namespace such as navigator or document from browser.
More and More?
<thank-you author="汤桂川" [date]="'2016.08'">
</thank-you>
Dream: World Peace!
Email: tangguichuan@gmail.com