Skip to content

Commit

Permalink
feat: remove usage of :host-context (#629)
Browse files Browse the repository at this point in the history
  • Loading branch information
pmdartus authored and diervo committed Sep 11, 2018
1 parent aed62e0 commit 5634840
Show file tree
Hide file tree
Showing 6 changed files with 18 additions and 83 deletions.
5 changes: 3 additions & 2 deletions packages/postcss-plugin-lwc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

* Selectors
* Scoping CSS selectors to enforce Shadow DOM style encapsulation
* Transform `:host` and `:host-context` pseudo-class selectors
* Transform `:host` pseudo-class selectors
* Custom Properties
* Inline replacement of `var()` CSS function

Expand Down Expand Up @@ -143,6 +143,7 @@ x-btn[min=0] {} /* 🚨 ERROR - invalid usage "min" attribute on "x-btn" */

* No support for [`::slotted`](https://drafts.csswg.org/css-scoping/#slotted-pseudo) pseudo-element.
* No support for [`>>>`](https://drafts.csswg.org/css-scoping/#deep-combinator) deep combinator (spec still under consideration: [issue](https://github.com/w3c/webcomponents/issues/78)).
* No support for [`:host-context`](https://drafts.csswg.org/css-scoping/#selectordef-host-context) pseudo-selector (browser vendors are not able to agree: [webkit](https://bugs.webkit.org/show_bug.cgi?id=160038), [gecko](https://bugzilla.mozilla.org/show_bug.cgi?id=1082060))
* Scoped CSS has a non-negligeable performance impact:
* Each selector chain is scoped and each compound expression passed to the `:host()` and `:host-context()` need to be spread into multiple selectors. This tranformation greatly increases the overall size and complexity of the generated CSS, leading to more bits on the wire, longer parsing time and longer style recalculation.
* Each selector chain is scoped and each compound expression passed to the `:host()` need to be spread into multiple selectors. This transformation greatly increases the overall size and complexity of the generated CSS, leading to more bits on the wire, longer parsing time and longer style recalculation.
* In order to ensure CSS encapsulation, each element needs to add an extra attribute. This increases the actual rendering time.
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,6 @@ describe('custom-element', () => {
].join(''),
);
});

it('should handle custom elements in the :host-context selector', async () => {
const { css } = await process(':host-context(x-bar) {}');
expect(css).toBe(
`x-bar [x-foo_tmpl-host],[is="x-bar"] [x-foo_tmpl-host] {}`,
);
});
});

describe(':host', () => {
Expand Down Expand Up @@ -142,19 +135,3 @@ describe(':host', () => {
expect(css).toBe(`[x-foo_tmpl-host]:hover {}`);
});
});

describe(':host-context', () => {
it('should handle selector', async () => {
const { css } = await process(':host-context(.darktheme) {}');
expect(css).toBe(`.darktheme [x-foo_tmpl-host] {}`);
});

it('should handle multiple selectors', async () => {
const { css } = await process(
':host-context(.darktheme, .nighttheme) {}',
);
expect(css).toBe(
`.darktheme [x-foo_tmpl-host],.nighttheme [x-foo_tmpl-host] {}`,
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@ describe('selector validation', () => {
column: 1,
});
});

it('should restrict usage of unsupported :host-context selector', () => {
return expect(process(':host-context(.foo) {}')).rejects.toMatchObject({
message: expect.stringMatching(
/Invalid usage of unsupported selector ":host-context"/,
),
file: FILE_NAME,
line: 1,
column: 1,
});
});
});

describe('attribute validation', () => {
Expand Down
56 changes: 3 additions & 53 deletions packages/postcss-plugin-lwc/src/selector-scoping/transform.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
attribute,
combinator,
isTag,
isPseudoElement,
isCombinator,
Expand All @@ -17,7 +16,6 @@ import {
findNode,
replaceNodeWith,
trimNodeWhitespaces,
isHostContextPseudoClass,
isHostPseudoClass,
} from './utils';
import { PluginConfig } from '../config';
Expand Down Expand Up @@ -117,11 +115,9 @@ function scopeSelector(selector: Selector, config: PluginConfig) {
});

for (const compoundSelector of compoundSelectors) {
// Compound selectors containing :host or :host-context have a special treatment and should
// not be scoped like the rest of the complex selectors
const shouldScopeCompoundSelector = compoundSelector.every(node => {
return !isHostPseudoClass(node) && !isHostContextPseudoClass(node);
});
// Compound selectors containing :host have a special treatment and should not be scoped like the rest of the
// complex selectors.
const shouldScopeCompoundSelector = compoundSelector.every(node => !isHostPseudoClass(node));

if (shouldScopeCompoundSelector) {
let nodeToScope: Node | undefined;
Expand Down Expand Up @@ -189,48 +185,6 @@ function transformHost(selector: Selector, config: PluginConfig) {
}
}

/**
* Mark transform :host-context by prepending the selector with the contextual selectors.
* :host-context(.bar) -> .bar [x-foo_tmpl-host]
* :host-context(.bar, .baz) -> .bar [x-foo_tmpl-host], .baz [x-foo_tmpl-host]
*/
function transformHostContext(selector: Selector, config: PluginConfig) {
// Locate the first :host-context pseudo-selector
const hostContextNode = findNode(selector, isHostContextPseudoClass) as
| Pseudo
| undefined;

if (hostContextNode) {
// Swap the :host-context pseudo-class with the host scoping token
const hostScopeAttr = scopeAttribute(config, { host: true });
hostContextNode.replaceWith(hostScopeAttr);

// Generate a unique contextualized version of the selector for each selector pass as argument
// to the :host-context
const contextualSelectors = hostContextNode.nodes.map(
(contextSelectors: Selector) => {
const cloneSelector = selector.clone({}) as Selector;

// Prepend the cloned selector with the context selector
cloneSelector.insertBefore(
cloneSelector.first,
combinator({ value: ' ' }),
);

contextSelectors.each(node => {
trimNodeWhitespaces(node);
cloneSelector.insertBefore(cloneSelector.first, node);
});

return cloneSelector;
},
);

// Replace the current selector with the different variants
replaceNodeWith(selector, ...contextualSelectors);
}
}

export default function transformSelector(
root: Root,
config: PluginConfig,
Expand All @@ -245,9 +199,5 @@ export default function transformSelector(
transformHost(selector, config);
});

root.each((selector: Selector) => {
transformHostContext(selector, config);
});

customElementSelector(root);
}
4 changes: 0 additions & 4 deletions packages/postcss-plugin-lwc/src/selector-scoping/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@ export function isHostPseudoClass(node: Node): node is Pseudo {
return isPseudoClass(node) && node.value === ':host';
}

export function isHostContextPseudoClass(node: Node): node is Pseudo {
return isPseudoClass(node) && node.value === ':host-context';
}

export function findNode(
container: Container,
predicate: (node: Node) => boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
} from './html-attributes';

const DEPRECATED_SELECTORS = new Set(['/deep/', '::shadow', '>>>']);
const UNSUPPORTED_SELECTORS = new Set(['::slotted', ':root']);
const UNSUPPORTED_SELECTORS = new Set(['::slotted', ':root', ':host-context']);

function validateSelectors(root: Root) {
root.walk(node => {
Expand Down

0 comments on commit 5634840

Please sign in to comment.