fix(platform-server): render styles in app component instead of <head>
This ensures when the tree is serialized to the client and the app is later bootstrapped, the <style> tags created during server-side rendering are destroyed.
This commit is contained in:
parent
17486fd696
commit
30380d010b
|
@ -8,15 +8,15 @@
|
|||
|
||||
import {PlatformLocation} from '@angular/common';
|
||||
import {platformCoreDynamic} from '@angular/compiler';
|
||||
import {InjectionToken, Injector, NgModule, PLATFORM_INITIALIZER, PlatformRef, Provider, RENDERER_V2_DIRECT, RendererV2, RootRenderer, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
|
||||
import {APP_BOOTSTRAP_LISTENER, InjectionToken, Injector, NgModule, PLATFORM_INITIALIZER, PlatformRef, Provider, RENDERER_V2_DIRECT, RendererV2, RootRenderer, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
|
||||
import {BrowserModule, DOCUMENT} from '@angular/platform-browser';
|
||||
|
||||
import {ServerPlatformLocation} from './location';
|
||||
import {Parse5DomAdapter, parseDocument} from './parse5_adapter';
|
||||
import {PlatformState} from './platform_state';
|
||||
import {ALLOW_MULTIPLE_PLATFORMS, DebugDomRendererV2, DebugDomRootRenderer} from './private_import_core';
|
||||
import {SharedStylesHost, getDOM} from './private_import_platform-browser';
|
||||
import {ServerRendererV2, ServerRootRenderer} from './server_renderer';
|
||||
import {ServerStylesHost} from './styles_host';
|
||||
|
||||
function notSupported(feature: string): Error {
|
||||
throw new Error(`platform-server does not support '${feature}'.`);
|
||||
|
@ -42,12 +42,24 @@ export function _createDebugRendererV2(renderer: RendererV2): RendererV2 {
|
|||
return isDevMode() ? new DebugDomRendererV2(renderer) : renderer;
|
||||
}
|
||||
|
||||
export function _addStylesToRootComponentFactory(stylesHost: ServerStylesHost) {
|
||||
const initializer = () => stylesHost.rootComponentIsReady();
|
||||
return initializer;
|
||||
}
|
||||
|
||||
export const SERVER_RENDER_PROVIDERS: Provider[] = [
|
||||
ServerRootRenderer, {provide: RENDERER_V2_DIRECT, useClass: ServerRendererV2},
|
||||
ServerRootRenderer,
|
||||
{provide: RENDERER_V2_DIRECT, useClass: ServerRendererV2},
|
||||
{provide: RendererV2, useFactory: _createDebugRendererV2, deps: [RENDERER_V2_DIRECT]},
|
||||
{provide: RootRenderer, useFactory: _createConditionalRootRenderer, deps: [ServerRootRenderer]},
|
||||
// use plain SharedStylesHost, not the DomSharedStylesHost
|
||||
SharedStylesHost
|
||||
ServerStylesHost,
|
||||
{provide: SharedStylesHost, useExisting: ServerStylesHost},
|
||||
{
|
||||
provide: APP_BOOTSTRAP_LISTENER,
|
||||
useFactory: _addStylesToRootComponentFactory,
|
||||
deps: [ServerStylesHost],
|
||||
multi: true
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
@ -47,6 +47,7 @@ export class ServerRenderer implements Renderer {
|
|||
if (componentProto.encapsulation === ViewEncapsulation.Native) {
|
||||
throw new Error('Native encapsulation is not supported on the server!');
|
||||
}
|
||||
this._rootRenderer.sharedStylesHost.addStyles(this._styles);
|
||||
if (this.componentProto.encapsulation === ViewEncapsulation.Emulated) {
|
||||
this._contentAttr = shimContentAttribute(styleShimId);
|
||||
this._hostAttr = shimHostAttribute(styleShimId);
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ApplicationRef, Inject, Injectable} from '@angular/core';
|
||||
import {DOCUMENT} from '@angular/platform-browser';
|
||||
|
||||
import {Parse5DomAdapter} from './parse5_adapter';
|
||||
import {SharedStylesHost, getDOM} from './private_import_platform-browser';
|
||||
|
||||
@Injectable()
|
||||
export class ServerStylesHost extends SharedStylesHost {
|
||||
private root: any = null;
|
||||
private buffer: string[] = [];
|
||||
|
||||
constructor(@Inject(DOCUMENT) private doc: any, private appRef: ApplicationRef) { super(); }
|
||||
|
||||
private _addStyle(style: string): void {
|
||||
let adapter: Parse5DomAdapter = getDOM() as Parse5DomAdapter;
|
||||
const el = adapter.createElement('style');
|
||||
adapter.setText(el, style);
|
||||
adapter.appendChild(this.root, el);
|
||||
}
|
||||
|
||||
onStylesAdded(additions: Set<string>) {
|
||||
if (!this.root) {
|
||||
additions.forEach(style => this.buffer.push(style));
|
||||
} else {
|
||||
additions.forEach(style => this._addStyle(style));
|
||||
}
|
||||
}
|
||||
|
||||
rootComponentIsReady(): void {
|
||||
if (!!this.root) {
|
||||
return;
|
||||
}
|
||||
this.root = this.appRef.components[0].location.nativeElement;
|
||||
this.buffer.forEach(style => this._addStyle(style));
|
||||
this.buffer = null;
|
||||
}
|
||||
}
|
|
@ -47,6 +47,14 @@ class MyAsyncServerApp {
|
|||
class AsyncServerModule {
|
||||
}
|
||||
|
||||
@Component({selector: 'app', template: `Works!`, styles: [':host { color: red; }']})
|
||||
class MyStylesApp {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyStylesApp], imports: [ServerModule], bootstrap: [MyStylesApp]})
|
||||
class ExampleStylesModule {
|
||||
}
|
||||
|
||||
export function main() {
|
||||
if (getDOM().supportsDOMEvents()) return; // NODE only
|
||||
|
||||
|
@ -87,6 +95,19 @@ export function main() {
|
|||
});
|
||||
}));
|
||||
|
||||
it('adds styles to the root component', async(() => {
|
||||
const platform = platformDynamicServer(
|
||||
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
|
||||
platform.bootstrapModule(ExampleStylesModule).then(ref => {
|
||||
const appRef: ApplicationRef = ref.injector.get(ApplicationRef);
|
||||
const app = appRef.components[0].location.nativeElement;
|
||||
expect(app.children.length).toBe(2);
|
||||
const style = app.children[1];
|
||||
expect(style.type).toBe('style');
|
||||
expect(style.children[0].data).toContain('color: red');
|
||||
});
|
||||
}));
|
||||
|
||||
describe('PlatformLocation', () => {
|
||||
it('is injectable', async(() => {
|
||||
const platform = platformDynamicServer(
|
||||
|
|
Loading…
Reference in New Issue