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 {PlatformLocation} from '@angular/common';
|
||||||
import {platformCoreDynamic} from '@angular/compiler';
|
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 {BrowserModule, DOCUMENT} from '@angular/platform-browser';
|
||||||
|
|
||||||
import {ServerPlatformLocation} from './location';
|
import {ServerPlatformLocation} from './location';
|
||||||
import {Parse5DomAdapter, parseDocument} from './parse5_adapter';
|
import {Parse5DomAdapter, parseDocument} from './parse5_adapter';
|
||||||
import {PlatformState} from './platform_state';
|
import {PlatformState} from './platform_state';
|
||||||
import {ALLOW_MULTIPLE_PLATFORMS, DebugDomRendererV2, DebugDomRootRenderer} from './private_import_core';
|
import {ALLOW_MULTIPLE_PLATFORMS, DebugDomRendererV2, DebugDomRootRenderer} from './private_import_core';
|
||||||
import {SharedStylesHost, getDOM} from './private_import_platform-browser';
|
import {SharedStylesHost, getDOM} from './private_import_platform-browser';
|
||||||
import {ServerRendererV2, ServerRootRenderer} from './server_renderer';
|
import {ServerRendererV2, ServerRootRenderer} from './server_renderer';
|
||||||
|
import {ServerStylesHost} from './styles_host';
|
||||||
|
|
||||||
function notSupported(feature: string): Error {
|
function notSupported(feature: string): Error {
|
||||||
throw new Error(`platform-server does not support '${feature}'.`);
|
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;
|
return isDevMode() ? new DebugDomRendererV2(renderer) : renderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function _addStylesToRootComponentFactory(stylesHost: ServerStylesHost) {
|
||||||
|
const initializer = () => stylesHost.rootComponentIsReady();
|
||||||
|
return initializer;
|
||||||
|
}
|
||||||
|
|
||||||
export const SERVER_RENDER_PROVIDERS: Provider[] = [
|
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: RendererV2, useFactory: _createDebugRendererV2, deps: [RENDERER_V2_DIRECT]},
|
||||||
{provide: RootRenderer, useFactory: _createConditionalRootRenderer, deps: [ServerRootRenderer]},
|
{provide: RootRenderer, useFactory: _createConditionalRootRenderer, deps: [ServerRootRenderer]},
|
||||||
// use plain SharedStylesHost, not the DomSharedStylesHost
|
ServerStylesHost,
|
||||||
SharedStylesHost
|
{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) {
|
if (componentProto.encapsulation === ViewEncapsulation.Native) {
|
||||||
throw new Error('Native encapsulation is not supported on the server!');
|
throw new Error('Native encapsulation is not supported on the server!');
|
||||||
}
|
}
|
||||||
|
this._rootRenderer.sharedStylesHost.addStyles(this._styles);
|
||||||
if (this.componentProto.encapsulation === ViewEncapsulation.Emulated) {
|
if (this.componentProto.encapsulation === ViewEncapsulation.Emulated) {
|
||||||
this._contentAttr = shimContentAttribute(styleShimId);
|
this._contentAttr = shimContentAttribute(styleShimId);
|
||||||
this._hostAttr = shimHostAttribute(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 {
|
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() {
|
export function main() {
|
||||||
if (getDOM().supportsDOMEvents()) return; // NODE only
|
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', () => {
|
describe('PlatformLocation', () => {
|
||||||
it('is injectable', async(() => {
|
it('is injectable', async(() => {
|
||||||
const platform = platformDynamicServer(
|
const platform = platformDynamicServer(
|
||||||
|
|
Loading…
Reference in New Issue