Universal Angular 2 In Action
汤桂川
@GF SECURITIES
汤桂川
- Github : @lightningtgc
- Weibo : @汤桂川_gc
- Zhihu : @汤桂川
- Email : tangguichuan@gmail.com
Speaker/Writer/Globetrotter
-- Front End Expert @ GF.S
-- Ex Tencenter
A
① What
-
What 's universal
-
what's the purpose
② How
-
How to start
-
How it works
③ More
-
More Universal
-
In FinTech
g
e
n
a
d
① What
What happens
when you type
www.google.com
in your browser?
Client Server
Three Periods
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
Period I
* Picture by Wassim Chegham
Get Page
Dynamic HTML(with data in tag...)
server
(CDN
/CGI)
Data fetch by server
Render Template
Period II
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
Period III server-side rendering
Render Template
client
More
Optimize?
Node.js
Front End Community
renderToString
2.0
Backbone
FastBoot
WAKE UP
Angular Universal is coming!!!
There are only two hard things in Computer Science:
-
Cache invalidation
-
Naming things
-- Phil Karlton
not the same
but only look the same
- 2011: isomorphic-javascript
- 2013: AirBnb Isomorphic
- 2015: Universal Javascript
Full Stack
Front End:
Back End:
- Angular 2 (DI, Router...)
- TypeScript
- Webpack, system.js...
- Sass, postcss...
- rx.js (Observable)
- zone.js (async)
- Node.js (Express, Hapi)
- Asp.net
- preboot.js
- Parse5...
广发证券原创书籍
《揭秘 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
What's
the purpose?
(Why?)
< 1s
Initial Load
APP take over
Server rendered view
became visible
Without server-side rendering
With server-side rendering
SEO
Initial Load
Legacy Browers
YES
YES
YES
NO
NO
NO
Angular 2
Universal
Angular 2
SPA
Social Link
YES
NO
HTML Mail
or non-js
FB, Twitter
Link Preview
Lazy Loding
Performance
② How
How To Start?
At First
Projects Relations:
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
How it works?
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
Universal
Mechanism
Angular 1.x SSR?
DOM, jqLite
Adapter Pattern
Angular 1.x SSR in Github:
- runvnc/angular-on-server
- a-lucas/angular.js-server
...
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
"Nothing
was achieved
in the comfort zone."
③
More
More
Universal
Universal CLI
Pre-rendering
At Build Time
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
isBrowser,isNoew
More
Back End
Universal Support :
- Node.js ( Express, hapi)
- Asp.net
Plan:
- Java
- PHP(Best Lang?)
- Node: KoaJS 1 ( 2)
More
Optimize
- node-cache (& Universal cache LRU)
- Front End Optimization
( Without localstorage)
Call HTTP request twice?
- Lazy loading (Chunk):
Others?
- Aot & tree-shaking
Best Practice
/////////////////////////
// ** 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.
In FinTech
More and More?
<thank-you author="汤桂川" [date]="'2016.12'">
</thank-you>
Email: tangguichuan@gmail.com
Universal Angular 2 In Action
By Tang Guichuan
Universal Angular 2 In Action
universal angular 2 in action
- 1,806