diff --git a/modules/@angular/common/src/location/platform_location.ts b/modules/@angular/common/src/location/platform_location.ts index 0ca4de54ca87c7..4f98da6aad4c45 100644 --- a/modules/@angular/common/src/location/platform_location.ts +++ b/modules/@angular/common/src/location/platform_location.ts @@ -29,9 +29,9 @@ export abstract class PlatformLocation { abstract onPopState(fn: UrlChangeListener): void; abstract onHashChange(fn: UrlChangeListener): void; - /* abstract */ get pathname(): string { return null; } - /* abstract */ get search(): string { return null; } - /* abstract */ get hash(): string { return null; } + get pathname(): string { return null; } + get search(): string { return null; } + get hash(): string { return null; } abstract replaceState(state: any, title: string, url: string): void; diff --git a/modules/@angular/compiler-cli/integrationtest/test/animate_spec.ts b/modules/@angular/compiler-cli/integrationtest/test/animate_spec.ts index 3e9e30f2165831..187cd6f957a6ce 100644 --- a/modules/@angular/compiler-cli/integrationtest/test/animate_spec.ts +++ b/modules/@angular/compiler-cli/integrationtest/test/animate_spec.ts @@ -4,7 +4,7 @@ require('zone.js/dist/long-stack-trace-zone.js'); import {AnimateCmpNgFactory} from '../src/animate.ngfactory'; import {ReflectiveInjector, DebugElement, getDebugNode} from '@angular/core'; -import {browserPlatform, BROWSER_APP_PROVIDERS} from '@angular/platform-browser'; +import {serverPlatform, SERVER_APPLICATION_PROVIDERS} from '@angular/platform-server'; describe('template codegen output', () => { function findTargetElement(elm: DebugElement): DebugElement { @@ -14,8 +14,8 @@ describe('template codegen output', () => { } it('should apply the animate states to the element', (done) => { - const appInjector = - ReflectiveInjector.resolveAndCreate(BROWSER_APP_PROVIDERS, browserPlatform().injector); + const appInjector = ReflectiveInjector.resolveAndCreate( + SERVER_APPLICATION_PROVIDERS, serverPlatform().injector); var comp = AnimateCmpNgFactory.create(appInjector); var debugElement = getDebugNode(comp.location.nativeElement); @@ -42,8 +42,8 @@ describe('template codegen output', () => { }); it('should apply the default animate state to the element', (done) => { - const appInjector = - ReflectiveInjector.resolveAndCreate(BROWSER_APP_PROVIDERS, browserPlatform().injector); + const appInjector = ReflectiveInjector.resolveAndCreate( + SERVER_APPLICATION_PROVIDERS, serverPlatform().injector); var comp = AnimateCmpNgFactory.create(appInjector); var debugElement = getDebugNode(comp.location.nativeElement); diff --git a/modules/@angular/compiler-cli/integrationtest/test/basic_spec.ts b/modules/@angular/compiler-cli/integrationtest/test/basic_spec.ts index 493cd5ae8b31e5..455bf55683521e 100644 --- a/modules/@angular/compiler-cli/integrationtest/test/basic_spec.ts +++ b/modules/@angular/compiler-cli/integrationtest/test/basic_spec.ts @@ -10,7 +10,8 @@ import * as path from 'path'; import {BasicNgFactory} from '../src/basic.ngfactory'; import {MyComp} from '../src/a/multiple_components'; import {ReflectiveInjector, DebugElement, getDebugNode} from '@angular/core'; -import {browserPlatform, BROWSER_APP_PROVIDERS} from '@angular/platform-browser'; +import {serverPlatform} from '@angular/platform-server'; +import {BROWSER_APP_PROVIDERS} from '@angular/platform-browser'; describe('template codegen output', () => { const outDir = 'src'; @@ -37,14 +38,14 @@ describe('template codegen output', () => { it('should be able to create the basic component', () => { const appInjector = - ReflectiveInjector.resolveAndCreate(BROWSER_APP_PROVIDERS, browserPlatform().injector); + ReflectiveInjector.resolveAndCreate(BROWSER_APP_PROVIDERS, serverPlatform().injector); var comp = BasicNgFactory.create(appInjector); expect(comp.instance).toBeTruthy(); }); it('should support ngIf', () => { const appInjector = - ReflectiveInjector.resolveAndCreate(BROWSER_APP_PROVIDERS, browserPlatform().injector); + ReflectiveInjector.resolveAndCreate(BROWSER_APP_PROVIDERS, serverPlatform().injector); var comp = BasicNgFactory.create(appInjector); var debugElement = getDebugNode(comp.location.nativeElement); expect(debugElement.children.length).toBe(2); @@ -57,7 +58,7 @@ describe('template codegen output', () => { it('should support ngFor', () => { const appInjector = - ReflectiveInjector.resolveAndCreate(BROWSER_APP_PROVIDERS, browserPlatform().injector); + ReflectiveInjector.resolveAndCreate(BROWSER_APP_PROVIDERS, serverPlatform().injector); var comp = BasicNgFactory.create(appInjector); var debugElement = getDebugNode(comp.location.nativeElement); expect(debugElement.children.length).toBe(2); diff --git a/modules/@angular/compiler-cli/integrationtest/test/projection_spec.ts b/modules/@angular/compiler-cli/integrationtest/test/projection_spec.ts index 2ddc9b635d2e62..5be79358755933 100644 --- a/modules/@angular/compiler-cli/integrationtest/test/projection_spec.ts +++ b/modules/@angular/compiler-cli/integrationtest/test/projection_spec.ts @@ -1,5 +1,6 @@ import {DebugElement, ReflectiveInjector, getDebugNode} from '@angular/core'; -import {BROWSER_APP_PROVIDERS, By, browserPlatform} from '@angular/platform-browser'; +import {BROWSER_APP_PROVIDERS, By} from '@angular/platform-browser'; +import {serverPlatform} from '@angular/platform-server'; import {CompWithProjection} from '../src/projection'; import {MainCompNgFactory} from '../src/projection.ngfactory'; @@ -7,7 +8,7 @@ import {MainCompNgFactory} from '../src/projection.ngfactory'; describe('content projection', () => { it('should support basic content projection', () => { const appInjector = - ReflectiveInjector.resolveAndCreate(BROWSER_APP_PROVIDERS, browserPlatform().injector); + ReflectiveInjector.resolveAndCreate(BROWSER_APP_PROVIDERS, serverPlatform().injector); var mainComp = MainCompNgFactory.create(appInjector); var debugElement = getDebugNode(mainComp.location.nativeElement); diff --git a/modules/@angular/platform-server/core_private.ts b/modules/@angular/platform-server/core_private.ts index 1c7f41a3791024..c5b8be3d1f9f9c 100644 --- a/modules/@angular/platform-server/core_private.ts +++ b/modules/@angular/platform-server/core_private.ts @@ -1,9 +1,8 @@ import {__core_private__ as r, __core_private_types__ as t} from '@angular/core'; -export type NoOpAnimationPlayer = t.NoOpAnimationPlayer; -export var NoOpAnimationPlayer: typeof t.NoOpAnimationPlayer = r.NoOpAnimationPlayer; -export type AnimationPlayer = t.AnimationPlayer; -export var AnimationPlayer: typeof t.AnimationPlayer = r.AnimationPlayer; +export var reflector: typeof t.reflector = r.reflector; +export var ReflectionCapabilities: typeof t.ReflectionCapabilities = r.ReflectionCapabilities; +export var wtfInit: typeof t.wtfInit = r.wtfInit; export type NoOpAnimationDriver = t.NoOpAnimationDriver; export var NoOpAnimationDriver: typeof t.NoOpAnimationDriver = r.NoOpAnimationDriver; export type AnimationDriver = t.AnimationDriver; diff --git a/modules/@angular/platform-server/index.dart b/modules/@angular/platform-server/index.dart index 61ae1b68095692..ff9d6668222352 100644 --- a/modules/@angular/platform-server/index.dart +++ b/modules/@angular/platform-server/index.dart @@ -1,2 +1,2 @@ -// TODO: vsavkin add SERVER_PROVIDERS and SERVER_APP_PROVIDERS +// TODO: vsavkin add SERVER_PROVIDERS and SERVER_APPLICATION_PROVIDERS export 'package:angular2/src/platform/server/html_adapter.dart'; diff --git a/modules/@angular/platform-server/index.ts b/modules/@angular/platform-server/index.ts index 2676ccb72cc891..df948c2c941e7f 100644 --- a/modules/@angular/platform-server/index.ts +++ b/modules/@angular/platform-server/index.ts @@ -1,2 +1 @@ -// TODO: vsavkin add SERVER_PROVIDERS and SERVER_APP_PROVIDERS -export {Parse5DomAdapter} from './src/parse5_adapter'; +export {SERVER_PLATFORM_PROVIDERS, serverBootstrap, serverPlatform} from './src/server'; diff --git a/modules/@angular/platform-server/src/server.ts b/modules/@angular/platform-server/src/server.ts new file mode 100644 index 00000000000000..f964ebe3f712af --- /dev/null +++ b/modules/@angular/platform-server/src/server.ts @@ -0,0 +1,72 @@ +import {PlatformLocation} from '@angular/common'; +import {ComponentRef, OpaqueToken, PLATFORM_COMMON_PROVIDERS, PLATFORM_INITIALIZER, PlatformRef, ReflectiveInjector, Type, assertPlatform, coreLoadAndBootstrap, createPlatform, getPlatform} from '@angular/core'; + +import {ReflectionCapabilities, reflector, wtfInit} from '../core_private'; + +import {Parse5DomAdapter} from './parse5_adapter'; + +const SERVER_PLATFORM_MARKER = new OpaqueToken('ServerPlatformMarker'); + +function notSupported(feature: string): Error { + throw new Error(`platform-server does not support '${feature}'.`); +} + +class ServerPlatformLocation extends PlatformLocation { + getBaseHrefFromDOM(): string { throw notSupported('getBaseHrefFromDOM'); }; + onPopState(fn: any): void { notSupported('onPopState'); }; + onHashChange(fn: any): void { notSupported('onHashChange'); }; + get pathname(): string { throw notSupported('pathname'); } + get search(): string { throw notSupported('search'); } + get hash(): string { throw notSupported('hash'); } + replaceState(state: any, title: string, url: string): void { notSupported('replaceState'); }; + pushState(state: any, title: string, url: string): void { notSupported('pushState'); }; + forward(): void { notSupported('forward'); }; + back(): void { notSupported('back'); }; +} + +/** + * A set of providers to initialize the Angular platform in a server. + * + * Used automatically by `serverBootstrap`, or can be passed to {@link platform}. + */ +export const SERVER_PLATFORM_PROVIDERS: Array = [ + {provide: SERVER_PLATFORM_MARKER, useValue: true}, PLATFORM_COMMON_PROVIDERS, + {provide: PLATFORM_INITIALIZER, useValue: initParse5Adapter, multi: true}, + {provide: PlatformLocation, useClass: ServerPlatformLocation} +]; + + +function initParse5Adapter() { + Parse5DomAdapter.makeCurrent(); + wtfInit(); +} + + +export function serverPlatform(): PlatformRef { + if (!getPlatform()) { + createPlatform(ReflectiveInjector.resolveAndCreate(SERVER_PLATFORM_PROVIDERS)); + } + return assertPlatform(SERVER_PLATFORM_MARKER); +} + +/** + * Used to bootstrap Angular in server environment (such as node). + * + * This version of bootstrap only creates platform injector and does not define anything for + * application injector. It is expected that application providers are imported from other + * packages such as `@angular/platform-browser` or `@angular/platform-browser-dynamic`. + * + * ``` + * import {BROWSER_APP_PROVIDERS} from '@angular/platform-browser'; + * import {BROWSER_APP_COMPILER_PROVIDERS} from '@angular/platform-browser-dynamic'; + * + * serverBootstrap(..., [BROWSER_APP_PROVIDERS, BROWSER_APP_COMPILER_PROVIDERS]) + * ``` + */ +export function serverBootstrap( + appComponentType: Type, + providers: Array): Promise> { + reflector.reflectionCapabilities = new ReflectionCapabilities(); + var appInjector = ReflectiveInjector.resolveAndCreate(providers, serverPlatform().injector); + return coreLoadAndBootstrap(appComponentType, appInjector); +} diff --git a/modules/@angular/platform-server/test/integration_spec.ts b/modules/@angular/platform-server/test/integration_spec.ts new file mode 100644 index 00000000000000..3c42453a9a1937 --- /dev/null +++ b/modules/@angular/platform-server/test/integration_spec.ts @@ -0,0 +1,34 @@ +import {Component, disposePlatform} from '@angular/core'; +import {afterEach, async, beforeEach, ddescribe, describe, expect, iit, inject, it, xdescribe, xit} from '@angular/core/testing'; +import {BROWSER_APP_PROVIDERS} from '@angular/platform-browser'; +import {BROWSER_APP_COMPILER_PROVIDERS} from '@angular/platform-browser-dynamic'; +import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; +import {serverBootstrap} from '@angular/platform-server'; + +function writeBody(html: string): any { + var dom = getDOM(); + var doc = dom.defaultDoc(); + var body = dom.querySelector(doc, 'body'); + dom.setInnerHTML(body, html); + return body; +} + +export function main() { + if (getDOM().supportsDOMEvents()) return; // NODE only + + describe('platform-server integration', () => { + + afterEach(() => disposePlatform()); + + it('should bootstrap', async(() => { + var body = writeBody(''); + serverBootstrap(MyServerApp, [ + BROWSER_APP_PROVIDERS, BROWSER_APP_COMPILER_PROVIDERS + ]).then(() => { expect(getDOM().getText(body)).toEqual('Works!'); }); + })); + }); +} + +@Component({selector: 'app', template: `Works!`}) +class MyServerApp { +} diff --git a/tools/public_api_guard/public_api_spec.ts b/tools/public_api_guard/public_api_spec.ts index d4af12dbff20d0..60849cb7504f5a 100644 --- a/tools/public_api_guard/public_api_spec.ts +++ b/tools/public_api_guard/public_api_spec.ts @@ -1661,129 +1661,9 @@ const PLATFORM_BROWSER_DYNAMIC_TESTING = [ const PLATFORM_SERVER = [ - 'Parse5DomAdapter', - 'Parse5DomAdapter.addClass(element:any, className:string):any', - 'Parse5DomAdapter.adoptNode(node:any):any', - 'Parse5DomAdapter.animate(element:any, keyframes:any[], options:any):any', - 'Parse5DomAdapter.appendChild(el:any, node:any):any', - 'Parse5DomAdapter.attributeMap(element:any):Map', - 'Parse5DomAdapter.attrToPropMap:any', - 'Parse5DomAdapter.cancelAnimationFrame(id:number):any', - 'Parse5DomAdapter.childNodes(el:any):Node[]', - 'Parse5DomAdapter.childNodesAsList(el:any):any[]', - 'Parse5DomAdapter.classList(element:any):string[]', - 'Parse5DomAdapter.clearNodes(el:any):any', - 'Parse5DomAdapter.clone(node:Node):Node', - 'Parse5DomAdapter.content(node:any):string', - 'Parse5DomAdapter.createComment(text:string):Comment', - 'Parse5DomAdapter.createElement(tagName:any):HTMLElement', - 'Parse5DomAdapter.createElementNS(ns:any, tagName:any):HTMLElement', - 'Parse5DomAdapter.createEvent(eventType:string):Event', - 'Parse5DomAdapter.createHtmlDocument():Document', - 'Parse5DomAdapter.createMouseEvent(eventType:any):Event', - 'Parse5DomAdapter.createScriptTag(attrName:string, attrValue:string):HTMLElement', - 'Parse5DomAdapter.createShadowRoot(el:any):HTMLElement', - 'Parse5DomAdapter.createStyleElement(css:string):HTMLStyleElement', - 'Parse5DomAdapter.createTemplate(html:any):HTMLElement', - 'Parse5DomAdapter.createTextNode(text:string):Text', - 'Parse5DomAdapter.defaultDoc():Document', - 'Parse5DomAdapter.dispatchEvent(el:any, evt:any):any', - 'Parse5DomAdapter.elementMatches(node:any, selector:string, matcher:any=null):boolean', - 'Parse5DomAdapter.firstChild(el:any):Node', - 'Parse5DomAdapter.getAnimationPrefix():string', - 'Parse5DomAdapter.getAttribute(element:any, attribute:string):string', - 'Parse5DomAdapter.getAttributeNS(element:any, ns:string, attribute:string):string', - 'Parse5DomAdapter.getBaseHref():string', - 'Parse5DomAdapter.getBoundingClientRect(el:any):any', - 'Parse5DomAdapter.getChecked(el:any):boolean', - 'Parse5DomAdapter.getComputedStyle(el:any):any', - 'Parse5DomAdapter.getCookie(name:string):string', - 'Parse5DomAdapter.getData(el:any, name:string):string', - 'Parse5DomAdapter.getDistributedNodes(el:any):Node[]', - 'Parse5DomAdapter.getElementsByClassName(element:any, name:string):HTMLElement[]', - 'Parse5DomAdapter.getElementsByTagName(element:any, name:string):HTMLElement[]', - 'Parse5DomAdapter.getEventKey(event:any):string', - 'Parse5DomAdapter.getGlobalEventTarget(target:string):any', - 'Parse5DomAdapter.getHistory():History', - 'Parse5DomAdapter.getHost(el:any):string', - 'Parse5DomAdapter.getHref(el:any):string', - 'Parse5DomAdapter.getInnerHTML(el:any):string', - 'Parse5DomAdapter.getLocation():Location', - 'Parse5DomAdapter.getOuterHTML(el:any):string', - 'Parse5DomAdapter.getProperty(el:any, name:string):any', - 'Parse5DomAdapter.getShadowRoot(el:any):Element', - 'Parse5DomAdapter.getStyle(element:any, styleName:string):string', - 'Parse5DomAdapter.getTemplateContent(el:any):Node', - 'Parse5DomAdapter.getText(el:any, isRecursive?:boolean):string', - 'Parse5DomAdapter.getTitle():string', - 'Parse5DomAdapter.getTransitionEnd():string', - 'Parse5DomAdapter.getUserAgent():string', - 'Parse5DomAdapter.getValue(el:any):string', - 'Parse5DomAdapter.getXHR():Type', - 'Parse5DomAdapter.hasAttribute(element:any, attribute:string):boolean', - 'Parse5DomAdapter.hasAttributeNS(element:any, ns:string, attribute:string):boolean', - 'Parse5DomAdapter.hasClass(element:any, className:string):boolean', - 'Parse5DomAdapter.hasProperty(element:any, name:string):boolean', - 'Parse5DomAdapter.hasShadowRoot(node:any):boolean', - 'Parse5DomAdapter.hasStyle(element:any, styleName:string, styleValue:string=null):boolean', - 'Parse5DomAdapter.importIntoDoc(node:any):any', - 'Parse5DomAdapter.insertAfter(el:any, node:any):any', - 'Parse5DomAdapter.insertAllBefore(el:any, nodes:any):any', - 'Parse5DomAdapter.insertBefore(el:any, node:any):any', - 'Parse5DomAdapter.invoke(el:Element, methodName:string, args:any[]):any', - 'Parse5DomAdapter.isCommentNode(node:any):boolean', - 'Parse5DomAdapter.isElementNode(node:any):boolean', - 'Parse5DomAdapter.isPrevented(evt:any):boolean', - 'Parse5DomAdapter.isShadowRoot(node:any):boolean', - 'Parse5DomAdapter.isTemplateElement(el:any):boolean', - 'Parse5DomAdapter.isTextNode(node:any):boolean', - 'Parse5DomAdapter.log(error:any):any', - 'Parse5DomAdapter.logError(error:any):any', - 'Parse5DomAdapter.logGroup(error:any):any', - 'Parse5DomAdapter.logGroupEnd():any', - 'Parse5DomAdapter.makeCurrent():any', - 'Parse5DomAdapter.nextSibling(el:any):Node', - 'Parse5DomAdapter.nodeName(node:any):string', - 'Parse5DomAdapter.nodeValue(node:any):string', - 'Parse5DomAdapter.on(el:any, evt:any, listener:any):any', - 'Parse5DomAdapter.onAndCancel(el:any, evt:any, listener:any):Function', - 'Parse5DomAdapter.parentElement(el:any):Node', - 'Parse5DomAdapter.parse(templateHtml:string):any', - 'Parse5DomAdapter.performanceNow():number', - 'Parse5DomAdapter.preventDefault(evt:any):any', - 'Parse5DomAdapter.query(selector:any):any', - 'Parse5DomAdapter.querySelector(el:any, selector:string):any', - 'Parse5DomAdapter.querySelectorAll(el:any, selector:string):any[]', - 'Parse5DomAdapter.remove(el:any):HTMLElement', - 'Parse5DomAdapter.removeAttribute(element:any, attribute:string):any', - 'Parse5DomAdapter.removeAttributeNS(element:any, ns:string, name:string):any', - 'Parse5DomAdapter.removeChild(el:any, node:any):any', - 'Parse5DomAdapter.removeClass(element:any, className:string):any', - 'Parse5DomAdapter.removeStyle(element:any, styleName:string):any', - 'Parse5DomAdapter.replaceChild(el:any, newNode:any, oldNode:any):any', - 'Parse5DomAdapter.requestAnimationFrame(callback:any):number', - 'Parse5DomAdapter.resetBaseElement():void', - 'Parse5DomAdapter.resolveAndSetHref(el:any, baseUrl:string, href:string):any', - 'Parse5DomAdapter.setAttribute(element:any, attribute:string, value:string):any', - 'Parse5DomAdapter.setAttributeNS(element:any, ns:string, attribute:string, value:string):any', - 'Parse5DomAdapter.setChecked(el:any, value:boolean):any', - 'Parse5DomAdapter.setCookie(name:string, value:string):any', - 'Parse5DomAdapter.setData(el:any, name:string, value:string):any', - 'Parse5DomAdapter.setGlobalVar(path:string, value:any):any', - 'Parse5DomAdapter.setInnerHTML(el:any, value:any):any', - 'Parse5DomAdapter.setProperty(el:any, name:string, value:any):any', - 'Parse5DomAdapter.setStyle(element:any, styleName:string, styleValue:string):any', - 'Parse5DomAdapter.setText(el:any, value:string):any', - 'Parse5DomAdapter.setTitle(newTitle:string):any', - 'Parse5DomAdapter.setValue(el:any, value:string):any', - 'Parse5DomAdapter.supportsAnimation():boolean', - 'Parse5DomAdapter.supportsCookies():boolean', - 'Parse5DomAdapter.supportsDOMEvents():boolean', - 'Parse5DomAdapter.supportsNativeShadowDOM():boolean', - 'Parse5DomAdapter.supportsWebAnimation():boolean', - 'Parse5DomAdapter.tagName(element:any):string', - 'Parse5DomAdapter.templateAwareRoot(el:any):any', - 'Parse5DomAdapter.type(node:any):string', + 'const SERVER_PLATFORM_PROVIDERS:Array', + 'serverBootstrap(appComponentType:Type, providers:Array):Promise>', + 'serverPlatform():PlatformRef', ]; const PLATFORM_SERVER_TESTING = [