Skip to content

wip #543

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

wip #543

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions plugin-packs/postcss-preset-env/.tape.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ postcssTape(plugin)({
stage: 0,
browsers: '> 0%'
},
warnings: 1,
},
'layers-basic:preserve:true': {
message: 'supports layers usage with { preserve: true }',
Expand All @@ -180,6 +181,7 @@ postcssTape(plugin)({
stage: 0,
browsers: '> 0%'
},
warnings: 1,
},
'client-side-polyfills:stage-1': {
message: 'stable client side polyfill behavior',
Expand Down
1 change: 0 additions & 1 deletion plugin-packs/postcss-preset-env/FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ The `ID` listed is the key for PostCSS Preset Env configuration in your project.
| `dir-pseudo-class` | `:dir` Directionality Pseudo-Class | [example](https://preset-env.cssdb.org/features/#dir-pseudo-class) | [docs](https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-dir-pseudo-class#readme) |
| `display-two-values` | Two values syntax for `display` | [example](https://preset-env.cssdb.org/features/#display-two-values) | [docs](https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-normalize-display-values#readme) |
| `double-position-gradients` | Double Position Gradients | [example](https://preset-env.cssdb.org/features/#double-position-gradients) | [docs](https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-double-position-gradients#readme) |
| `environment-variables` | Custom Environment Variables | [example](https://preset-env.cssdb.org/features/#environment-variables) | [docs](https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-env-function#readme) |
| `focus-visible-pseudo-class` | `:focus-visible` Focus-Indicated Pseudo-Class | [example](https://preset-env.cssdb.org/features/#focus-visible-pseudo-class) | [docs](https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-focus-visible#readme) |
| `focus-within-pseudo-class` | `:focus-within` Focus Container Pseudo-Class | [example](https://preset-env.cssdb.org/features/#focus-within-pseudo-class) | [docs](https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-focus-within#readme) |
| `font-format-keywords` | Font `format()` Keywords | [example](https://preset-env.cssdb.org/features/#font-format-keywords) | [docs](https://github.com/valtlai/postcss-font-format-keywords#readme) |
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// ids ordered by required execution, then alphabetically
export default [
'cascade-layers',
'custom-media-queries',
'custom-properties',
'environment-variables', // run environment-variables here to access transpiled custom media params and properties
Expand Down Expand Up @@ -32,7 +33,6 @@ export default [
'overflow-wrap-property',
'place-properties',
'system-ui-font-family',
'cascade-layers',
'stepped-value-functions',
'trigonometric-functions',
];
10 changes: 7 additions & 3 deletions plugin-packs/postcss-preset-env/test/layers-basic.css
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,13 @@
order: 9;
}

@custom-media --narrow-window (max-width: 30em);
@custom-media --narrow-window (max-width: 500px);

@layer extensions {
@custom-media --narrow-window (max-width: 30em);

@custom-selector :--heading h1, h2, h3, h4, h5, h6;
}

@media (--narrow-window) {
.test-custom-media-queries {
Expand All @@ -85,8 +91,6 @@
}
}

@custom-selector :--heading h1, h2, h3, h4, h5, h6;

.test-custom-selectors:--heading {
order:12;
}
Expand Down
204 changes: 84 additions & 120 deletions plugin-packs/postcss-preset-env/test/layers-basic.expect.css

Large diffs are not rendered by default.

Large diffs are not rendered by default.

10 changes: 8 additions & 2 deletions plugins/postcss-cascade-layers/.tape.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ postcssTape(plugin)({
'anon-layer': {
message: "supports anonymous layer usage",
},
'examples/example': {
message: "minimal example",
'invalid-nested-css': {
message: "ignores nested css",
},
'extensions': {
message: "css custom extensions",
},
'unlayered-styles': {
message: 'supports unlayered styles alongside layers',
Expand Down Expand Up @@ -64,4 +67,7 @@ postcssTape(plugin)({
'specificity-buckets-b': {
message: "creates non overlapping specificity buckets",
},
'examples/example': {
message: "minimal example",
},
});
5 changes: 5 additions & 0 deletions plugins/postcss-cascade-layers/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changes to PostCSS Cascade Layers

### Unreleased (major)

- Run `postcss-cascade-layers` early compared to other PostCSS plugins (breaking)
- Fix broken `@keyframes` in `@layer`

### 1.0.5 (July 8, 2022)

- Fix case insensitive `@layer` matching (`@LaYeR`).
Expand Down
1 change: 1 addition & 0 deletions plugins/postcss-cascade-layers/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const IMPLICIT_LAYER_SUFFIX = 'b147acf6-11a6-4338-a4d0-80aef4cd1a2f';
export const CONDITIONAL_ATRULES = [
'media',
'supports',
'container',
];

export const ATRULES_WITH_NON_SELECTOR_BLOCK_LISTS = [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import type { Container } from 'postcss';
import type { Model } from './model';
import selectorParser from 'postcss-selector-parser';
import { INVALID_LAYER_NAME } from './constants';
import { CONDITIONAL_ATRULES, INVALID_LAYER_NAME } from './constants';
import { someAtRuleInTree, someInTree } from './some-in-tree';
import { getLayerAtRuleAncestor } from './get-layer-atrule-ancestor';
import { removeEmptyAncestorBlocks, removeEmptyDescendantBlocks } from './clean-blocks';
import { isProcessableLayerRule } from './is-processable-layer-rule';

export function desugarAndParseLayerNames(root: Container, model: Model) {
// - parse layer names
// - rename anon layers
// - handle empty layers
root.walkAtRules((layerRule) => {
if (layerRule.name.toLowerCase() !== 'layer') {
if (!isProcessableLayerRule(layerRule)) {
return;
}

Expand Down Expand Up @@ -90,7 +91,7 @@ export function desugarAndParseLayerNames(root: Container, model: Model) {
layerRule.params = model.createAnonymousLayerName();
}

const hasNestedLayers = someAtRuleInTree(layerRule, (node) => node.name.toLowerCase() === 'layer');
const hasNestedLayers = someAtRuleInTree(layerRule, (node) => isProcessableLayerRule(node));
const hasUnlayeredStyles = someInTree(layerRule, (node) => {
if (node.type !== 'rule') {
return;
Expand All @@ -109,7 +110,7 @@ export function desugarAndParseLayerNames(root: Container, model: Model) {

// only keep unlayered styles for the implicit layer.
implicitLayer.walkAtRules((node) => {
if (node.name.toLowerCase() !== 'layer') {
if (!isProcessableLayerRule(node)) {
return;
}

Expand All @@ -118,7 +119,11 @@ export function desugarAndParseLayerNames(root: Container, model: Model) {

// go through the unlayered rules and delete these from top level atRule
layerRule.walk((node) => {
if (node.type !== 'rule') {
if (node.type === 'atrule' && isProcessableLayerRule(node)) {
return;
}

if (node.type === 'atrule' && CONDITIONAL_ATRULES.includes(node.name.toLowerCase())) {
return;
}

Expand Down
7 changes: 4 additions & 3 deletions plugins/postcss-cascade-layers/src/desugar-nested-layers.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
import type { Container, AtRule, ChildNode } from 'postcss';
import { removeEmptyAncestorBlocks, removeEmptyDescendantBlocks } from './clean-blocks';
import { isProcessableLayerRule } from './is-processable-layer-rule';
import type { Model } from './model';
import { someAtRuleInTree } from './some-in-tree';

export function desugarNestedLayers(root: Container<ChildNode>, model: Model) {
while (someAtRuleInTree(root, (node) => {
return node.nodes && someAtRuleInTree(node, (nested) => {
return nested.name.toLowerCase() === 'layer';
return isProcessableLayerRule(nested);
});
})) {
let foundUnexpectedLayerNesting = false;

root.walkAtRules((layerRule) => {
if (layerRule.name.toLowerCase() !== 'layer') {
if (!isProcessableLayerRule(layerRule)) {
return;
}

if (layerRule.parent === root) {
return;
}

if (layerRule.parent.type === 'atrule' && (layerRule.parent as AtRule).name.toLowerCase() === 'layer') {
if (layerRule.parent.type === 'atrule' && isProcessableLayerRule(layerRule.parent as AtRule)) {
const parent = layerRule.parent as AtRule;

// Concatenate the current layer params with those of the parent. Store the result in the data model.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { AtRule, Node } from 'postcss';
import { isProcessableLayerRule } from './is-processable-layer-rule';

// Returns the first ancestor of the current node that is an @layer rule.
export function getLayerAtRuleAncestor(node: Node): AtRule | null {
Expand All @@ -9,7 +10,7 @@ export function getLayerAtRuleAncestor(node: Node): AtRule | null {
continue;
}

if ((parent as AtRule).name.toLowerCase() === 'layer') {
if (isProcessableLayerRule(parent as AtRule)) {
return parent as AtRule;
}

Expand Down
7 changes: 4 additions & 3 deletions plugins/postcss-cascade-layers/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { recordLayerOrder } from './record-layer-order';
import { ATRULES_WITH_NON_SELECTOR_BLOCK_LISTS, INVALID_LAYER_NAME } from './constants';
import { splitImportantStyles } from './split-important-styles';
import { pluginOptions } from './options';
import { isProcessableLayerRule } from './is-processable-layer-rule';

const creator: PluginCreator<pluginOptions> = (opts?: pluginOptions) => {
const options = Object.assign({
Expand All @@ -22,7 +23,7 @@ const creator: PluginCreator<pluginOptions> = (opts?: pluginOptions) => {

return {
postcssPlugin: 'postcss-cascade-layers',
OnceExit(root: Container, { result }: { result: Result }) {
Once(root: Container, { result }: { result: Result }) {

// Warnings
if (options.onRevertLayerKeyword) {
Expand Down Expand Up @@ -135,9 +136,9 @@ const creator: PluginCreator<pluginOptions> = (opts?: pluginOptions) => {

// Remove all @layer at-rules
// Contained styles are inserted before
while (someAtRuleInTree(root, (node) => node.name.toLowerCase() === 'layer')) {
while (someAtRuleInTree(root, (node) => isProcessableLayerRule(node))) {
root.walkAtRules((atRule) => {
if (atRule.name.toLowerCase() !== 'layer') {
if (!isProcessableLayerRule(atRule)) {
return;
}

Expand Down
28 changes: 28 additions & 0 deletions plugins/postcss-cascade-layers/src/is-processable-layer-rule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { AtRule, ChildNode, Container, Document } from 'postcss';

const allowedParentAtRules = new Set(['layer', 'supports', 'media']);

export function isProcessableLayerRule(atRule: AtRule): boolean {
if (atRule.type !== 'atrule') {
return false;
}

if (atRule.name.toLowerCase() !== 'layer') {
return false;
}

let parent : Container<ChildNode>|Document = atRule.parent;
while (parent) {
if (parent.type === 'rule') {
return false;
}

if (parent.type === 'atrule' && !allowedParentAtRules.has((parent as AtRule).name.toLowerCase())) {
return false;
}

parent = parent.parent;
}

return true;
}
3 changes: 2 additions & 1 deletion plugins/postcss-cascade-layers/src/model.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { AtRule, Node } from 'postcss';
import { ANONYMOUS_LAYER_SUFFIX, IMPLICIT_LAYER_SUFFIX } from './constants';
import { isProcessableLayerRule } from './is-processable-layer-rule';

export class Model {
anonymousLayerCount = 0;
Expand Down Expand Up @@ -79,7 +80,7 @@ export class Model {
continue;
}

if ((parent as AtRule).name.toLowerCase() === 'layer') {
if (isProcessableLayerRule(parent as AtRule)) {
params.push(...this.layerParamsParsed.get((parent as AtRule).params));
}

Expand Down
3 changes: 2 additions & 1 deletion plugins/postcss-cascade-layers/src/record-layer-order.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import type { Container, Result } from 'postcss';
import { ANONYMOUS_LAYER_SUFFIX, IMPLICIT_LAYER_SUFFIX } from './constants';
import { getConditionalAtRuleAncestor } from './get-conditional-atrule-ancestor';
import { isProcessableLayerRule } from './is-processable-layer-rule';
import type { Model } from './model';
import { pluginOptions } from './options';

export function recordLayerOrder(root: Container, model: Model, { result, options }: { result: Result, options: pluginOptions }) {
// record layer order
root.walkAtRules((layerRule) => {
if (layerRule.name.toLowerCase() !== 'layer') {
if (!isProcessableLayerRule(layerRule)) {
return;
}

Expand Down
19 changes: 2 additions & 17 deletions plugins/postcss-cascade-layers/src/sort-root-nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import type { Model } from './model';
import { ATRULES_WITH_NON_SELECTOR_BLOCK_LISTS, CONDITIONAL_ATRULES, WITH_SELECTORS_LAYER_NAME } from './constants';
import { someInTree } from './some-in-tree';
import { removeEmptyAncestorBlocks, removeEmptyDescendantBlocks } from './clean-blocks';
import { isProcessableLayerRule } from './is-processable-layer-rule';

// Sort root nodes to apply the preferred order by layer priority for non-selector rules.
// Selector rules are adjusted by specificity.
export function sortRootNodes(root: Container, model: Model) {
// Separate selector rules from other rules
root.walkAtRules((layerRule) => {
if (layerRule.name.toLowerCase() !== 'layer') {
if (!isProcessableLayerRule(layerRule)) {
return;
}

Expand Down Expand Up @@ -70,22 +71,6 @@ export function sortRootNodes(root: Container, model: Model) {
});

root.nodes.sort((a, b) => {
const aIsCharset = a.type === 'atrule' && a.name.toLowerCase() === 'charset';
const bIsCharset = b.type === 'atrule' && b.name.toLowerCase() === 'charset';
if (aIsCharset && bIsCharset) {
return 0;
} else if (aIsCharset !== bIsCharset) {
return aIsCharset ? -1 : 1;
}

const aIsImport = a.type === 'atrule' && a.name.toLowerCase() === 'import';
const bIsImport = b.type === 'atrule' && b.name.toLowerCase() === 'import';
if (aIsImport && bIsImport) {
return 0;
} else if (aIsImport !== bIsImport) {
return aIsImport ? -1 : 1;
}

const aIsLayer = a.type === 'atrule' && a.name.toLowerCase() === 'layer';
const bIsLayer = b.type === 'atrule' && b.name.toLowerCase() === 'layer';
if (aIsLayer && bIsLayer) {
Expand Down
23 changes: 23 additions & 0 deletions plugins/postcss-cascade-layers/test/extensions.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* We expect unlayered extensions to appear later so that they override layered extensions */

@custom-media --custom-media-query: unlayered;

@custom-selector --custom-selector: #unlayered;

@property --property {
syntax: '<color>';
inherits: false;
initial-value: unlayered;
}

@layer A {
@custom-media --custom-media-query: layered;

@custom-selector --custom-selector: #layered;

@property --property {
syntax: '<color>';
inherits: false;
initial-value: layered;
}
}
20 changes: 20 additions & 0 deletions plugins/postcss-cascade-layers/test/extensions.expect.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

@custom-media --custom-media-query: layered;

@custom-selector --custom-selector: #layered;

@property --property {
syntax: '<color>';
inherits: false;
initial-value: layered;
}/* We expect unlayered extensions to appear later so that they override layered extensions */

@custom-media --custom-media-query: unlayered;

@custom-selector --custom-selector: #unlayered;

@property --property {
syntax: '<color>';
inherits: false;
initial-value: unlayered;
}
14 changes: 14 additions & 0 deletions plugins/postcss-cascade-layers/test/invalid-nested-css.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.unlayered {
contain: layout inline-size;
}

.partially-layered {
background-color: blue;
height: 100px;
width: 100px;

@layer A {
/* Only conditional rules are allowed to be nested */
background-color: red;
}
}
Loading