feat(core): remove ViewEncapsulation.Native (#38882)

Removes `ViewEncapsulation.Native` which has been deprecated for several major versions.

BREAKING CHANGES:
* `ViewEncapsulation.Native` has been removed. Use `ViewEncapsulation.ShadowDom` instead. Existing
usages will be updated automatically by `ng update`.

PR Close #38882
This commit is contained in:
Kristiyan Kostadinov 2020-10-08 16:59:29 +02:00 committed by atscott
parent 0e733f3689
commit 4a1c12c773
25 changed files with 77 additions and 91 deletions

View File

@ -13,8 +13,8 @@ import { Component, ViewEncapsulation } from '@angular/core';
export class QuestSummaryComponent { }
// #enddocregion
/*
// #docregion encapsulation.native
// #docregion encapsulation.shadow
// warning: few browsers support shadow DOM encapsulation at this time
encapsulation: ViewEncapsulation.Native
// #enddocregion encapsulation.native
encapsulation: ViewEncapsulation.ShadowDom
// #enddocregion encapsulation.shadow
*/

View File

@ -41,7 +41,6 @@ v9 - v12
| `@angular/core` | [`DefaultIterableDiffer`](#core) | <!--v7--> v11 |
| `@angular/core` | [`ReflectiveKey`](#core) | <!--v8--> v11 |
| `@angular/core` | [`RenderComponentType`](#core) | <!--v7--> v11 |
| `@angular/core` | [`ViewEncapsulation.Native`](#core) | <!--v6--> v11 |
| `@angular/core` | [`WrappedValue`](#core) | <!--v10--> v12 |
| `@angular/forms` | [`ngModel` with reactive forms](#ngmodel-reactive) | <!--v6--> v11 |
| `@angular/router` | [`preserveQueryParams`](#router) | <!--v7--> v11 |
@ -89,7 +88,6 @@ Tip: In the [API reference section](api) of this doc site, deprecated APIs are i
| [`DefaultIterableDiffer`](api/core/DefaultIterableDiffer) | n/a | v4 | Not part of public API. |
| [`ReflectiveInjector`](api/core/ReflectiveInjector) | [`Injector.create`](api/core/Injector#create) | v5 | See [`ReflectiveInjector`](#reflectiveinjector) |
| [`ReflectiveKey`](api/core/ReflectiveKey) | none | v5 | none |
| [`ViewEncapsulation.Native`](api/core/ViewEncapsulation#Native) | [`ViewEncapsulation.ShadowDom`](api/core/ViewEncapsulation#ShadowDom) | v6 | Use the native encapsulation mechanism of the renderer. See [view.ts](https://github.com/angular/angular/blob/3e992e18ebf51d6036818f26c3d77b52d3ec48eb/packages/core/src/metadata/view.ts#L32).
| [`defineInjectable`](api/core/defineInjectable) | `ɵɵdefineInjectable` | v8 | Used only in generated code. No source code should depend on this API. |
| [`entryComponents`](api/core/NgModule#entryComponents) | none | v9 | See [`entryComponents`](#entryComponents) |
| [`ANALYZE_FOR_ENTRY_COMPONENTS`](api/core/ANALYZE_FOR_ENTRY_COMPONENTS) | none | v9 | See [`ANALYZE_FOR_ENTRY_COMPONENTS`](#entryComponents) |

View File

@ -13,8 +13,6 @@ Choose from the following modes:
to attach a shadow DOM to the component's host element, and then puts the component
view inside that shadow DOM. The component's styles are included within the shadow DOM.
* `Native` view encapsulation uses a now deprecated version of the browser's native shadow DOM implementation - [learn about the changes](https://hayato.io/2016/shadowdomv1/).
* `Emulated` view encapsulation (the default) emulates the behavior of shadow DOM by preprocessing
(and renaming) the CSS code to effectively scope the CSS to the component's view.
For details, see [Inspecting generated CSS](guide/view-encapsulation#inspect-generated-css) below.
@ -26,7 +24,7 @@ Choose from the following modes:
To set the components encapsulation mode, use the `encapsulation` property in the component metadata:
<code-example path="component-styles/src/app/quest-summary.component.ts" region="encapsulation.native" header="src/app/quest-summary.component.ts"></code-example>
<code-example path="component-styles/src/app/quest-summary.component.ts" region="encapsulation.shadow" header="src/app/quest-summary.component.ts"></code-example>
`ShadowDom` view encapsulation only works on browsers that have native support
for shadow DOM (see [Shadow DOM v1](https://caniuse.com/#feat=shadowdomv1) on the
@ -80,4 +78,4 @@ by the generated component styles, which are in the `<head>` section of the DOM:
These styles are post-processed so that each selector is augmented
with `_nghost` or `_ngcontent` attribute selectors.
These extra selectors enable the scoping rules described in this page.
These extra selectors enable the scoping rules described in this page.

View File

@ -21,7 +21,7 @@ const initialDocViewerContent = initialDocViewerElement ? initialDocViewerElemen
selector: 'aio-doc-viewer',
template: ''
// TODO(robwormald): shadow DOM and emulated don't work here (?!)
// encapsulation: ViewEncapsulation.Native
// encapsulation: ViewEncapsulation.ShadowDom
})
export class DocViewerComponent implements OnDestroy {
// Enable/Disable view transition animations.

View File

@ -1032,7 +1032,6 @@ export declare abstract class ViewContainerRef {
export declare enum ViewEncapsulation {
Emulated = 0,
Native = 1,
None = 2,
ShadowDom = 3
}

View File

@ -30,7 +30,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 1485,
"main-es2015": 134891,
"main-es2015": 135003,
"polyfills-es2015": 37248
}
}

View File

@ -107,7 +107,6 @@ export declare enum Style {
*/
export declare enum ViewEncapsulation {
Emulated = 'Emulated',
Native = 'Native',
None = 'None',
ShadowDom = 'ShadowDom'
}

View File

@ -72,7 +72,7 @@ describe('compiler compliance: styling', () => {
expectEmit(result.source, template, 'Incorrect template');
});
it('should pass in the component metadata styles into the component definition but skip shimming when style encapsulation is set to native',
it('should pass in the component metadata styles into the component definition but skip shimming when style encapsulation is set to shadow dom',
() => {
const files = {
app: {
@ -80,7 +80,7 @@ describe('compiler compliance: styling', () => {
import {Component, NgModule, ViewEncapsulation} from '@angular/core';
@Component({
encapsulation: ViewEncapsulation.Native,
encapsulation: ViewEncapsulation.ShadowDom,
selector: "my-component",
styles: ["div.cool { color: blue; }", ":host.nice p { color: gold; }"],
template: "..."
@ -98,7 +98,7 @@ describe('compiler compliance: styling', () => {
MyComponent.ɵcmp = $r3$.ɵɵdefineComponent({
styles: ["div.cool { color: blue; }", ":host.nice p { color: gold; }"],
encapsulation: 1
encapsulation: 3
})
`;
const result = compile(files, angularFiles);

View File

@ -77,7 +77,7 @@ export type ɵɵPipeDefWithMeta<PipeT, NameT> = any;
export enum ViewEncapsulation {
Emulated = 0,
Native = 1,
// Historically the 1 value was for `Native` encapsulation which has been removed as of v11.
None = 2,
ShadowDom = 3
}

View File

@ -173,7 +173,7 @@ export interface R3FactoryDefMetadataFacade {
export enum ViewEncapsulation {
Emulated = 0,
Native = 1,
// Historically the 1 value was for `Native` encapsulation which has been removed as of v11.
None = 2,
ShadowDom = 3
}

View File

@ -82,7 +82,7 @@ export interface Component extends Directive {
}
export enum ViewEncapsulation {
Emulated = 0,
Native = 1,
// Historically the 1 value was for `Native` encapsulation which has been removed as of v11.
None = 2,
ShadowDom = 3
}

View File

@ -163,8 +163,6 @@ export interface R3ComponentMetadata extends R3DirectiveMetadata {
/**
* An encapsulation policy for the template and CSS styles. One of:
* - `ViewEncapsulation.Native`: Use shadow roots. This works only if natively available on the
* platform (note that this is marked the as the "deprecated shadow DOM" as of Angular v6.1.
* - `ViewEncapsulation.Emulated`: Use shimmed CSS that emulates the native behavior.
* - `ViewEncapsulation.None`: Use global CSS without any encapsulation.
* - `ViewEncapsulation.ShadowDom`: Use the latest ShadowDOM API to natively encapsulate styles

View File

@ -226,7 +226,7 @@ function normalizeTemplate(normalizer: DirectiveNormalizer, o: {
describe('normalizeLoadedTemplate', () => {
it('should store the viewEncapsulation in the result',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const viewEncapsulation = ViewEncapsulation.Native;
const viewEncapsulation = ViewEncapsulation.ShadowDom;
const template = <CompileTemplateMetadata>normalizeTemplate(normalizer, {
encapsulation: viewEncapsulation,
template: '',

View File

@ -173,7 +173,7 @@ export interface R3FactoryDefMetadataFacade {
export enum ViewEncapsulation {
Emulated = 0,
Native = 1,
// Historically the 1 value was for `Native` encapsulation which has been removed as of v11.
None = 2,
ShadowDom = 3
}

View File

@ -518,7 +518,6 @@ export interface Component extends Directive {
/**
* An encapsulation policy for the template and CSS styles. One of:
* - `ViewEncapsulation.Native`: Deprecated. Use `ViewEncapsulation.ShadowDom` instead.
* - `ViewEncapsulation.Emulated`: Use shimmed CSS that
* emulates the native behavior.
* - `ViewEncapsulation.None`: Use global CSS without any

View File

@ -28,15 +28,9 @@ export enum ViewEncapsulation {
* This is the default option.
*/
Emulated = 0,
/**
* @deprecated v6.1.0 - use {ViewEncapsulation.ShadowDom} instead.
* Use the native encapsulation mechanism of the renderer.
*
* For the DOM this means using the deprecated [Shadow DOM
* v0](https://w3c.github.io/webcomponents/spec/shadow/) and
* creating a ShadowRoot for Component's Host Element.
*/
Native = 1,
// Historically the 1 value was for `Native` encapsulation which has been removed as of v11.
/**
* Don't provide any template or style encapsulation.
*/

View File

@ -517,8 +517,8 @@ function getRenderParent(tView: TView, tNode: TNode, currentView: LView): REleme
// Since the projection would then move it to its final destination. Note that we can't
// make this assumption when using the Shadow DOM, because the native projection placeholders
// (<content> or <slot>) have to be in place as elements are being inserted.
if (encapsulation !== ViewEncapsulation.ShadowDom &&
encapsulation !== ViewEncapsulation.Native) {
if (encapsulation === ViewEncapsulation.None ||
encapsulation === ViewEncapsulation.Emulated) {
return null;
}
}

View File

@ -230,7 +230,10 @@ export function getParentRenderElement(view: ViewData, renderHost: any, def: Nod
if ((renderParent.flags & NodeFlags.TypeElement) === 0 ||
(renderParent.flags & NodeFlags.ComponentView) === 0 ||
(renderParent.element!.componentRendererType &&
renderParent.element!.componentRendererType!.encapsulation === ViewEncapsulation.Native)) {
(renderParent.element!.componentRendererType!.encapsulation ===
ViewEncapsulation.ShadowDom ||
// TODO(FW-2290): remove the `encapsulation === 1` fallback logic in v12.
renderParent.element!.componentRendererType!.encapsulation === 1))) {
// only children of non components, or children of components with native encapsulation should
// be attached.
return asElementData(view, def.renderParent!.nodeIndex).renderElement;

View File

@ -490,13 +490,13 @@ describe('projection', () => {
expect(main.nativeElement).toHaveText('TREE(0:TREE2(1:TREE(2:)))');
});
if (supportsNativeShadowDOM()) {
it('should support native content projection and isolate styles per component', () => {
TestBed.configureTestingModule({declarations: [SimpleNative1, SimpleNative2]});
if (supportsShadowDOM()) {
it('should support shadow dom content projection and isolate styles per component', () => {
TestBed.configureTestingModule({declarations: [SimpleShadowDom1, SimpleShadowDom2]});
TestBed.overrideComponent(MainComp, {
set: {
template: '<simple-native1><div>A</div></simple-native1>' +
'<simple-native2><div>B</div></simple-native2>'
template: '<simple-shadow-dom1><div>A</div></simple-shadow-dom1>' +
'<simple-shadow-dom2><div>B</div></simple-shadow-dom2>'
}
});
const main = TestBed.createComponent(MainComp);
@ -857,21 +857,21 @@ class Simple {
}
@Component({
selector: 'simple-native1',
template: 'SIMPLE1(<content></content>)',
encapsulation: ViewEncapsulation.Native,
selector: 'simple-shadow-dom1',
template: 'SIMPLE1(<slot></slot>)',
encapsulation: ViewEncapsulation.ShadowDom,
styles: ['div {color: red}']
})
class SimpleNative1 {
class SimpleShadowDom1 {
}
@Component({
selector: 'simple-native2',
template: 'SIMPLE2(<content></content>)',
encapsulation: ViewEncapsulation.Native,
selector: 'simple-shadow-dom2',
template: 'SIMPLE2(<slot></slot>)',
encapsulation: ViewEncapsulation.ShadowDom,
styles: ['div {color: blue}']
})
class SimpleNative2 {
class SimpleShadowDom2 {
}
@Component({selector: 'empty', template: ''})
@ -1043,6 +1043,6 @@ class CmpA1 {
class CmpA2 {
}
function supportsNativeShadowDOM(): boolean {
return typeof (<any>document.body).createShadowRoot === 'function';
function supportsShadowDOM(): boolean {
return typeof (<any>document.body).attachShadow !== 'undefined';
}

View File

@ -74,6 +74,8 @@ function decoratePreventDefault(eventHandler: Function): Function {
};
}
let hasLoggedNativeEncapsulationWarning = false;
@Injectable()
export class DomRendererFactory2 implements RendererFactory2 {
private rendererByCompId = new Map<string, Renderer2>();
@ -100,8 +102,16 @@ export class DomRendererFactory2 implements RendererFactory2 {
(<EmulatedEncapsulationDomRenderer2>renderer).applyToHost(element);
return renderer;
}
case ViewEncapsulation.Native:
case 1:
case ViewEncapsulation.ShadowDom:
// TODO(FW-2290): remove the `case 1:` fallback logic and the warning in v12.
if ((typeof ngDevMode === 'undefined' || ngDevMode) &&
!hasLoggedNativeEncapsulationWarning && type.encapsulation === 1) {
hasLoggedNativeEncapsulationWarning = true;
console.warn(
'ViewEncapsulation.Native is no longer supported. Falling back to ViewEncapsulation.ShadowDom. The fallback will be removed in v12.');
}
return new ShadowDomRenderer(this.eventManager, this.sharedStylesHost, element, type);
default: {
if (!this.rendererByCompId.has(type.id)) {
@ -302,13 +312,9 @@ class ShadowDomRenderer extends DefaultDomRenderer2 {
constructor(
eventManager: EventManager, private sharedStylesHost: DomSharedStylesHost,
private hostEl: any, private component: RendererType2) {
private hostEl: any, component: RendererType2) {
super(eventManager);
if (component.encapsulation === ViewEncapsulation.ShadowDom) {
this.shadowRoot = (hostEl as any).attachShadow({mode: 'open'});
} else {
this.shadowRoot = (hostEl as any).createShadowRoot();
}
this.shadowRoot = (hostEl as any).attachShadow({mode: 'open'});
this.sharedStylesHost.addHost(this.shadowRoot);
const styles = flattenStyles(component.id, component.styles, []);
for (let i = 0; i < styles.length; i++) {

View File

@ -20,8 +20,8 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
TestCmp, SomeApp, CmpEncapsulationEmulated, CmpEncapsulationNative, CmpEncapsulationNone,
CmpEncapsulationNative
TestCmp, SomeApp, CmpEncapsulationEmulated, CmpEncapsulationShadow, CmpEncapsulationNone,
CmpEncapsulationShadow
]
});
renderer = TestBed.createComponent(TestCmp).componentInstance.renderer;
@ -98,16 +98,14 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
});
});
if (browserDetection.supportsDeprecatedShadowDomV0) {
if (browserDetection.supportsShadowDom) {
it('should allow to style components with emulated encapsulation and no encapsulation inside of components with shadow DOM',
() => {
const fixture = TestBed.createComponent(SomeApp);
const cmp = fixture.debugElement.query(By.css('cmp-shadow')).nativeElement;
const shadow = cmp.shadowRoot.querySelector('.shadow');
const cmp = fixture.debugElement.query(By.css('cmp-native')).nativeElement;
const native = cmp.shadowRoot.querySelector('.native');
expect(window.getComputedStyle(native).color).toEqual('rgb(255, 0, 0)');
expect(window.getComputedStyle(shadow).color).toEqual('rgb(255, 0, 0)');
const emulated = cmp.shadowRoot.querySelector('.emulated');
expect(window.getComputedStyle(emulated).color).toEqual('rgb(0, 0, 255)');
@ -119,15 +117,6 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
});
}
@Component({
selector: 'cmp-native',
template: `<div class="native"></div><cmp-emulated></cmp-emulated><cmp-none></cmp-none>`,
styles: [`.native { color: red; }`],
encapsulation: ViewEncapsulation.Native
})
class CmpEncapsulationNative {
}
@Component({
selector: 'cmp-emulated',
template: `<div class="emulated"></div>`,
@ -149,7 +138,7 @@ class CmpEncapsulationNone {
@Component({
selector: 'cmp-shadow',
template: `<div class="shadow"></div><cmp-emulated></cmp-emulated><cmp-none></cmp-none>`,
styles: [`.native { color: red; }`],
styles: [`.shadow { color: red; }`],
encapsulation: ViewEncapsulation.ShadowDom
})
class CmpEncapsulationShadow {
@ -158,7 +147,7 @@ class CmpEncapsulationShadow {
@Component({
selector: 'some-app',
template: `
<cmp-native></cmp-native>
<cmp-shadow></cmp-shadow>
<cmp-emulated></cmp-emulated>
<cmp-none></cmp-none>
`,

View File

@ -15,13 +15,11 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
if (browserDetection.supportsShadowDom) {
describe('ShadowDOM Support', () => {
let testContainer: HTMLDivElement;
beforeEach(() => {
TestBed.configureTestingModule({imports: [TestModule]});
});
it('should attach and use a shadowRoot when ViewEncapsulation.Native is set', () => {
it('should attach and use a shadowRoot when ViewEncapsulation.ShadowDom is set', () => {
const compEl = TestBed.createComponent(ShadowComponent).nativeElement;
expect(compEl.shadowRoot!.textContent).toEqual('Hello World');
});

View File

@ -305,8 +305,14 @@ function elementText(n: any): string {
return '';
}
if (getDOM().isElementNode(n) && (n as Element).tagName == 'CONTENT') {
return elementText(Array.prototype.slice.apply((<any>n).getDistributedNodes()));
if (getDOM().isElementNode(n)) {
const tagName = (n as Element).tagName;
if (tagName === 'CONTENT') {
return elementText(Array.prototype.slice.apply((<any>n).getDistributedNodes()));
} else if (tagName === 'SLOT') {
return elementText(Array.prototype.slice.apply((<any>n).assignedNodes()));
}
}
if (hasShadowRoot(n)) {

View File

@ -32,7 +32,6 @@ export class ServerRendererFactory2 implements RendererFactory2 {
return this.defaultRenderer;
}
switch (type.encapsulation) {
case ViewEncapsulation.Native:
case ViewEncapsulation.Emulated: {
let renderer = this.rendererByCompId.get(type.id);
if (!renderer) {

View File

@ -272,19 +272,19 @@ class ImageExampleModule {
@Component({
selector: 'app',
template: 'Native works',
encapsulation: ViewEncapsulation.Native,
template: 'Shadow DOM works',
encapsulation: ViewEncapsulation.ShadowDom,
styles: [':host { color: red; }']
})
class NativeEncapsulationApp {
class ShadowDomEncapsulationApp {
}
@NgModule({
declarations: [NativeEncapsulationApp],
declarations: [ShadowDomEncapsulationApp],
imports: [BrowserModule.withServerTransition({appId: 'test'}), ServerModule],
bootstrap: [NativeEncapsulationApp]
bootstrap: [ShadowDomEncapsulationApp]
})
class NativeExampleModule {
class ShadowDomExampleModule {
}
@Component({selector: 'my-child', template: 'Works!'})
@ -666,8 +666,8 @@ describe('platform-server integration', () => {
});
}));
it('should handle ViewEncapsulation.Native', waitForAsync(() => {
renderModule(NativeExampleModule, {document: doc}).then(output => {
it('should handle ViewEncapsulation.ShadowDom', waitForAsync(() => {
renderModule(ShadowDomExampleModule, {document: doc}).then(output => {
expect(output).not.toBe('');
expect(output).toContain('color: red');
called = true;