From c0db55d604e362e36a4d6656ea1e2eb7ce1c96a1 Mon Sep 17 00:00:00 2001 From: Olu Niyi-Awosusi Date: Fri, 11 Feb 2022 12:56:06 +0000 Subject: [PATCH 01/33] initial commit --- plugins/postcss-cascade-layers/.gitignore | 6 + plugins/postcss-cascade-layers/.nvmrc | 1 + plugins/postcss-cascade-layers/.tape.mjs | 17 +++ plugins/postcss-cascade-layers/CHANGELOG.md | 1 + plugins/postcss-cascade-layers/INSTALL.md | 3 + plugins/postcss-cascade-layers/LICENSE.md | 108 ++++++++++++++++++ plugins/postcss-cascade-layers/README.md | 5 + plugins/postcss-cascade-layers/package.json | 55 +++++++++ plugins/postcss-cascade-layers/src/cli.ts | 16 +++ plugins/postcss-cascade-layers/src/index.ts | 21 ++++ .../postcss-cascade-layers/stryker.conf.json | 19 +++ .../postcss-cascade-layers/test/_import.mjs | 6 + .../postcss-cascade-layers/test/_require.cjs | 6 + .../test/basic.color.expect.css | 7 ++ plugins/postcss-cascade-layers/test/basic.css | 7 ++ .../test/basic.expect.css | 7 ++ .../test/cli/basic.color.expect.css | 5 + .../postcss-cascade-layers/test/cli/basic.css | 3 + .../test/cli/basic.expect.css | 5 + .../test/cli/basic.external-map.expect.css | 5 + .../cli/basic.external-map.expect.css.map | 1 + .../test/cli/basic.failure.expect.css | 0 .../test/cli/basic.no-map.expect.css | 3 + .../test/cli/basic.replace.css | 5 + .../test/cli/basic.replace.expect.css | 5 + .../test/cli/basic.stdin.expect.css | 5 + .../postcss-cascade-layers/test/cli/out/a.css | 5 + .../test/cli/out/a.css.map | 1 + .../test/cli/out/a.expect.css | 5 + .../test/cli/out/a.expect.css.map | 1 + .../postcss-cascade-layers/test/cli/out/b.css | 5 + .../test/cli/out/b.css.map | 1 + .../test/cli/out/b.expect.css | 5 + .../test/cli/out/b.expect.css.map | 1 + .../test/cli/out/concatenated.css | 6 + .../test/cli/out/concatenated.expect.css | 6 + .../postcss-cascade-layers/test/cli/src/a.css | 3 + .../postcss-cascade-layers/test/cli/src/b.css | 3 + .../postcss-cascade-layers/test/cli/test.sh | 94 +++++++++++++++ .../postcss-cascade-layers/test/example.css | 7 ++ .../test/example.expect.css | 7 ++ plugins/postcss-cascade-layers/tsconfig.json | 9 ++ 42 files changed, 481 insertions(+) create mode 100644 plugins/postcss-cascade-layers/.gitignore create mode 100644 plugins/postcss-cascade-layers/.nvmrc create mode 100644 plugins/postcss-cascade-layers/.tape.mjs create mode 100644 plugins/postcss-cascade-layers/CHANGELOG.md create mode 100644 plugins/postcss-cascade-layers/INSTALL.md create mode 100644 plugins/postcss-cascade-layers/LICENSE.md create mode 100644 plugins/postcss-cascade-layers/README.md create mode 100644 plugins/postcss-cascade-layers/package.json create mode 100644 plugins/postcss-cascade-layers/src/cli.ts create mode 100644 plugins/postcss-cascade-layers/src/index.ts create mode 100644 plugins/postcss-cascade-layers/stryker.conf.json create mode 100644 plugins/postcss-cascade-layers/test/_import.mjs create mode 100644 plugins/postcss-cascade-layers/test/_require.cjs create mode 100644 plugins/postcss-cascade-layers/test/basic.color.expect.css create mode 100644 plugins/postcss-cascade-layers/test/basic.css create mode 100644 plugins/postcss-cascade-layers/test/basic.expect.css create mode 100644 plugins/postcss-cascade-layers/test/cli/basic.color.expect.css create mode 100644 plugins/postcss-cascade-layers/test/cli/basic.css create mode 100644 plugins/postcss-cascade-layers/test/cli/basic.expect.css create mode 100644 plugins/postcss-cascade-layers/test/cli/basic.external-map.expect.css create mode 100644 plugins/postcss-cascade-layers/test/cli/basic.external-map.expect.css.map create mode 100644 plugins/postcss-cascade-layers/test/cli/basic.failure.expect.css create mode 100644 plugins/postcss-cascade-layers/test/cli/basic.no-map.expect.css create mode 100644 plugins/postcss-cascade-layers/test/cli/basic.replace.css create mode 100644 plugins/postcss-cascade-layers/test/cli/basic.replace.expect.css create mode 100644 plugins/postcss-cascade-layers/test/cli/basic.stdin.expect.css create mode 100644 plugins/postcss-cascade-layers/test/cli/out/a.css create mode 100644 plugins/postcss-cascade-layers/test/cli/out/a.css.map create mode 100644 plugins/postcss-cascade-layers/test/cli/out/a.expect.css create mode 100644 plugins/postcss-cascade-layers/test/cli/out/a.expect.css.map create mode 100644 plugins/postcss-cascade-layers/test/cli/out/b.css create mode 100644 plugins/postcss-cascade-layers/test/cli/out/b.css.map create mode 100644 plugins/postcss-cascade-layers/test/cli/out/b.expect.css create mode 100644 plugins/postcss-cascade-layers/test/cli/out/b.expect.css.map create mode 100644 plugins/postcss-cascade-layers/test/cli/out/concatenated.css create mode 100644 plugins/postcss-cascade-layers/test/cli/out/concatenated.expect.css create mode 100644 plugins/postcss-cascade-layers/test/cli/src/a.css create mode 100644 plugins/postcss-cascade-layers/test/cli/src/b.css create mode 100644 plugins/postcss-cascade-layers/test/cli/test.sh create mode 100644 plugins/postcss-cascade-layers/test/example.css create mode 100644 plugins/postcss-cascade-layers/test/example.expect.css create mode 100644 plugins/postcss-cascade-layers/tsconfig.json diff --git a/plugins/postcss-cascade-layers/.gitignore b/plugins/postcss-cascade-layers/.gitignore new file mode 100644 index 000000000..7172b04f1 --- /dev/null +++ b/plugins/postcss-cascade-layers/.gitignore @@ -0,0 +1,6 @@ +node_modules +package-lock.json +yarn.lock +*.result.css +*.result.css.map +dist/* diff --git a/plugins/postcss-cascade-layers/.nvmrc b/plugins/postcss-cascade-layers/.nvmrc new file mode 100644 index 000000000..f0b10f153 --- /dev/null +++ b/plugins/postcss-cascade-layers/.nvmrc @@ -0,0 +1 @@ +v16.13.1 diff --git a/plugins/postcss-cascade-layers/.tape.mjs b/plugins/postcss-cascade-layers/.tape.mjs new file mode 100644 index 000000000..d75456c1e --- /dev/null +++ b/plugins/postcss-cascade-layers/.tape.mjs @@ -0,0 +1,17 @@ +import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import plugin from '@csstools/postcss-cascade-layers'; + +postcssTape(plugin)({ + basic: { + message: "supports basic usage", + }, + 'basic:color': { + message: "supports { color: '' }", + options: { + color: 'purple' + } + }, + example: { + message: "minimal example", + }, +}); diff --git a/plugins/postcss-cascade-layers/CHANGELOG.md b/plugins/postcss-cascade-layers/CHANGELOG.md new file mode 100644 index 000000000..2475e1207 --- /dev/null +++ b/plugins/postcss-cascade-layers/CHANGELOG.md @@ -0,0 +1 @@ +# Changes to PostCSS Base Plugin diff --git a/plugins/postcss-cascade-layers/INSTALL.md b/plugins/postcss-cascade-layers/INSTALL.md new file mode 100644 index 000000000..ceacefa74 --- /dev/null +++ b/plugins/postcss-cascade-layers/INSTALL.md @@ -0,0 +1,3 @@ +# Installing PostCSS Base Plugin + + diff --git a/plugins/postcss-cascade-layers/LICENSE.md b/plugins/postcss-cascade-layers/LICENSE.md new file mode 100644 index 000000000..0bc1fa706 --- /dev/null +++ b/plugins/postcss-cascade-layers/LICENSE.md @@ -0,0 +1,108 @@ +# CC0 1.0 Universal + +## Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator and +subsequent owner(s) (each and all, an “owner”) of an original work of +authorship and/or a database (each, a “Work”). + +Certain owners wish to permanently relinquish those rights to a Work for the +purpose of contributing to a commons of creative, cultural and scientific works +(“Commons”) that the public can reliably and without fear of later claims of +infringement build upon, modify, incorporate in other works, reuse and +redistribute as freely as possible in any form whatsoever and for any purposes, +including without limitation commercial purposes. These owners may contribute +to the Commons to promote the ideal of a free culture and the further +production of creative, cultural and scientific works, or to gain reputation or +greater distribution for their Work in part through the use and efforts of +others. + +For these and/or other purposes and motivations, and without any expectation of +additional consideration or compensation, the person associating CC0 with a +Work (the “Affirmer”), to the extent that he or she is an owner of Copyright +and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and +publicly distribute the Work under its terms, with knowledge of his or her +Copyright and Related Rights in the Work and the meaning and intended legal +effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be + protected by copyright and related or neighboring rights (“Copyright and + Related Rights”). Copyright and Related Rights include, but are not limited + to, the following: + 1. the right to reproduce, adapt, distribute, perform, display, communicate, + and translate a Work; + 2. moral rights retained by the original author(s) and/or performer(s); + 3. publicity and privacy rights pertaining to a person’s image or likeness + depicted in a Work; + 4. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(i), below; + 5. rights protecting the extraction, dissemination, use and reuse of data in + a Work; + 6. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation thereof, + including any amended or successor version of such directive); and + 7. other similar, equivalent or corresponding rights throughout the world + based on applicable law or treaty, and any national implementations + thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention of, + applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and + unconditionally waives, abandons, and surrenders all of Affirmer’s Copyright + and Related Rights and associated claims and causes of action, whether now + known or unknown (including existing as well as future claims and causes of + action), in the Work (i) in all territories worldwide, (ii) for the maximum + duration provided by applicable law or treaty (including future time + extensions), (iii) in any current or future medium and for any number of + copies, and (iv) for any purpose whatsoever, including without limitation + commercial, advertising or promotional purposes (the “Waiver”). Affirmer + makes the Waiver for the benefit of each member of the public at large and + to the detriment of Affirmer’s heirs and successors, fully intending that + such Waiver shall not be subject to revocation, rescission, cancellation, + termination, or any other legal or equitable action to disrupt the quiet + enjoyment of the Work by the public as contemplated by Affirmer’s express + Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason be + judged legally invalid or ineffective under applicable law, then the Waiver + shall be preserved to the maximum extent permitted taking into account + Affirmer’s express Statement of Purpose. In addition, to the extent the + Waiver is so judged Affirmer hereby grants to each affected person a + royalty-free, non transferable, non sublicensable, non exclusive, + irrevocable and unconditional license to exercise Affirmer’s Copyright and + Related Rights in the Work (i) in all territories worldwide, (ii) for the + maximum duration provided by applicable law or treaty (including future time + extensions), (iii) in any current or future medium and for any number of + copies, and (iv) for any purpose whatsoever, including without limitation + commercial, advertising or promotional purposes (the “License”). The License + shall be deemed effective as of the date CC0 was applied by Affirmer to the + Work. Should any part of the License for any reason be judged legally + invalid or ineffective under applicable law, such partial invalidity or + ineffectiveness shall not invalidate the remainder of the License, and in + such case Affirmer hereby affirms that he or she will not (i) exercise any + of his or her remaining Copyright and Related Rights in the Work or (ii) + assert any associated claims and causes of action with respect to the Work, + in either case contrary to Affirmer’s express Statement of Purpose. + +4. Limitations and Disclaimers. + 1. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + 2. Affirmer offers the Work as-is and makes no representations or warranties + of any kind concerning the Work, express, implied, statutory or + otherwise, including without limitation warranties of title, + merchantability, fitness for a particular purpose, non infringement, or + the absence of latent or other defects, accuracy, or the present or + absence of errors, whether or not discoverable, all to the greatest + extent permissible under applicable law. + 3. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person’s Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the Work. + 4. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to this + CC0 or use of the Work. + +For more information, please see +http://creativecommons.org/publicdomain/zero/1.0/. diff --git a/plugins/postcss-cascade-layers/README.md b/plugins/postcss-cascade-layers/README.md new file mode 100644 index 000000000..a631e79c7 --- /dev/null +++ b/plugins/postcss-cascade-layers/README.md @@ -0,0 +1,5 @@ +# PostCSS Base Plugin [PostCSS Logo][postcss] + + + +[postcss]: https://github.com/postcss/postcss diff --git a/plugins/postcss-cascade-layers/package.json b/plugins/postcss-cascade-layers/package.json new file mode 100644 index 000000000..12d3de10d --- /dev/null +++ b/plugins/postcss-cascade-layers/package.json @@ -0,0 +1,55 @@ +{ + "name": "@csstools/postcss-cascade-layers", + "private": true, + "version": "0.0.0", + "description": "A base plugin", + "author": "Jonathan Neal ", + "license": "CC0-1.0", + "engines": { + "node": "^12 || ^14 || >=16" + }, + "main": "dist/index.cjs", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.cjs", + "default": "./dist/index.mjs" + } + }, + "files": [ + "CHANGELOG.md", + "LICENSE.md", + "README.md", + "dist" + ], + "bin": { + "postcss-base-plugin": "dist/cli.cjs" + }, + "scripts": { + "build": "rollup -c ../../rollup/default.js", + "clean": "node -e \"fs.rmSync('./dist', { recursive: true, force: true });\"", + "lint": "eslint ./src --ext .js --ext .ts --ext .mjs --no-error-on-unmatched-pattern", + "prepublishOnly": "npm run clean && npm run build && npm run test", + "stryker": "stryker run --logLevel error", + "test": "node .tape.mjs && npm run test:exports", + "test:rewrite-expects": "REWRITE_EXPECTS=true node .tape.mjs", + "test:cli": "bash ./test/cli/test.sh", + "test:exports": "node ./test/_import.mjs && node ./test/_require.cjs" + }, + "peerDependencies": { + "postcss": "^8.3" + }, + "keywords": [ + "postcss-plugin" + ], + "repository": { + "type": "git", + "url": "https://github.com/csstools/postcss-plugins.git", + "directory": "plugins/postcss-cascade-layers" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/plugins/postcss-cascade-layers/src/cli.ts b/plugins/postcss-cascade-layers/src/cli.ts new file mode 100644 index 000000000..f087923cf --- /dev/null +++ b/plugins/postcss-cascade-layers/src/cli.ts @@ -0,0 +1,16 @@ +import plugin from './index'; +import { cli, helpTextLogger } from '@csstools/base-cli'; + +cli( + plugin, + ['color', 'another_option'], + helpTextLogger( + 'postcss-cascade-layers', + 'Base Plugin', + 'An example plugin CLI', + { + color: 'A CSS color', + another_option: true, + }, + ), +); diff --git a/plugins/postcss-cascade-layers/src/index.ts b/plugins/postcss-cascade-layers/src/index.ts new file mode 100644 index 000000000..ee1f96bc0 --- /dev/null +++ b/plugins/postcss-cascade-layers/src/index.ts @@ -0,0 +1,21 @@ +import type { PluginCreator } from 'postcss'; + +const creator: PluginCreator<{ color: string }> = (opts?: { color: string }) => { + return { + postcssPlugin: 'postcss-base-plugin', + Declaration(decl) { + if (decl.value === 'red') { + if (opts && opts.color) { + decl.value = opts.color; + } else { + decl.value = 'blue'; + } + } + }, + }; +}; + +creator.postcss = true; + +export default creator; + diff --git a/plugins/postcss-cascade-layers/stryker.conf.json b/plugins/postcss-cascade-layers/stryker.conf.json new file mode 100644 index 000000000..091dc740c --- /dev/null +++ b/plugins/postcss-cascade-layers/stryker.conf.json @@ -0,0 +1,19 @@ +{ + "$schema": "../../node_modules/@stryker-mutator/core/schema/stryker-schema.json", + "mutate": [ + "src/**/*.ts" + ], + "buildCommand": "npm run build", + "testRunner": "command", + "coverageAnalysis": "perTest", + "tempDirName": "../../.stryker-tmp", + "commandRunner": { + "command": "npm run test:tape" + }, + "thresholds": { + "high": 100, + "low": 100, + "break": 100 + }, + "inPlace": true +} diff --git a/plugins/postcss-cascade-layers/test/_import.mjs b/plugins/postcss-cascade-layers/test/_import.mjs new file mode 100644 index 000000000..0e4c84832 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/_import.mjs @@ -0,0 +1,6 @@ +import assert from 'assert'; +import plugin from '@csstools/postcss-base-plugin'; +plugin(); + +assert.ok(plugin.postcss, 'should have "postcss flag"'); +assert.equal(typeof plugin, 'function', 'should return a function'); diff --git a/plugins/postcss-cascade-layers/test/_require.cjs b/plugins/postcss-cascade-layers/test/_require.cjs new file mode 100644 index 000000000..8b21a7929 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/_require.cjs @@ -0,0 +1,6 @@ +const assert = require('assert'); +const plugin = require('@csstools/postcss-base-plugin'); +plugin(); + +assert.ok(plugin.postcss, 'should have "postcss flag"'); +assert.equal(typeof plugin, 'function', 'should return a function'); diff --git a/plugins/postcss-cascade-layers/test/basic.color.expect.css b/plugins/postcss-cascade-layers/test/basic.color.expect.css new file mode 100644 index 000000000..041b1dabf --- /dev/null +++ b/plugins/postcss-cascade-layers/test/basic.color.expect.css @@ -0,0 +1,7 @@ +.foo { + color: purple; +} + +.baz { + color: green; +} diff --git a/plugins/postcss-cascade-layers/test/basic.css b/plugins/postcss-cascade-layers/test/basic.css new file mode 100644 index 000000000..181f83a54 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/basic.css @@ -0,0 +1,7 @@ +.foo { + color: red; +} + +.baz { + color: green; +} diff --git a/plugins/postcss-cascade-layers/test/basic.expect.css b/plugins/postcss-cascade-layers/test/basic.expect.css new file mode 100644 index 000000000..9d738d5ac --- /dev/null +++ b/plugins/postcss-cascade-layers/test/basic.expect.css @@ -0,0 +1,7 @@ +.foo { + color: blue; +} + +.baz { + color: green; +} diff --git a/plugins/postcss-cascade-layers/test/cli/basic.color.expect.css b/plugins/postcss-cascade-layers/test/cli/basic.color.expect.css new file mode 100644 index 000000000..93f6b5280 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/cli/basic.color.expect.css @@ -0,0 +1,5 @@ +.foo { + color: purple; +} + +/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImJhc2ljLmNzcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtDQUNDLGFBQVU7QUFDWCIsImZpbGUiOiJiYXNpYy5jb2xvci5yZXN1bHQuY3NzIiwic291cmNlc0NvbnRlbnQiOlsiLmZvbyB7XG5cdGNvbG9yOiByZWQ7XG59XG4iXX0= */ diff --git a/plugins/postcss-cascade-layers/test/cli/basic.css b/plugins/postcss-cascade-layers/test/cli/basic.css new file mode 100644 index 000000000..cedf0a6d1 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/cli/basic.css @@ -0,0 +1,3 @@ +.foo { + color: red; +} diff --git a/plugins/postcss-cascade-layers/test/cli/basic.expect.css b/plugins/postcss-cascade-layers/test/cli/basic.expect.css new file mode 100644 index 000000000..3f6f0d3a4 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/cli/basic.expect.css @@ -0,0 +1,5 @@ +.foo { + color: blue; +} + +/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImJhc2ljLmNzcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtDQUNDLFdBQVU7QUFDWCIsImZpbGUiOiJiYXNpYy5yZXN1bHQuY3NzIiwic291cmNlc0NvbnRlbnQiOlsiLmZvbyB7XG5cdGNvbG9yOiByZWQ7XG59XG4iXX0= */ diff --git a/plugins/postcss-cascade-layers/test/cli/basic.external-map.expect.css b/plugins/postcss-cascade-layers/test/cli/basic.external-map.expect.css new file mode 100644 index 000000000..b8aaa92b1 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/cli/basic.external-map.expect.css @@ -0,0 +1,5 @@ +.foo { + color: blue; +} + +/*# sourceMappingURL=basic.external-map.result.css.map */ \ No newline at end of file diff --git a/plugins/postcss-cascade-layers/test/cli/basic.external-map.expect.css.map b/plugins/postcss-cascade-layers/test/cli/basic.external-map.expect.css.map new file mode 100644 index 000000000..8f8e733cc --- /dev/null +++ b/plugins/postcss-cascade-layers/test/cli/basic.external-map.expect.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../stdin"],"names":[],"mappings":"AAAA;CACC,WAAU;AACX","file":"basic.external-map.result.css","sourcesContent":[".foo {\n\tcolor: red;\n}\n"]} \ No newline at end of file diff --git a/plugins/postcss-cascade-layers/test/cli/basic.failure.expect.css b/plugins/postcss-cascade-layers/test/cli/basic.failure.expect.css new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/postcss-cascade-layers/test/cli/basic.no-map.expect.css b/plugins/postcss-cascade-layers/test/cli/basic.no-map.expect.css new file mode 100644 index 000000000..c47fa33b8 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/cli/basic.no-map.expect.css @@ -0,0 +1,3 @@ +.foo { + color: blue; +} diff --git a/plugins/postcss-cascade-layers/test/cli/basic.replace.css b/plugins/postcss-cascade-layers/test/cli/basic.replace.css new file mode 100644 index 000000000..49bc11102 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/cli/basic.replace.css @@ -0,0 +1,5 @@ +.foo { + color: blue; +} + +/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImJhc2ljLnJlcGxhY2UuY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0NBQ0MsV0FBVTtBQUNYIiwiZmlsZSI6ImJhc2ljLnJlcGxhY2UuY3NzIiwic291cmNlc0NvbnRlbnQiOlsiLmZvbyB7XG5cdGNvbG9yOiByZWQ7XG59XG4iXX0= */ diff --git a/plugins/postcss-cascade-layers/test/cli/basic.replace.expect.css b/plugins/postcss-cascade-layers/test/cli/basic.replace.expect.css new file mode 100644 index 000000000..49bc11102 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/cli/basic.replace.expect.css @@ -0,0 +1,5 @@ +.foo { + color: blue; +} + +/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImJhc2ljLnJlcGxhY2UuY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0NBQ0MsV0FBVTtBQUNYIiwiZmlsZSI6ImJhc2ljLnJlcGxhY2UuY3NzIiwic291cmNlc0NvbnRlbnQiOlsiLmZvbyB7XG5cdGNvbG9yOiByZWQ7XG59XG4iXX0= */ diff --git a/plugins/postcss-cascade-layers/test/cli/basic.stdin.expect.css b/plugins/postcss-cascade-layers/test/cli/basic.stdin.expect.css new file mode 100644 index 000000000..7def14035 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/cli/basic.stdin.expect.css @@ -0,0 +1,5 @@ +.foo { + color: blue; +} + +/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInN0ZGluIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0NBQ0MsV0FBVTtBQUNYIiwiZmlsZSI6InN0ZG91dCIsInNvdXJjZXNDb250ZW50IjpbIi5mb28ge1xuXHRjb2xvcjogcmVkO1xufVxuIl19 */ diff --git a/plugins/postcss-cascade-layers/test/cli/out/a.css b/plugins/postcss-cascade-layers/test/cli/out/a.css new file mode 100644 index 000000000..60e2001c0 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/cli/out/a.css @@ -0,0 +1,5 @@ +.foo { + color: blue; +} + +/*# sourceMappingURL=a.css.map */ \ No newline at end of file diff --git a/plugins/postcss-cascade-layers/test/cli/out/a.css.map b/plugins/postcss-cascade-layers/test/cli/out/a.css.map new file mode 100644 index 000000000..d5c8b6f40 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/cli/out/a.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/a.css"],"names":[],"mappings":"AAAA;CACC,WAAU;AACX","file":"a.css","sourcesContent":[".foo {\n\tcolor: red;\n}\n"]} \ No newline at end of file diff --git a/plugins/postcss-cascade-layers/test/cli/out/a.expect.css b/plugins/postcss-cascade-layers/test/cli/out/a.expect.css new file mode 100644 index 000000000..60e2001c0 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/cli/out/a.expect.css @@ -0,0 +1,5 @@ +.foo { + color: blue; +} + +/*# sourceMappingURL=a.css.map */ \ No newline at end of file diff --git a/plugins/postcss-cascade-layers/test/cli/out/a.expect.css.map b/plugins/postcss-cascade-layers/test/cli/out/a.expect.css.map new file mode 100644 index 000000000..d5c8b6f40 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/cli/out/a.expect.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/a.css"],"names":[],"mappings":"AAAA;CACC,WAAU;AACX","file":"a.css","sourcesContent":[".foo {\n\tcolor: red;\n}\n"]} \ No newline at end of file diff --git a/plugins/postcss-cascade-layers/test/cli/out/b.css b/plugins/postcss-cascade-layers/test/cli/out/b.css new file mode 100644 index 000000000..ac091faeb --- /dev/null +++ b/plugins/postcss-cascade-layers/test/cli/out/b.css @@ -0,0 +1,5 @@ +.baz { + color: blue; +} + +/*# sourceMappingURL=b.css.map */ \ No newline at end of file diff --git a/plugins/postcss-cascade-layers/test/cli/out/b.css.map b/plugins/postcss-cascade-layers/test/cli/out/b.css.map new file mode 100644 index 000000000..0a97ca290 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/cli/out/b.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/b.css"],"names":[],"mappings":"AAAA;CACC,WAAU;AACX","file":"b.css","sourcesContent":[".baz {\n\tcolor: red;\n}\n"]} \ No newline at end of file diff --git a/plugins/postcss-cascade-layers/test/cli/out/b.expect.css b/plugins/postcss-cascade-layers/test/cli/out/b.expect.css new file mode 100644 index 000000000..ac091faeb --- /dev/null +++ b/plugins/postcss-cascade-layers/test/cli/out/b.expect.css @@ -0,0 +1,5 @@ +.baz { + color: blue; +} + +/*# sourceMappingURL=b.css.map */ \ No newline at end of file diff --git a/plugins/postcss-cascade-layers/test/cli/out/b.expect.css.map b/plugins/postcss-cascade-layers/test/cli/out/b.expect.css.map new file mode 100644 index 000000000..0a97ca290 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/cli/out/b.expect.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/b.css"],"names":[],"mappings":"AAAA;CACC,WAAU;AACX","file":"b.css","sourcesContent":[".baz {\n\tcolor: red;\n}\n"]} \ No newline at end of file diff --git a/plugins/postcss-cascade-layers/test/cli/out/concatenated.css b/plugins/postcss-cascade-layers/test/cli/out/concatenated.css new file mode 100644 index 000000000..80726df77 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/cli/out/concatenated.css @@ -0,0 +1,6 @@ +.foo { + color: blue; +} +.baz { + color: blue; +} diff --git a/plugins/postcss-cascade-layers/test/cli/out/concatenated.expect.css b/plugins/postcss-cascade-layers/test/cli/out/concatenated.expect.css new file mode 100644 index 000000000..80726df77 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/cli/out/concatenated.expect.css @@ -0,0 +1,6 @@ +.foo { + color: blue; +} +.baz { + color: blue; +} diff --git a/plugins/postcss-cascade-layers/test/cli/src/a.css b/plugins/postcss-cascade-layers/test/cli/src/a.css new file mode 100644 index 000000000..cedf0a6d1 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/cli/src/a.css @@ -0,0 +1,3 @@ +.foo { + color: red; +} diff --git a/plugins/postcss-cascade-layers/test/cli/src/b.css b/plugins/postcss-cascade-layers/test/cli/src/b.css new file mode 100644 index 000000000..5f2435156 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/cli/src/b.css @@ -0,0 +1,3 @@ +.baz { + color: red; +} diff --git a/plugins/postcss-cascade-layers/test/cli/test.sh b/plugins/postcss-cascade-layers/test/cli/test.sh new file mode 100644 index 000000000..3cb230406 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/cli/test.sh @@ -0,0 +1,94 @@ +set -e + +# Zero out result file +echo '' > ./test/cli/basic.result.css; + +# Test with long flag +postcss-base-plugin ./test/cli/basic.css --output ./test/cli/basic.result.css + +# Check result +git --no-pager diff --no-index --word-diff ./test/cli/basic.expect.css ./test/cli/basic.result.css + +# Reset result file +cat ./test/cli/basic.css > ./test/cli/basic.replace.css; + +# Test replace +postcss-base-plugin ./test/cli/basic.replace.css -r + +# Check result +git --no-pager diff --no-index --word-diff ./test/cli/basic.replace.css ./test/cli/basic.replace.expect.css + +# Zero out result file +echo '' > ./test/cli/basic.color.result.css; + +# Test with short flags and plugin option +postcss-base-plugin ./test/cli/basic.css -o ./test/cli/basic.color.result.css -p '{ "color": "purple" }' + +# Check result +git --no-pager diff --no-index --word-diff ./test/cli/basic.color.expect.css ./test/cli/basic.color.result.css + +# Zero out result file +echo '' > ./test/cli/basic.stdin.result.css; + +# Test with stdin +cat ./test/cli/basic.css | postcss-base-plugin > ./test/cli/basic.stdin.result.css + +# Check result +git --no-pager diff --no-index --word-diff ./test/cli/basic.stdin.expect.css ./test/cli/basic.stdin.result.css + +# Zero out result file +echo '' > ./test/cli/basic.no-map.result.css; + +# Test source maps +postcss-base-plugin ./test/cli/basic.css --no-map -o ./test/cli/basic.no-map.result.css + +# Check result +git --no-pager diff --no-index --word-diff ./test/cli/basic.no-map.expect.css ./test/cli/basic.no-map.result.css + +# Zero out result file +echo '' > ./test/cli/basic.external-map.result.css; +echo '' > ./test/cli/basic.external-map.result.css.map; + +# Test source maps +cat ./test/cli/basic.css | postcss-base-plugin --map -o ./test/cli/basic.external-map.result.css + +# Check result +git --no-pager diff --no-index --word-diff ./test/cli/basic.external-map.expect.css ./test/cli/basic.external-map.result.css +git --no-pager diff --no-index --word-diff ./test/cli/basic.external-map.expect.css.map ./test/cli/basic.external-map.result.css.map + +# Zero out result file +echo '' > ./test/cli/out/a.css +echo '' > ./test/cli/out/a.css.map +echo '' > ./test/cli/out/b.css +echo '' > ./test/cli/out/b.css.map + +# Test source maps +postcss-base-plugin ./test/cli/src/a.css ./test/cli/src/b.css -m -d ./test/cli/out/ + +# Check result +git --no-pager diff --no-index --word-diff ./test/cli/out/a.css ./test/cli/out/a.expect.css +git --no-pager diff --no-index --word-diff ./test/cli/out/b.css ./test/cli/out/b.expect.css +git --no-pager diff --no-index --word-diff ./test/cli/out/a.css.map ./test/cli/out/a.expect.css.map +git --no-pager diff --no-index --word-diff ./test/cli/out/b.css.map ./test/cli/out/b.expect.css.map + + +# Zero out result file +echo '' > ./test/cli/out/concatenated.css + +# Test concat +postcss-base-plugin ./test/cli/src/a.css ./test/cli/src/b.css > ./test/cli/out/concatenated.css + +# Check result +git --no-pager diff --no-index --word-diff ./test/cli/out/concatenated.css ./test/cli/out/concatenated.expect.css + +# Dump some content +echo 'foo' > ./test/cli/basic.failure.result.css + +# Test with incorrect arugments +if postcss-base-plugin ./test/cli/basic.css --does-not-exist > ./test/cli/basic.failure.result.css; then + echo 'Test should have failed'; + exit 1; +else + # Check result + git --no-pager diff --no-index --word-diff ./test/cli/basic.failure.result.css ./test/cli/basic.failure.expect.css +fi diff --git a/plugins/postcss-cascade-layers/test/example.css b/plugins/postcss-cascade-layers/test/example.css new file mode 100644 index 000000000..181f83a54 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/example.css @@ -0,0 +1,7 @@ +.foo { + color: red; +} + +.baz { + color: green; +} diff --git a/plugins/postcss-cascade-layers/test/example.expect.css b/plugins/postcss-cascade-layers/test/example.expect.css new file mode 100644 index 000000000..9d738d5ac --- /dev/null +++ b/plugins/postcss-cascade-layers/test/example.expect.css @@ -0,0 +1,7 @@ +.foo { + color: blue; +} + +.baz { + color: green; +} diff --git a/plugins/postcss-cascade-layers/tsconfig.json b/plugins/postcss-cascade-layers/tsconfig.json new file mode 100644 index 000000000..68a2606f6 --- /dev/null +++ b/plugins/postcss-cascade-layers/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "declarationDir": "." + }, + "include": ["./src/**/*"], + "exclude": ["dist"], +} From f46bc470e8d9b5829ffee5022c0aed3f9489fe74 Mon Sep 17 00:00:00 2001 From: Olu Niyi-Awosusi Date: Fri, 11 Feb 2022 13:57:43 +0000 Subject: [PATCH 02/33] tidying up --- plugins/postcss-cascade-layers/package.json | 4 - plugins/postcss-cascade-layers/src/cli.ts | 16 ---- plugins/postcss-cascade-layers/src/index.ts | 3 +- .../postcss-cascade-layers/test/_import.mjs | 2 +- .../postcss-cascade-layers/test/_require.cjs | 2 +- .../test/cli/basic.color.expect.css | 5 - .../postcss-cascade-layers/test/cli/basic.css | 3 - .../test/cli/basic.expect.css | 5 - .../test/cli/basic.external-map.expect.css | 5 - .../cli/basic.external-map.expect.css.map | 1 - .../test/cli/basic.failure.expect.css | 0 .../test/cli/basic.no-map.expect.css | 3 - .../test/cli/basic.replace.css | 5 - .../test/cli/basic.replace.expect.css | 5 - .../test/cli/basic.stdin.expect.css | 5 - .../postcss-cascade-layers/test/cli/out/a.css | 5 - .../test/cli/out/a.css.map | 1 - .../test/cli/out/a.expect.css | 5 - .../test/cli/out/a.expect.css.map | 1 - .../postcss-cascade-layers/test/cli/out/b.css | 5 - .../test/cli/out/b.css.map | 1 - .../test/cli/out/b.expect.css | 5 - .../test/cli/out/b.expect.css.map | 1 - .../test/cli/out/concatenated.css | 6 -- .../test/cli/out/concatenated.expect.css | 6 -- .../postcss-cascade-layers/test/cli/src/a.css | 3 - .../postcss-cascade-layers/test/cli/src/b.css | 3 - .../postcss-cascade-layers/test/cli/test.sh | 94 ------------------- 28 files changed, 3 insertions(+), 197 deletions(-) delete mode 100644 plugins/postcss-cascade-layers/src/cli.ts delete mode 100644 plugins/postcss-cascade-layers/test/cli/basic.color.expect.css delete mode 100644 plugins/postcss-cascade-layers/test/cli/basic.css delete mode 100644 plugins/postcss-cascade-layers/test/cli/basic.expect.css delete mode 100644 plugins/postcss-cascade-layers/test/cli/basic.external-map.expect.css delete mode 100644 plugins/postcss-cascade-layers/test/cli/basic.external-map.expect.css.map delete mode 100644 plugins/postcss-cascade-layers/test/cli/basic.failure.expect.css delete mode 100644 plugins/postcss-cascade-layers/test/cli/basic.no-map.expect.css delete mode 100644 plugins/postcss-cascade-layers/test/cli/basic.replace.css delete mode 100644 plugins/postcss-cascade-layers/test/cli/basic.replace.expect.css delete mode 100644 plugins/postcss-cascade-layers/test/cli/basic.stdin.expect.css delete mode 100644 plugins/postcss-cascade-layers/test/cli/out/a.css delete mode 100644 plugins/postcss-cascade-layers/test/cli/out/a.css.map delete mode 100644 plugins/postcss-cascade-layers/test/cli/out/a.expect.css delete mode 100644 plugins/postcss-cascade-layers/test/cli/out/a.expect.css.map delete mode 100644 plugins/postcss-cascade-layers/test/cli/out/b.css delete mode 100644 plugins/postcss-cascade-layers/test/cli/out/b.css.map delete mode 100644 plugins/postcss-cascade-layers/test/cli/out/b.expect.css delete mode 100644 plugins/postcss-cascade-layers/test/cli/out/b.expect.css.map delete mode 100644 plugins/postcss-cascade-layers/test/cli/out/concatenated.css delete mode 100644 plugins/postcss-cascade-layers/test/cli/out/concatenated.expect.css delete mode 100644 plugins/postcss-cascade-layers/test/cli/src/a.css delete mode 100644 plugins/postcss-cascade-layers/test/cli/src/b.css delete mode 100644 plugins/postcss-cascade-layers/test/cli/test.sh diff --git a/plugins/postcss-cascade-layers/package.json b/plugins/postcss-cascade-layers/package.json index 12d3de10d..e0a9e1f59 100644 --- a/plugins/postcss-cascade-layers/package.json +++ b/plugins/postcss-cascade-layers/package.json @@ -24,9 +24,6 @@ "README.md", "dist" ], - "bin": { - "postcss-base-plugin": "dist/cli.cjs" - }, "scripts": { "build": "rollup -c ../../rollup/default.js", "clean": "node -e \"fs.rmSync('./dist', { recursive: true, force: true });\"", @@ -35,7 +32,6 @@ "stryker": "stryker run --logLevel error", "test": "node .tape.mjs && npm run test:exports", "test:rewrite-expects": "REWRITE_EXPECTS=true node .tape.mjs", - "test:cli": "bash ./test/cli/test.sh", "test:exports": "node ./test/_import.mjs && node ./test/_require.cjs" }, "peerDependencies": { diff --git a/plugins/postcss-cascade-layers/src/cli.ts b/plugins/postcss-cascade-layers/src/cli.ts deleted file mode 100644 index f087923cf..000000000 --- a/plugins/postcss-cascade-layers/src/cli.ts +++ /dev/null @@ -1,16 +0,0 @@ -import plugin from './index'; -import { cli, helpTextLogger } from '@csstools/base-cli'; - -cli( - plugin, - ['color', 'another_option'], - helpTextLogger( - 'postcss-cascade-layers', - 'Base Plugin', - 'An example plugin CLI', - { - color: 'A CSS color', - another_option: true, - }, - ), -); diff --git a/plugins/postcss-cascade-layers/src/index.ts b/plugins/postcss-cascade-layers/src/index.ts index ee1f96bc0..08c6fb48d 100644 --- a/plugins/postcss-cascade-layers/src/index.ts +++ b/plugins/postcss-cascade-layers/src/index.ts @@ -2,7 +2,7 @@ import type { PluginCreator } from 'postcss'; const creator: PluginCreator<{ color: string }> = (opts?: { color: string }) => { return { - postcssPlugin: 'postcss-base-plugin', + postcssPlugin: 'postcss-cascade-layers', Declaration(decl) { if (decl.value === 'red') { if (opts && opts.color) { @@ -18,4 +18,3 @@ const creator: PluginCreator<{ color: string }> = (opts?: { color: string }) => creator.postcss = true; export default creator; - diff --git a/plugins/postcss-cascade-layers/test/_import.mjs b/plugins/postcss-cascade-layers/test/_import.mjs index 0e4c84832..34ff16211 100644 --- a/plugins/postcss-cascade-layers/test/_import.mjs +++ b/plugins/postcss-cascade-layers/test/_import.mjs @@ -1,5 +1,5 @@ import assert from 'assert'; -import plugin from '@csstools/postcss-base-plugin'; +import plugin from '@csstools/postcss-cascade-layers'; plugin(); assert.ok(plugin.postcss, 'should have "postcss flag"'); diff --git a/plugins/postcss-cascade-layers/test/_require.cjs b/plugins/postcss-cascade-layers/test/_require.cjs index 8b21a7929..0567d76bf 100644 --- a/plugins/postcss-cascade-layers/test/_require.cjs +++ b/plugins/postcss-cascade-layers/test/_require.cjs @@ -1,5 +1,5 @@ const assert = require('assert'); -const plugin = require('@csstools/postcss-base-plugin'); +const plugin = require('@csstools/postcss-cascade-layers'); plugin(); assert.ok(plugin.postcss, 'should have "postcss flag"'); diff --git a/plugins/postcss-cascade-layers/test/cli/basic.color.expect.css b/plugins/postcss-cascade-layers/test/cli/basic.color.expect.css deleted file mode 100644 index 93f6b5280..000000000 --- a/plugins/postcss-cascade-layers/test/cli/basic.color.expect.css +++ /dev/null @@ -1,5 +0,0 @@ -.foo { - color: purple; -} - -/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImJhc2ljLmNzcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtDQUNDLGFBQVU7QUFDWCIsImZpbGUiOiJiYXNpYy5jb2xvci5yZXN1bHQuY3NzIiwic291cmNlc0NvbnRlbnQiOlsiLmZvbyB7XG5cdGNvbG9yOiByZWQ7XG59XG4iXX0= */ diff --git a/plugins/postcss-cascade-layers/test/cli/basic.css b/plugins/postcss-cascade-layers/test/cli/basic.css deleted file mode 100644 index cedf0a6d1..000000000 --- a/plugins/postcss-cascade-layers/test/cli/basic.css +++ /dev/null @@ -1,3 +0,0 @@ -.foo { - color: red; -} diff --git a/plugins/postcss-cascade-layers/test/cli/basic.expect.css b/plugins/postcss-cascade-layers/test/cli/basic.expect.css deleted file mode 100644 index 3f6f0d3a4..000000000 --- a/plugins/postcss-cascade-layers/test/cli/basic.expect.css +++ /dev/null @@ -1,5 +0,0 @@ -.foo { - color: blue; -} - -/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImJhc2ljLmNzcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtDQUNDLFdBQVU7QUFDWCIsImZpbGUiOiJiYXNpYy5yZXN1bHQuY3NzIiwic291cmNlc0NvbnRlbnQiOlsiLmZvbyB7XG5cdGNvbG9yOiByZWQ7XG59XG4iXX0= */ diff --git a/plugins/postcss-cascade-layers/test/cli/basic.external-map.expect.css b/plugins/postcss-cascade-layers/test/cli/basic.external-map.expect.css deleted file mode 100644 index b8aaa92b1..000000000 --- a/plugins/postcss-cascade-layers/test/cli/basic.external-map.expect.css +++ /dev/null @@ -1,5 +0,0 @@ -.foo { - color: blue; -} - -/*# sourceMappingURL=basic.external-map.result.css.map */ \ No newline at end of file diff --git a/plugins/postcss-cascade-layers/test/cli/basic.external-map.expect.css.map b/plugins/postcss-cascade-layers/test/cli/basic.external-map.expect.css.map deleted file mode 100644 index 8f8e733cc..000000000 --- a/plugins/postcss-cascade-layers/test/cli/basic.external-map.expect.css.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../../stdin"],"names":[],"mappings":"AAAA;CACC,WAAU;AACX","file":"basic.external-map.result.css","sourcesContent":[".foo {\n\tcolor: red;\n}\n"]} \ No newline at end of file diff --git a/plugins/postcss-cascade-layers/test/cli/basic.failure.expect.css b/plugins/postcss-cascade-layers/test/cli/basic.failure.expect.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/plugins/postcss-cascade-layers/test/cli/basic.no-map.expect.css b/plugins/postcss-cascade-layers/test/cli/basic.no-map.expect.css deleted file mode 100644 index c47fa33b8..000000000 --- a/plugins/postcss-cascade-layers/test/cli/basic.no-map.expect.css +++ /dev/null @@ -1,3 +0,0 @@ -.foo { - color: blue; -} diff --git a/plugins/postcss-cascade-layers/test/cli/basic.replace.css b/plugins/postcss-cascade-layers/test/cli/basic.replace.css deleted file mode 100644 index 49bc11102..000000000 --- a/plugins/postcss-cascade-layers/test/cli/basic.replace.css +++ /dev/null @@ -1,5 +0,0 @@ -.foo { - color: blue; -} - -/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImJhc2ljLnJlcGxhY2UuY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0NBQ0MsV0FBVTtBQUNYIiwiZmlsZSI6ImJhc2ljLnJlcGxhY2UuY3NzIiwic291cmNlc0NvbnRlbnQiOlsiLmZvbyB7XG5cdGNvbG9yOiByZWQ7XG59XG4iXX0= */ diff --git a/plugins/postcss-cascade-layers/test/cli/basic.replace.expect.css b/plugins/postcss-cascade-layers/test/cli/basic.replace.expect.css deleted file mode 100644 index 49bc11102..000000000 --- a/plugins/postcss-cascade-layers/test/cli/basic.replace.expect.css +++ /dev/null @@ -1,5 +0,0 @@ -.foo { - color: blue; -} - -/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImJhc2ljLnJlcGxhY2UuY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0NBQ0MsV0FBVTtBQUNYIiwiZmlsZSI6ImJhc2ljLnJlcGxhY2UuY3NzIiwic291cmNlc0NvbnRlbnQiOlsiLmZvbyB7XG5cdGNvbG9yOiByZWQ7XG59XG4iXX0= */ diff --git a/plugins/postcss-cascade-layers/test/cli/basic.stdin.expect.css b/plugins/postcss-cascade-layers/test/cli/basic.stdin.expect.css deleted file mode 100644 index 7def14035..000000000 --- a/plugins/postcss-cascade-layers/test/cli/basic.stdin.expect.css +++ /dev/null @@ -1,5 +0,0 @@ -.foo { - color: blue; -} - -/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInN0ZGluIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0NBQ0MsV0FBVTtBQUNYIiwiZmlsZSI6InN0ZG91dCIsInNvdXJjZXNDb250ZW50IjpbIi5mb28ge1xuXHRjb2xvcjogcmVkO1xufVxuIl19 */ diff --git a/plugins/postcss-cascade-layers/test/cli/out/a.css b/plugins/postcss-cascade-layers/test/cli/out/a.css deleted file mode 100644 index 60e2001c0..000000000 --- a/plugins/postcss-cascade-layers/test/cli/out/a.css +++ /dev/null @@ -1,5 +0,0 @@ -.foo { - color: blue; -} - -/*# sourceMappingURL=a.css.map */ \ No newline at end of file diff --git a/plugins/postcss-cascade-layers/test/cli/out/a.css.map b/plugins/postcss-cascade-layers/test/cli/out/a.css.map deleted file mode 100644 index d5c8b6f40..000000000 --- a/plugins/postcss-cascade-layers/test/cli/out/a.css.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../src/a.css"],"names":[],"mappings":"AAAA;CACC,WAAU;AACX","file":"a.css","sourcesContent":[".foo {\n\tcolor: red;\n}\n"]} \ No newline at end of file diff --git a/plugins/postcss-cascade-layers/test/cli/out/a.expect.css b/plugins/postcss-cascade-layers/test/cli/out/a.expect.css deleted file mode 100644 index 60e2001c0..000000000 --- a/plugins/postcss-cascade-layers/test/cli/out/a.expect.css +++ /dev/null @@ -1,5 +0,0 @@ -.foo { - color: blue; -} - -/*# sourceMappingURL=a.css.map */ \ No newline at end of file diff --git a/plugins/postcss-cascade-layers/test/cli/out/a.expect.css.map b/plugins/postcss-cascade-layers/test/cli/out/a.expect.css.map deleted file mode 100644 index d5c8b6f40..000000000 --- a/plugins/postcss-cascade-layers/test/cli/out/a.expect.css.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../src/a.css"],"names":[],"mappings":"AAAA;CACC,WAAU;AACX","file":"a.css","sourcesContent":[".foo {\n\tcolor: red;\n}\n"]} \ No newline at end of file diff --git a/plugins/postcss-cascade-layers/test/cli/out/b.css b/plugins/postcss-cascade-layers/test/cli/out/b.css deleted file mode 100644 index ac091faeb..000000000 --- a/plugins/postcss-cascade-layers/test/cli/out/b.css +++ /dev/null @@ -1,5 +0,0 @@ -.baz { - color: blue; -} - -/*# sourceMappingURL=b.css.map */ \ No newline at end of file diff --git a/plugins/postcss-cascade-layers/test/cli/out/b.css.map b/plugins/postcss-cascade-layers/test/cli/out/b.css.map deleted file mode 100644 index 0a97ca290..000000000 --- a/plugins/postcss-cascade-layers/test/cli/out/b.css.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../src/b.css"],"names":[],"mappings":"AAAA;CACC,WAAU;AACX","file":"b.css","sourcesContent":[".baz {\n\tcolor: red;\n}\n"]} \ No newline at end of file diff --git a/plugins/postcss-cascade-layers/test/cli/out/b.expect.css b/plugins/postcss-cascade-layers/test/cli/out/b.expect.css deleted file mode 100644 index ac091faeb..000000000 --- a/plugins/postcss-cascade-layers/test/cli/out/b.expect.css +++ /dev/null @@ -1,5 +0,0 @@ -.baz { - color: blue; -} - -/*# sourceMappingURL=b.css.map */ \ No newline at end of file diff --git a/plugins/postcss-cascade-layers/test/cli/out/b.expect.css.map b/plugins/postcss-cascade-layers/test/cli/out/b.expect.css.map deleted file mode 100644 index 0a97ca290..000000000 --- a/plugins/postcss-cascade-layers/test/cli/out/b.expect.css.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../src/b.css"],"names":[],"mappings":"AAAA;CACC,WAAU;AACX","file":"b.css","sourcesContent":[".baz {\n\tcolor: red;\n}\n"]} \ No newline at end of file diff --git a/plugins/postcss-cascade-layers/test/cli/out/concatenated.css b/plugins/postcss-cascade-layers/test/cli/out/concatenated.css deleted file mode 100644 index 80726df77..000000000 --- a/plugins/postcss-cascade-layers/test/cli/out/concatenated.css +++ /dev/null @@ -1,6 +0,0 @@ -.foo { - color: blue; -} -.baz { - color: blue; -} diff --git a/plugins/postcss-cascade-layers/test/cli/out/concatenated.expect.css b/plugins/postcss-cascade-layers/test/cli/out/concatenated.expect.css deleted file mode 100644 index 80726df77..000000000 --- a/plugins/postcss-cascade-layers/test/cli/out/concatenated.expect.css +++ /dev/null @@ -1,6 +0,0 @@ -.foo { - color: blue; -} -.baz { - color: blue; -} diff --git a/plugins/postcss-cascade-layers/test/cli/src/a.css b/plugins/postcss-cascade-layers/test/cli/src/a.css deleted file mode 100644 index cedf0a6d1..000000000 --- a/plugins/postcss-cascade-layers/test/cli/src/a.css +++ /dev/null @@ -1,3 +0,0 @@ -.foo { - color: red; -} diff --git a/plugins/postcss-cascade-layers/test/cli/src/b.css b/plugins/postcss-cascade-layers/test/cli/src/b.css deleted file mode 100644 index 5f2435156..000000000 --- a/plugins/postcss-cascade-layers/test/cli/src/b.css +++ /dev/null @@ -1,3 +0,0 @@ -.baz { - color: red; -} diff --git a/plugins/postcss-cascade-layers/test/cli/test.sh b/plugins/postcss-cascade-layers/test/cli/test.sh deleted file mode 100644 index 3cb230406..000000000 --- a/plugins/postcss-cascade-layers/test/cli/test.sh +++ /dev/null @@ -1,94 +0,0 @@ -set -e - -# Zero out result file -echo '' > ./test/cli/basic.result.css; - -# Test with long flag -postcss-base-plugin ./test/cli/basic.css --output ./test/cli/basic.result.css - -# Check result -git --no-pager diff --no-index --word-diff ./test/cli/basic.expect.css ./test/cli/basic.result.css - -# Reset result file -cat ./test/cli/basic.css > ./test/cli/basic.replace.css; - -# Test replace -postcss-base-plugin ./test/cli/basic.replace.css -r - -# Check result -git --no-pager diff --no-index --word-diff ./test/cli/basic.replace.css ./test/cli/basic.replace.expect.css - -# Zero out result file -echo '' > ./test/cli/basic.color.result.css; - -# Test with short flags and plugin option -postcss-base-plugin ./test/cli/basic.css -o ./test/cli/basic.color.result.css -p '{ "color": "purple" }' - -# Check result -git --no-pager diff --no-index --word-diff ./test/cli/basic.color.expect.css ./test/cli/basic.color.result.css - -# Zero out result file -echo '' > ./test/cli/basic.stdin.result.css; - -# Test with stdin -cat ./test/cli/basic.css | postcss-base-plugin > ./test/cli/basic.stdin.result.css - -# Check result -git --no-pager diff --no-index --word-diff ./test/cli/basic.stdin.expect.css ./test/cli/basic.stdin.result.css - -# Zero out result file -echo '' > ./test/cli/basic.no-map.result.css; - -# Test source maps -postcss-base-plugin ./test/cli/basic.css --no-map -o ./test/cli/basic.no-map.result.css - -# Check result -git --no-pager diff --no-index --word-diff ./test/cli/basic.no-map.expect.css ./test/cli/basic.no-map.result.css - -# Zero out result file -echo '' > ./test/cli/basic.external-map.result.css; -echo '' > ./test/cli/basic.external-map.result.css.map; - -# Test source maps -cat ./test/cli/basic.css | postcss-base-plugin --map -o ./test/cli/basic.external-map.result.css - -# Check result -git --no-pager diff --no-index --word-diff ./test/cli/basic.external-map.expect.css ./test/cli/basic.external-map.result.css -git --no-pager diff --no-index --word-diff ./test/cli/basic.external-map.expect.css.map ./test/cli/basic.external-map.result.css.map - -# Zero out result file -echo '' > ./test/cli/out/a.css -echo '' > ./test/cli/out/a.css.map -echo '' > ./test/cli/out/b.css -echo '' > ./test/cli/out/b.css.map - -# Test source maps -postcss-base-plugin ./test/cli/src/a.css ./test/cli/src/b.css -m -d ./test/cli/out/ - -# Check result -git --no-pager diff --no-index --word-diff ./test/cli/out/a.css ./test/cli/out/a.expect.css -git --no-pager diff --no-index --word-diff ./test/cli/out/b.css ./test/cli/out/b.expect.css -git --no-pager diff --no-index --word-diff ./test/cli/out/a.css.map ./test/cli/out/a.expect.css.map -git --no-pager diff --no-index --word-diff ./test/cli/out/b.css.map ./test/cli/out/b.expect.css.map - - -# Zero out result file -echo '' > ./test/cli/out/concatenated.css - -# Test concat -postcss-base-plugin ./test/cli/src/a.css ./test/cli/src/b.css > ./test/cli/out/concatenated.css - -# Check result -git --no-pager diff --no-index --word-diff ./test/cli/out/concatenated.css ./test/cli/out/concatenated.expect.css - -# Dump some content -echo 'foo' > ./test/cli/basic.failure.result.css - -# Test with incorrect arugments -if postcss-base-plugin ./test/cli/basic.css --does-not-exist > ./test/cli/basic.failure.result.css; then - echo 'Test should have failed'; - exit 1; -else - # Check result - git --no-pager diff --no-index --word-diff ./test/cli/basic.failure.result.css ./test/cli/basic.failure.expect.css -fi From 233b186cbb63fc7c89b4fa6def804935a1c90a38 Mon Sep 17 00:00:00 2001 From: romainmenke Date: Wed, 23 Feb 2022 08:55:05 +0100 Subject: [PATCH 03/33] cascade layers tests --- plugins/postcss-cascade-layers/package.json | 1 + plugins/postcss-cascade-layers/src/index.ts | 8 +- .../postcss-cascade-layers/stryker.conf.json | 19 - .../postcss-cascade-layers/test/_browser.mjs | 101 ++++ plugins/postcss-cascade-layers/test/basic.css | 16 +- .../test/basic.expect.css | 10 +- .../test/wpt/layer-basic.html | 548 ++++++++++++++++++ .../wpt/layer-counter-style-override.html | 180 ++++++ 8 files changed, 850 insertions(+), 33 deletions(-) delete mode 100644 plugins/postcss-cascade-layers/stryker.conf.json create mode 100644 plugins/postcss-cascade-layers/test/_browser.mjs create mode 100644 plugins/postcss-cascade-layers/test/wpt/layer-basic.html create mode 100644 plugins/postcss-cascade-layers/test/wpt/layer-counter-style-override.html diff --git a/plugins/postcss-cascade-layers/package.json b/plugins/postcss-cascade-layers/package.json index e0a9e1f59..f315debce 100644 --- a/plugins/postcss-cascade-layers/package.json +++ b/plugins/postcss-cascade-layers/package.json @@ -31,6 +31,7 @@ "prepublishOnly": "npm run clean && npm run build && npm run test", "stryker": "stryker run --logLevel error", "test": "node .tape.mjs && npm run test:exports", + "test:browser": "node ./test/_browser.mjs", "test:rewrite-expects": "REWRITE_EXPECTS=true node .tape.mjs", "test:exports": "node ./test/_import.mjs && node ./test/_require.cjs" }, diff --git a/plugins/postcss-cascade-layers/src/index.ts b/plugins/postcss-cascade-layers/src/index.ts index 08c6fb48d..3a3b51669 100644 --- a/plugins/postcss-cascade-layers/src/index.ts +++ b/plugins/postcss-cascade-layers/src/index.ts @@ -4,13 +4,7 @@ const creator: PluginCreator<{ color: string }> = (opts?: { color: string }) => return { postcssPlugin: 'postcss-cascade-layers', Declaration(decl) { - if (decl.value === 'red') { - if (opts && opts.color) { - decl.value = opts.color; - } else { - decl.value = 'blue'; - } - } + decl.remove(); }, }; }; diff --git a/plugins/postcss-cascade-layers/stryker.conf.json b/plugins/postcss-cascade-layers/stryker.conf.json deleted file mode 100644 index 091dc740c..000000000 --- a/plugins/postcss-cascade-layers/stryker.conf.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "$schema": "../../node_modules/@stryker-mutator/core/schema/stryker-schema.json", - "mutate": [ - "src/**/*.ts" - ], - "buildCommand": "npm run build", - "testRunner": "command", - "coverageAnalysis": "perTest", - "tempDirName": "../../.stryker-tmp", - "commandRunner": { - "command": "npm run test:tape" - }, - "thresholds": { - "high": 100, - "low": 100, - "break": 100 - }, - "inPlace": true -} diff --git a/plugins/postcss-cascade-layers/test/_browser.mjs b/plugins/postcss-cascade-layers/test/_browser.mjs new file mode 100644 index 000000000..fd81693ad --- /dev/null +++ b/plugins/postcss-cascade-layers/test/_browser.mjs @@ -0,0 +1,101 @@ +import puppeteer from 'puppeteer'; +import http from 'http'; +import { promises as fsp } from 'fs'; +import plugin from '@csstools/postcss-cascade-layers'; +import postcss from 'postcss'; + +// TODO : bring over more tests from WPT + +(async () => { + const requestListener = async function (req, res) { + + const parsedUrl = new URL(req.url, 'http://localhost:8080'); + const pathname = parsedUrl.pathname; + + switch (pathname) { + case '/wpt/layer-basic.html': + res.setHeader('Content-type', 'text/html'); + res.writeHead(200); + res.end(await fsp.readFile('test/wpt/layer-basic.html', 'utf8')); + break; + case '/wpt/layer-counter-style-override.html': + res.setHeader('Content-type', 'text/html'); + res.writeHead(200); + res.end(await fsp.readFile('test/wpt/layer-counter-style-override.html', 'utf8')); + break; + case '/test/styles.css': + if (req.method === 'POST') { + const data = await new Promise((resolve, reject) => { + let buf = []; + req.on('data', (chunk) => { + buf.push(chunk); + }); + + req.on('end', () => { + resolve(Buffer.concat(buf).toString()); + }); + + req.on('error', (err) => { + reject(err); + }); + }); + + const css = await postcss([plugin]).process(data, { from: 'test/styles.css', to: 'test/styles.css' }); + res.setHeader('Content-type', 'text/css'); + res.writeHead(200); + res.end(css.css); + break; + } + + // eslint-disable-next-line no-fallthrough + default: + res.setHeader('Content-type', 'text/plain' ); + res.writeHead(404); + res.end('Not found'); + break; + } + }; + + const server = http.createServer(requestListener); + server.listen(8080); + + if (!process.env.DEBUG) { + const browser = await puppeteer.launch({ + headless: true, + }); + + const page = await browser.newPage(); + page.on('pageerror', (msg) => { + throw msg; + }); + + { + await page.goto('http://localhost:8080/wpt/layer-basic.html'); + const result = await page.evaluate(async () => { + // eslint-disable-next-line no-undef + return await window.runTest(); + }); + if (!result) { + throw new Error('Test failed, expected "window.runTest()" to return true'); + } + } + + // TODO : uncomment + // { + // await page.goto('http://localhost:8080/wpt/layer-counter-style-override.html'); + // const result = await page.evaluate(async () => { + // // eslint-disable-next-line no-undef + // return await window.runTest(); + // }); + // if (!result) { + // throw new Error('Test failed, expected "window.runTest()" to return true'); + // } + // } + + await browser.close(); + + await server.close(); + } else { + console.log('visit : http://localhost:8080'); + } +})(); diff --git a/plugins/postcss-cascade-layers/test/basic.css b/plugins/postcss-cascade-layers/test/basic.css index 181f83a54..f7ed69e5e 100644 --- a/plugins/postcss-cascade-layers/test/basic.css +++ b/plugins/postcss-cascade-layers/test/basic.css @@ -1,7 +1,15 @@ -.foo { - color: red; +@layer A { + target { + color: red; + } } -.baz { - color: green; +@layer { + target { + color: green; + } +} + +target { + color: purple; } diff --git a/plugins/postcss-cascade-layers/test/basic.expect.css b/plugins/postcss-cascade-layers/test/basic.expect.css index 9d738d5ac..5a19d28b9 100644 --- a/plugins/postcss-cascade-layers/test/basic.expect.css +++ b/plugins/postcss-cascade-layers/test/basic.expect.css @@ -1,7 +1,11 @@ -.foo { - color: blue; +target { + color: red; } -.baz { +target:not(#something) { color: green; } + +target:not(#something#something) { + color: purple; +} diff --git a/plugins/postcss-cascade-layers/test/wpt/layer-basic.html b/plugins/postcss-cascade-layers/test/wpt/layer-basic.html new file mode 100644 index 000000000..791e6872b --- /dev/null +++ b/plugins/postcss-cascade-layers/test/wpt/layer-basic.html @@ -0,0 +1,548 @@ + + + + + CSS Cascade Layers: Basic functionality + + + + + + + + + +
+ + + + diff --git a/plugins/postcss-cascade-layers/test/wpt/layer-counter-style-override.html b/plugins/postcss-cascade-layers/test/wpt/layer-counter-style-override.html new file mode 100644 index 000000000..0a67d1e6d --- /dev/null +++ b/plugins/postcss-cascade-layers/test/wpt/layer-counter-style-override.html @@ -0,0 +1,180 @@ + +Resolving @counter-style name conflicts with cascade layers + + + + + +
    +
  • +
  • +
+ +
+ + From 0d4e0010d07d3fd3e738c1126c23dc5fb45feb82 Mon Sep 17 00:00:00 2001 From: romainmenke Date: Wed, 23 Feb 2022 09:03:05 +0100 Subject: [PATCH 04/33] add test index --- .../postcss-cascade-layers/test/_browser.mjs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/plugins/postcss-cascade-layers/test/_browser.mjs b/plugins/postcss-cascade-layers/test/_browser.mjs index fd81693ad..dc3a1c32c 100644 --- a/plugins/postcss-cascade-layers/test/_browser.mjs +++ b/plugins/postcss-cascade-layers/test/_browser.mjs @@ -13,6 +13,27 @@ import postcss from 'postcss'; const pathname = parsedUrl.pathname; switch (pathname) { + case '': + case '/': + res.setHeader('Content-type', 'text/html'); + res.writeHead(200); + + // write html string with list of links to cases. + res.end(` + + + Cascade Layers Test + + +

Cascade Layers Test

+
+ + + `); + break; case '/wpt/layer-basic.html': res.setHeader('Content-type', 'text/html'); res.writeHead(200); From 98671bd5401e7881c786bfac9e9aa8213deb1092 Mon Sep 17 00:00:00 2001 From: Olu Niyi-Awosusi Date: Fri, 25 Feb 2022 15:51:19 +0000 Subject: [PATCH 05/33] barebones start --- package-lock.json | 30 +++++++++++++++ plugins/postcss-cascade-layers/src/index.ts | 35 ++++++++++++++---- plugins/postcss-color-rebeccapurple/.DS_Store | Bin 0 -> 6148 bytes 3 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 plugins/postcss-color-rebeccapurple/.DS_Store diff --git a/package-lock.json b/package-lock.json index b1cb41e66..20d011409 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ } }, "cli/csstools-cli": { + "name": "@csstools/csstools-cli", "version": "1.1.0", "license": "CC0-1.0", "dependencies": { @@ -74,6 +75,7 @@ } }, "experimental/css-has-pseudo": { + "name": "@csstools/css-has-pseudo-experimental", "version": "0.2.1", "license": "CC0-1.0", "dependencies": { @@ -1759,6 +1761,10 @@ "resolved": "plugins/postcss-base-plugin", "link": true }, + "node_modules/@csstools/postcss-cascade-layers": { + "resolved": "plugins/postcss-cascade-layers", + "link": true + }, "node_modules/@csstools/postcss-color-function": { "resolved": "plugins/postcss-color-function", "link": true @@ -6379,6 +6385,7 @@ } }, "packages/base-cli": { + "name": "@csstools/base-cli", "version": "0.1.0", "license": "CC0-1.0", "dependencies": { @@ -6389,6 +6396,7 @@ } }, "packages/generate-test-cases": { + "name": "@csstools/generate-test-cases", "version": "1.0.0", "license": "CC0-1.0", "devDependencies": { @@ -6399,6 +6407,7 @@ } }, "packages/postcss-tape": { + "name": "@csstools/postcss-tape", "version": "1.0.0", "license": "CC0-1.0", "dependencies": { @@ -6516,6 +6525,7 @@ } }, "plugins/postcss-base-plugin": { + "name": "@csstools/postcss-base-plugin", "version": "0.0.0", "license": "CC0-1.0", "bin": { @@ -6528,7 +6538,18 @@ "postcss": "^8.3" } }, + "plugins/postcss-cascade-layers": { + "version": "0.0.0", + "license": "CC0-1.0", + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, "plugins/postcss-color-function": { + "name": "@csstools/postcss-color-function", "version": "1.0.0", "license": "CC0-1.0", "dependencies": { @@ -6698,6 +6719,7 @@ } }, "plugins/postcss-font-format-keywords": { + "name": "@csstools/postcss-font-format-keywords", "version": "1.0.0", "license": "CC0-1.0", "dependencies": { @@ -6725,6 +6747,7 @@ } }, "plugins/postcss-hwb-function": { + "name": "@csstools/postcss-hwb-function", "version": "1.0.0", "license": "CC0-1.0", "dependencies": { @@ -6751,6 +6774,7 @@ } }, "plugins/postcss-is-pseudo-class": { + "name": "@csstools/postcss-is-pseudo-class", "version": "2.0.0", "license": "CC0-1.0", "dependencies": { @@ -6811,6 +6835,7 @@ } }, "plugins/postcss-normalize-display-values": { + "name": "@csstools/postcss-normalize-display-values", "version": "1.0.0", "license": "CC0-1.0", "dependencies": { @@ -6855,6 +6880,7 @@ } }, "plugins/postcss-progressive-custom-properties": { + "name": "@csstools/postcss-progressive-custom-properties", "version": "1.0.0", "license": "CC0-1.0", "engines": { @@ -8075,6 +8101,10 @@ "version": "file:plugins/postcss-base-plugin", "requires": {} }, + "@csstools/postcss-cascade-layers": { + "version": "file:plugins/postcss-cascade-layers", + "requires": {} + }, "@csstools/postcss-color-function": { "version": "file:plugins/postcss-color-function", "requires": { diff --git a/plugins/postcss-cascade-layers/src/index.ts b/plugins/postcss-cascade-layers/src/index.ts index 3a3b51669..4bbe2b396 100644 --- a/plugins/postcss-cascade-layers/src/index.ts +++ b/plugins/postcss-cascade-layers/src/index.ts @@ -1,14 +1,35 @@ -import type { PluginCreator } from 'postcss'; +import type { Container } from 'postcss'; -const creator: PluginCreator<{ color: string }> = (opts?: { color: string }) => { +function postcssCascadeLayers(opts) { return { postcssPlugin: 'postcss-cascade-layers', - Declaration(decl) { - decl.remove(); + Once(root: Container) { + root.walkAtRules((atRule) => { + if (atRule.name !== 'layer') { + return; + } + + if (atRule.nodes && atRule.nodes.length) { + console.log(atRule.name, 'layer name', atRule.params); + // parse .params as layer name + // replace layer.name with :is() + // add layer.param after + // duplicate node contents + + console.log('nodes', atRule.nodes); + } else { + console.log(atRule.name, atRule.params); + // parse .params as list of layer names + /* + pattern: + @layer foo foo.baz; + */ + } + }); }, }; -}; +} -creator.postcss = true; +postcssCascadeLayers.postcss = true; -export default creator; +export default postcssCascadeLayers; diff --git a/plugins/postcss-color-rebeccapurple/.DS_Store b/plugins/postcss-color-rebeccapurple/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5172429f264de2441865cb4700216d4256da9242 GIT binary patch literal 6148 zcmeH~J!%6%427R!7lt%jx}3%b$PET#pTHLgIFQEJ;E>dF^gR7ES*H$5cmnB-G%I%Z zD|S`@Z2$T80!#olbXV*=%*>dt@PRwdU#I)^a=X5>;#J@&VrHyNnC;iLL0pQvfVyTmjO&;ssLc!1UOG})p;=82 zR;?Ceh}WZ?+UmMqI#RP8R>OzYoz15hnq@nzF`-!xQ4j$Um=RcIKKc27r2jVm&svm< zfC&6E0=7P!4tu^-ovjbA=k?dB`g+i*aXG_}p8zI)6mRKa+;6_1_R^8c3Qa!(fk8n8 H{*=HsM+*^= literal 0 HcmV?d00001 From 194881f824f3033dfde6f6dc6d8788c6c29d317f Mon Sep 17 00:00:00 2001 From: Olu Niyi-Awosusi Date: Tue, 8 Mar 2022 14:28:48 +0000 Subject: [PATCH 06/33] replace anon layers --- plugins/postcss-cascade-layers/src/index.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/plugins/postcss-cascade-layers/src/index.ts b/plugins/postcss-cascade-layers/src/index.ts index 4bbe2b396..9ccd41c41 100644 --- a/plugins/postcss-cascade-layers/src/index.ts +++ b/plugins/postcss-cascade-layers/src/index.ts @@ -9,12 +9,19 @@ function postcssCascadeLayers(opts) { return; } + // if layer anon, name + if(atRule.params === '') { + atRule.params = ` anon${(Math.random()).toString()}`; + } + if (atRule.nodes && atRule.nodes.length) { - console.log(atRule.name, 'layer name', atRule.params); - // parse .params as layer name - // replace layer.name with :is() - // add layer.param after - // duplicate node contents + const atRuleClone = atRule.clone(); + atRuleClone.nodes.forEach((node) => { + const modifiedSelectors = node.selectors.map((selector) => { + return `${selector}:not()`; + }); + atRule.parent.insertBefore(atRule, node.clone({ selectors: modifiedSelectors })); + }); console.log('nodes', atRule.nodes); } else { From 1dddaa54141cfe7b33372456fde399e9c6549cd2 Mon Sep 17 00:00:00 2001 From: Sana Javed Date: Mon, 14 Mar 2022 09:36:15 -0500 Subject: [PATCH 07/33] First walkthrough, initializing data model --- package-lock.json | 8 ++- plugins/postcss-cascade-layers/.gitignore | 2 + plugins/postcss-cascade-layers/package.json | 3 ++ plugins/postcss-cascade-layers/src/index.ts | 51 +++++++++---------- .../test/anon.layer.css | 27 ++++++++++ .../test/anon.layer.expect.css | 19 +++++++ 6 files changed, 82 insertions(+), 28 deletions(-) create mode 100644 plugins/postcss-cascade-layers/test/anon.layer.css create mode 100644 plugins/postcss-cascade-layers/test/anon.layer.expect.css diff --git a/package-lock.json b/package-lock.json index 20d011409..fab154a6e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6539,8 +6539,12 @@ } }, "plugins/postcss-cascade-layers": { + "name": "@csstools/postcss-cascade-layers", "version": "0.0.0", "license": "CC0-1.0", + "devDependencies": { + "postcss-tape": "^6.0.1" + }, "engines": { "node": "^12 || ^14 || >=16" }, @@ -8103,7 +8107,9 @@ }, "@csstools/postcss-cascade-layers": { "version": "file:plugins/postcss-cascade-layers", - "requires": {} + "requires": { + "postcss-tape": "^6.0.1" + } }, "@csstools/postcss-color-function": { "version": "file:plugins/postcss-color-function", diff --git a/plugins/postcss-cascade-layers/.gitignore b/plugins/postcss-cascade-layers/.gitignore index 7172b04f1..3336e93ae 100644 --- a/plugins/postcss-cascade-layers/.gitignore +++ b/plugins/postcss-cascade-layers/.gitignore @@ -1,6 +1,8 @@ node_modules +dist package-lock.json yarn.lock +*.log* *.result.css *.result.css.map dist/* diff --git a/plugins/postcss-cascade-layers/package.json b/plugins/postcss-cascade-layers/package.json index f315debce..2731fd062 100644 --- a/plugins/postcss-cascade-layers/package.json +++ b/plugins/postcss-cascade-layers/package.json @@ -48,5 +48,8 @@ }, "volta": { "extends": "../../package.json" + }, + "devDependencies": { + "postcss-tape": "^6.0.1" } } diff --git a/plugins/postcss-cascade-layers/src/index.ts b/plugins/postcss-cascade-layers/src/index.ts index 9ccd41c41..becc724bc 100644 --- a/plugins/postcss-cascade-layers/src/index.ts +++ b/plugins/postcss-cascade-layers/src/index.ts @@ -1,38 +1,35 @@ -import type { Container } from 'postcss'; +import { Container } from "postcss"; function postcssCascadeLayers(opts) { return { - postcssPlugin: 'postcss-cascade-layers', + postcssPlugin: "postcss-cascade-layers", Once(root: Container) { - root.walkAtRules((atRule) => { - if (atRule.name !== 'layer') { - return; - } + let layerCount = 0; + let layerOrder = {}; - // if layer anon, name - if(atRule.params === '') { - atRule.params = ` anon${(Math.random()).toString()}`; + // 1st walkthrough to rename anon layers and store state (no modification of layer styles) + root.walkAtRules("layer", (atRule) => { + // give anonymous layers a name + if (!atRule.params) { + atRule.params = `anon${layerCount}`; } - if (atRule.nodes && atRule.nodes.length) { - const atRuleClone = atRule.clone(); - atRuleClone.nodes.forEach((node) => { - const modifiedSelectors = node.selectors.map((selector) => { - return `${selector}:not()`; - }); - atRule.parent.insertBefore(atRule, node.clone({ selectors: modifiedSelectors })); - }); - - console.log('nodes', atRule.nodes); - } else { - console.log(atRule.name, atRule.params); - // parse .params as list of layer names - /* - pattern: - @layer foo foo.baz; - */ - } + layerCount += 1; + layerOrder[atRule.params] = layerCount; }); + + // 2nd walkthrough to transform unlayered styles - need highest specificity (layerCount + 1) + // root.walkRules((rule) => { + // console.log(rule, "second walkthrough"); + // }); + + // 3rd walkthrough to transform layered styles: + // - move out styles from atRule, insert before: https://postcss.org/api/#container-insertbefore + // - delete empty atRule + // - give selectors the specifity they need based on layerPriority state + // root.walkAtRules((atRule) => { + // console.log(atRule, "third walkthrough"); + // }); }, }; } diff --git a/plugins/postcss-cascade-layers/test/anon.layer.css b/plugins/postcss-cascade-layers/test/anon.layer.css new file mode 100644 index 000000000..1cd1f743d --- /dev/null +++ b/plugins/postcss-cascade-layers/test/anon.layer.css @@ -0,0 +1,27 @@ +@layer A { + target { + color: red; + } +} + +@layer B { + target { + color: orange; + } +} + +@layer C { + target { + color: yellow; + } +} + +@layer { + target { + color: green; + } +} + +target { + color: purple; +} diff --git a/plugins/postcss-cascade-layers/test/anon.layer.expect.css b/plugins/postcss-cascade-layers/test/anon.layer.expect.css new file mode 100644 index 000000000..869955a7c --- /dev/null +++ b/plugins/postcss-cascade-layers/test/anon.layer.expect.css @@ -0,0 +1,19 @@ +target:not(#something #something #something #something #something) { + color: purple; +} + +target:not(#something #something #something #something) { + color: green; +} + +target:not(#something #something #something) { + color: yellow; +} + +target:not(#something #something) { + color: orange; +} + +target:not(#something #something #something) { + color: red; +} From c35564366b7ec515e3484d3c7eac9e6e26af945f Mon Sep 17 00:00:00 2001 From: Sana Javed Date: Tue, 22 Mar 2022 09:15:31 -0500 Subject: [PATCH 08/33] Handling nested layers (#4) Handling layer nesting --- plugins/postcss-cascade-layers/.gitignore | 1 - plugins/postcss-cascade-layers/.tape.mjs | 3 ++ plugins/postcss-cascade-layers/src/index.ts | 40 ++++++++++++++++++- .../postcss-cascade-layers/test/nested.css | 15 +++++++ .../test/nested.expect.css | 16 ++++++++ 5 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 plugins/postcss-cascade-layers/test/nested.css create mode 100644 plugins/postcss-cascade-layers/test/nested.expect.css diff --git a/plugins/postcss-cascade-layers/.gitignore b/plugins/postcss-cascade-layers/.gitignore index 3336e93ae..2d56f5208 100644 --- a/plugins/postcss-cascade-layers/.gitignore +++ b/plugins/postcss-cascade-layers/.gitignore @@ -5,4 +5,3 @@ yarn.lock *.log* *.result.css *.result.css.map -dist/* diff --git a/plugins/postcss-cascade-layers/.tape.mjs b/plugins/postcss-cascade-layers/.tape.mjs index d75456c1e..033e5e2cf 100644 --- a/plugins/postcss-cascade-layers/.tape.mjs +++ b/plugins/postcss-cascade-layers/.tape.mjs @@ -14,4 +14,7 @@ postcssTape(plugin)({ example: { message: "minimal example", }, + nested: { + message: "supporting nested layer usage", + }, }); diff --git a/plugins/postcss-cascade-layers/src/index.ts b/plugins/postcss-cascade-layers/src/index.ts index becc724bc..48c073b0c 100644 --- a/plugins/postcss-cascade-layers/src/index.ts +++ b/plugins/postcss-cascade-layers/src/index.ts @@ -14,13 +14,48 @@ function postcssCascadeLayers(opts) { atRule.params = `anon${layerCount}`; } + let hasNestedLayers = false; + let hasUnlayeredStyles = false; + + // check for where a layer has nested layers AND styles outside of those layers + atRule.each((node) => { + if (node.type == "atrule") { + hasNestedLayers = true; + } else if (node.type == "rule") { + hasUnlayeredStyles = true; + } + }); + + if (hasNestedLayers && hasUnlayeredStyles) { + //create new final layer via cloning, empty it + const implicitLayer = atRule.clone({ + params: `${atRule.params}-implicit`, + }); + implicitLayer.each((node) => { + node.remove(); + }); + + // insert new layer + atRule.append(implicitLayer); + + // go through the unlayered rules, clone, and delete from top level atRule + atRule.each((node) => { + if (node.type == "rule") { + implicitLayer.append(node.clone()); + node.remove(); + } + }); + } + }); + + root.walkAtRules("layer", (layer) => { layerCount += 1; - layerOrder[atRule.params] = layerCount; + layerOrder[layer.params] = layerCount; }); // 2nd walkthrough to transform unlayered styles - need highest specificity (layerCount + 1) // root.walkRules((rule) => { - // console.log(rule, "second walkthrough"); + // console.log("second walkthrough"); // }); // 3rd walkthrough to transform layered styles: @@ -30,6 +65,7 @@ function postcssCascadeLayers(opts) { // root.walkAtRules((atRule) => { // console.log(atRule, "third walkthrough"); // }); + console.log(layerOrder); }, }; } diff --git a/plugins/postcss-cascade-layers/test/nested.css b/plugins/postcss-cascade-layers/test/nested.css new file mode 100644 index 000000000..5d66d89fc --- /dev/null +++ b/plugins/postcss-cascade-layers/test/nested.css @@ -0,0 +1,15 @@ +@layer A { + target { + color: red; + } + + p { + color: blue; + } + + @layer Z { + target { + color: yellow; + } + } +} diff --git a/plugins/postcss-cascade-layers/test/nested.expect.css b/plugins/postcss-cascade-layers/test/nested.expect.css new file mode 100644 index 000000000..ac93bbbe8 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/nested.expect.css @@ -0,0 +1,16 @@ +@layer A { + + @layer Z { + target { + color: yellow; + } + }@layer A-implicit { + target { + color: red; + } + + p { + color: blue; + } +} +} From 6589bdfa1d9e0a55f49477ec610d4b4418dd0a31 Mon Sep 17 00:00:00 2001 From: romainmenke Date: Tue, 22 Mar 2022 19:47:49 +0100 Subject: [PATCH 09/33] cascade layers : linting and a few fixes --- package-lock.json | 7 +-- plugins/postcss-cascade-layers/package.json | 3 -- plugins/postcss-cascade-layers/src/index.ts | 26 ++++++----- .../test/basic.color.expect.css | 16 +++++-- .../test/basic.expect.css | 14 +++--- .../test/example.expect.css | 2 +- .../postcss-cascade-layers/test/nested.css | 37 ++++++++++++++++ .../test/nested.expect.css | 43 +++++++++++++++++++ 8 files changed, 119 insertions(+), 29 deletions(-) diff --git a/package-lock.json b/package-lock.json index fab154a6e..a5f8d6f34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6542,9 +6542,6 @@ "name": "@csstools/postcss-cascade-layers", "version": "0.0.0", "license": "CC0-1.0", - "devDependencies": { - "postcss-tape": "^6.0.1" - }, "engines": { "node": "^12 || ^14 || >=16" }, @@ -8107,9 +8104,7 @@ }, "@csstools/postcss-cascade-layers": { "version": "file:plugins/postcss-cascade-layers", - "requires": { - "postcss-tape": "^6.0.1" - } + "requires": {} }, "@csstools/postcss-color-function": { "version": "file:plugins/postcss-color-function", diff --git a/plugins/postcss-cascade-layers/package.json b/plugins/postcss-cascade-layers/package.json index 2731fd062..f315debce 100644 --- a/plugins/postcss-cascade-layers/package.json +++ b/plugins/postcss-cascade-layers/package.json @@ -48,8 +48,5 @@ }, "volta": { "extends": "../../package.json" - }, - "devDependencies": { - "postcss-tape": "^6.0.1" } } diff --git a/plugins/postcss-cascade-layers/src/index.ts b/plugins/postcss-cascade-layers/src/index.ts index 48c073b0c..b30388f9e 100644 --- a/plugins/postcss-cascade-layers/src/index.ts +++ b/plugins/postcss-cascade-layers/src/index.ts @@ -1,14 +1,14 @@ -import { Container } from "postcss"; +import { Container } from 'postcss'; function postcssCascadeLayers(opts) { return { - postcssPlugin: "postcss-cascade-layers", + postcssPlugin: 'postcss-cascade-layers', Once(root: Container) { let layerCount = 0; - let layerOrder = {}; + const layerOrder = {}; // 1st walkthrough to rename anon layers and store state (no modification of layer styles) - root.walkAtRules("layer", (atRule) => { + root.walkAtRules('layer', (atRule) => { // give anonymous layers a name if (!atRule.params) { atRule.params = `anon${layerCount}`; @@ -19,11 +19,15 @@ function postcssCascadeLayers(opts) { // check for where a layer has nested layers AND styles outside of those layers atRule.each((node) => { - if (node.type == "atrule") { + if (node.type == 'atrule' && node.name === 'layer') { hasNestedLayers = true; - } else if (node.type == "rule") { + } else { hasUnlayeredStyles = true; } + + if (hasNestedLayers && hasUnlayeredStyles) { + return false; + } }); if (hasNestedLayers && hasUnlayeredStyles) { @@ -40,15 +44,17 @@ function postcssCascadeLayers(opts) { // go through the unlayered rules, clone, and delete from top level atRule atRule.each((node) => { - if (node.type == "rule") { - implicitLayer.append(node.clone()); - node.remove(); + if (node.type == 'atrule' && node.name === 'layer') { + return; } + + implicitLayer.append(node.clone()); + node.remove(); }); } }); - root.walkAtRules("layer", (layer) => { + root.walkAtRules('layer', (layer) => { layerCount += 1; layerOrder[layer.params] = layerCount; }); diff --git a/plugins/postcss-cascade-layers/test/basic.color.expect.css b/plugins/postcss-cascade-layers/test/basic.color.expect.css index 041b1dabf..b8450fece 100644 --- a/plugins/postcss-cascade-layers/test/basic.color.expect.css +++ b/plugins/postcss-cascade-layers/test/basic.color.expect.css @@ -1,7 +1,15 @@ -.foo { - color: purple; +@layer A { + target { + color: red; + } +} + +@layeranon0 { + target { + color: green; + } } -.baz { - color: green; +target { + color: purple; } diff --git a/plugins/postcss-cascade-layers/test/basic.expect.css b/plugins/postcss-cascade-layers/test/basic.expect.css index 5a19d28b9..b8450fece 100644 --- a/plugins/postcss-cascade-layers/test/basic.expect.css +++ b/plugins/postcss-cascade-layers/test/basic.expect.css @@ -1,11 +1,15 @@ -target { - color: red; +@layer A { + target { + color: red; + } } -target:not(#something) { - color: green; +@layeranon0 { + target { + color: green; + } } -target:not(#something#something) { +target { color: purple; } diff --git a/plugins/postcss-cascade-layers/test/example.expect.css b/plugins/postcss-cascade-layers/test/example.expect.css index 9d738d5ac..181f83a54 100644 --- a/plugins/postcss-cascade-layers/test/example.expect.css +++ b/plugins/postcss-cascade-layers/test/example.expect.css @@ -1,5 +1,5 @@ .foo { - color: blue; + color: red; } .baz { diff --git a/plugins/postcss-cascade-layers/test/nested.css b/plugins/postcss-cascade-layers/test/nested.css index 5d66d89fc..c9b05e915 100644 --- a/plugins/postcss-cascade-layers/test/nested.css +++ b/plugins/postcss-cascade-layers/test/nested.css @@ -12,4 +12,41 @@ color: yellow; } } + + i { + color: red; + } +} + +@layer B { + @keyframes slide-left { + from { + margin-left: 0; + } + + to { + margin-left: -100%; + } + } + + @layer Z { + target { + color: yellow; + } + } +} + +@layer C { + @media (prefers-color-scheme: dark) { + h1 { + color: red; + background: black; + } + } + + @layer Z { + target { + color: yellow; + } + } } diff --git a/plugins/postcss-cascade-layers/test/nested.expect.css b/plugins/postcss-cascade-layers/test/nested.expect.css index ac93bbbe8..31b40d981 100644 --- a/plugins/postcss-cascade-layers/test/nested.expect.css +++ b/plugins/postcss-cascade-layers/test/nested.expect.css @@ -12,5 +12,48 @@ p { color: blue; } + + i { + color: red; + } +} +} + +@layer B { + + @layer Z { + target { + color: yellow; + } + } + +@layer B-implicit { + @keyframes slide-left { + from { + margin-left: 0; + } + + to { + margin-left: -100%; + } + } +} +} + +@layer C { + + @layer Z { + target { + color: yellow; + } + } + +@layer C-implicit { + @media (prefers-color-scheme: dark) { + h1 { + color: red; + background: black; + } + } } } From abefae97d7d700a34c42dfa62a223047c35c97f4 Mon Sep 17 00:00:00 2001 From: romainmenke Date: Tue, 22 Mar 2022 20:00:44 +0100 Subject: [PATCH 10/33] more fixes --- plugins/postcss-cascade-layers/.tape.mjs | 6 +++++ plugins/postcss-cascade-layers/src/index.ts | 1 + .../test/{anon.layer.css => anon-layer.css} | 0 .../test/anon-layer.expect.css | 27 +++++++++++++++++++ .../test/anon.layer.expect.css | 19 ------------- .../test/basic.color.expect.css | 2 +- .../test/basic.expect.css | 2 +- 7 files changed, 36 insertions(+), 21 deletions(-) rename plugins/postcss-cascade-layers/test/{anon.layer.css => anon-layer.css} (100%) create mode 100644 plugins/postcss-cascade-layers/test/anon-layer.expect.css delete mode 100644 plugins/postcss-cascade-layers/test/anon.layer.expect.css diff --git a/plugins/postcss-cascade-layers/.tape.mjs b/plugins/postcss-cascade-layers/.tape.mjs index 033e5e2cf..67c6fa058 100644 --- a/plugins/postcss-cascade-layers/.tape.mjs +++ b/plugins/postcss-cascade-layers/.tape.mjs @@ -17,4 +17,10 @@ postcssTape(plugin)({ nested: { message: "supporting nested layer usage", }, + 'anon-layer': { + message: "supporting anonymous layer usage", + }, + nested: { + message: "supporting nested layer usage", + }, }); diff --git a/plugins/postcss-cascade-layers/src/index.ts b/plugins/postcss-cascade-layers/src/index.ts index b30388f9e..557c543fb 100644 --- a/plugins/postcss-cascade-layers/src/index.ts +++ b/plugins/postcss-cascade-layers/src/index.ts @@ -11,6 +11,7 @@ function postcssCascadeLayers(opts) { root.walkAtRules('layer', (atRule) => { // give anonymous layers a name if (!atRule.params) { + atRule.raws.afterName = ' '; atRule.params = `anon${layerCount}`; } diff --git a/plugins/postcss-cascade-layers/test/anon.layer.css b/plugins/postcss-cascade-layers/test/anon-layer.css similarity index 100% rename from plugins/postcss-cascade-layers/test/anon.layer.css rename to plugins/postcss-cascade-layers/test/anon-layer.css diff --git a/plugins/postcss-cascade-layers/test/anon-layer.expect.css b/plugins/postcss-cascade-layers/test/anon-layer.expect.css new file mode 100644 index 000000000..b30e8198c --- /dev/null +++ b/plugins/postcss-cascade-layers/test/anon-layer.expect.css @@ -0,0 +1,27 @@ +@layer A { + target { + color: red; + } +} + +@layer B { + target { + color: orange; + } +} + +@layer C { + target { + color: yellow; + } +} + +@layer anon0 { + target { + color: green; + } +} + +target { + color: purple; +} diff --git a/plugins/postcss-cascade-layers/test/anon.layer.expect.css b/plugins/postcss-cascade-layers/test/anon.layer.expect.css deleted file mode 100644 index 869955a7c..000000000 --- a/plugins/postcss-cascade-layers/test/anon.layer.expect.css +++ /dev/null @@ -1,19 +0,0 @@ -target:not(#something #something #something #something #something) { - color: purple; -} - -target:not(#something #something #something #something) { - color: green; -} - -target:not(#something #something #something) { - color: yellow; -} - -target:not(#something #something) { - color: orange; -} - -target:not(#something #something #something) { - color: red; -} diff --git a/plugins/postcss-cascade-layers/test/basic.color.expect.css b/plugins/postcss-cascade-layers/test/basic.color.expect.css index b8450fece..8c2d3172e 100644 --- a/plugins/postcss-cascade-layers/test/basic.color.expect.css +++ b/plugins/postcss-cascade-layers/test/basic.color.expect.css @@ -4,7 +4,7 @@ } } -@layeranon0 { +@layer anon0 { target { color: green; } diff --git a/plugins/postcss-cascade-layers/test/basic.expect.css b/plugins/postcss-cascade-layers/test/basic.expect.css index b8450fece..8c2d3172e 100644 --- a/plugins/postcss-cascade-layers/test/basic.expect.css +++ b/plugins/postcss-cascade-layers/test/basic.expect.css @@ -4,7 +4,7 @@ } } -@layeranon0 { +@layer anon0 { target { color: green; } From e1e6d5ec3c748ee3842ac21138591f5648b028c9 Mon Sep 17 00:00:00 2001 From: Olu Niyi-Awosusi Date: Tue, 22 Mar 2022 19:45:24 +0000 Subject: [PATCH 11/33] adding unlayered styles to data model --- plugins/postcss-cascade-layers/src/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/postcss-cascade-layers/src/index.ts b/plugins/postcss-cascade-layers/src/index.ts index 48c073b0c..b9818091f 100644 --- a/plugins/postcss-cascade-layers/src/index.ts +++ b/plugins/postcss-cascade-layers/src/index.ts @@ -54,9 +54,9 @@ function postcssCascadeLayers(opts) { }); // 2nd walkthrough to transform unlayered styles - need highest specificity (layerCount + 1) - // root.walkRules((rule) => { - // console.log("second walkthrough"); - // }); + root.walkRules((rule) => { + layerOrder['unlayered ' + rule.selector] = layerCount + 1; + }); // 3rd walkthrough to transform layered styles: // - move out styles from atRule, insert before: https://postcss.org/api/#container-insertbefore From 31f73a8cbd07d5584ffadf28f73dc56de5d8f304 Mon Sep 17 00:00:00 2001 From: Olu Niyi-Awosusi Date: Thu, 24 Mar 2022 16:28:14 +0000 Subject: [PATCH 12/33] add suggested code --- plugins/postcss-cascade-layers/src/index.ts | 38 ++++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/plugins/postcss-cascade-layers/src/index.ts b/plugins/postcss-cascade-layers/src/index.ts index b9818091f..320f07313 100644 --- a/plugins/postcss-cascade-layers/src/index.ts +++ b/plugins/postcss-cascade-layers/src/index.ts @@ -1,4 +1,4 @@ -import { Container } from "postcss"; +import { Container, AtRule, Node} from "postcss"; function postcssCascadeLayers(opts) { return { @@ -53,10 +53,38 @@ function postcssCascadeLayers(opts) { layerOrder[layer.params] = layerCount; }); - // 2nd walkthrough to transform unlayered styles - need highest specificity (layerCount + 1) - root.walkRules((rule) => { - layerOrder['unlayered ' + rule.selector] = layerCount + 1; - }); + // functions for 2nd walkthrough + function generateNot(specificity: number) { + let list = ''; for (let i = 0; i < specificity; i++) { + list += '#\\#'; // something short but still very uncommon + } return `:not(${list})`; + } + function hasLayerAtRuleAncestor(node: Node): boolean { + let parent = node.parent; + while (parent) { + if (parent.type !== 'atrule') { + parent = parent.parent; continue; + } + if ((parent as AtRule).name === 'layer') { + return true; + } + parent = parent.parent; + } + return false; } + + if (!layerCount) { + // no layers, so nothing to transform. + return; + } // 2nd walkthrough to transform unlayered styles - need highest specificity (layerCount) + root.walkRules((rule) => { + if (hasLayerAtRuleAncestor(rule)) { + return; + } rule.selectors = rule.selectors.map((selector) => { + // Needs `postcss-selector-parser` to insert `:not()` before any pseudo elements like `::after` + // This is a side track and can be fixed later. + return `${generateNot(layerCount)} ${selector}`; + }); + }); // 3rd walkthrough to transform layered styles: // - move out styles from atRule, insert before: https://postcss.org/api/#container-insertbefore From cee8ce4c4f7b59fdcd4dbbd5a8d9410f77ae91f9 Mon Sep 17 00:00:00 2001 From: romainmenke Date: Sun, 27 Mar 2022 19:04:22 +0200 Subject: [PATCH 13/33] more tweaks --- plugins/postcss-cascade-layers/.tape.mjs | 7 +- plugins/postcss-cascade-layers/INSTALL.md | 177 +++++++++++++++++- plugins/postcss-cascade-layers/README.md | 72 ++++++- plugins/postcss-cascade-layers/docs/README.md | 34 ++++ plugins/postcss-cascade-layers/package.json | 108 ++++++----- plugins/postcss-cascade-layers/src/index.ts | 10 +- .../postcss-cascade-layers/test/example.css | 7 - .../test/example.expect.css | 7 - .../test/examples/example.css | 9 + .../test/examples/example.expect.css | 9 + 10 files changed, 361 insertions(+), 79 deletions(-) create mode 100644 plugins/postcss-cascade-layers/docs/README.md delete mode 100644 plugins/postcss-cascade-layers/test/example.css delete mode 100644 plugins/postcss-cascade-layers/test/example.expect.css create mode 100644 plugins/postcss-cascade-layers/test/examples/example.css create mode 100644 plugins/postcss-cascade-layers/test/examples/example.expect.css diff --git a/plugins/postcss-cascade-layers/.tape.mjs b/plugins/postcss-cascade-layers/.tape.mjs index 67c6fa058..364936753 100644 --- a/plugins/postcss-cascade-layers/.tape.mjs +++ b/plugins/postcss-cascade-layers/.tape.mjs @@ -11,16 +11,13 @@ postcssTape(plugin)({ color: 'purple' } }, - example: { - message: "minimal example", - }, nested: { message: "supporting nested layer usage", }, 'anon-layer': { message: "supporting anonymous layer usage", }, - nested: { - message: "supporting nested layer usage", + 'examples/example': { + message: "minimal example", }, }); diff --git a/plugins/postcss-cascade-layers/INSTALL.md b/plugins/postcss-cascade-layers/INSTALL.md index ceacefa74..7ff70288f 100644 --- a/plugins/postcss-cascade-layers/INSTALL.md +++ b/plugins/postcss-cascade-layers/INSTALL.md @@ -1,3 +1,176 @@ -# Installing PostCSS Base Plugin +# Installing PostCSS Cascade Layers - +[PostCSS Cascade Layers] runs in all Node environments, with special instructions for: + +| [Node](#node) | [PostCSS CLI](#postcss-cli) | [Webpack](#webpack) | [Create React App](#create-react-app) | [Gulp](#gulp) | [Grunt](#grunt) | +| --- | --- | --- | --- | --- | --- | + +## Node + +Add [PostCSS Cascade Layers] to your project: + +```bash +npm install postcss @csstools/postcss-cascade-layers --save-dev +``` + +Use it as a [PostCSS] plugin: + +```js +const postcss = require('postcss'); +const postcssCascadeLayers = require('@csstools/postcss-cascade-layers'); + +postcss([ + postcssCascadeLayers(/* pluginOptions */) +]).process(YOUR_CSS /*, processOptions */); +``` + +## PostCSS CLI + +Add [PostCSS CLI] to your project: + +```bash +npm install postcss-cli @csstools/postcss-cascade-layers --save-dev +``` + +Use [PostCSS Cascade Layers] in your `postcss.config.js` configuration file: + +```js +const postcssCascadeLayers = require('@csstools/postcss-cascade-layers'); + +module.exports = { + plugins: [ + postcssCascadeLayers(/* pluginOptions */) + ] +} +``` + +## Webpack + +_Webpack version 5_ + +Add [PostCSS Loader] to your project: + +```bash +npm install postcss-loader @csstools/postcss-cascade-layers --save-dev +``` + +Use [PostCSS Cascade Layers] in your Webpack configuration: + +```js +module.exports = { + module: { + rules: [ + { + test: /\.css$/i, + use: [ + "style-loader", + { + loader: "css-loader", + options: { importLoaders: 1 }, + }, + { + loader: "postcss-loader", + options: { + postcssOptions: { + plugins: [ + [ + "@csstools/postcss-cascade-layers", + { + // Options + }, + ], + ], + }, + }, + }, + ], + }, + ], + }, +}; +``` + +## Create React App + +Add [React App Rewired] and [React App Rewire PostCSS] to your project: + +```bash +npm install react-app-rewired react-app-rewire-postcss @csstools/postcss-cascade-layers --save-dev +``` + +Use [React App Rewire PostCSS] and [PostCSS Cascade Layers] in your +`config-overrides.js` file: + +```js +const reactAppRewirePostcss = require('react-app-rewire-postcss'); +const postcssCascadeLayers = require('@csstools/postcss-cascade-layers'); + +module.exports = config => reactAppRewirePostcss(config, { + plugins: () => [ + postcssCascadeLayers(/* pluginOptions */) + ] +}); +``` + +## Gulp + +Add [Gulp PostCSS] to your project: + +```bash +npm install gulp-postcss @csstools/postcss-cascade-layers --save-dev +``` + +Use [PostCSS Cascade Layers] in your Gulpfile: + +```js +const postcss = require('gulp-postcss'); +const postcssCascadeLayers = require('@csstools/postcss-cascade-layers'); + +gulp.task('css', function () { + var plugins = [ + postcssCascadeLayers(/* pluginOptions */) + ]; + + return gulp.src('./src/*.css') + .pipe(postcss(plugins)) + .pipe(gulp.dest('.')); +}); +``` + +## Grunt + +Add [Grunt PostCSS] to your project: + +```bash +npm install grunt-postcss @csstools/postcss-cascade-layers --save-dev +``` + +Use [PostCSS Cascade Layers] in your Gruntfile: + +```js +const postcssCascadeLayers = require('@csstools/postcss-cascade-layers'); + +grunt.loadNpmTasks('grunt-postcss'); + +grunt.initConfig({ + postcss: { + options: { + processors: [ + postcssCascadeLayers(/* pluginOptions */) + ] + }, + dist: { + src: '*.css' + } + } +}); +``` + +[Gulp PostCSS]: https://github.com/postcss/gulp-postcss +[Grunt PostCSS]: https://github.com/nDmitry/grunt-postcss +[PostCSS]: https://github.com/postcss/postcss +[PostCSS CLI]: https://github.com/postcss/postcss-cli +[PostCSS Loader]: https://github.com/postcss/postcss-loader +[PostCSS Cascade Layers]: https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-cascade-layers +[React App Rewire PostCSS]: https://github.com/csstools/react-app-rewire-postcss +[React App Rewired]: https://github.com/timarney/react-app-rewired diff --git a/plugins/postcss-cascade-layers/README.md b/plugins/postcss-cascade-layers/README.md index a631e79c7..cba698666 100644 --- a/plugins/postcss-cascade-layers/README.md +++ b/plugins/postcss-cascade-layers/README.md @@ -1,5 +1,71 @@ -# PostCSS Base Plugin [PostCSS Logo][postcss] +# PostCSS Cascade Layers [PostCSS Logo][postcss] - +[npm version][npm-url] +[CSS Standard Status][css-url] +[Build Status][cli-url] +[Discord][discord] -[postcss]: https://github.com/postcss/postcss +[PostCSS Cascade Layers] lets use `@layer` following the [Cascade Layers Specification]. + +```pcss +@layer { + target { + color: green; + } +} + +target { + color: purple; +} + +/* becomes */ + +@layer anon0 { + target { + color: green; + } +} + +target { + color: purple; +} +``` + +## Usage + +Add [PostCSS Cascade Layers] to your project: + +```bash +npm install postcss @csstools/postcss-cascade-layers --save-dev +``` + +Use it as a [PostCSS] plugin: + +```js +const postcss = require('postcss'); +const postcssCascadeLayers = require('@csstools/postcss-cascade-layers'); + +postcss([ + postcssCascadeLayers(/* pluginOptions */) +]).process(YOUR_CSS /*, processOptions */); +``` + +[PostCSS Cascade Layers] runs in all Node environments, with special +instructions for: + +| [Node](INSTALL.md#node) | [PostCSS CLI](INSTALL.md#postcss-cli) | [Webpack](INSTALL.md#webpack) | [Create React App](INSTALL.md#create-react-app) | [Gulp](INSTALL.md#gulp) | [Grunt](INSTALL.md#grunt) | +| --- | --- | --- | --- | --- | --- | + + + +[cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test +[css-url]: https://cssdb.org/#cascade-layers +[discord]: https://discord.gg/bUadyRwkJS +[npm-url]: https://www.npmjs.com/package/@csstools/postcss-cascade-layers + +[Gulp PostCSS]: https://github.com/postcss/gulp-postcss +[Grunt PostCSS]: https://github.com/nDmitry/grunt-postcss +[PostCSS]: https://github.com/postcss/postcss +[PostCSS Loader]: https://github.com/postcss/postcss-loader +[PostCSS Cascade Layers]: https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-cascade-layers +[Cascade Layers Specification]: https://www.w3.org/TR/css-cascade-5/#layering diff --git a/plugins/postcss-cascade-layers/docs/README.md b/plugins/postcss-cascade-layers/docs/README.md new file mode 100644 index 000000000..3325b8725 --- /dev/null +++ b/plugins/postcss-cascade-layers/docs/README.md @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + +
+ +[] lets use `@layer` following the [Cascade Layers Specification]. + +```pcss + + +/* becomes */ + + +``` + + + + + + + + +[Cascade Layers Specification]: diff --git a/plugins/postcss-cascade-layers/package.json b/plugins/postcss-cascade-layers/package.json index f315debce..4ab887aa2 100644 --- a/plugins/postcss-cascade-layers/package.json +++ b/plugins/postcss-cascade-layers/package.json @@ -1,52 +1,60 @@ { - "name": "@csstools/postcss-cascade-layers", - "private": true, - "version": "0.0.0", - "description": "A base plugin", - "author": "Jonathan Neal ", - "license": "CC0-1.0", - "engines": { - "node": "^12 || ^14 || >=16" - }, - "main": "dist/index.cjs", - "module": "dist/index.mjs", - "types": "dist/index.d.ts", - "exports": { - ".": { - "import": "./dist/index.mjs", - "require": "./dist/index.cjs", - "default": "./dist/index.mjs" - } - }, - "files": [ - "CHANGELOG.md", - "LICENSE.md", - "README.md", - "dist" - ], - "scripts": { - "build": "rollup -c ../../rollup/default.js", - "clean": "node -e \"fs.rmSync('./dist', { recursive: true, force: true });\"", - "lint": "eslint ./src --ext .js --ext .ts --ext .mjs --no-error-on-unmatched-pattern", - "prepublishOnly": "npm run clean && npm run build && npm run test", - "stryker": "stryker run --logLevel error", - "test": "node .tape.mjs && npm run test:exports", - "test:browser": "node ./test/_browser.mjs", - "test:rewrite-expects": "REWRITE_EXPECTS=true node .tape.mjs", - "test:exports": "node ./test/_import.mjs && node ./test/_require.cjs" - }, - "peerDependencies": { - "postcss": "^8.3" - }, - "keywords": [ - "postcss-plugin" - ], - "repository": { - "type": "git", - "url": "https://github.com/csstools/postcss-plugins.git", - "directory": "plugins/postcss-cascade-layers" - }, - "volta": { - "extends": "../../package.json" - } + "name": "@csstools/postcss-cascade-layers", + "description": "A base plugin", + "version": "0.0.0", + "author": "Jonathan Neal ", + "license": "CC0-1.0", + "private": true, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "main": "dist/index.cjs", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.cjs", + "default": "./dist/index.mjs" + } + }, + "files": [ + "CHANGELOG.md", + "LICENSE.md", + "README.md", + "dist" + ], + "peerDependencies": { + "postcss": "^8.3" + }, + "scripts": { + "build": "rollup -c ../../rollup/default.js", + "clean": "node -e \"fs.rmSync('./dist', { recursive: true, force: true });\"", + "docs": "node ../../.github/bin/generate-docs/install.mjs && node ../../.github/bin/generate-docs/readme.mjs", + "lint": "npm run lint:eslint && npm run lint:package-json", + "lint:eslint": "eslint ./src --ext .js --ext .ts --ext .mjs --no-error-on-unmatched-pattern", + "lint:package-json": "node ../../.github/bin/format-package-json.mjs", + "prepublishOnly": "npm run clean && npm run build && npm run test", + "test": "node .tape.mjs && npm run test:exports", + "test:browser": "node ./test/_browser.mjs", + "test:exports": "node ./test/_import.mjs && node ./test/_require.cjs", + "test:rewrite-expects": "REWRITE_EXPECTS=true node .tape.mjs" + }, + "repository": { + "type": "git", + "url": "https://github.com/csstools/postcss-plugins.git", + "directory": "plugins/postcss-cascade-layers" + }, + "keywords": [ + "postcss-plugin" + ], + "csstools": { + "cssdbId": "cascade-layers", + "exportName": "postcssCascadeLayers", + "humanReadableName": "PostCSS Cascade Layers", + "specUrl": "https://www.w3.org/TR/css-cascade-5/#layering" + }, + "volta": { + "extends": "../../package.json" + } } diff --git a/plugins/postcss-cascade-layers/src/index.ts b/plugins/postcss-cascade-layers/src/index.ts index 557c543fb..8c51cb5ee 100644 --- a/plugins/postcss-cascade-layers/src/index.ts +++ b/plugins/postcss-cascade-layers/src/index.ts @@ -1,6 +1,6 @@ -import { Container } from 'postcss'; +import type { Container, PluginCreator } from 'postcss'; -function postcssCascadeLayers(opts) { +const creator: PluginCreator = () => { return { postcssPlugin: 'postcss-cascade-layers', Once(root: Container) { @@ -75,8 +75,8 @@ function postcssCascadeLayers(opts) { console.log(layerOrder); }, }; -} +}; -postcssCascadeLayers.postcss = true; +creator.postcss = true; -export default postcssCascadeLayers; +export default creator; diff --git a/plugins/postcss-cascade-layers/test/example.css b/plugins/postcss-cascade-layers/test/example.css deleted file mode 100644 index 181f83a54..000000000 --- a/plugins/postcss-cascade-layers/test/example.css +++ /dev/null @@ -1,7 +0,0 @@ -.foo { - color: red; -} - -.baz { - color: green; -} diff --git a/plugins/postcss-cascade-layers/test/example.expect.css b/plugins/postcss-cascade-layers/test/example.expect.css deleted file mode 100644 index 181f83a54..000000000 --- a/plugins/postcss-cascade-layers/test/example.expect.css +++ /dev/null @@ -1,7 +0,0 @@ -.foo { - color: red; -} - -.baz { - color: green; -} diff --git a/plugins/postcss-cascade-layers/test/examples/example.css b/plugins/postcss-cascade-layers/test/examples/example.css new file mode 100644 index 000000000..217b8c85b --- /dev/null +++ b/plugins/postcss-cascade-layers/test/examples/example.css @@ -0,0 +1,9 @@ +@layer { + target { + color: green; + } +} + +target { + color: purple; +} diff --git a/plugins/postcss-cascade-layers/test/examples/example.expect.css b/plugins/postcss-cascade-layers/test/examples/example.expect.css new file mode 100644 index 000000000..5370a381a --- /dev/null +++ b/plugins/postcss-cascade-layers/test/examples/example.expect.css @@ -0,0 +1,9 @@ +@layer anon0 { + target { + color: green; + } +} + +target { + color: purple; +} From b2c3f60edb74384e871e7ebd55299b4a74a4b118 Mon Sep 17 00:00:00 2001 From: Jonny Gerig Meyer Date: Thu, 31 Mar 2022 16:30:44 -0400 Subject: [PATCH 14/33] formatting --- plugins/postcss-cascade-layers/src/index.ts | 55 +++++++++++---------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/plugins/postcss-cascade-layers/src/index.ts b/plugins/postcss-cascade-layers/src/index.ts index 320f07313..0a9b6d1ea 100644 --- a/plugins/postcss-cascade-layers/src/index.ts +++ b/plugins/postcss-cascade-layers/src/index.ts @@ -1,14 +1,14 @@ -import { Container, AtRule, Node} from "postcss"; +import { Container, AtRule, Node } from 'postcss'; function postcssCascadeLayers(opts) { return { - postcssPlugin: "postcss-cascade-layers", + postcssPlugin: 'postcss-cascade-layers', Once(root: Container) { let layerCount = 0; - let layerOrder = {}; + const layerOrder = {}; // 1st walkthrough to rename anon layers and store state (no modification of layer styles) - root.walkAtRules("layer", (atRule) => { + root.walkAtRules('layer', (atRule) => { // give anonymous layers a name if (!atRule.params) { atRule.params = `anon${layerCount}`; @@ -19,9 +19,9 @@ function postcssCascadeLayers(opts) { // check for where a layer has nested layers AND styles outside of those layers atRule.each((node) => { - if (node.type == "atrule") { + if (node.type == 'atrule') { hasNestedLayers = true; - } else if (node.type == "rule") { + } else if (node.type == 'rule') { hasUnlayeredStyles = true; } }); @@ -40,7 +40,7 @@ function postcssCascadeLayers(opts) { // go through the unlayered rules, clone, and delete from top level atRule atRule.each((node) => { - if (node.type == "rule") { + if (node.type == 'rule') { implicitLayer.append(node.clone()); node.remove(); } @@ -48,7 +48,7 @@ function postcssCascadeLayers(opts) { } }); - root.walkAtRules("layer", (layer) => { + root.walkAtRules('layer', (layer) => { layerCount += 1; layerOrder[layer.params] = layerCount; }); @@ -66,30 +66,33 @@ function postcssCascadeLayers(opts) { parent = parent.parent; continue; } if ((parent as AtRule).name === 'layer') { - return true; - } - parent = parent.parent; + return true; } - return false; } + parent = parent.parent; + } + return false; + } - if (!layerCount) { - // no layers, so nothing to transform. - return; - } // 2nd walkthrough to transform unlayered styles - need highest specificity (layerCount) - root.walkRules((rule) => { - if (hasLayerAtRuleAncestor(rule)) { - return; - } rule.selectors = rule.selectors.map((selector) => { - // Needs `postcss-selector-parser` to insert `:not()` before any pseudo elements like `::after` - // This is a side track and can be fixed later. - return `${generateNot(layerCount)} ${selector}`; - }); - }); + if (!layerCount) { + // no layers, so nothing to transform. + return; + } + + // 2nd walkthrough to transform unlayered styles - need highest specificity (layerCount) + root.walkRules((rule) => { + if (hasLayerAtRuleAncestor(rule)) { + return; + } rule.selectors = rule.selectors.map((selector) => { + // Needs `postcss-selector-parser` to insert `:not()` before any pseudo elements like `::after` + // This is a side track and can be fixed later. + return `${generateNot(layerCount)} ${selector}`; + }); + }); // 3rd walkthrough to transform layered styles: // - move out styles from atRule, insert before: https://postcss.org/api/#container-insertbefore // - delete empty atRule - // - give selectors the specifity they need based on layerPriority state + // - give selectors the specificity they need based on layerPriority state // root.walkAtRules((atRule) => { // console.log(atRule, "third walkthrough"); // }); From 19e733cbbbf7b655f9a486218c6163c67348f0fe Mon Sep 17 00:00:00 2001 From: Jonny Gerig Meyer Date: Thu, 31 Mar 2022 16:37:17 -0400 Subject: [PATCH 15/33] review --- plugins/postcss-cascade-layers/src/index.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/plugins/postcss-cascade-layers/src/index.ts b/plugins/postcss-cascade-layers/src/index.ts index 0a9b6d1ea..9cfedc2dd 100644 --- a/plugins/postcss-cascade-layers/src/index.ts +++ b/plugins/postcss-cascade-layers/src/index.ts @@ -27,7 +27,7 @@ function postcssCascadeLayers(opts) { }); if (hasNestedLayers && hasUnlayeredStyles) { - //create new final layer via cloning, empty it + // create new final layer via cloning, empty it const implicitLayer = atRule.clone({ params: `${atRule.params}-implicit`, }); @@ -55,15 +55,18 @@ function postcssCascadeLayers(opts) { // functions for 2nd walkthrough function generateNot(specificity: number) { - let list = ''; for (let i = 0; i < specificity; i++) { + let list = ''; + for (let i = 0; i < specificity; i++) { list += '#\\#'; // something short but still very uncommon - } return `:not(${list})`; + } + return `:not(${list})`; } function hasLayerAtRuleAncestor(node: Node): boolean { let parent = node.parent; while (parent) { if (parent.type !== 'atrule') { - parent = parent.parent; continue; + parent = parent.parent; + continue; } if ((parent as AtRule).name === 'layer') { return true; @@ -82,7 +85,8 @@ function postcssCascadeLayers(opts) { root.walkRules((rule) => { if (hasLayerAtRuleAncestor(rule)) { return; - } rule.selectors = rule.selectors.map((selector) => { + } + rule.selectors = rule.selectors.map((selector) => { // Needs `postcss-selector-parser` to insert `:not()` before any pseudo elements like `::after` // This is a side track and can be fixed later. return `${generateNot(layerCount)} ${selector}`; From 4717b982ef34bd591dce98a633c92983feeecf9d Mon Sep 17 00:00:00 2001 From: Olu Niyi-Awosusi Date: Tue, 5 Apr 2022 20:16:46 +0100 Subject: [PATCH 16/33] fix test --- .../test/anon-layer.expect.css | 2 +- .../test/basic.color.expect.css | 2 +- .../test/basic.expect.css | 2 +- .../test/examples/example.expect.css | 2 +- .../test/nested.expect.css | 40 ++++--------------- 5 files changed, 12 insertions(+), 36 deletions(-) diff --git a/plugins/postcss-cascade-layers/test/anon-layer.expect.css b/plugins/postcss-cascade-layers/test/anon-layer.expect.css index b30e8198c..0c03fc94f 100644 --- a/plugins/postcss-cascade-layers/test/anon-layer.expect.css +++ b/plugins/postcss-cascade-layers/test/anon-layer.expect.css @@ -22,6 +22,6 @@ } } -target { +:not(#\##\##\##\#) target { color: purple; } diff --git a/plugins/postcss-cascade-layers/test/basic.color.expect.css b/plugins/postcss-cascade-layers/test/basic.color.expect.css index 8c2d3172e..e888eb114 100644 --- a/plugins/postcss-cascade-layers/test/basic.color.expect.css +++ b/plugins/postcss-cascade-layers/test/basic.color.expect.css @@ -10,6 +10,6 @@ } } -target { +:not(#\##\#) target { color: purple; } diff --git a/plugins/postcss-cascade-layers/test/basic.expect.css b/plugins/postcss-cascade-layers/test/basic.expect.css index e9fbe9616..e888eb114 100644 --- a/plugins/postcss-cascade-layers/test/basic.expect.css +++ b/plugins/postcss-cascade-layers/test/basic.expect.css @@ -10,6 +10,6 @@ } } -:not('#\\##\\#') target { +:not(#\##\#) target { color: purple; } diff --git a/plugins/postcss-cascade-layers/test/examples/example.expect.css b/plugins/postcss-cascade-layers/test/examples/example.expect.css index 5370a381a..5992d061d 100644 --- a/plugins/postcss-cascade-layers/test/examples/example.expect.css +++ b/plugins/postcss-cascade-layers/test/examples/example.expect.css @@ -4,6 +4,6 @@ } } -target { +:not(#\#) target { color: purple; } diff --git a/plugins/postcss-cascade-layers/test/nested.expect.css b/plugins/postcss-cascade-layers/test/nested.expect.css index 31b40d981..75b67ed84 100644 --- a/plugins/postcss-cascade-layers/test/nested.expect.css +++ b/plugins/postcss-cascade-layers/test/nested.expect.css @@ -1,33 +1,7 @@ @layer A { - - @layer Z { - target { - color: yellow; - } - }@layer A-implicit { - target { - color: red; - } - - p { - color: blue; - } - - i { - color: red; - } -} } @layer B { - - @layer Z { - target { - color: yellow; - } - } - -@layer B-implicit { @keyframes slide-left { from { margin-left: 0; @@ -37,23 +11,25 @@ margin-left: -100%; } } -} -} - -@layer C { @layer Z { target { color: yellow; } } +} -@layer C-implicit { +@layer C { @media (prefers-color-scheme: dark) { h1 { color: red; background: black; } } -} + + @layer Z { + target { + color: yellow; + } + } } From c129d951b754d11261dacb4b6a2c12bbd58f0a54 Mon Sep 17 00:00:00 2001 From: Olu Niyi-Awosusi Date: Tue, 5 Apr 2022 20:25:59 +0100 Subject: [PATCH 17/33] add additional test for unlayered styles --- plugins/postcss-cascade-layers/.tape.mjs | 3 +++ .../test/unlayered-styles.css | 23 +++++++++++++++++++ .../test/unlayered-styles.expect.css | 23 +++++++++++++++++++ 3 files changed, 49 insertions(+) create mode 100644 plugins/postcss-cascade-layers/test/unlayered-styles.css create mode 100644 plugins/postcss-cascade-layers/test/unlayered-styles.expect.css diff --git a/plugins/postcss-cascade-layers/.tape.mjs b/plugins/postcss-cascade-layers/.tape.mjs index 364936753..381564432 100644 --- a/plugins/postcss-cascade-layers/.tape.mjs +++ b/plugins/postcss-cascade-layers/.tape.mjs @@ -20,4 +20,7 @@ postcssTape(plugin)({ 'examples/example': { message: "minimal example", }, + 'unlayered-styles': { + message: 'supports unlayered styles alonsgide layers', + }, }); diff --git a/plugins/postcss-cascade-layers/test/unlayered-styles.css b/plugins/postcss-cascade-layers/test/unlayered-styles.css new file mode 100644 index 000000000..312972877 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/unlayered-styles.css @@ -0,0 +1,23 @@ +@layer A { + target { + color: red; + } +} + +@layer { + target { + color: green; + } +} + +a { + color: purple; +} + +target { + color: green; +} + +h1, p { + color:black +} diff --git a/plugins/postcss-cascade-layers/test/unlayered-styles.expect.css b/plugins/postcss-cascade-layers/test/unlayered-styles.expect.css new file mode 100644 index 000000000..c35397f3f --- /dev/null +++ b/plugins/postcss-cascade-layers/test/unlayered-styles.expect.css @@ -0,0 +1,23 @@ +@layer A { + target { + color: red; + } +} + +@layer anon0 { + target { + color: green; + } +} + +:not(#\##\#) a { + color: purple; +} + +:not(#\##\#) target { + color: green; +} + +:not(#\##\#) h1, :not(#\##\#) p { + color:black +} From 4543413385ce69010587a55cf05e2b98a0cbe3f9 Mon Sep 17 00:00:00 2001 From: Olu Niyi-Awosusi Date: Wed, 6 Apr 2022 00:57:43 +0100 Subject: [PATCH 18/33] change rule --- plugins/postcss-cascade-layers/test/unlayered-styles.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/postcss-cascade-layers/test/unlayered-styles.css b/plugins/postcss-cascade-layers/test/unlayered-styles.css index 312972877..f38eb053d 100644 --- a/plugins/postcss-cascade-layers/test/unlayered-styles.css +++ b/plugins/postcss-cascade-layers/test/unlayered-styles.css @@ -18,6 +18,6 @@ target { color: green; } -h1, p { +span h1, span p { color:black } From 3e444a87f946e42983cf17ebeeef71cc0cc9e760 Mon Sep 17 00:00:00 2001 From: Olu Niyi-Awosusi Date: Wed, 6 Apr 2022 01:01:36 +0100 Subject: [PATCH 19/33] changing color of rule to see if it's interfering --- plugins/postcss-cascade-layers/test/unlayered-styles.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/postcss-cascade-layers/test/unlayered-styles.css b/plugins/postcss-cascade-layers/test/unlayered-styles.css index f38eb053d..5d8f24833 100644 --- a/plugins/postcss-cascade-layers/test/unlayered-styles.css +++ b/plugins/postcss-cascade-layers/test/unlayered-styles.css @@ -19,5 +19,5 @@ target { } span h1, span p { - color:black + color:red; } From d4815ede200c0b21d1d3260ab54d3bfcc409da75 Mon Sep 17 00:00:00 2001 From: Olu Niyi-Awosusi Date: Wed, 6 Apr 2022 01:05:18 +0100 Subject: [PATCH 20/33] test changes --- .../postcss-cascade-layers/test/unlayered-styles.expect.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/postcss-cascade-layers/test/unlayered-styles.expect.css b/plugins/postcss-cascade-layers/test/unlayered-styles.expect.css index c35397f3f..9a15dbcd4 100644 --- a/plugins/postcss-cascade-layers/test/unlayered-styles.expect.css +++ b/plugins/postcss-cascade-layers/test/unlayered-styles.expect.css @@ -18,6 +18,6 @@ color: green; } -:not(#\##\#) h1, :not(#\##\#) p { - color:black +:not(#\##\#) span h1, :not(#\##\#) span p { + color:red; } From 5c1611e3f8b392eda4ff429c29001647b2a25ca4 Mon Sep 17 00:00:00 2001 From: romainmenke Date: Wed, 6 Apr 2022 08:43:32 +0200 Subject: [PATCH 21/33] cascade layers : fix tests --- plugins/postcss-cascade-layers/src/index.ts | 59 ++++++++++--------- .../test/anon-layer.expect.css | 2 +- .../test/basic.color.expect.css | 2 +- .../test/basic.expect.css | 2 +- .../test/examples/example.expect.css | 2 +- .../test/nested.expect.css | 18 ++++++ .../test/unlayered-styles.expect.css | 6 +- 7 files changed, 57 insertions(+), 34 deletions(-) diff --git a/plugins/postcss-cascade-layers/src/index.ts b/plugins/postcss-cascade-layers/src/index.ts index 78787d823..e4da9912d 100644 --- a/plugins/postcss-cascade-layers/src/index.ts +++ b/plugins/postcss-cascade-layers/src/index.ts @@ -35,6 +35,7 @@ const creator: PluginCreator = () => { const implicitLayer = atRule.clone({ params: `${atRule.params}-implicit`, }); + implicitLayer.each((node) => { node.remove(); }); @@ -44,9 +45,8 @@ const creator: PluginCreator = () => { // go through the unlayered rules, clone, and delete from top level atRule atRule.each((node) => { - if (node.type == 'rule') { - implicitLayer.append(node.clone()); - node.remove(); + if (node.type == 'atrule' && node.name === 'layer') { + return; } implicitLayer.append(node.clone()); @@ -60,29 +60,6 @@ const creator: PluginCreator = () => { layerOrder[layer.params] = layerCount; }); - // functions for 2nd walkthrough - function generateNot(specificity: number) { - let list = ''; - for (let i = 0; i < specificity; i++) { - list += '#\\#'; // something short but still very uncommon - } - return `:not(${list})`; - } - function hasLayerAtRuleAncestor(node: Node): boolean { - let parent = node.parent; - while (parent) { - if (parent.type !== 'atrule') { - parent = parent.parent; - continue; - } - if ((parent as AtRule).name === 'layer') { - return true; - } - parent = parent.parent; - } - return false; - } - if (!layerCount) { // no layers, so nothing to transform. return; @@ -93,10 +70,11 @@ const creator: PluginCreator = () => { if (hasLayerAtRuleAncestor(rule)) { return; } + rule.selectors = rule.selectors.map((selector) => { // Needs `postcss-selector-parser` to insert `:not()` before any pseudo elements like `::after` // This is a side track and can be fixed later. - return `${generateNot(layerCount)} ${selector}`; + return `${selector}${generateNot(layerCount)}`; }); }); @@ -114,3 +92,30 @@ const creator: PluginCreator = () => { creator.postcss = true; export default creator; + +function generateNot(specificity: number) { + let list = ''; + for (let i = 0; i < specificity; i++) { + list += '#\\#'; // something short but still very uncommon + } + + return `:not(${list})`; +} + +function hasLayerAtRuleAncestor(node: Node): boolean { + let parent = node.parent; + while (parent) { + if (parent.type !== 'atrule') { + parent = parent.parent; + continue; + } + + if ((parent as AtRule).name === 'layer') { + return true; + } + + parent = parent.parent; + } + + return false; +} diff --git a/plugins/postcss-cascade-layers/test/anon-layer.expect.css b/plugins/postcss-cascade-layers/test/anon-layer.expect.css index 0c03fc94f..0338108b8 100644 --- a/plugins/postcss-cascade-layers/test/anon-layer.expect.css +++ b/plugins/postcss-cascade-layers/test/anon-layer.expect.css @@ -22,6 +22,6 @@ } } -:not(#\##\##\##\#) target { +target:not(#\##\##\##\#) { color: purple; } diff --git a/plugins/postcss-cascade-layers/test/basic.color.expect.css b/plugins/postcss-cascade-layers/test/basic.color.expect.css index e888eb114..51a83a9c5 100644 --- a/plugins/postcss-cascade-layers/test/basic.color.expect.css +++ b/plugins/postcss-cascade-layers/test/basic.color.expect.css @@ -10,6 +10,6 @@ } } -:not(#\##\#) target { +target:not(#\##\#) { color: purple; } diff --git a/plugins/postcss-cascade-layers/test/basic.expect.css b/plugins/postcss-cascade-layers/test/basic.expect.css index e888eb114..51a83a9c5 100644 --- a/plugins/postcss-cascade-layers/test/basic.expect.css +++ b/plugins/postcss-cascade-layers/test/basic.expect.css @@ -10,6 +10,6 @@ } } -:not(#\##\#) target { +target:not(#\##\#) { color: purple; } diff --git a/plugins/postcss-cascade-layers/test/examples/example.expect.css b/plugins/postcss-cascade-layers/test/examples/example.expect.css index 5992d061d..6cc824c2c 100644 --- a/plugins/postcss-cascade-layers/test/examples/example.expect.css +++ b/plugins/postcss-cascade-layers/test/examples/example.expect.css @@ -4,6 +4,6 @@ } } -:not(#\#) target { +target:not(#\#) { color: purple; } diff --git a/plugins/postcss-cascade-layers/test/nested.expect.css b/plugins/postcss-cascade-layers/test/nested.expect.css index 75b67ed84..e39b54df1 100644 --- a/plugins/postcss-cascade-layers/test/nested.expect.css +++ b/plugins/postcss-cascade-layers/test/nested.expect.css @@ -1,4 +1,22 @@ @layer A { + + @layer Z { + target { + color: yellow; + } + }@layer A-implicit { + target { + color: red; + } + + p { + color: blue; + } + + i { + color: red; + } +} } @layer B { diff --git a/plugins/postcss-cascade-layers/test/unlayered-styles.expect.css b/plugins/postcss-cascade-layers/test/unlayered-styles.expect.css index 9a15dbcd4..0e10d1a54 100644 --- a/plugins/postcss-cascade-layers/test/unlayered-styles.expect.css +++ b/plugins/postcss-cascade-layers/test/unlayered-styles.expect.css @@ -10,14 +10,14 @@ } } -:not(#\##\#) a { +a:not(#\##\#) { color: purple; } -:not(#\##\#) target { +target:not(#\##\#) { color: green; } -:not(#\##\#) span h1, :not(#\##\#) span p { +span h1:not(#\##\#), span p:not(#\##\#) { color:red; } From 27c309def1cd0a6e38765c74b1ca9ffbf11bd718 Mon Sep 17 00:00:00 2001 From: Olu Niyi-Awosusi Date: Wed, 20 Apr 2022 23:02:32 +0100 Subject: [PATCH 22/33] first attempt --- plugins/postcss-cascade-layers/src/index.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/plugins/postcss-cascade-layers/src/index.ts b/plugins/postcss-cascade-layers/src/index.ts index e4da9912d..ad493d7b2 100644 --- a/plugins/postcss-cascade-layers/src/index.ts +++ b/plugins/postcss-cascade-layers/src/index.ts @@ -1,4 +1,4 @@ -import { Container, AtRule, Node, PluginCreator } from 'postcss'; +import { Container, AtRule, Node, PluginCreator, decl, Declaration, Rule } from 'postcss'; const creator: PluginCreator = () => { return { postcssPlugin: 'postcss-cascade-layers', @@ -6,6 +6,7 @@ const creator: PluginCreator = () => { let layerCount = 0; const layerOrder = {}; + // 1st walkthrough to rename anon layers and store state (no modification of layer styles) root.walkAtRules('layer', (atRule) => { // give anonymous layers a name @@ -82,9 +83,11 @@ const creator: PluginCreator = () => { // - move out styles from atRule, insert before: https://postcss.org/api/#container-insertbefore // - delete empty atRule // - give selectors the specificity they need based on layerPriority state - // root.walkAtRules((atRule) => { - // console.log(atRule, "third walkthrough"); - // }); + root.walkAtRules('layer', (atRule) => { + // move out styles from atRule, insert before + + atRule.replaceWith(new Rule({nodes: atRule.nodes, source: atRule.source, selector: `${generateNot(layerOrder[atRule.params])}`})); + }); }, }; }; From a8568ada6d13d2ca9405f1afb15cbedcfa364deb Mon Sep 17 00:00:00 2001 From: romainmenke Date: Fri, 22 Apr 2022 15:01:27 +0200 Subject: [PATCH 23/33] implement specificity adjustments and rule re-ordering --- plugins/postcss-cascade-layers/.tape.mjs | 12 +- plugins/postcss-cascade-layers/README.md | 15 +- plugins/postcss-cascade-layers/package.json | 4 + .../src/adjust-selector-specificity.ts | 27 ++ .../src/compound-selector-order.ts | 88 +++++ .../src/desugar-nested-layers.ts | 61 ++++ .../src/has-layer-atrule-ancestor.ts | 20 ++ plugins/postcss-cascade-layers/src/index.ts | 302 ++++++++++++++---- plugins/postcss-cascade-layers/src/model.ts | 194 +++++++++++ .../src/some-in-tree.ts | 14 + .../postcss-cascade-layers/src/specificity.ts | 111 +++++++ .../postcss-cascade-layers/test/_browser.mjs | 42 +-- .../test/anon-layer.expect.css | 15 +- .../postcss-cascade-layers/test/atrules.css | 43 +++ .../test/atrules.expect.css | 32 ++ .../test/basic.color.expect.css | 11 +- plugins/postcss-cascade-layers/test/basic.css | 4 +- .../test/basic.expect.css | 11 +- .../test/examples/example.css | 8 +- .../test/examples/example.expect.css | 10 +- .../test/invalid-rules.css | 9 + .../test/invalid-rules.expect.css | 9 + .../postcss-cascade-layers/test/nested.css | 18 ++ .../test/nested.expect.css | 56 ++-- .../test/unlayered-styles.expect.css | 7 +- .../wpt/layer-counter-style-override.html | 7 +- .../test/wpt/layer-important.html | 103 ++++++ .../test/wpt/layer-keyframes-override.html | 170 ++++++++++ .../test/wpt/layer-media-query.html | 155 +++++++++ .../test/wpt/layer-property-override.html | 186 +++++++++++ .../test/wpt/layer-vs-inline-style.html | 84 +++++ rollup/presets/package-typescript.js | 2 +- 32 files changed, 1667 insertions(+), 163 deletions(-) create mode 100644 plugins/postcss-cascade-layers/src/adjust-selector-specificity.ts create mode 100644 plugins/postcss-cascade-layers/src/compound-selector-order.ts create mode 100644 plugins/postcss-cascade-layers/src/desugar-nested-layers.ts create mode 100644 plugins/postcss-cascade-layers/src/has-layer-atrule-ancestor.ts create mode 100644 plugins/postcss-cascade-layers/src/model.ts create mode 100644 plugins/postcss-cascade-layers/src/some-in-tree.ts create mode 100644 plugins/postcss-cascade-layers/src/specificity.ts create mode 100644 plugins/postcss-cascade-layers/test/atrules.css create mode 100644 plugins/postcss-cascade-layers/test/atrules.expect.css create mode 100644 plugins/postcss-cascade-layers/test/invalid-rules.css create mode 100644 plugins/postcss-cascade-layers/test/invalid-rules.expect.css create mode 100644 plugins/postcss-cascade-layers/test/wpt/layer-important.html create mode 100644 plugins/postcss-cascade-layers/test/wpt/layer-keyframes-override.html create mode 100644 plugins/postcss-cascade-layers/test/wpt/layer-media-query.html create mode 100644 plugins/postcss-cascade-layers/test/wpt/layer-property-override.html create mode 100644 plugins/postcss-cascade-layers/test/wpt/layer-vs-inline-style.html diff --git a/plugins/postcss-cascade-layers/.tape.mjs b/plugins/postcss-cascade-layers/.tape.mjs index 381564432..5d285eb40 100644 --- a/plugins/postcss-cascade-layers/.tape.mjs +++ b/plugins/postcss-cascade-layers/.tape.mjs @@ -11,16 +11,22 @@ postcssTape(plugin)({ color: 'purple' } }, + atrules: { + message: "supports @keyframes usage", + }, nested: { - message: "supporting nested layer usage", + message: "supports nested layer usage", }, 'anon-layer': { - message: "supporting anonymous layer usage", + message: "supports anonymous layer usage", }, 'examples/example': { message: "minimal example", }, 'unlayered-styles': { - message: 'supports unlayered styles alonsgide layers', + message: 'supports unlayered styles alongside layers', + }, + 'invalid-rules': { + message: 'correctly handles invalid rules', }, }); diff --git a/plugins/postcss-cascade-layers/README.md b/plugins/postcss-cascade-layers/README.md index cba698666..d632f2934 100644 --- a/plugins/postcss-cascade-layers/README.md +++ b/plugins/postcss-cascade-layers/README.md @@ -8,27 +8,28 @@ [PostCSS Cascade Layers] lets use `@layer` following the [Cascade Layers Specification]. ```pcss + +target { + color: purple; +} + @layer { target { color: green; } } -target { - color: purple; -} /* becomes */ -@layer anon0 { + target { color: green; } -} - -target { +target:not(#\#) { color: purple; } + ``` ## Usage diff --git a/plugins/postcss-cascade-layers/package.json b/plugins/postcss-cascade-layers/package.json index 4ab887aa2..846cf9a65 100644 --- a/plugins/postcss-cascade-layers/package.json +++ b/plugins/postcss-cascade-layers/package.json @@ -24,6 +24,10 @@ "README.md", "dist" ], + "dependencies": { + "postcss-selector-parser": "^6.0.10", + "postcss-value-parser": "^4.2.0" + }, "peerDependencies": { "postcss": "^8.3" }, diff --git a/plugins/postcss-cascade-layers/src/adjust-selector-specificity.ts b/plugins/postcss-cascade-layers/src/adjust-selector-specificity.ts new file mode 100644 index 000000000..f2a9e9296 --- /dev/null +++ b/plugins/postcss-cascade-layers/src/adjust-selector-specificity.ts @@ -0,0 +1,27 @@ +import selectorParser from 'postcss-selector-parser'; +import { sortCompoundSelectorsInsideComplexSelector } from './compound-selector-order'; + +export function adjustSelectorSpecificity(selector: string, amount: number): string { + const selectorAST = selectorParser().astSync(selector + generateNot(amount)); + + selectorAST.walk((node) => { + if ('nodes' in node) { + sortCompoundSelectorsInsideComplexSelector(node); + } + }); + + return selectorAST.toString(); +} + +function generateNot(specificity: number) { + if (specificity === 0) { + return ''; + } + + let list = ''; + for (let i = 0; i < specificity; i++) { + list += '#\\#'; // something short but still very uncommon + } + + return `:not(${list})`; +} diff --git a/plugins/postcss-cascade-layers/src/compound-selector-order.ts b/plugins/postcss-cascade-layers/src/compound-selector-order.ts new file mode 100644 index 000000000..379f8f6fe --- /dev/null +++ b/plugins/postcss-cascade-layers/src/compound-selector-order.ts @@ -0,0 +1,88 @@ +import selectorParser from 'postcss-selector-parser'; + +export function sortCompoundSelectorsInsideComplexSelector(node) { + if (!node || !node.nodes) { + return; + } + + let compound = []; + const nodes = [...node.nodes]; + + for (let i = 0; i < (nodes.length + 1); i++) { + const child = nodes[i]; + if (!child || child.type === 'combinator') { + if (compound.length > 1) { + const compoundSelector = selectorParser.selector({ value: '' }); + compound[0].replaceWith(compoundSelector); + + compound.slice(1).forEach((compoundPart) => { + compoundPart.remove(); + }); + + compound.forEach((compoundPart) => { + compoundSelector.append(compoundPart); + }); + + sortCompoundSelector(compoundSelector); + compoundSelector.replaceWith(...(compoundSelector.nodes)); + } + + compound = []; + continue; + } + + compound.push(child); + } +} + +export function sortCompoundSelector(node) { + if (!node || !node.nodes) { + return; + } + // simply concatenating with selectors can lead to : + // `.fooh1` + // + // applying a sort where tag selectors are first will result in : + // `h1.foo` + + node.nodes.sort((a, b) => { + if (a.type === 'selector' && b.type === 'selector' && a.nodes.length && b.nodes.length) { + return selectorTypeOrder(a.nodes[0].value, a.nodes[0].type) - selectorTypeOrder(b.nodes[0].value, b.nodes[0].type); + } + + if (a.type === 'selector' && a.nodes.length) { + return selectorTypeOrder(a.nodes[0].value, a.nodes[0].type) - selectorTypeOrder(b.value, b.type); + } + + if (b.type === 'selector' && b.nodes.length) { + return selectorTypeOrder(a.value, a.type) - selectorTypeOrder(b.nodes[0].value, b.nodes[0].type); + } + + return selectorTypeOrder(a.value, a.type) - selectorTypeOrder(b.value, b.type); + }); +} + +function selectorTypeOrder(selector, type) { + if (selectorParser.isPseudoElement(selector)) { + return selectorTypeOrderIndex['pseudoElement']; + } + + if (type === 'pseudo' && selector && selector.indexOf('::') === 0) { + return selectorTypeOrderIndex['pseudoElement']; + } + return selectorTypeOrderIndex[type]; +} + +const selectorTypeOrderIndex = { + universal: 0, + tag: 1, + id: 2, + class: 3, + attribute: 4, + selector: 5, + pseudo: 6, + pseudoElement: 7, + string: 8, + root: 9, + comment: 10, +}; diff --git a/plugins/postcss-cascade-layers/src/desugar-nested-layers.ts b/plugins/postcss-cascade-layers/src/desugar-nested-layers.ts new file mode 100644 index 000000000..8f657691b --- /dev/null +++ b/plugins/postcss-cascade-layers/src/desugar-nested-layers.ts @@ -0,0 +1,61 @@ +import type { Container, AtRule, ChildNode } from 'postcss'; +import { Model } from './model'; +import { someInTree } from './some-in-tree'; + +export function desugarNestedLayers(root: Container, model: Model) { + while (someInTree(root, (node) => { + return node.type === 'atrule' && node.nodes && someInTree(node, (nested) => { + return nested.type === 'atrule' && nested.name === 'layer'; + }); + })) { + let foundUnexpectedLayerNesting = false; + + root.walkAtRules('layer', (layerRule) => { + if (layerRule.parent === root) { + return; + } + + if (layerRule.parent.type === 'atrule' && (layerRule.parent as AtRule).name === 'layer') { + const parent = layerRule.parent as AtRule; + + model.layerNameParts.set(`${parent.params}.${layerRule.params}`, [...model.layerNameParts.get(parent.params), ...model.layerNameParts.get(layerRule.params)]); + model.layerParamsParsed.set(`${parent.params}.${layerRule.params}`, [`${parent.params}.${layerRule.params}`]); + + layerRule.params = `${parent.params}.${layerRule.params}`; + + parent.before(layerRule); + if (parent.nodes.length === 0) { + parent.remove(); + } + + return; + } + + if (layerRule.parent.type === 'atrule') { + const parent = layerRule.parent as AtRule; + const parentClone = parent.clone(); + const layerRuleClone = layerRule.clone(); + + parentClone.removeAll(); + + layerRuleClone.removeAll(); + parentClone.append(layerRule.nodes); + + layerRuleClone.append(parentClone); + parent.before(layerRuleClone); + + layerRule.remove(); + if (parent.nodes.length === 0) { + parent.remove(); + } + return; + } + + foundUnexpectedLayerNesting = true; + }); + + if (foundUnexpectedLayerNesting) { + break; + } + } +} diff --git a/plugins/postcss-cascade-layers/src/has-layer-atrule-ancestor.ts b/plugins/postcss-cascade-layers/src/has-layer-atrule-ancestor.ts new file mode 100644 index 000000000..2a2af6027 --- /dev/null +++ b/plugins/postcss-cascade-layers/src/has-layer-atrule-ancestor.ts @@ -0,0 +1,20 @@ +import type { AtRule, Node } from 'postcss'; + +// Returns the first ancestor of the current node that is an @layer rule. +export function getLayerAtRuleAncestor(node: Node): AtRule | null { + let parent = node.parent; + while (parent) { + if (parent.type !== 'atrule') { + parent = parent.parent; + continue; + } + + if ((parent as AtRule).name === 'layer') { + return parent as AtRule; + } + + parent = parent.parent; + } + + return null; +} diff --git a/plugins/postcss-cascade-layers/src/index.ts b/plugins/postcss-cascade-layers/src/index.ts index ad493d7b2..a8e205ab8 100644 --- a/plugins/postcss-cascade-layers/src/index.ts +++ b/plugins/postcss-cascade-layers/src/index.ts @@ -1,28 +1,98 @@ -import { Container, AtRule, Node, PluginCreator, decl, Declaration, Rule } from 'postcss'; +import type { Container, AtRule, Node, PluginCreator } from 'postcss'; +import selectorParser from 'postcss-selector-parser'; +import { adjustSelectorSpecificity } from './adjust-selector-specificity'; +import { desugarNestedLayers } from './desugar-nested-layers'; +import { getLayerAtRuleAncestor } from './has-layer-atrule-ancestor'; +import { Model } from './model'; +import { someInTree } from './some-in-tree'; +import { selectorSpecificity } from './specificity'; + const creator: PluginCreator = () => { return { postcssPlugin: 'postcss-cascade-layers', Once(root: Container) { - let layerCount = 0; - const layerOrder = {}; + const model = new Model(); + + // - parse layer names + // - rename anon layers + // - handle empty layers + root.walkAtRules('layer', (layerRule) => { + if (layerRule.params) { + const layerNameList: Array = []; + let isInvalidLayerName = false; + + selectorParser().astSync(layerRule.params).each((selector) => { + const currentLayerNameParts: Array = []; + + selector.walk((node) => { + switch (node.type) { + case 'class': + currentLayerNameParts.push(node.value); + break; + case 'tag': + currentLayerNameParts.push(node.value); + break; + default: + isInvalidLayerName = true; + break; + } + }); + + if (isInvalidLayerName) { + return; + } + + layerNameList.push(currentLayerNameParts.join('.')); + + model.addLayerNameParts(currentLayerNameParts); + }); + + model.addLayerParams(layerRule.params, layerNameList); + if (layerRule.nodes && layerNameList.length > 1) { + // If the layer is a container rule it can not have multiple layer names. + isInvalidLayerName = true; + } + + if (isInvalidLayerName) { + // Set invalid layers to "invalid-layer" + // We reset these later. + layerRule.name = 'invalid-layer'; + return; + } + + // handle empty layer at-rules. + if (!layerRule.nodes || layerRule.nodes.length === 0) { + layerNameList.forEach((name) => { + model.getLayerNameList(name).forEach((part) => { + if (model.layerOrder.has(part)) { + return; + } + + model.layerOrder.set(part, model.layerCount); + model.layerCount += 1; + }); + }); + + layerRule.remove(); + return; + } + } - // 1st walkthrough to rename anon layers and store state (no modification of layer styles) - root.walkAtRules('layer', (atRule) => { // give anonymous layers a name - if (!atRule.params) { - atRule.raws.afterName = ' '; - atRule.params = `anon${layerCount}`; + if (!layerRule.params) { + layerRule.raws.afterName = ' '; + layerRule.params = model.createAnonymousLayerName(); } let hasNestedLayers = false; let hasUnlayeredStyles = false; // check for where a layer has nested layers AND styles outside of those layers - atRule.each((node) => { - if (node.type == 'atrule') { + layerRule.each((node) => { + if (node.type === 'atrule' && node.name === 'layer') { hasNestedLayers = true; - } else if (node.type == 'rule') { + } else { hasUnlayeredStyles = true; } @@ -32,61 +102,196 @@ const creator: PluginCreator = () => { }); if (hasNestedLayers && hasUnlayeredStyles) { - // create new final layer via cloning, empty it - const implicitLayer = atRule.clone({ - params: `${atRule.params}-implicit`, + // create new final layer via cloning and keep only the styles + const implicitLayerName = model.createImplicitLayerName(layerRule.params); + const implicitLayer = layerRule.clone({ + params: implicitLayerName, }); implicitLayer.each((node) => { - node.remove(); + if (node.type === 'atrule' && node.name === 'layer') { + node.remove(); + } }); // insert new layer - atRule.append(implicitLayer); + layerRule.append(implicitLayer); - // go through the unlayered rules, clone, and delete from top level atRule - atRule.each((node) => { - if (node.type == 'atrule' && node.name === 'layer') { + // go through the unlayered rules and delete from top level atRule + layerRule.each((node) => { + if (node.type === 'atrule' && node.name === 'layer') { return; } - implicitLayer.append(node.clone()); node.remove(); }); } }); - root.walkAtRules('layer', (layer) => { - layerCount += 1; - layerOrder[layer.params] = layerCount; + // record layer order + root.walkAtRules('layer', (layerRule) => { + const currentLayerNameParts = model.getLayerParams(layerRule); + const fullLayerName = currentLayerNameParts.join('.'); + if (model.layerOrder.has(fullLayerName)) { + return; + } + + if (!model.layerParamsParsed.has(fullLayerName)) { + model.layerParamsParsed.set(fullLayerName, [fullLayerName]); + } + + if (!model.layerNameParts.has(fullLayerName)) { + model.layerNameParts.set(fullLayerName, [...currentLayerNameParts]); + } + + model.getLayerNameList(fullLayerName).forEach((name) => { + if (model.layerOrder.has(name)) { + return; + } + + model.layerOrder.set(name, model.layerCount); + model.layerCount += 1; + }); }); - if (!layerCount) { + if (!model.layerCount) { + // Reset "invalid-layer" at rules + root.walkAtRules('invalid-layer', (layerRule) => { + layerRule.name = 'layer'; + }); + // no layers, so nothing to transform. return; } - // 2nd walkthrough to transform unlayered styles - need highest specificity (layerCount) + // record selector specificity + let highestASpecificity = 0; + root.walkRules((rule) => { + rule.selectors.forEach((selector) => { + const specificity = selectorSpecificity(selectorParser().astSync(selector)); + highestASpecificity = Math.max(highestASpecificity, specificity.a + 1); + }); + }); + + + // transform unlayered styles - need highest specificity (layerCount) root.walkRules((rule) => { - if (hasLayerAtRuleAncestor(rule)) { + if (getLayerAtRuleAncestor(rule)) { + return; + } + + // Skip any at rules that do not contain regular declarations (@keyframes) + if (rule.parent && rule.parent.type === 'atrule' && (rule.parent as AtRule).name === 'keyframes') { return; } rule.selectors = rule.selectors.map((selector) => { - // Needs `postcss-selector-parser` to insert `:not()` before any pseudo elements like `::after` - // This is a side track and can be fixed later. - return `${selector}${generateNot(layerCount)}`; + return adjustSelectorSpecificity(selector, model.layerCount * highestASpecificity); }); }); - // 3rd walkthrough to transform layered styles: - // - move out styles from atRule, insert before: https://postcss.org/api/#container-insertbefore - // - delete empty atRule + // Sort layer names + model.sortLayerNames(); + + // Desugar nested layers + desugarNestedLayers(root, model); + + // Transform order of CSS + // - split selector rules from non-selector rules + // - sort non-selector rules + { + // Separate selector rules from other rules + root.walkAtRules('layer', (layerRule) => { + const withSelectorRules = layerRule.clone(); + const withoutSelectorRules = layerRule.clone(); + + withSelectorRules.walkAtRules((atRule) => { + if (atRule.name === 'keyframes') { + const parent = atRule.parent; + atRule.remove(); + if (parent.nodes.length === 0) { + parent.remove(); + } + + return; + } + + if (someInTree(atRule, (node) => { + return node.type === 'rule'; + })) { + return; + } + + const parent = atRule.parent; + atRule.remove(); + if (parent.nodes.length === 0) { + parent.remove(); + } + }); + + withoutSelectorRules.walkRules((rule) => { + if (rule.parent && rule.parent.type === 'atrule' && (rule.parent as AtRule).name === 'keyframes') { + return; + } + + const parent = rule.parent; + rule.remove(); + if (parent.nodes.length === 0) { + parent.remove(); + } + }); + + withSelectorRules.name = 'layer-with-selector-rules'; + + layerRule.replaceWith(withSelectorRules, withoutSelectorRules); + if (withSelectorRules.nodes.length === 0) { + withSelectorRules.remove(); + } + + if (withoutSelectorRules.nodes.length === 0) { + withoutSelectorRules.remove(); + } + }); + + // Sort layer nodes + model.sortRootNodes(root.nodes); + + // Reset "layer-with-selector-rules" at rules + root.walkAtRules('layer-with-selector-rules', (atRule) => { + atRule.name = 'layer'; + }); + } + + // transform layered styles: // - give selectors the specificity they need based on layerPriority state - root.walkAtRules('layer', (atRule) => { - // move out styles from atRule, insert before + root.walkRules((rule) => { + const layerForCurrentRule = getLayerAtRuleAncestor(rule); + if (!layerForCurrentRule) { + return; + } + + // Skip any at rules that do not contain regular declarations (@keyframes) + if (rule.parent && rule.parent.type === 'atrule' && (rule.parent as AtRule).name === 'keyframes') { + return; + } + + const fullLayerName = model.getLayerParams(layerForCurrentRule).join('.'); + rule.selectors = rule.selectors.map((selector) => { + return adjustSelectorSpecificity(selector, model.layerOrder.get(fullLayerName) * highestASpecificity); + }); + }); + + // Remove all @layer at-rules + // Contained styles are inserted before + while (someInTree(root, (node) => node.type === 'atrule' && node.name === 'layer')) { + root.walkAtRules('layer', (atRule) => { + atRule.replaceWith(atRule.nodes); + }); + } - atRule.replaceWith(new Rule({nodes: atRule.nodes, source: atRule.source, selector: `${generateNot(layerOrder[atRule.params])}`})); + // Reset "invalid-layer" at rules + root.walkAtRules('invalid-layer', (atRule) => { + atRule.name = 'layer'; }); }, }; @@ -95,30 +300,3 @@ const creator: PluginCreator = () => { creator.postcss = true; export default creator; - -function generateNot(specificity: number) { - let list = ''; - for (let i = 0; i < specificity; i++) { - list += '#\\#'; // something short but still very uncommon - } - - return `:not(${list})`; -} - -function hasLayerAtRuleAncestor(node: Node): boolean { - let parent = node.parent; - while (parent) { - if (parent.type !== 'atrule') { - parent = parent.parent; - continue; - } - - if ((parent as AtRule).name === 'layer') { - return true; - } - - parent = parent.parent; - } - - return false; -} diff --git a/plugins/postcss-cascade-layers/src/model.ts b/plugins/postcss-cascade-layers/src/model.ts new file mode 100644 index 000000000..0a6bd0c0c --- /dev/null +++ b/plugins/postcss-cascade-layers/src/model.ts @@ -0,0 +1,194 @@ +import type { AtRule, ChildNode, Node } from 'postcss'; + +export class Model { + anonymousLayerCount = 0; + layerCount = 0; + layerOrder: Map; + + // List of layer names extracted from @layer params. + // example : @layer A, B; -> ['A', 'B'] + // example : @layer A.B; -> ['A.B'] + layerParamsParsed: Map>; + + // List of sub layer name parts extracted from a single layer name. + // example : @layer A.B {}; -> ['A', 'B'] + layerNameParts: Map>; + + constructor() { + this.anonymousLayerCount = 0; + this.layerCount = 0; + this.layerOrder = new Map(); + this.layerParamsParsed = new Map(); + this.layerNameParts = new Map(); + } + + createAnonymousLayerName(): string { + const name = `anon${this.anonymousLayerCount}`; + this.addLayerNameParts(name); + this.layerParamsParsed.set(name, [name]); + + this.anonymousLayerCount++; + + return name; + } + + createImplicitLayerName(layerName: string): string { + const parts = this.layerNameParts.get(layerName); + const last = parts[parts.length - 1]; + const name = `${last}-implicit`; + + this.addLayerNameParts([...parts, name]); + this.layerParamsParsed.set(name, [name]); + + return name; + } + + addLayerParams(key: string, parts?: string): void + addLayerParams(key: string, parts: Array): void + addLayerParams(key: string, parts: Array | string | undefined): void { + if (!parts) { + this.layerParamsParsed.set(key, [key]); + return; + } + + if (typeof parts === 'string') { + this.layerParamsParsed.set(key, [parts]); + return; + } + this.layerParamsParsed.set(key, parts); + } + + addLayerNameParts(parts: string): void + addLayerNameParts(parts: Array): void + addLayerNameParts(parts: Array | string): void { + if (typeof parts === 'string') { + this.layerNameParts.set(parts, [parts]); + return; + } + this.layerNameParts.set(parts.join('.'), parts); + } + + getLayerParams(layer: AtRule): Array { + const params: Array = [...this.layerParamsParsed.get(layer.params)]; + + let parent: Node = layer.parent; + while (parent) { + if (parent.type !== 'atrule') { + parent = parent.parent; + continue; + } + + if ((parent as AtRule).name === 'layer') { + params.push(...this.layerParamsParsed.get((parent as AtRule).params)); + } + + parent = parent.parent; + } + + // Layer names were collected inside out, so order needs to be reversed. + params.reverse(); + + // Individual layers can also be specified as `@layer foo.bar {}`. + // Joining and splitting by "." ensures that we handle each sub layer. + return params.flatMap((param) => { + return this.layerNameParts.get(param); + }); + } + + getLayerNameList(layerName: string): Array { + const parts = this.layerNameParts.get(layerName); + const layerNameList = []; + for (let i = 0; i < parts.length; i++) { + const partialLayerName = parts.slice(0, i + 1).join('.'); + if (!this.layerParamsParsed.has(partialLayerName)) { + this.layerParamsParsed.set(partialLayerName, [partialLayerName]); + } + + if (!this.layerNameParts.has(partialLayerName)) { + this.layerNameParts.set(partialLayerName, parts.slice(0, i + 1)); + } + + layerNameList.push(parts.slice(0, i + 1).join('.')); + } + + return layerNameList; + } + + sortLayerNames() { + for (const [layerName, layerIndex] of this.layerOrder) { + const parts = this.layerNameParts.get(layerName); + + for (let i = 1; i < (parts.length); i++) { + const parentLayer = parts.slice(0, i).join('.'); + if (!this.layerOrder.has(parentLayer)) { + this.layerOrder.set(parentLayer, layerIndex); + } + } + } + + let layerOrderStructured = Array.from(this.layerOrder.entries()); + layerOrderStructured = layerOrderStructured.sort((a, b) => { + const aParts = this.layerNameParts.get(a[0]); + const bParts = this.layerNameParts.get(b[0]); + if (aParts[0] !== bParts[0]) { + return this.layerOrder.get(aParts[0]) - this.layerOrder.get(bParts[0]); + } + + const len = Math.max(aParts.length, bParts.length); + for (let i = 0; i < len; i++) { + const aPart = aParts[i]; + const bPart = bParts[i]; + if (aPart === bPart) { + continue; + } + + if (!aPart) { + return 1; + } + + if (!bPart) { + return -1; + } + + return this.layerOrder.get(aParts.slice(0, i).join('.')) - this.layerOrder.get(bParts.slice(0, i).join('.')); + } + }); + + this.layerOrder.clear(); + layerOrderStructured.forEach((pair, index) => { + this.layerOrder.set(pair[0], index); + }); + } + + // Sort root nodes to apply the preferred order by layer priority for non-selector rules. + // Selector rules are adjusted by specificity. + sortRootNodes(rootNodes: Array) { + rootNodes.sort((a, b) => { + const aIsCharset = a.type === 'atrule' && a.name === 'charset'; + const bIsCharset = b.type === 'atrule' && b.name === 'charset'; + if (aIsCharset && bIsCharset) { + return 0; + } else if (aIsCharset !== bIsCharset) { + return aIsCharset ? -1 : 1; + } + + const aIsImport = a.type === 'atrule' && a.name === 'import'; + const bIsImport = b.type === 'atrule' && b.name === 'import'; + if (aIsImport && bIsImport) { + return 0; + } else if (aIsImport !== bIsImport) { + return aIsImport ? -1 : 1; + } + + const aIsLayer = a.type === 'atrule' && a.name === 'layer'; + const bIsLayer = b.type === 'atrule' && b.name === 'layer'; + if (aIsLayer && bIsLayer) { + return this.layerOrder.get(a.params) - this.layerOrder.get(b.params); + } else if (aIsLayer !== bIsLayer) { + return aIsLayer ? -1 : 1; + } + + return 0; + }); + } +} diff --git a/plugins/postcss-cascade-layers/src/some-in-tree.ts b/plugins/postcss-cascade-layers/src/some-in-tree.ts new file mode 100644 index 000000000..8a4e430a1 --- /dev/null +++ b/plugins/postcss-cascade-layers/src/some-in-tree.ts @@ -0,0 +1,14 @@ +import type { Container, ChildNode } from 'postcss'; + +// Walks the container node and returns true when any node passes the condition. +export function someInTree(container: Container, predicate: (node: ChildNode) => boolean): boolean { + let found = false; + container.walk((node) => { + if (predicate(node)) { + found = true; + return false; + } + }); + + return found; +} diff --git a/plugins/postcss-cascade-layers/src/specificity.ts b/plugins/postcss-cascade-layers/src/specificity.ts new file mode 100644 index 000000000..9ed2f377e --- /dev/null +++ b/plugins/postcss-cascade-layers/src/specificity.ts @@ -0,0 +1,111 @@ +import selectorParser from 'postcss-selector-parser'; + +export function selectorSpecificity(node) { + let a = 0; + let b = 0; + let c = 0; + + if (node.type == 'universal') { + return { + a: 0, + b: 0, + c: 0, + }; + } else if (node.type === 'id') { + a += 1; + } else if (node.type === 'tag') { + c += 1; + } else if (node.type === 'class') { + b += 1; + } else if (node.type === 'attribute') { + b += 1; + } else if (node.type === 'pseudo' && node.value.indexOf('::') === 0) { + c += 1; + } else if (node.type === 'pseudo') { + switch (node.value) { + case ':after': + case ':before': + c += 1; + break; + + case ':is': + case ':has': + case ':not': + { + if (node.nodes && node.nodes.length > 0) { + let mostSpecificListItem = { + a: 0, + b: 0, + c: 0, + }; + + node.nodes.forEach((child) => { + const itemSpecificity = selectorSpecificity(child); + if (itemSpecificity.a > mostSpecificListItem.a) { + mostSpecificListItem = itemSpecificity; + return; + } else if (itemSpecificity.a < mostSpecificListItem.a) { + return; + } + + if (itemSpecificity.b > mostSpecificListItem.b) { + mostSpecificListItem = itemSpecificity; + return; + } else if (itemSpecificity.b < mostSpecificListItem.b) { + return; + } + + if (itemSpecificity.c > mostSpecificListItem.c) { + mostSpecificListItem = itemSpecificity; + return; + } + }); + + a += mostSpecificListItem.a; + b += mostSpecificListItem.b; + c += mostSpecificListItem.c; + } + break; + } + + case 'where': + break; + + case ':nth-child': + case ':nth-last-child': + { + const ofSeparatorIndex = node.nodes.findIndex((x) => { + x.value === 'of'; + }); + + if (ofSeparatorIndex > -1) { + const ofSpecificity = selectorSpecificity(selectorParser.selector({ nodes: node.nodes.slice(ofSeparatorIndex + 1), value: '' })); + a += ofSpecificity.a; + b += ofSpecificity.b; + c += ofSpecificity.c; + } else { + a += a; + b += b; + c += c; + } + } + break; + + default: + b += 1; + } + } else if (node.nodes && node.nodes.length > 0) { + node.nodes.forEach((child) => { + const specificity = selectorSpecificity(child); + a += specificity.a; + b += specificity.b; + c += specificity.c; + }); + } + + return { + a, + b, + c, + }; +} diff --git a/plugins/postcss-cascade-layers/test/_browser.mjs b/plugins/postcss-cascade-layers/test/_browser.mjs index dc3a1c32c..1918458cb 100644 --- a/plugins/postcss-cascade-layers/test/_browser.mjs +++ b/plugins/postcss-cascade-layers/test/_browser.mjs @@ -29,20 +29,26 @@ import postcss from 'postcss'; +
  • important
  • +
  • keyframes override
  • +
  • media query
  • +
  • property override
  • +
  • layer vs. inline style
  • + `); break; case '/wpt/layer-basic.html': - res.setHeader('Content-type', 'text/html'); - res.writeHead(200); - res.end(await fsp.readFile('test/wpt/layer-basic.html', 'utf8')); - break; case '/wpt/layer-counter-style-override.html': + case '/wpt/layer-important.html': + case '/wpt/layer-keyframes-override.html': + case '/wpt/layer-media-query.html': + case '/wpt/layer-property-override.html': + case '/wpt/layer-vs-inline-style.html': res.setHeader('Content-type', 'text/html'); res.writeHead(200); - res.end(await fsp.readFile('test/wpt/layer-counter-style-override.html', 'utf8')); + res.end(await fsp.readFile('test' + pathname, 'utf8')); break; case '/test/styles.css': if (req.method === 'POST') { @@ -90,8 +96,16 @@ import postcss from 'postcss'; throw msg; }); - { - await page.goto('http://localhost:8080/wpt/layer-basic.html'); + for (const url of [ + 'wpt/layer-basic.html', + 'wpt/layer-counter-style-override.html', + 'wpt/layer-important.html', + 'wpt/layer-keyframes-override.html', + 'wpt/layer-media-query.html', + 'wpt/layer-property-override.html', + 'wpt/layer-vs-inline-style.html', + ]) { + await page.goto('http://localhost:8080/' + url); const result = await page.evaluate(async () => { // eslint-disable-next-line no-undef return await window.runTest(); @@ -101,18 +115,6 @@ import postcss from 'postcss'; } } - // TODO : uncomment - // { - // await page.goto('http://localhost:8080/wpt/layer-counter-style-override.html'); - // const result = await page.evaluate(async () => { - // // eslint-disable-next-line no-undef - // return await window.runTest(); - // }); - // if (!result) { - // throw new Error('Test failed, expected "window.runTest()" to return true'); - // } - // } - await browser.close(); await server.close(); diff --git a/plugins/postcss-cascade-layers/test/anon-layer.expect.css b/plugins/postcss-cascade-layers/test/anon-layer.expect.css index 0338108b8..9df0ab62e 100644 --- a/plugins/postcss-cascade-layers/test/anon-layer.expect.css +++ b/plugins/postcss-cascade-layers/test/anon-layer.expect.css @@ -1,26 +1,19 @@ -@layer A { + target { color: red; } -} -@layer B { - target { +target:not(#\#) { color: orange; } -} -@layer C { - target { +target:not(#\##\#) { color: yellow; } -} -@layer anon0 { - target { +target:not(#\##\##\#) { color: green; } -} target:not(#\##\##\##\#) { color: purple; diff --git a/plugins/postcss-cascade-layers/test/atrules.css b/plugins/postcss-cascade-layers/test/atrules.css new file mode 100644 index 000000000..69c270921 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/atrules.css @@ -0,0 +1,43 @@ +@layer framework, override; + +@layer override { + @keyframes slide-left { + from { translate: 0; } + to { translate: -100% 0; } + } +} + +@layer framework { + @keyframes slide-left { + from { margin-left: 0; } + to { margin-left: -100%; } + } +} + +#target { + animation: anim 1s paused; +} + +@keyframes anim { + from { background-color: green; } +} + +@layer { + @keyframes anim { + from { background-color: red; } + } +} + +#target::before { + content: counter(dont-care, custom-counter-style); +} + +@counter-style custom-counter-style { + system: extends four; +} + +@layer { + @counter-style custom-counter-style { + system: extends three; + } +} diff --git a/plugins/postcss-cascade-layers/test/atrules.expect.css b/plugins/postcss-cascade-layers/test/atrules.expect.css new file mode 100644 index 000000000..3feb546aa --- /dev/null +++ b/plugins/postcss-cascade-layers/test/atrules.expect.css @@ -0,0 +1,32 @@ + + @keyframes slide-left { + from { margin-left: 0; } + to { margin-left: -100%; } + }@keyframes slide-left { + from { translate: 0; } + to { translate: -100% 0; } + } + +@keyframes anim { + from { background-color: red; } + } + +@counter-style custom-counter-style { + system: extends three; + } + +#target:not(#\##\##\##\##\##\##\##\#) { + animation: anim 1s paused; +} + +@keyframes anim { + from { background-color: green; } +} + +#target:not(#\##\##\##\##\##\##\##\#)::before { + content: counter(dont-care, custom-counter-style); +} + +@counter-style custom-counter-style { + system: extends four; +} diff --git a/plugins/postcss-cascade-layers/test/basic.color.expect.css b/plugins/postcss-cascade-layers/test/basic.color.expect.css index 51a83a9c5..2f7e5f132 100644 --- a/plugins/postcss-cascade-layers/test/basic.color.expect.css +++ b/plugins/postcss-cascade-layers/test/basic.color.expect.css @@ -1,15 +1,12 @@ -@layer A { - target { + + #target { color: red; } -} -@layer anon0 { - target { +#foo #bar target:not(#\##\##\#)::before { color: green; } -} -target:not(#\##\#) { +target:not(#\##\##\##\##\##\#) { color: purple; } diff --git a/plugins/postcss-cascade-layers/test/basic.css b/plugins/postcss-cascade-layers/test/basic.css index f7ed69e5e..3aa2811c0 100644 --- a/plugins/postcss-cascade-layers/test/basic.css +++ b/plugins/postcss-cascade-layers/test/basic.css @@ -1,11 +1,11 @@ @layer A { - target { + #target { color: red; } } @layer { - target { + #foo #bar target::before { color: green; } } diff --git a/plugins/postcss-cascade-layers/test/basic.expect.css b/plugins/postcss-cascade-layers/test/basic.expect.css index 51a83a9c5..2f7e5f132 100644 --- a/plugins/postcss-cascade-layers/test/basic.expect.css +++ b/plugins/postcss-cascade-layers/test/basic.expect.css @@ -1,15 +1,12 @@ -@layer A { - target { + + #target { color: red; } -} -@layer anon0 { - target { +#foo #bar target:not(#\##\##\#)::before { color: green; } -} -target:not(#\##\#) { +target:not(#\##\##\##\##\##\#) { color: purple; } diff --git a/plugins/postcss-cascade-layers/test/examples/example.css b/plugins/postcss-cascade-layers/test/examples/example.css index 217b8c85b..0460543e9 100644 --- a/plugins/postcss-cascade-layers/test/examples/example.css +++ b/plugins/postcss-cascade-layers/test/examples/example.css @@ -1,9 +1,11 @@ + +target { + color: purple; +} + @layer { target { color: green; } } -target { - color: purple; -} diff --git a/plugins/postcss-cascade-layers/test/examples/example.expect.css b/plugins/postcss-cascade-layers/test/examples/example.expect.css index 6cc824c2c..bbfb7d52f 100644 --- a/plugins/postcss-cascade-layers/test/examples/example.expect.css +++ b/plugins/postcss-cascade-layers/test/examples/example.expect.css @@ -1,9 +1,9 @@ -@layer anon0 { - target { - color: green; - } -} target:not(#\#) { color: purple; } + +target { + color: green; + } + diff --git a/plugins/postcss-cascade-layers/test/invalid-rules.css b/plugins/postcss-cascade-layers/test/invalid-rules.css new file mode 100644 index 000000000..9ba3b72ef --- /dev/null +++ b/plugins/postcss-cascade-layers/test/invalid-rules.css @@ -0,0 +1,9 @@ +@layer A . A; + +@layer A . B { + +} + +@layer A, B, C { + +} diff --git a/plugins/postcss-cascade-layers/test/invalid-rules.expect.css b/plugins/postcss-cascade-layers/test/invalid-rules.expect.css new file mode 100644 index 000000000..9ba3b72ef --- /dev/null +++ b/plugins/postcss-cascade-layers/test/invalid-rules.expect.css @@ -0,0 +1,9 @@ +@layer A . A; + +@layer A . B { + +} + +@layer A, B, C { + +} diff --git a/plugins/postcss-cascade-layers/test/nested.css b/plugins/postcss-cascade-layers/test/nested.css index c9b05e915..96f523292 100644 --- a/plugins/postcss-cascade-layers/test/nested.css +++ b/plugins/postcss-cascade-layers/test/nested.css @@ -42,6 +42,24 @@ color: red; background: black; } + + @layer Z { + target { + color: lime; + } + } + } + + @layer Z { + target { + color: yellow; + } + } +} + +@layer C.D { + target { + color: red; } @layer Z { diff --git a/plugins/postcss-cascade-layers/test/nested.expect.css b/plugins/postcss-cascade-layers/test/nested.expect.css index e39b54df1..17903dc02 100644 --- a/plugins/postcss-cascade-layers/test/nested.expect.css +++ b/plugins/postcss-cascade-layers/test/nested.expect.css @@ -1,53 +1,55 @@ -@layer A { - @layer Z { - target { - color: yellow; + @keyframes slide-left { + from { + margin-left: 0; } - }@layer A-implicit { + + to { + margin-left: -100%; + } + } + target { + color: yellow; + } + +target:not(#\#) { color: red; } - p { +p:not(#\#) { color: blue; } - i { +i:not(#\#) { color: red; } -} -} -@layer B { - @keyframes slide-left { - from { - margin-left: 0; - } - - to { - margin-left: -100%; +target:not(#\##\##\#) { + color: yellow; } - } - @layer Z { - target { +target:not(#\##\##\##\##\##\#) { color: yellow; } + +@media (prefers-color-scheme: dark) { + target:not(#\##\##\##\##\##\##\#) { + color: lime; + } } -} -@layer C { - @media (prefers-color-scheme: dark) { - h1 { +@media (prefers-color-scheme: dark) { + h1:not(#\##\##\##\##\##\##\##\#) { color: red; background: black; } } - @layer Z { - target { +target:not(#\##\##\##\##\##\##\##\##\#) { color: yellow; } + +target:not(#\##\##\##\##\##\##\##\##\##\#) { + color: red; } -} diff --git a/plugins/postcss-cascade-layers/test/unlayered-styles.expect.css b/plugins/postcss-cascade-layers/test/unlayered-styles.expect.css index 0e10d1a54..870f8ad83 100644 --- a/plugins/postcss-cascade-layers/test/unlayered-styles.expect.css +++ b/plugins/postcss-cascade-layers/test/unlayered-styles.expect.css @@ -1,14 +1,11 @@ -@layer A { + target { color: red; } -} -@layer anon0 { - target { +target:not(#\#) { color: green; } -} a:not(#\##\#) { color: purple; diff --git a/plugins/postcss-cascade-layers/test/wpt/layer-counter-style-override.html b/plugins/postcss-cascade-layers/test/wpt/layer-counter-style-override.html index 0a67d1e6d..e4157c53f 100644 --- a/plugins/postcss-cascade-layers/test/wpt/layer-counter-style-override.html +++ b/plugins/postcss-cascade-layers/test/wpt/layer-counter-style-override.html @@ -2,7 +2,7 @@ Resolving @counter-style name conflicts with cascade layers - + + +
    +
    + +
    + + diff --git a/plugins/postcss-cascade-layers/test/wpt/layer-media-query.html b/plugins/postcss-cascade-layers/test/wpt/layer-media-query.html new file mode 100644 index 000000000..a0def06a5 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/wpt/layer-media-query.html @@ -0,0 +1,155 @@ + + + + + CSS Cascade Layers: Media queries + + + + + + + + +
    + + + + diff --git a/plugins/postcss-cascade-layers/test/wpt/layer-property-override.html b/plugins/postcss-cascade-layers/test/wpt/layer-property-override.html new file mode 100644 index 000000000..8b5484961 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/wpt/layer-property-override.html @@ -0,0 +1,186 @@ + +Resolving @property name conflicts with cascade layers + + + + + + +
    +
    +
    + + diff --git a/plugins/postcss-cascade-layers/test/wpt/layer-vs-inline-style.html b/plugins/postcss-cascade-layers/test/wpt/layer-vs-inline-style.html new file mode 100644 index 000000000..ef8a5d673 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/wpt/layer-vs-inline-style.html @@ -0,0 +1,84 @@ + + + + + + + +
    +
    +
    + + diff --git a/rollup/presets/package-typescript.js b/rollup/presets/package-typescript.js index d765bf703..6e739ef65 100644 --- a/rollup/presets/package-typescript.js +++ b/rollup/presets/package-typescript.js @@ -21,7 +21,7 @@ export function packageTypescript() { extensions: ['.js', '.ts'], presets: packageBabelPreset, }), - terser(), + // terser(), ], }, ]; From af210faa2b42e5d4c44275ee9c26625401b94808 Mon Sep 17 00:00:00 2001 From: romainmenke Date: Sat, 23 Apr 2022 13:01:39 +0200 Subject: [PATCH 24/33] cleanup --- plugins/postcss-cascade-layers/package.json | 3 +-- rollup/presets/package-typescript.js | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/postcss-cascade-layers/package.json b/plugins/postcss-cascade-layers/package.json index 846cf9a65..9fd80d711 100644 --- a/plugins/postcss-cascade-layers/package.json +++ b/plugins/postcss-cascade-layers/package.json @@ -25,8 +25,7 @@ "dist" ], "dependencies": { - "postcss-selector-parser": "^6.0.10", - "postcss-value-parser": "^4.2.0" + "postcss-selector-parser": "^6.0.10" }, "peerDependencies": { "postcss": "^8.3" diff --git a/rollup/presets/package-typescript.js b/rollup/presets/package-typescript.js index 6e739ef65..d765bf703 100644 --- a/rollup/presets/package-typescript.js +++ b/rollup/presets/package-typescript.js @@ -21,7 +21,7 @@ export function packageTypescript() { extensions: ['.js', '.ts'], presets: packageBabelPreset, }), - // terser(), + terser(), ], }, ]; From 0f35db2b2069031a68e2a7acb303d6e83401a35d Mon Sep 17 00:00:00 2001 From: romainmenke Date: Sat, 23 Apr 2022 13:04:41 +0200 Subject: [PATCH 25/33] cleanup --- package-lock.json | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 77b161a0f..974060f2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5508,9 +5508,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.9.tgz", - "integrity": "sha512-UO3SgnZOVTwu4kyLR22UQ1xZh086RyNZppb7lLAKBFK8a32ttG5i87Y/P3+2bRSjZNyJ1B7hfFNo273tKe9YxQ==", + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -6740,6 +6740,9 @@ "name": "@csstools/postcss-cascade-layers", "version": "0.0.0", "license": "CC0-1.0", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, "engines": { "node": "^12 || ^14 || >=16" }, @@ -6959,6 +6962,7 @@ } }, "plugins/postcss-gradients-interpolation-method": { + "name": "@csstools/postcss-gradients-interpolation-method", "version": "1.0.1", "license": "CC0-1.0", "dependencies": { @@ -6995,6 +6999,7 @@ } }, "plugins/postcss-ic-unit": { + "name": "@csstools/postcss-ic-unit", "version": "1.0.0", "license": "CC0-1.0", "dependencies": { @@ -7118,6 +7123,7 @@ } }, "plugins/postcss-oklab-function": { + "name": "@csstools/postcss-oklab-function", "version": "1.0.2", "license": "CC0-1.0", "dependencies": { @@ -7202,6 +7208,7 @@ } }, "plugins/postcss-unset-value": { + "name": "@csstools/postcss-unset-value", "version": "1.0.0", "license": "CC0-1.0", "engines": { @@ -8415,7 +8422,9 @@ }, "@csstools/postcss-cascade-layers": { "version": "file:plugins/postcss-cascade-layers", - "requires": {} + "requires": { + "postcss-selector-parser": "^6.0.10" + } }, "@csstools/postcss-color-function": { "version": "file:plugins/postcss-color-function", @@ -11233,9 +11242,9 @@ } }, "postcss-selector-parser": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.9.tgz", - "integrity": "sha512-UO3SgnZOVTwu4kyLR22UQ1xZh086RyNZppb7lLAKBFK8a32ttG5i87Y/P3+2bRSjZNyJ1B7hfFNo273tKe9YxQ==", + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", "requires": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" From 7ae107cb8c5d7700db85024ab0b960a343b2cb34 Mon Sep 17 00:00:00 2001 From: romainmenke Date: Sat, 23 Apr 2022 13:10:48 +0200 Subject: [PATCH 26/33] cleanup --- ...as-layer-atrule-ancestor.ts => get-layer-atrule-ancestor.ts} | 0 plugins/postcss-cascade-layers/src/index.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename plugins/postcss-cascade-layers/src/{has-layer-atrule-ancestor.ts => get-layer-atrule-ancestor.ts} (100%) diff --git a/plugins/postcss-cascade-layers/src/has-layer-atrule-ancestor.ts b/plugins/postcss-cascade-layers/src/get-layer-atrule-ancestor.ts similarity index 100% rename from plugins/postcss-cascade-layers/src/has-layer-atrule-ancestor.ts rename to plugins/postcss-cascade-layers/src/get-layer-atrule-ancestor.ts diff --git a/plugins/postcss-cascade-layers/src/index.ts b/plugins/postcss-cascade-layers/src/index.ts index a8e205ab8..0ab3cd9e0 100644 --- a/plugins/postcss-cascade-layers/src/index.ts +++ b/plugins/postcss-cascade-layers/src/index.ts @@ -2,7 +2,7 @@ import type { Container, AtRule, Node, PluginCreator } from 'postcss'; import selectorParser from 'postcss-selector-parser'; import { adjustSelectorSpecificity } from './adjust-selector-specificity'; import { desugarNestedLayers } from './desugar-nested-layers'; -import { getLayerAtRuleAncestor } from './has-layer-atrule-ancestor'; +import { getLayerAtRuleAncestor } from './get-layer-atrule-ancestor'; import { Model } from './model'; import { someInTree } from './some-in-tree'; import { selectorSpecificity } from './specificity'; From c282abe9dd2c5de2b85c2600d6d9aa144df12ca3 Mon Sep 17 00:00:00 2001 From: romainmenke Date: Sun, 24 Apr 2022 16:48:06 +0200 Subject: [PATCH 27/33] cleanup --- plugins/postcss-cascade-layers/README.md | 7 +- .../src/compound-selector-order.ts | 4 +- .../postcss-cascade-layers/src/constants.ts | 2 + .../src/desugar-and-parse-layer-names.ts | 121 +++++++++ .../src/desugar-nested-layers.ts | 10 +- plugins/postcss-cascade-layers/src/index.ts | 234 ++---------------- plugins/postcss-cascade-layers/src/model.ts | 34 +-- .../src/record-layer-order.ts | 30 +++ .../src/some-in-tree.ts | 15 +- .../src/sort-root-nodes.ts | 94 +++++++ 10 files changed, 293 insertions(+), 258 deletions(-) create mode 100644 plugins/postcss-cascade-layers/src/constants.ts create mode 100644 plugins/postcss-cascade-layers/src/desugar-and-parse-layer-names.ts create mode 100644 plugins/postcss-cascade-layers/src/record-layer-order.ts create mode 100644 plugins/postcss-cascade-layers/src/sort-root-nodes.ts diff --git a/plugins/postcss-cascade-layers/README.md b/plugins/postcss-cascade-layers/README.md index d632f2934..af382ccce 100644 --- a/plugins/postcss-cascade-layers/README.md +++ b/plugins/postcss-cascade-layers/README.md @@ -23,13 +23,14 @@ target { /* becomes */ - target { - color: green; - } target:not(#\#) { color: purple; } +target { + color: green; + } + ``` ## Usage diff --git a/plugins/postcss-cascade-layers/src/compound-selector-order.ts b/plugins/postcss-cascade-layers/src/compound-selector-order.ts index 379f8f6fe..de8ba5744 100644 --- a/plugins/postcss-cascade-layers/src/compound-selector-order.ts +++ b/plugins/postcss-cascade-layers/src/compound-selector-order.ts @@ -64,11 +64,11 @@ export function sortCompoundSelector(node) { function selectorTypeOrder(selector, type) { if (selectorParser.isPseudoElement(selector)) { - return selectorTypeOrderIndex['pseudoElement']; + return selectorTypeOrderIndex.pseudoElement; } if (type === 'pseudo' && selector && selector.indexOf('::') === 0) { - return selectorTypeOrderIndex['pseudoElement']; + return selectorTypeOrderIndex.pseudoElement; } return selectorTypeOrderIndex[type]; } diff --git a/plugins/postcss-cascade-layers/src/constants.ts b/plugins/postcss-cascade-layers/src/constants.ts new file mode 100644 index 000000000..8cc386c8b --- /dev/null +++ b/plugins/postcss-cascade-layers/src/constants.ts @@ -0,0 +1,2 @@ +export const INVALID_LAYER_NAME = 'invalid-layer'; +export const WITH_SELECTORS_LAYER_NAME = 'layer-with-selector-rules'; diff --git a/plugins/postcss-cascade-layers/src/desugar-and-parse-layer-names.ts b/plugins/postcss-cascade-layers/src/desugar-and-parse-layer-names.ts new file mode 100644 index 000000000..476e0b764 --- /dev/null +++ b/plugins/postcss-cascade-layers/src/desugar-and-parse-layer-names.ts @@ -0,0 +1,121 @@ +import type { Container } from 'postcss'; +import type { Model } from './model'; +import selectorParser from 'postcss-selector-parser'; +import { INVALID_LAYER_NAME } from './constants'; + +export function desugarAndParseLayerNames(root: Container, model: Model) { + // - parse layer names + // - rename anon layers + // - handle empty layers + root.walkAtRules('layer', (layerRule) => { + if (layerRule.params) { + const layerNameList: Array = []; + let isInvalidLayerName = false; + + selectorParser().astSync(layerRule.params).each((selector) => { + const currentLayerNameParts: Array = []; + + selector.walk((node) => { + switch (node.type) { + case 'class': + currentLayerNameParts.push(node.value); + break; + case 'tag': + currentLayerNameParts.push(node.value); + break; + default: + isInvalidLayerName = true; + break; + } + }); + + if (isInvalidLayerName) { + return; + } + + layerNameList.push(currentLayerNameParts.join('.')); + + model.addLayerNameParts(currentLayerNameParts); + }); + + model.addLayerParams(layerRule.params, layerNameList); + + if (layerRule.nodes && layerNameList.length > 1) { + // If the layer is a container rule it can not have multiple layer names. + isInvalidLayerName = true; + } + + if (isInvalidLayerName) { + // Set invalid layers to "invalid-layer" + // We reset these later. + layerRule.name = INVALID_LAYER_NAME; + return; + } + + // handle empty layer at-rules. + if (!layerRule.nodes || layerRule.nodes.length === 0) { + layerNameList.forEach((name) => { + model.getLayerNameList(name).forEach((part) => { + if (model.layerOrder.has(part)) { + return; + } + + model.layerOrder.set(part, model.layerCount); + model.layerCount += 1; + }); + }); + + layerRule.remove(); + return; + } + } + + // give anonymous layers a name + if (!layerRule.params) { + layerRule.raws.afterName = ' '; + layerRule.params = model.createAnonymousLayerName(); + } + + let hasNestedLayers = false; + let hasUnlayeredStyles = false; + + // check for where a layer has nested layers AND styles outside of those layers + layerRule.each((node) => { + if (node.type === 'atrule' && node.name === 'layer') { + hasNestedLayers = true; + } else { + hasUnlayeredStyles = true; + } + + if (hasNestedLayers && hasUnlayeredStyles) { + return false; + } + }); + + if (hasNestedLayers && hasUnlayeredStyles) { + // create new final layer via cloning and keep only the styles + const implicitLayerName = model.createImplicitLayerName(layerRule.params); + const implicitLayer = layerRule.clone({ + params: implicitLayerName, + }); + + implicitLayer.each((node) => { + if (node.type === 'atrule' && node.name === 'layer') { + node.remove(); + } + }); + + // insert new layer + layerRule.append(implicitLayer); + + // go through the unlayered rules and delete from top level atRule + layerRule.each((node) => { + if (node.type === 'atrule' && node.name === 'layer') { + return; + } + + node.remove(); + }); + } + }); +} diff --git a/plugins/postcss-cascade-layers/src/desugar-nested-layers.ts b/plugins/postcss-cascade-layers/src/desugar-nested-layers.ts index 8f657691b..61ad9e6b6 100644 --- a/plugins/postcss-cascade-layers/src/desugar-nested-layers.ts +++ b/plugins/postcss-cascade-layers/src/desugar-nested-layers.ts @@ -1,11 +1,11 @@ import type { Container, AtRule, ChildNode } from 'postcss'; -import { Model } from './model'; -import { someInTree } from './some-in-tree'; +import type { Model } from './model'; +import { someAtRuleInTree } from './some-in-tree'; export function desugarNestedLayers(root: Container, model: Model) { - while (someInTree(root, (node) => { - return node.type === 'atrule' && node.nodes && someInTree(node, (nested) => { - return nested.type === 'atrule' && nested.name === 'layer'; + while (someAtRuleInTree(root, (node) => { + return node.nodes && someAtRuleInTree(node, (nested) => { + return nested.name === 'layer'; }); })) { let foundUnexpectedLayerNesting = false; diff --git a/plugins/postcss-cascade-layers/src/index.ts b/plugins/postcss-cascade-layers/src/index.ts index 0ab3cd9e0..e922415c0 100644 --- a/plugins/postcss-cascade-layers/src/index.ts +++ b/plugins/postcss-cascade-layers/src/index.ts @@ -1,11 +1,15 @@ -import type { Container, AtRule, Node, PluginCreator } from 'postcss'; import selectorParser from 'postcss-selector-parser'; +import type { Container, AtRule, PluginCreator } from 'postcss'; +import { Model } from './model'; import { adjustSelectorSpecificity } from './adjust-selector-specificity'; +import { desugarAndParseLayerNames } from './desugar-and-parse-layer-names'; import { desugarNestedLayers } from './desugar-nested-layers'; import { getLayerAtRuleAncestor } from './get-layer-atrule-ancestor'; -import { Model } from './model'; -import { someInTree } from './some-in-tree'; import { selectorSpecificity } from './specificity'; +import { someAtRuleInTree } from './some-in-tree'; +import { sortRootNodes } from './sort-root-nodes'; +import { recordLayerOrder } from './record-layer-order'; +import { INVALID_LAYER_NAME } from './constants'; const creator: PluginCreator = () => { return { @@ -13,150 +17,13 @@ const creator: PluginCreator = () => { Once(root: Container) { const model = new Model(); - // - parse layer names - // - rename anon layers - // - handle empty layers - root.walkAtRules('layer', (layerRule) => { - if (layerRule.params) { - const layerNameList: Array = []; - let isInvalidLayerName = false; - - selectorParser().astSync(layerRule.params).each((selector) => { - const currentLayerNameParts: Array = []; - - selector.walk((node) => { - switch (node.type) { - case 'class': - currentLayerNameParts.push(node.value); - break; - case 'tag': - currentLayerNameParts.push(node.value); - break; - default: - isInvalidLayerName = true; - break; - } - }); - - if (isInvalidLayerName) { - return; - } - - layerNameList.push(currentLayerNameParts.join('.')); - - model.addLayerNameParts(currentLayerNameParts); - }); - - model.addLayerParams(layerRule.params, layerNameList); - - if (layerRule.nodes && layerNameList.length > 1) { - // If the layer is a container rule it can not have multiple layer names. - isInvalidLayerName = true; - } - - if (isInvalidLayerName) { - // Set invalid layers to "invalid-layer" - // We reset these later. - layerRule.name = 'invalid-layer'; - return; - } - - // handle empty layer at-rules. - if (!layerRule.nodes || layerRule.nodes.length === 0) { - layerNameList.forEach((name) => { - model.getLayerNameList(name).forEach((part) => { - if (model.layerOrder.has(part)) { - return; - } - - model.layerOrder.set(part, model.layerCount); - model.layerCount += 1; - }); - }); - - layerRule.remove(); - return; - } - } - - // give anonymous layers a name - if (!layerRule.params) { - layerRule.raws.afterName = ' '; - layerRule.params = model.createAnonymousLayerName(); - } - - let hasNestedLayers = false; - let hasUnlayeredStyles = false; + desugarAndParseLayerNames(root, model); - // check for where a layer has nested layers AND styles outside of those layers - layerRule.each((node) => { - if (node.type === 'atrule' && node.name === 'layer') { - hasNestedLayers = true; - } else { - hasUnlayeredStyles = true; - } - - if (hasNestedLayers && hasUnlayeredStyles) { - return false; - } - }); - - if (hasNestedLayers && hasUnlayeredStyles) { - // create new final layer via cloning and keep only the styles - const implicitLayerName = model.createImplicitLayerName(layerRule.params); - const implicitLayer = layerRule.clone({ - params: implicitLayerName, - }); - - implicitLayer.each((node) => { - if (node.type === 'atrule' && node.name === 'layer') { - node.remove(); - } - }); - - // insert new layer - layerRule.append(implicitLayer); - - // go through the unlayered rules and delete from top level atRule - layerRule.each((node) => { - if (node.type === 'atrule' && node.name === 'layer') { - return; - } - - node.remove(); - }); - } - }); - - // record layer order - root.walkAtRules('layer', (layerRule) => { - const currentLayerNameParts = model.getLayerParams(layerRule); - const fullLayerName = currentLayerNameParts.join('.'); - if (model.layerOrder.has(fullLayerName)) { - return; - } - - if (!model.layerParamsParsed.has(fullLayerName)) { - model.layerParamsParsed.set(fullLayerName, [fullLayerName]); - } - - if (!model.layerNameParts.has(fullLayerName)) { - model.layerNameParts.set(fullLayerName, [...currentLayerNameParts]); - } - - model.getLayerNameList(fullLayerName).forEach((name) => { - if (model.layerOrder.has(name)) { - return; - } - - model.layerOrder.set(name, model.layerCount); - model.layerCount += 1; - }); - }); + recordLayerOrder(root, model); if (!model.layerCount) { // Reset "invalid-layer" at rules - root.walkAtRules('invalid-layer', (layerRule) => { + root.walkAtRules(INVALID_LAYER_NAME, (layerRule) => { layerRule.name = 'layer'; }); @@ -176,12 +43,12 @@ const creator: PluginCreator = () => { // transform unlayered styles - need highest specificity (layerCount) root.walkRules((rule) => { - if (getLayerAtRuleAncestor(rule)) { + // Skip any at rules that do not contain regular declarations (@keyframes) + if (rule.parent && rule.parent.type === 'atrule' && (rule.parent as AtRule).name === 'keyframes') { return; } - // Skip any at rules that do not contain regular declarations (@keyframes) - if (rule.parent && rule.parent.type === 'atrule' && (rule.parent as AtRule).name === 'keyframes') { + if (getLayerAtRuleAncestor(rule)) { return; } @@ -199,79 +66,18 @@ const creator: PluginCreator = () => { // Transform order of CSS // - split selector rules from non-selector rules // - sort non-selector rules - { - // Separate selector rules from other rules - root.walkAtRules('layer', (layerRule) => { - const withSelectorRules = layerRule.clone(); - const withoutSelectorRules = layerRule.clone(); - - withSelectorRules.walkAtRules((atRule) => { - if (atRule.name === 'keyframes') { - const parent = atRule.parent; - atRule.remove(); - if (parent.nodes.length === 0) { - parent.remove(); - } - - return; - } - - if (someInTree(atRule, (node) => { - return node.type === 'rule'; - })) { - return; - } - - const parent = atRule.parent; - atRule.remove(); - if (parent.nodes.length === 0) { - parent.remove(); - } - }); - - withoutSelectorRules.walkRules((rule) => { - if (rule.parent && rule.parent.type === 'atrule' && (rule.parent as AtRule).name === 'keyframes') { - return; - } - - const parent = rule.parent; - rule.remove(); - if (parent.nodes.length === 0) { - parent.remove(); - } - }); - - withSelectorRules.name = 'layer-with-selector-rules'; - - layerRule.replaceWith(withSelectorRules, withoutSelectorRules); - if (withSelectorRules.nodes.length === 0) { - withSelectorRules.remove(); - } - - if (withoutSelectorRules.nodes.length === 0) { - withoutSelectorRules.remove(); - } - }); - - // Sort layer nodes - model.sortRootNodes(root.nodes); - - // Reset "layer-with-selector-rules" at rules - root.walkAtRules('layer-with-selector-rules', (atRule) => { - atRule.name = 'layer'; - }); - } + sortRootNodes(root, model); // transform layered styles: // - give selectors the specificity they need based on layerPriority state root.walkRules((rule) => { - const layerForCurrentRule = getLayerAtRuleAncestor(rule); - if (!layerForCurrentRule) { + // Skip any at rules that do not contain regular declarations (@keyframes) + if (rule.parent && rule.parent.type === 'atrule' && (rule.parent as AtRule).name === 'keyframes') { return; } - // Skip any at rules that do not contain regular declarations (@keyframes) - if (rule.parent && rule.parent.type === 'atrule' && (rule.parent as AtRule).name === 'keyframes') { + const layerForCurrentRule = getLayerAtRuleAncestor(rule); + if (!layerForCurrentRule) { return; } @@ -283,14 +89,14 @@ const creator: PluginCreator = () => { // Remove all @layer at-rules // Contained styles are inserted before - while (someInTree(root, (node) => node.type === 'atrule' && node.name === 'layer')) { + while (someAtRuleInTree(root, (node) => node.name === 'layer')) { root.walkAtRules('layer', (atRule) => { atRule.replaceWith(atRule.nodes); }); } // Reset "invalid-layer" at rules - root.walkAtRules('invalid-layer', (atRule) => { + root.walkAtRules(INVALID_LAYER_NAME, (atRule) => { atRule.name = 'layer'; }); }, diff --git a/plugins/postcss-cascade-layers/src/model.ts b/plugins/postcss-cascade-layers/src/model.ts index 0a6bd0c0c..14a2661d3 100644 --- a/plugins/postcss-cascade-layers/src/model.ts +++ b/plugins/postcss-cascade-layers/src/model.ts @@ -1,4 +1,4 @@ -import type { AtRule, ChildNode, Node } from 'postcss'; +import type { AtRule, Node } from 'postcss'; export class Model { anonymousLayerCount = 0; @@ -159,36 +159,4 @@ export class Model { this.layerOrder.set(pair[0], index); }); } - - // Sort root nodes to apply the preferred order by layer priority for non-selector rules. - // Selector rules are adjusted by specificity. - sortRootNodes(rootNodes: Array) { - rootNodes.sort((a, b) => { - const aIsCharset = a.type === 'atrule' && a.name === 'charset'; - const bIsCharset = b.type === 'atrule' && b.name === 'charset'; - if (aIsCharset && bIsCharset) { - return 0; - } else if (aIsCharset !== bIsCharset) { - return aIsCharset ? -1 : 1; - } - - const aIsImport = a.type === 'atrule' && a.name === 'import'; - const bIsImport = b.type === 'atrule' && b.name === 'import'; - if (aIsImport && bIsImport) { - return 0; - } else if (aIsImport !== bIsImport) { - return aIsImport ? -1 : 1; - } - - const aIsLayer = a.type === 'atrule' && a.name === 'layer'; - const bIsLayer = b.type === 'atrule' && b.name === 'layer'; - if (aIsLayer && bIsLayer) { - return this.layerOrder.get(a.params) - this.layerOrder.get(b.params); - } else if (aIsLayer !== bIsLayer) { - return aIsLayer ? -1 : 1; - } - - return 0; - }); - } } diff --git a/plugins/postcss-cascade-layers/src/record-layer-order.ts b/plugins/postcss-cascade-layers/src/record-layer-order.ts new file mode 100644 index 000000000..765029c56 --- /dev/null +++ b/plugins/postcss-cascade-layers/src/record-layer-order.ts @@ -0,0 +1,30 @@ +import type { Container } from 'postcss'; +import type { Model } from './model'; + +export function recordLayerOrder(root: Container, model: Model) { + // record layer order + root.walkAtRules('layer', (layerRule) => { + const currentLayerNameParts = model.getLayerParams(layerRule); + const fullLayerName = currentLayerNameParts.join('.'); + if (model.layerOrder.has(fullLayerName)) { + return; + } + + if (!model.layerParamsParsed.has(fullLayerName)) { + model.layerParamsParsed.set(fullLayerName, [fullLayerName]); + } + + if (!model.layerNameParts.has(fullLayerName)) { + model.layerNameParts.set(fullLayerName, [...currentLayerNameParts]); + } + + model.getLayerNameList(fullLayerName).forEach((name) => { + if (model.layerOrder.has(name)) { + return; + } + + model.layerOrder.set(name, model.layerCount); + model.layerCount += 1; + }); + }); +} diff --git a/plugins/postcss-cascade-layers/src/some-in-tree.ts b/plugins/postcss-cascade-layers/src/some-in-tree.ts index 8a4e430a1..195235455 100644 --- a/plugins/postcss-cascade-layers/src/some-in-tree.ts +++ b/plugins/postcss-cascade-layers/src/some-in-tree.ts @@ -1,4 +1,4 @@ -import type { Container, ChildNode } from 'postcss'; +import type { Container, ChildNode, AtRule } from 'postcss'; // Walks the container node and returns true when any node passes the condition. export function someInTree(container: Container, predicate: (node: ChildNode) => boolean): boolean { @@ -12,3 +12,16 @@ export function someInTree(container: Container, predicate: (node: ChildNode) => return found; } + +// Walks all at rules in the container node and returns true when any node passes the condition. +export function someAtRuleInTree(container: Container, predicate: (node: AtRule) => boolean): boolean { + let found = false; + container.walkAtRules((node) => { + if (predicate(node)) { + found = true; + return false; + } + }); + + return found; +} diff --git a/plugins/postcss-cascade-layers/src/sort-root-nodes.ts b/plugins/postcss-cascade-layers/src/sort-root-nodes.ts new file mode 100644 index 000000000..5b7790135 --- /dev/null +++ b/plugins/postcss-cascade-layers/src/sort-root-nodes.ts @@ -0,0 +1,94 @@ +import type { AtRule, Container } from 'postcss'; +import type { Model } from './model'; +import { WITH_SELECTORS_LAYER_NAME } from './constants'; +import { someInTree } from './some-in-tree'; + +// 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('layer', (layerRule) => { + const withSelectorRules = layerRule.clone(); + const withoutSelectorRules = layerRule.clone(); + + withSelectorRules.walkAtRules((atRule) => { + if (atRule.name === 'keyframes') { + const parent = atRule.parent; + atRule.remove(); + if (parent.nodes.length === 0) { + parent.remove(); + } + + return; + } + + if (someInTree(atRule, (node) => { + return node.type === 'rule'; + })) { + return; + } + + const parent = atRule.parent; + atRule.remove(); + if (parent.nodes.length === 0) { + parent.remove(); + } + }); + + withoutSelectorRules.walkRules((rule) => { + if (rule.parent && rule.parent.type === 'atrule' && (rule.parent as AtRule).name === 'keyframes') { + return; + } + + const parent = rule.parent; + rule.remove(); + if (parent.nodes.length === 0) { + parent.remove(); + } + }); + + withSelectorRules.name = WITH_SELECTORS_LAYER_NAME; + + layerRule.replaceWith(withSelectorRules, withoutSelectorRules); + if (withSelectorRules.nodes.length === 0) { + withSelectorRules.remove(); + } + + if (withoutSelectorRules.nodes.length === 0) { + withoutSelectorRules.remove(); + } + }); + + root.nodes.sort((a, b) => { + const aIsCharset = a.type === 'atrule' && a.name === 'charset'; + const bIsCharset = b.type === 'atrule' && b.name === 'charset'; + if (aIsCharset && bIsCharset) { + return 0; + } else if (aIsCharset !== bIsCharset) { + return aIsCharset ? -1 : 1; + } + + const aIsImport = a.type === 'atrule' && a.name === 'import'; + const bIsImport = b.type === 'atrule' && b.name === 'import'; + if (aIsImport && bIsImport) { + return 0; + } else if (aIsImport !== bIsImport) { + return aIsImport ? -1 : 1; + } + + const aIsLayer = a.type === 'atrule' && a.name === 'layer'; + const bIsLayer = b.type === 'atrule' && b.name === 'layer'; + if (aIsLayer && bIsLayer) { + return model.layerOrder.get(a.params) - model.layerOrder.get(b.params); + } else if (aIsLayer !== bIsLayer) { + return aIsLayer ? -1 : 1; + } + + return 0; + }); + + // Reset "layer-with-selector-rules" at rules + root.walkAtRules(WITH_SELECTORS_LAYER_NAME, (atRule) => { + atRule.name = 'layer'; + }); +} From e068bfafa429e1ef07f87f54e1af78388f36f853 Mon Sep 17 00:00:00 2001 From: romainmenke Date: Tue, 26 Apr 2022 20:12:41 +0200 Subject: [PATCH 28/33] cleanup --- package-lock.json | 24 ++++ plugins/postcss-cascade-layers/package.json | 1 + .../postcss-cascade-layers/src/constants.ts | 6 +- plugins/postcss-cascade-layers/src/index.ts | 2 +- .../postcss-cascade-layers/src/specificity.ts | 111 ------------------ 5 files changed, 30 insertions(+), 114 deletions(-) delete mode 100644 plugins/postcss-cascade-layers/src/specificity.ts diff --git a/package-lock.json b/package-lock.json index 974060f2b..4146dc827 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1818,6 +1818,22 @@ "resolved": "plugins/postcss-unset-value", "link": true }, + "node_modules/@csstools/selector-specificity": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-1.0.0.tgz", + "integrity": "sha512-RkYG5KiGNX0fJ5YoI0f4Wfq2Yo74D25Hru4fxTOioYdQvHBxcrrtTTyT5Ozzh2ejcNrhFy7IEts2WyEY7yi5yw==", + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.3", + "postcss-selector-parser": "^6.0.10" + } + }, "node_modules/@eslint/eslintrc": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz", @@ -6741,6 +6757,7 @@ "version": "0.0.0", "license": "CC0-1.0", "dependencies": { + "@csstools/selector-specificity": "^1.0.0", "postcss-selector-parser": "^6.0.10" }, "engines": { @@ -8423,6 +8440,7 @@ "@csstools/postcss-cascade-layers": { "version": "file:plugins/postcss-cascade-layers", "requires": { + "@csstools/selector-specificity": "^1.0.0", "postcss-selector-parser": "^6.0.10" } }, @@ -8497,6 +8515,12 @@ "version": "file:plugins/postcss-unset-value", "requires": {} }, + "@csstools/selector-specificity": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-1.0.0.tgz", + "integrity": "sha512-RkYG5KiGNX0fJ5YoI0f4Wfq2Yo74D25Hru4fxTOioYdQvHBxcrrtTTyT5Ozzh2ejcNrhFy7IEts2WyEY7yi5yw==", + "requires": {} + }, "@eslint/eslintrc": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz", diff --git a/plugins/postcss-cascade-layers/package.json b/plugins/postcss-cascade-layers/package.json index 9fd80d711..0a2a40462 100644 --- a/plugins/postcss-cascade-layers/package.json +++ b/plugins/postcss-cascade-layers/package.json @@ -25,6 +25,7 @@ "dist" ], "dependencies": { + "@csstools/selector-specificity": "^1.0.0", "postcss-selector-parser": "^6.0.10" }, "peerDependencies": { diff --git a/plugins/postcss-cascade-layers/src/constants.ts b/plugins/postcss-cascade-layers/src/constants.ts index 8cc386c8b..53aa656f8 100644 --- a/plugins/postcss-cascade-layers/src/constants.ts +++ b/plugins/postcss-cascade-layers/src/constants.ts @@ -1,2 +1,4 @@ -export const INVALID_LAYER_NAME = 'invalid-layer'; -export const WITH_SELECTORS_LAYER_NAME = 'layer-with-selector-rules'; +/** @constant {string} INVALID_LAYER_NAME Used to replace "layer" temporarily when an invalid layer is detected. This allows us to ignore this rule in further processing. */ +export const INVALID_LAYER_NAME = 'csstools-invalid-layer'; +/** @constant {string} WITH_SELECTORS_LAYER_NAME Used to replace "layer" temporarily for any layer at rules that contain selectors. This allows us to sort these differently from other layer at rules. */ +export const WITH_SELECTORS_LAYER_NAME = 'csstools-layer-with-selector-rules'; diff --git a/plugins/postcss-cascade-layers/src/index.ts b/plugins/postcss-cascade-layers/src/index.ts index e922415c0..4a74c4ef9 100644 --- a/plugins/postcss-cascade-layers/src/index.ts +++ b/plugins/postcss-cascade-layers/src/index.ts @@ -1,11 +1,11 @@ import selectorParser from 'postcss-selector-parser'; +import selectorSpecificity from '@csstools/selector-specificity'; import type { Container, AtRule, PluginCreator } from 'postcss'; import { Model } from './model'; import { adjustSelectorSpecificity } from './adjust-selector-specificity'; import { desugarAndParseLayerNames } from './desugar-and-parse-layer-names'; import { desugarNestedLayers } from './desugar-nested-layers'; import { getLayerAtRuleAncestor } from './get-layer-atrule-ancestor'; -import { selectorSpecificity } from './specificity'; import { someAtRuleInTree } from './some-in-tree'; import { sortRootNodes } from './sort-root-nodes'; import { recordLayerOrder } from './record-layer-order'; diff --git a/plugins/postcss-cascade-layers/src/specificity.ts b/plugins/postcss-cascade-layers/src/specificity.ts deleted file mode 100644 index 9ed2f377e..000000000 --- a/plugins/postcss-cascade-layers/src/specificity.ts +++ /dev/null @@ -1,111 +0,0 @@ -import selectorParser from 'postcss-selector-parser'; - -export function selectorSpecificity(node) { - let a = 0; - let b = 0; - let c = 0; - - if (node.type == 'universal') { - return { - a: 0, - b: 0, - c: 0, - }; - } else if (node.type === 'id') { - a += 1; - } else if (node.type === 'tag') { - c += 1; - } else if (node.type === 'class') { - b += 1; - } else if (node.type === 'attribute') { - b += 1; - } else if (node.type === 'pseudo' && node.value.indexOf('::') === 0) { - c += 1; - } else if (node.type === 'pseudo') { - switch (node.value) { - case ':after': - case ':before': - c += 1; - break; - - case ':is': - case ':has': - case ':not': - { - if (node.nodes && node.nodes.length > 0) { - let mostSpecificListItem = { - a: 0, - b: 0, - c: 0, - }; - - node.nodes.forEach((child) => { - const itemSpecificity = selectorSpecificity(child); - if (itemSpecificity.a > mostSpecificListItem.a) { - mostSpecificListItem = itemSpecificity; - return; - } else if (itemSpecificity.a < mostSpecificListItem.a) { - return; - } - - if (itemSpecificity.b > mostSpecificListItem.b) { - mostSpecificListItem = itemSpecificity; - return; - } else if (itemSpecificity.b < mostSpecificListItem.b) { - return; - } - - if (itemSpecificity.c > mostSpecificListItem.c) { - mostSpecificListItem = itemSpecificity; - return; - } - }); - - a += mostSpecificListItem.a; - b += mostSpecificListItem.b; - c += mostSpecificListItem.c; - } - break; - } - - case 'where': - break; - - case ':nth-child': - case ':nth-last-child': - { - const ofSeparatorIndex = node.nodes.findIndex((x) => { - x.value === 'of'; - }); - - if (ofSeparatorIndex > -1) { - const ofSpecificity = selectorSpecificity(selectorParser.selector({ nodes: node.nodes.slice(ofSeparatorIndex + 1), value: '' })); - a += ofSpecificity.a; - b += ofSpecificity.b; - c += ofSpecificity.c; - } else { - a += a; - b += b; - c += c; - } - } - break; - - default: - b += 1; - } - } else if (node.nodes && node.nodes.length > 0) { - node.nodes.forEach((child) => { - const specificity = selectorSpecificity(child); - a += specificity.a; - b += specificity.b; - c += specificity.c; - }); - } - - return { - a, - b, - c, - }; -} From 96a01ae51a813b2c15fb107b90181bf1e4632eed Mon Sep 17 00:00:00 2001 From: Romain Menke <11521496+romainmenke@users.noreply.github.com> Date: Thu, 28 Apr 2022 22:52:15 +0200 Subject: [PATCH 29/33] Apply suggestions from code review --- .../src/desugar-and-parse-layer-names.ts | 4 ++++ plugins/postcss-cascade-layers/src/desugar-nested-layers.ts | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/postcss-cascade-layers/src/desugar-and-parse-layer-names.ts b/plugins/postcss-cascade-layers/src/desugar-and-parse-layer-names.ts index 476e0b764..7d2518ccf 100644 --- a/plugins/postcss-cascade-layers/src/desugar-and-parse-layer-names.ts +++ b/plugins/postcss-cascade-layers/src/desugar-and-parse-layer-names.ts @@ -12,6 +12,10 @@ export function desugarAndParseLayerNames(root: Container, model: Model) { const layerNameList: Array = []; let isInvalidLayerName = false; + // Layer names ("A.B, C") have a similar syntax as selector lists with only class and tag name selectors. + // - comma separated + // - "." as a delimiter between idents. + // We can use "postcss-selector-parser" to analyse layer names so that we don't have to implement our own tokenizer and parser. selectorParser().astSync(layerRule.params).each((selector) => { const currentLayerNameParts: Array = []; diff --git a/plugins/postcss-cascade-layers/src/desugar-nested-layers.ts b/plugins/postcss-cascade-layers/src/desugar-nested-layers.ts index 61ad9e6b6..fa83588c4 100644 --- a/plugins/postcss-cascade-layers/src/desugar-nested-layers.ts +++ b/plugins/postcss-cascade-layers/src/desugar-nested-layers.ts @@ -18,7 +18,8 @@ export function desugarNestedLayers(root: Container, model: Model) { if (layerRule.parent.type === 'atrule' && (layerRule.parent as AtRule).name === 'layer') { const parent = layerRule.parent as AtRule; - model.layerNameParts.set(`${parent.params}.${layerRule.params}`, [...model.layerNameParts.get(parent.params), ...model.layerNameParts.get(layerRule.params)]); + // Concatenate the current layer params with those of the parent. Store the result in the data model. + model.layerNameParts.set(`${parent.params}.${layerRule.params}`, [...model.layerNameParts.get(parent.params), ...model.layerNameParts.get(layerRule.params)]); model.layerParamsParsed.set(`${parent.params}.${layerRule.params}`, [`${parent.params}.${layerRule.params}`]); layerRule.params = `${parent.params}.${layerRule.params}`; From c3cc5c8ffc23a68eaeb0f3fcdb5b4b0c8939d44b Mon Sep 17 00:00:00 2001 From: romainmenke Date: Thu, 28 Apr 2022 23:05:58 +0200 Subject: [PATCH 30/33] lint --- plugins/postcss-cascade-layers/src/desugar-nested-layers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/postcss-cascade-layers/src/desugar-nested-layers.ts b/plugins/postcss-cascade-layers/src/desugar-nested-layers.ts index fa83588c4..1d9072923 100644 --- a/plugins/postcss-cascade-layers/src/desugar-nested-layers.ts +++ b/plugins/postcss-cascade-layers/src/desugar-nested-layers.ts @@ -19,7 +19,7 @@ export function desugarNestedLayers(root: Container, model: Model) { const parent = layerRule.parent as AtRule; // Concatenate the current layer params with those of the parent. Store the result in the data model. - model.layerNameParts.set(`${parent.params}.${layerRule.params}`, [...model.layerNameParts.get(parent.params), ...model.layerNameParts.get(layerRule.params)]); + model.layerNameParts.set(`${parent.params}.${layerRule.params}`, [...model.layerNameParts.get(parent.params), ...model.layerNameParts.get(layerRule.params)]); model.layerParamsParsed.set(`${parent.params}.${layerRule.params}`, [`${parent.params}.${layerRule.params}`]); layerRule.params = `${parent.params}.${layerRule.params}`; From 6bdefb8478efd0b1f74f19b58eaa61986bd7683f Mon Sep 17 00:00:00 2001 From: Romain Menke <11521496+romainmenke@users.noreply.github.com> Date: Mon, 9 May 2022 18:41:41 +0200 Subject: [PATCH 31/33] cascade layers : important (#10) cascade layers : important and warnings --- plugins/postcss-cascade-layers/.tape.mjs | 14 ++++ plugins/postcss-cascade-layers/README.md | 56 +++++++++++++++ plugins/postcss-cascade-layers/docs/README.md | 56 +++++++++++++++ .../src/clean-blocks.ts | 30 ++++++++ .../postcss-cascade-layers/src/constants.ts | 12 ++++ .../src/desugar-and-parse-layer-names.ts | 69 ++++++++++--------- .../src/desugar-nested-layers.ts | 11 ++- .../src/get-conditional-atrule-ancestor.ts | 21 ++++++ plugins/postcss-cascade-layers/src/index.ts | 48 ++++++++++--- plugins/postcss-cascade-layers/src/model.ts | 7 +- plugins/postcss-cascade-layers/src/options.ts | 4 ++ .../src/record-layer-order.ts | 26 ++++++- .../src/sort-root-nodes.ts | 28 +++++--- .../src/split-important-styles.ts | 37 ++++++++++ .../postcss-cascade-layers/test/important.css | 36 ++++++++++ .../test/important.expect.css | 48 +++++++++++++ .../test/nested-complex.css | 63 +++++++++++++++++ .../test/nested-complex.expect.css | 69 +++++++++++++++++++ .../postcss-cascade-layers/test/nested.css | 12 ++-- .../test/nested.expect.css | 21 +++--- .../postcss-cascade-layers/test/warnings.css | 27 ++++++++ .../test/warnings.expect.css | 19 +++++ .../test/wpt/layer-important.html | 58 +++++++++++++--- 23 files changed, 685 insertions(+), 87 deletions(-) create mode 100644 plugins/postcss-cascade-layers/src/clean-blocks.ts create mode 100644 plugins/postcss-cascade-layers/src/get-conditional-atrule-ancestor.ts create mode 100644 plugins/postcss-cascade-layers/src/options.ts create mode 100644 plugins/postcss-cascade-layers/src/split-important-styles.ts create mode 100644 plugins/postcss-cascade-layers/test/important.css create mode 100644 plugins/postcss-cascade-layers/test/important.expect.css create mode 100644 plugins/postcss-cascade-layers/test/nested-complex.css create mode 100644 plugins/postcss-cascade-layers/test/nested-complex.expect.css create mode 100644 plugins/postcss-cascade-layers/test/warnings.css create mode 100644 plugins/postcss-cascade-layers/test/warnings.expect.css diff --git a/plugins/postcss-cascade-layers/.tape.mjs b/plugins/postcss-cascade-layers/.tape.mjs index 5d285eb40..d6ffc5837 100644 --- a/plugins/postcss-cascade-layers/.tape.mjs +++ b/plugins/postcss-cascade-layers/.tape.mjs @@ -17,6 +17,12 @@ postcssTape(plugin)({ nested: { message: "supports nested layer usage", }, + 'nested-complex': { + message: "supports nested layer usage", + }, + important: { + message: "supports important usage", + }, 'anon-layer': { message: "supports anonymous layer usage", }, @@ -29,4 +35,12 @@ postcssTape(plugin)({ 'invalid-rules': { message: 'correctly handles invalid rules', }, + 'warnings': { + message: 'correctly handles warnings', + options: { + onRevertLayerKeyword: 'warn', + onMixedLayerOrder: 'warn', + }, + warnings: 2, + } }); diff --git a/plugins/postcss-cascade-layers/README.md b/plugins/postcss-cascade-layers/README.md index af382ccce..1d9810b12 100644 --- a/plugins/postcss-cascade-layers/README.md +++ b/plugins/postcss-cascade-layers/README.md @@ -58,6 +58,62 @@ instructions for: | [Node](INSTALL.md#node) | [PostCSS CLI](INSTALL.md#postcss-cli) | [Webpack](INSTALL.md#webpack) | [Create React App](INSTALL.md#create-react-app) | [Gulp](INSTALL.md#gulp) | [Grunt](INSTALL.md#grunt) | | --- | --- | --- | --- | --- | --- | +## Options + +### onRevertLayerKeyword + +The `onRevertLayerKeyword` option enables warnings if `revert-layer` is used. +Transforming `revert-layer` for older browsers is not possible in this plugin. + +Defaults to `warn` + +```js +postcssCascadeLayers({ onRevertLayerKeyword: 'warn' }) // 'warn' | false +``` + +```pcss +/* [postcss-cascade-layers]: handling "revert-layer" is unsupported by this plugin and will cause style differences between browser versions. */ +@layer { + .foo { + color: revert-layer; + } +} +``` + +### onMixedLayerOrder + +The `onMixedLayerOrder` option enables warnings if layers are declared in multiple different orders in conditional rules. +Transforming these layers correctly for older browsers is not possible in this plugin. + +Defaults to `warn` + +```js +postcssCascadeLayers({ onMixedLayerOrder: 'warn' }) // 'warn' | false +``` + +```pcss +/* [postcss-cascade-layers]: handling different layer orders in conditional rules is unsupported by this plugin and will cause style differences between browser versions. */ +@media (min-width: 10px) { + @layer B { + .foo { + color: red; + } + } +} + +@layer A { + .foo { + color: pink; + } +} + +@layer B { + .foo { + color: red; + } +} +``` + [cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test diff --git a/plugins/postcss-cascade-layers/docs/README.md b/plugins/postcss-cascade-layers/docs/README.md index 3325b8725..843963b4a 100644 --- a/plugins/postcss-cascade-layers/docs/README.md +++ b/plugins/postcss-cascade-layers/docs/README.md @@ -28,6 +28,62 @@ +## Options + +### onRevertLayerKeyword + +The `onRevertLayerKeyword` option enables warnings if `revert-layer` is used. +Transforming `revert-layer` for older browsers is not possible in this plugin. + +Defaults to `warn` + +```js +({ onRevertLayerKeyword: 'warn' }) // 'warn' | false +``` + +```pcss +/* [postcss-cascade-layers]: handling "revert-layer" is unsupported by this plugin and will cause style differences between browser versions. */ +@layer { + .foo { + color: revert-layer; + } +} +``` + +### onMixedLayerOrder + +The `onMixedLayerOrder` option enables warnings if layers are declared in multiple different orders in conditional rules. +Transforming these layers correctly for older browsers is not possible in this plugin. + +Defaults to `warn` + +```js +({ onMixedLayerOrder: 'warn' }) // 'warn' | false +``` + +```pcss +/* [postcss-cascade-layers]: handling different layer orders in conditional rules is unsupported by this plugin and will cause style differences between browser versions. */ +@media (min-width: 10px) { + @layer B { + .foo { + color: red; + } + } +} + +@layer A { + .foo { + color: pink; + } +} + +@layer B { + .foo { + color: red; + } +} +``` + diff --git a/plugins/postcss-cascade-layers/src/clean-blocks.ts b/plugins/postcss-cascade-layers/src/clean-blocks.ts new file mode 100644 index 000000000..df72008bd --- /dev/null +++ b/plugins/postcss-cascade-layers/src/clean-blocks.ts @@ -0,0 +1,30 @@ +import type { ChildNode, Container, Document } from 'postcss'; +import { CONDITIONAL_ATRULES } from './constants'; + +export function removeEmptyDescendantBlocks(block: Container) { + block.walk((node) => { + if (node.type === 'rule' || (node.type === 'atrule' && ['layer', ...CONDITIONAL_ATRULES].includes(node.name))) { + if (!node.nodes || !node.nodes.length) { + node.remove(); + } + } + }); + + if (!block.nodes || !block.nodes.length) { + block.remove(); + } +} + +export function removeEmptyAncestorBlocks(block: Container) { + let currentNode: Document | Container = block; + + while (currentNode) { + if (currentNode.nodes && currentNode.nodes.length > 0) { + return; + } + + const parent = currentNode.parent; + currentNode.remove(); + currentNode = parent; + } +} diff --git a/plugins/postcss-cascade-layers/src/constants.ts b/plugins/postcss-cascade-layers/src/constants.ts index 53aa656f8..70cea3be7 100644 --- a/plugins/postcss-cascade-layers/src/constants.ts +++ b/plugins/postcss-cascade-layers/src/constants.ts @@ -2,3 +2,15 @@ export const INVALID_LAYER_NAME = 'csstools-invalid-layer'; /** @constant {string} WITH_SELECTORS_LAYER_NAME Used to replace "layer" temporarily for any layer at rules that contain selectors. This allows us to sort these differently from other layer at rules. */ export const WITH_SELECTORS_LAYER_NAME = 'csstools-layer-with-selector-rules'; + +export const ANONYMOUS_LAYER_SUFFIX = '6efdb677-bb05-44e5-840f-29d2175862fd'; +export const IMPLICIT_LAYER_SUFFIX = 'b147acf6-11a6-4338-a4d0-80aef4cd1a2f'; + +export const CONDITIONAL_ATRULES = [ + 'media', + 'supports', +]; + +export const ATRULES_WITH_NON_SELECTOR_BLOCK_LISTS = [ + 'keyframes', +]; diff --git a/plugins/postcss-cascade-layers/src/desugar-and-parse-layer-names.ts b/plugins/postcss-cascade-layers/src/desugar-and-parse-layer-names.ts index 7d2518ccf..6b254ac80 100644 --- a/plugins/postcss-cascade-layers/src/desugar-and-parse-layer-names.ts +++ b/plugins/postcss-cascade-layers/src/desugar-and-parse-layer-names.ts @@ -2,6 +2,9 @@ import type { Container } from 'postcss'; import type { Model } from './model'; import selectorParser from 'postcss-selector-parser'; import { 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'; export function desugarAndParseLayerNames(root: Container, model: Model) { // - parse layer names @@ -56,20 +59,23 @@ export function desugarAndParseLayerNames(root: Container, model: Model) { return; } - // handle empty layer at-rules. + // split empty layer at-rules. if (!layerRule.nodes || layerRule.nodes.length === 0) { - layerNameList.forEach((name) => { - model.getLayerNameList(name).forEach((part) => { - if (model.layerOrder.has(part)) { - return; - } - - model.layerOrder.set(part, model.layerCount); - model.layerCount += 1; + if (layerNameList.length <= 1) { + return; + } + + layerNameList.slice(0, -1).forEach((name) => { + model.addLayerParams(name, name); + + layerRule.cloneBefore({ + params: name, }); }); - layerRule.remove(); + model.addLayerParams(layerNameList[layerNameList.length - 1], layerNameList[layerNameList.length - 1]); + layerRule.params = layerNameList[layerNameList.length - 1]; + return; } } @@ -80,20 +86,14 @@ export function desugarAndParseLayerNames(root: Container, model: Model) { layerRule.params = model.createAnonymousLayerName(); } - let hasNestedLayers = false; - let hasUnlayeredStyles = false; - - // check for where a layer has nested layers AND styles outside of those layers - layerRule.each((node) => { - if (node.type === 'atrule' && node.name === 'layer') { - hasNestedLayers = true; - } else { - hasUnlayeredStyles = true; + const hasNestedLayers = someAtRuleInTree(layerRule, (node) => node.name === 'layer'); + const hasUnlayeredStyles = someInTree(layerRule, (node) => { + if (node.type !== 'rule') { + return; } - if (hasNestedLayers && hasUnlayeredStyles) { - return false; - } + const closestLayer = getLayerAtRuleAncestor(node); + return closestLayer === layerRule; }); if (hasNestedLayers && hasUnlayeredStyles) { @@ -103,8 +103,19 @@ export function desugarAndParseLayerNames(root: Container, model: Model) { params: implicitLayerName, }); - implicitLayer.each((node) => { - if (node.type === 'atrule' && node.name === 'layer') { + // only keep unlayered styles for the implicit layer. + implicitLayer.walkAtRules('layer', (node) => { + node.remove(); + }); + + // go through the unlayered rules and delete these from top level atRule + layerRule.walk((node) => { + if (node.type !== 'rule') { + return; + } + + const closestLayer = getLayerAtRuleAncestor(node); + if (closestLayer === layerRule) { node.remove(); } }); @@ -112,14 +123,8 @@ export function desugarAndParseLayerNames(root: Container, model: Model) { // insert new layer layerRule.append(implicitLayer); - // go through the unlayered rules and delete from top level atRule - layerRule.each((node) => { - if (node.type === 'atrule' && node.name === 'layer') { - return; - } - - node.remove(); - }); + removeEmptyDescendantBlocks(layerRule); + removeEmptyAncestorBlocks(layerRule); } }); } diff --git a/plugins/postcss-cascade-layers/src/desugar-nested-layers.ts b/plugins/postcss-cascade-layers/src/desugar-nested-layers.ts index 1d9072923..6c5518cd3 100644 --- a/plugins/postcss-cascade-layers/src/desugar-nested-layers.ts +++ b/plugins/postcss-cascade-layers/src/desugar-nested-layers.ts @@ -1,4 +1,5 @@ import type { Container, AtRule, ChildNode } from 'postcss'; +import { removeEmptyAncestorBlocks, removeEmptyDescendantBlocks } from './clean-blocks'; import type { Model } from './model'; import { someAtRuleInTree } from './some-in-tree'; @@ -25,9 +26,8 @@ export function desugarNestedLayers(root: Container, model: Model) { layerRule.params = `${parent.params}.${layerRule.params}`; parent.before(layerRule); - if (parent.nodes.length === 0) { - parent.remove(); - } + removeEmptyDescendantBlocks(parent); + removeEmptyAncestorBlocks(parent); return; } @@ -46,9 +46,8 @@ export function desugarNestedLayers(root: Container, model: Model) { parent.before(layerRuleClone); layerRule.remove(); - if (parent.nodes.length === 0) { - parent.remove(); - } + removeEmptyDescendantBlocks(parent); + removeEmptyAncestorBlocks(parent); return; } diff --git a/plugins/postcss-cascade-layers/src/get-conditional-atrule-ancestor.ts b/plugins/postcss-cascade-layers/src/get-conditional-atrule-ancestor.ts new file mode 100644 index 000000000..bd465378c --- /dev/null +++ b/plugins/postcss-cascade-layers/src/get-conditional-atrule-ancestor.ts @@ -0,0 +1,21 @@ +import type { AtRule, ChildNode, Document, Container } from 'postcss'; +import { CONDITIONAL_ATRULES } from './constants'; + +// Returns the first ancestor of the current layerRule that is a conditional rule; +export function getConditionalAtRuleAncestor(layerRule: AtRule): AtRule | null { + let parent: Container|Document = layerRule.parent; + while (parent) { + if (parent.type !== 'atrule') { + parent = parent.parent; + continue; + } + + if (CONDITIONAL_ATRULES.includes((parent as AtRule).name)) { + return parent as AtRule; + } + + parent = parent.parent; + } + + return null; +} diff --git a/plugins/postcss-cascade-layers/src/index.ts b/plugins/postcss-cascade-layers/src/index.ts index 4a74c4ef9..804104e2c 100644 --- a/plugins/postcss-cascade-layers/src/index.ts +++ b/plugins/postcss-cascade-layers/src/index.ts @@ -1,6 +1,6 @@ import selectorParser from 'postcss-selector-parser'; import selectorSpecificity from '@csstools/selector-specificity'; -import type { Container, AtRule, PluginCreator } from 'postcss'; +import type { Container, AtRule, PluginCreator, Result } from 'postcss'; import { Model } from './model'; import { adjustSelectorSpecificity } from './adjust-selector-specificity'; import { desugarAndParseLayerNames } from './desugar-and-parse-layer-names'; @@ -9,17 +9,36 @@ import { getLayerAtRuleAncestor } from './get-layer-atrule-ancestor'; import { someAtRuleInTree } from './some-in-tree'; import { sortRootNodes } from './sort-root-nodes'; import { recordLayerOrder } from './record-layer-order'; -import { INVALID_LAYER_NAME } from './constants'; +import { ATRULES_WITH_NON_SELECTOR_BLOCK_LISTS, INVALID_LAYER_NAME } from './constants'; +import { splitImportantStyles } from './split-important-styles'; +import { pluginOptions } from './options'; + +const creator: PluginCreator = (opts?: pluginOptions) => { + const options = Object.assign({ + onRevertLayerKeyword: 'warn', + onMixedLayerOrder: 'warn', + }, opts); -const creator: PluginCreator = () => { return { postcssPlugin: 'postcss-cascade-layers', - Once(root: Container) { + Once(root: Container, { result }: { result: Result }) { + + // Warnings + if (options.onRevertLayerKeyword) { + root.walkDecls((decl) => { + if (decl.value === 'revert-layer') { + decl.warn(result, 'handling "revert-layer" is unsupported by this plugin and will cause style differences between browser versions.'); + } + }); + } + + splitImportantStyles(root); + const model = new Model(); desugarAndParseLayerNames(root, model); - recordLayerOrder(root, model); + recordLayerOrder(root, model, { result, options }); if (!model.layerCount) { // Reset "invalid-layer" at rules @@ -44,7 +63,7 @@ const creator: PluginCreator = () => { // transform unlayered styles - need highest specificity (layerCount) root.walkRules((rule) => { // Skip any at rules that do not contain regular declarations (@keyframes) - if (rule.parent && rule.parent.type === 'atrule' && (rule.parent as AtRule).name === 'keyframes') { + if (rule.parent && rule.parent.type === 'atrule' && ATRULES_WITH_NON_SELECTOR_BLOCK_LISTS.includes((rule.parent as AtRule).name)) { return; } @@ -52,6 +71,12 @@ const creator: PluginCreator = () => { return; } + if (rule.some((decl) => decl.type === 'decl' && decl.important)) { + // !important declarations have inverse priority in layers + // doing nothing will give the lowest specificity + return; + } + rule.selectors = rule.selectors.map((selector) => { return adjustSelectorSpecificity(selector, model.layerCount * highestASpecificity); }); @@ -72,7 +97,7 @@ const creator: PluginCreator = () => { // - give selectors the specificity they need based on layerPriority state root.walkRules((rule) => { // Skip any at rules that do not contain regular declarations (@keyframes) - if (rule.parent && rule.parent.type === 'atrule' && (rule.parent as AtRule).name === 'keyframes') { + if (rule.parent && rule.parent.type === 'atrule' && ATRULES_WITH_NON_SELECTOR_BLOCK_LISTS.includes((rule.parent as AtRule).name)) { return; } @@ -82,8 +107,15 @@ const creator: PluginCreator = () => { } const fullLayerName = model.getLayerParams(layerForCurrentRule).join('.'); + + let specificityAdjustment = model.layerOrder.get(fullLayerName) * highestASpecificity; + if (rule.some((decl) => decl.type === 'decl' && decl.important)) { + // !important declarations have inverse priority in layers + specificityAdjustment = model.layerCount - specificityAdjustment; + } + rule.selectors = rule.selectors.map((selector) => { - return adjustSelectorSpecificity(selector, model.layerOrder.get(fullLayerName) * highestASpecificity); + return adjustSelectorSpecificity(selector, specificityAdjustment); }); }); diff --git a/plugins/postcss-cascade-layers/src/model.ts b/plugins/postcss-cascade-layers/src/model.ts index 14a2661d3..6f89b6f37 100644 --- a/plugins/postcss-cascade-layers/src/model.ts +++ b/plugins/postcss-cascade-layers/src/model.ts @@ -1,4 +1,5 @@ import type { AtRule, Node } from 'postcss'; +import { ANONYMOUS_LAYER_SUFFIX, IMPLICIT_LAYER_SUFFIX } from './constants'; export class Model { anonymousLayerCount = 0; @@ -23,7 +24,7 @@ export class Model { } createAnonymousLayerName(): string { - const name = `anon${this.anonymousLayerCount}`; + const name = `anonymous-${this.anonymousLayerCount}-${ANONYMOUS_LAYER_SUFFIX}`; this.addLayerNameParts(name); this.layerParamsParsed.set(name, [name]); @@ -35,7 +36,7 @@ export class Model { createImplicitLayerName(layerName: string): string { const parts = this.layerNameParts.get(layerName); const last = parts[parts.length - 1]; - const name = `${last}-implicit`; + const name = `implicit-${last}-${IMPLICIT_LAYER_SUFFIX}`; this.addLayerNameParts([...parts, name]); this.layerParamsParsed.set(name, [name]); @@ -88,8 +89,6 @@ export class Model { // Layer names were collected inside out, so order needs to be reversed. params.reverse(); - // Individual layers can also be specified as `@layer foo.bar {}`. - // Joining and splitting by "." ensures that we handle each sub layer. return params.flatMap((param) => { return this.layerNameParts.get(param); }); diff --git a/plugins/postcss-cascade-layers/src/options.ts b/plugins/postcss-cascade-layers/src/options.ts new file mode 100644 index 000000000..6dbf74339 --- /dev/null +++ b/plugins/postcss-cascade-layers/src/options.ts @@ -0,0 +1,4 @@ +export type pluginOptions = { + onRevertLayerKeyword: 'warn'|false, + onMixedLayerOrder: 'warn'|false, +} diff --git a/plugins/postcss-cascade-layers/src/record-layer-order.ts b/plugins/postcss-cascade-layers/src/record-layer-order.ts index 765029c56..14e56c06d 100644 --- a/plugins/postcss-cascade-layers/src/record-layer-order.ts +++ b/plugins/postcss-cascade-layers/src/record-layer-order.ts @@ -1,15 +1,32 @@ -import type { Container } from 'postcss'; +import type { Container, Result } from 'postcss'; +import { ANONYMOUS_LAYER_SUFFIX, IMPLICIT_LAYER_SUFFIX } from './constants'; +import { getConditionalAtRuleAncestor } from './get-conditional-atrule-ancestor'; import type { Model } from './model'; +import { pluginOptions } from './options'; -export function recordLayerOrder(root: Container, model: Model) { +export function recordLayerOrder(root: Container, model: Model, { result, options }: { result: Result, options: pluginOptions }) { // record layer order root.walkAtRules('layer', (layerRule) => { const currentLayerNameParts = model.getLayerParams(layerRule); const fullLayerName = currentLayerNameParts.join('.'); if (model.layerOrder.has(fullLayerName)) { + if (!layerRule.nodes || layerRule.nodes.length === 0) { + // Remove empty layers + layerRule.remove(); + } + return; } + if ( + options.onMixedLayerOrder && + getConditionalAtRuleAncestor(layerRule) && + !layerRule.params.endsWith(IMPLICIT_LAYER_SUFFIX) && + !layerRule.params.endsWith(ANONYMOUS_LAYER_SUFFIX) + ) { + layerRule.warn(result, 'handling different layer orders in conditional rules is unsupported by this plugin and will cause style differences between browser versions.'); + } + if (!model.layerParamsParsed.has(fullLayerName)) { model.layerParamsParsed.set(fullLayerName, [fullLayerName]); } @@ -26,5 +43,10 @@ export function recordLayerOrder(root: Container, model: Model) { model.layerOrder.set(name, model.layerCount); model.layerCount += 1; }); + + if (!layerRule.nodes || layerRule.nodes.length === 0) { + // Remove empty layers + layerRule.remove(); + } }); } diff --git a/plugins/postcss-cascade-layers/src/sort-root-nodes.ts b/plugins/postcss-cascade-layers/src/sort-root-nodes.ts index 5b7790135..9eac931a7 100644 --- a/plugins/postcss-cascade-layers/src/sort-root-nodes.ts +++ b/plugins/postcss-cascade-layers/src/sort-root-nodes.ts @@ -1,7 +1,8 @@ import type { AtRule, Container } from 'postcss'; import type { Model } from './model'; -import { WITH_SELECTORS_LAYER_NAME } from './constants'; +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'; // Sort root nodes to apply the preferred order by layer priority for non-selector rules. // Selector rules are adjusted by specificity. @@ -12,12 +13,11 @@ export function sortRootNodes(root: Container, model: Model) { const withoutSelectorRules = layerRule.clone(); withSelectorRules.walkAtRules((atRule) => { - if (atRule.name === 'keyframes') { + if (ATRULES_WITH_NON_SELECTOR_BLOCK_LISTS.includes(atRule.name)) { const parent = atRule.parent; atRule.remove(); - if (parent.nodes.length === 0) { - parent.remove(); - } + removeEmptyDescendantBlocks(parent); + removeEmptyAncestorBlocks(parent); return; } @@ -30,20 +30,26 @@ export function sortRootNodes(root: Container, model: Model) { const parent = atRule.parent; atRule.remove(); - if (parent.nodes.length === 0) { - parent.remove(); - } + removeEmptyDescendantBlocks(parent); + removeEmptyAncestorBlocks(parent); }); withoutSelectorRules.walkRules((rule) => { - if (rule.parent && rule.parent.type === 'atrule' && (rule.parent as AtRule).name === 'keyframes') { + if (rule.parent && rule.parent.type === 'atrule' && ATRULES_WITH_NON_SELECTOR_BLOCK_LISTS.includes((rule.parent as AtRule).name)) { return; } const parent = rule.parent; rule.remove(); - if (parent.nodes.length === 0) { - parent.remove(); + removeEmptyDescendantBlocks(parent); + removeEmptyAncestorBlocks(parent); + }); + + withoutSelectorRules.walkAtRules((atRule) => { + if (CONDITIONAL_ATRULES.includes(atRule.name)) { + removeEmptyDescendantBlocks(atRule); + removeEmptyAncestorBlocks(atRule); + return; } }); diff --git a/plugins/postcss-cascade-layers/src/split-important-styles.ts b/plugins/postcss-cascade-layers/src/split-important-styles.ts new file mode 100644 index 000000000..6c4223da9 --- /dev/null +++ b/plugins/postcss-cascade-layers/src/split-important-styles.ts @@ -0,0 +1,37 @@ +import type { AtRule, Container } from 'postcss'; +import { removeEmptyDescendantBlocks } from './clean-blocks'; +import { ATRULES_WITH_NON_SELECTOR_BLOCK_LISTS } from './constants'; + +// Declarations with !important have inverse priority in layers. +// Splitting rules allows us to assign different specificity to rules with or without !important declarations. +export function splitImportantStyles(root: Container) { + root.walkDecls((decl) => { + if (!decl.important) { + return; + } + + const parent = decl.parent; + if (parent.parent && parent.parent.type === 'atrule' && ATRULES_WITH_NON_SELECTOR_BLOCK_LISTS.includes((parent.parent as AtRule).name)) { + return; + } + + const parentClone = parent.clone(); + + parentClone.each((node) => { + if (node.type === 'decl' && node.important) { + return; + } + + node.remove(); + }); + + parent.each((node) => { + if (node.type === 'decl' && node.important) { + node.remove(); + } + }); + + parent.before(parentClone); + removeEmptyDescendantBlocks(parent); + }); +} diff --git a/plugins/postcss-cascade-layers/test/important.css b/plugins/postcss-cascade-layers/test/important.css new file mode 100644 index 000000000..a57898d17 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/important.css @@ -0,0 +1,36 @@ +@layer A, B; + +@layer B { + .foo { + background-color: red; + color: red !important; + something-else: red !important; + } + + .bar { + background-color: blue; + color: blue !important; + } +} + +@layer A { + .foo { + background-color: pink; + color: pink !important; + } + + .bar { + background-color: yellow; + color: green !important; + } +} + +.foo { + background-color: orange; + color: orange !important; +} + +.bar { + background-color: green; + color: purple !important; +} diff --git a/plugins/postcss-cascade-layers/test/important.expect.css b/plugins/postcss-cascade-layers/test/important.expect.css new file mode 100644 index 000000000..855669bfe --- /dev/null +++ b/plugins/postcss-cascade-layers/test/important.expect.css @@ -0,0 +1,48 @@ + + .foo:not(#\#) { + color: red !important; + something-else: red !important; + } + .foo:not(#\#) { + background-color: red; + } + + .bar:not(#\#) { + color: blue !important; + } + + .bar:not(#\#) { + background-color: blue; + } + +.foo:not(#\##\#) { + color: pink !important; + } + +.foo { + background-color: pink; + } + +.bar:not(#\##\#) { + color: green !important; + } + +.bar { + background-color: yellow; + } + +.foo { + color: orange !important; +} + +.foo:not(#\##\#) { + background-color: orange; +} + +.bar { + color: purple !important; +} + +.bar:not(#\##\#) { + background-color: green; +} diff --git a/plugins/postcss-cascade-layers/test/nested-complex.css b/plugins/postcss-cascade-layers/test/nested-complex.css new file mode 100644 index 000000000..8e87cff03 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/nested-complex.css @@ -0,0 +1,63 @@ +@layer D, E, F, F.G, F.G.H; + +@layer D { + target { + order: 1; + } + + @media screen { + target { + order: 2; + } + } +} + +@media screen { + target { + layered: no; + order: 3; + } + + @layer E { + target { + order: 4; + } + } +} + +@media screen { + target { + layered: no; + order: 5; + } + + @layer F { + target { + order: 6; + } + + @media screen { + target { + order: 7; + } + + @layer G { + target { + order: 8; + } + + @media screen { + target { + order: 9; + } + + @layer H { + target { + order: 10; + } + } + } + } + } + } +} diff --git a/plugins/postcss-cascade-layers/test/nested-complex.expect.css b/plugins/postcss-cascade-layers/test/nested-complex.expect.css new file mode 100644 index 000000000..a0baf1e11 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/nested-complex.expect.css @@ -0,0 +1,69 @@ + + target { + order: 1; + } + + @media screen { + target { + order: 2; + } + } + +@media screen { + target:not(#\#) { + order: 4; + } +} + +@media screen { + target:not(#\##\##\##\##\##\##\#) { + layered: no; + order: 3; + } +} + +@media screen { + target:not(#\##\##\##\##\#) { + order: 6; + } + + @media screen { + target:not(#\##\##\##\##\#) { + order: 7; + } + } +} + +@media screen { + + @media screen { + target:not(#\##\##\#) { + order: 8; + } + + @media screen { + target:not(#\##\##\#) { + order: 9; + } + } + } +} + +@media screen { + + @media screen { + + @media screen { + target:not(#\##\#) { + order: 10; + } + } + } +} + +@media screen { + target:not(#\##\##\##\##\##\##\#) { + layered: no; + order: 5; + } +} diff --git a/plugins/postcss-cascade-layers/test/nested.css b/plugins/postcss-cascade-layers/test/nested.css index 96f523292..b6363946b 100644 --- a/plugins/postcss-cascade-layers/test/nested.css +++ b/plugins/postcss-cascade-layers/test/nested.css @@ -37,6 +37,12 @@ } @layer C { + @layer Z { + target { + color: yellow; + } + } + @media (prefers-color-scheme: dark) { h1 { color: red; @@ -49,12 +55,6 @@ } } } - - @layer Z { - target { - color: yellow; - } - } } @layer C.D { diff --git a/plugins/postcss-cascade-layers/test/nested.expect.css b/plugins/postcss-cascade-layers/test/nested.expect.css index 17903dc02..108444454 100644 --- a/plugins/postcss-cascade-layers/test/nested.expect.css +++ b/plugins/postcss-cascade-layers/test/nested.expect.css @@ -9,6 +9,9 @@ } } +@keyframes slide-left { + } + target { color: yellow; } @@ -34,22 +37,22 @@ target:not(#\##\##\##\##\##\#) { } @media (prefers-color-scheme: dark) { - target:not(#\##\##\##\##\##\##\#) { - color: lime; - } - } - -@media (prefers-color-scheme: dark) { - h1:not(#\##\##\##\##\##\##\##\#) { + h1:not(#\##\##\##\##\##\##\#) { color: red; background: black; } } -target:not(#\##\##\##\##\##\##\##\##\#) { +@media (prefers-color-scheme: dark) { + target:not(#\##\##\##\##\##\#) { + color: lime; + } + } + +target:not(#\##\##\##\##\##\##\##\#) { color: yellow; } -target:not(#\##\##\##\##\##\##\##\##\##\#) { +target:not(#\##\##\##\##\##\##\##\##\#) { color: red; } diff --git a/plugins/postcss-cascade-layers/test/warnings.css b/plugins/postcss-cascade-layers/test/warnings.css new file mode 100644 index 000000000..18d588bc9 --- /dev/null +++ b/plugins/postcss-cascade-layers/test/warnings.css @@ -0,0 +1,27 @@ +/* [postcss-cascade-layers]: handling "revert-layer" is unsupported by this plugin and will cause style differences between browser versions. */ +@layer { + .foo { + color: revert-layer; + } +} + +/* [postcss-cascade-layers]: handling different layer orders in conditional rules is unsupported by this plugin and will cause style differences between browser versions. */ +@media (min-width: 10px) { + @layer B { + .foo { + color: red; + } + } +} + +@layer A { + .foo { + color: pink; + } +} + +@layer B { + .foo { + color: red; + } +} diff --git a/plugins/postcss-cascade-layers/test/warnings.expect.css b/plugins/postcss-cascade-layers/test/warnings.expect.css new file mode 100644 index 000000000..a07cf23bc --- /dev/null +++ b/plugins/postcss-cascade-layers/test/warnings.expect.css @@ -0,0 +1,19 @@ +/* [postcss-cascade-layers]: handling "revert-layer" is unsupported by this plugin and will cause style differences between browser versions. */ +.foo { + color: revert-layer; + } + +/* [postcss-cascade-layers]: handling different layer orders in conditional rules is unsupported by this plugin and will cause style differences between browser versions. */ +@media (min-width: 10px) { + .foo:not(#\#) { + color: red; + } +} + +.foo:not(#\##\#) { + color: pink; + } + +.foo:not(#\#) { + color: red; + } diff --git a/plugins/postcss-cascade-layers/test/wpt/layer-important.html b/plugins/postcss-cascade-layers/test/wpt/layer-important.html index cf98df940..702ac724b 100644 --- a/plugins/postcss-cascade-layers/test/wpt/layer-important.html +++ b/plugins/postcss-cascade-layers/test/wpt/layer-important.html @@ -1,10 +1,8 @@ - CSS Cascade Layers: !important - @@ -43,13 +41,55 @@ }, // TODO : Unsure if we can implement !important correctly // see : https://github.com/web-platform-tests/wpt/issues/33767 - // { - // title: 'Same specificity, both important', - // style: ` - // @layer { target { color: red !important; } } - // @layer { target { color: green !important; } } - // `, - // }, + { + title: 'Same specificity, all important (order A)', + style: ` + @layer { target { color: green !important; } } + @layer { target { color: red !important; } } + target { color: red !important; } + `, + }, + { + title: 'Same specificity, all important (order B)', + style: ` + @layer { target { color: green !important; } } + target { color: red !important; } + @layer { target { color: red !important; } } + `, + }, + { + title: 'Same specificity, all important (order C)', + style: ` + target { color: red !important; } + @layer { target { color: green !important; } } + @layer { target { color: red !important; } } + `, + }, + { + title: 'Same specificity, all important (order D)', + style: ` + @layer A, B; + @layer B { target { color: red !important; } } + @layer A { target { color: green !important; } } + target { color: red !important; } + `, + }, + { + title: 'Different specificity (A), all important', + style: ` + @layer { target { color: green !important; } } + @layer { target { color: red !important; } } + target.first { color: red !important; } + `, + }, + { + title: 'Different specificity (B), all important', + style: ` + @layer { target { color: green !important; } } + @layer { target.first { color: red !important; } } + target { color: red !important; } + `, + }, ]; for (let testCase of testCases) { From bac0abd72732b1591c5d4c49c9b7423fb60773c8 Mon Sep 17 00:00:00 2001 From: Sana Javed Date: Mon, 9 May 2022 18:56:38 +0200 Subject: [PATCH 32/33] Conditional layer ordering warning name change (#11) --- plugins/postcss-cascade-layers/.tape.mjs | 2 +- plugins/postcss-cascade-layers/README.md | 6 +++--- plugins/postcss-cascade-layers/docs/README.md | 6 +++--- plugins/postcss-cascade-layers/src/index.ts | 2 +- plugins/postcss-cascade-layers/src/options.ts | 2 +- plugins/postcss-cascade-layers/src/record-layer-order.ts | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/plugins/postcss-cascade-layers/.tape.mjs b/plugins/postcss-cascade-layers/.tape.mjs index d6ffc5837..a277ac872 100644 --- a/plugins/postcss-cascade-layers/.tape.mjs +++ b/plugins/postcss-cascade-layers/.tape.mjs @@ -39,7 +39,7 @@ postcssTape(plugin)({ message: 'correctly handles warnings', options: { onRevertLayerKeyword: 'warn', - onMixedLayerOrder: 'warn', + onConditionalRulesChangingLayerOrder: 'warn', }, warnings: 2, } diff --git a/plugins/postcss-cascade-layers/README.md b/plugins/postcss-cascade-layers/README.md index 1d9810b12..f30b2b858 100644 --- a/plugins/postcss-cascade-layers/README.md +++ b/plugins/postcss-cascade-layers/README.md @@ -80,15 +80,15 @@ postcssCascadeLayers({ onRevertLayerKeyword: 'warn' }) // 'warn' | false } ``` -### onMixedLayerOrder +### onConditionalRulesChangingLayerOrder -The `onMixedLayerOrder` option enables warnings if layers are declared in multiple different orders in conditional rules. +The `onConditionalRulesChangingLayerOrder` option enables warnings if layers are declared in multiple different orders in conditional rules. Transforming these layers correctly for older browsers is not possible in this plugin. Defaults to `warn` ```js -postcssCascadeLayers({ onMixedLayerOrder: 'warn' }) // 'warn' | false +postcssCascadeLayers({ onConditionalRulesChangingLayerOrder: 'warn' }) // 'warn' | false ``` ```pcss diff --git a/plugins/postcss-cascade-layers/docs/README.md b/plugins/postcss-cascade-layers/docs/README.md index 843963b4a..cc83f462e 100644 --- a/plugins/postcss-cascade-layers/docs/README.md +++ b/plugins/postcss-cascade-layers/docs/README.md @@ -50,15 +50,15 @@ Defaults to `warn` } ``` -### onMixedLayerOrder +### onConditionalRulesChangingLayerOrder -The `onMixedLayerOrder` option enables warnings if layers are declared in multiple different orders in conditional rules. +The `onConditionalRulesChangingLayerOrder` option enables warnings if layers are declared in multiple different orders in conditional rules. Transforming these layers correctly for older browsers is not possible in this plugin. Defaults to `warn` ```js -({ onMixedLayerOrder: 'warn' }) // 'warn' | false +({ onConditionalRulesChangingLayerOrder: 'warn' }) // 'warn' | false ``` ```pcss diff --git a/plugins/postcss-cascade-layers/src/index.ts b/plugins/postcss-cascade-layers/src/index.ts index 804104e2c..0252ed7d0 100644 --- a/plugins/postcss-cascade-layers/src/index.ts +++ b/plugins/postcss-cascade-layers/src/index.ts @@ -16,7 +16,7 @@ import { pluginOptions } from './options'; const creator: PluginCreator = (opts?: pluginOptions) => { const options = Object.assign({ onRevertLayerKeyword: 'warn', - onMixedLayerOrder: 'warn', + onConditionalRulesChangingLayerOrder: 'warn', }, opts); return { diff --git a/plugins/postcss-cascade-layers/src/options.ts b/plugins/postcss-cascade-layers/src/options.ts index 6dbf74339..7620d91e4 100644 --- a/plugins/postcss-cascade-layers/src/options.ts +++ b/plugins/postcss-cascade-layers/src/options.ts @@ -1,4 +1,4 @@ export type pluginOptions = { onRevertLayerKeyword: 'warn'|false, - onMixedLayerOrder: 'warn'|false, + onConditionalRulesChangingLayerOrder: 'warn'|false, } diff --git a/plugins/postcss-cascade-layers/src/record-layer-order.ts b/plugins/postcss-cascade-layers/src/record-layer-order.ts index 14e56c06d..1c3801d28 100644 --- a/plugins/postcss-cascade-layers/src/record-layer-order.ts +++ b/plugins/postcss-cascade-layers/src/record-layer-order.ts @@ -19,7 +19,7 @@ export function recordLayerOrder(root: Container, model: Model, { result, option } if ( - options.onMixedLayerOrder && + options.onConditionalRulesChangingLayerOrder && getConditionalAtRuleAncestor(layerRule) && !layerRule.params.endsWith(IMPLICIT_LAYER_SUFFIX) && !layerRule.params.endsWith(ANONYMOUS_LAYER_SUFFIX) From 3acf84ba16f45429735734662d7ebb7b153d0b9f Mon Sep 17 00:00:00 2001 From: Sana Javed Date: Wed, 11 May 2022 17:15:38 +0200 Subject: [PATCH 33/33] Updating docs and warning for @import (#12) Docs changes and warning for @import at-rule --- plugins/postcss-cascade-layers/.tape.mjs | 3 ++- plugins/postcss-cascade-layers/README.md | 19 +++++++++++++++++-- plugins/postcss-cascade-layers/package.json | 6 +++--- plugins/postcss-cascade-layers/src/index.ts | 10 ++++++++++ plugins/postcss-cascade-layers/src/options.ts | 1 + .../postcss-cascade-layers/test/warnings.css | 3 +++ .../test/warnings.expect.css | 3 +++ 7 files changed, 39 insertions(+), 6 deletions(-) diff --git a/plugins/postcss-cascade-layers/.tape.mjs b/plugins/postcss-cascade-layers/.tape.mjs index a277ac872..a44e61320 100644 --- a/plugins/postcss-cascade-layers/.tape.mjs +++ b/plugins/postcss-cascade-layers/.tape.mjs @@ -40,7 +40,8 @@ postcssTape(plugin)({ options: { onRevertLayerKeyword: 'warn', onConditionalRulesChangingLayerOrder: 'warn', + onImportLayerRule: 'warn' }, - warnings: 2, + warnings: 3, } }); diff --git a/plugins/postcss-cascade-layers/README.md b/plugins/postcss-cascade-layers/README.md index f30b2b858..145aa7093 100644 --- a/plugins/postcss-cascade-layers/README.md +++ b/plugins/postcss-cascade-layers/README.md @@ -5,7 +5,7 @@ [Build Status][cli-url] [Discord][discord] -[PostCSS Cascade Layers] lets use `@layer` following the [Cascade Layers Specification]. +[PostCSS Cascade Layers] lets you use `@layer` following the [Cascade Layers Specification]. For more information on layers, checkout [A Complete Guide to CSS Cascade Layers] by Miriam Suzanne. ```pcss @@ -114,7 +114,16 @@ postcssCascadeLayers({ onConditionalRulesChangingLayerOrder: 'warn' }) // 'warn' } ``` - +### Using `@import` with layers +The `@import` at-rule can also be used with cascade layers, specifically to create a new layer like so: +``` +@import 'theme.css' layer(utilities); +``` +If your CSS uses `@import` with layers, you will also need the [postcss-import] plugin. This plugin alone will not handle the `@import` at-rule. + + +### Contributors +The contributors to this plugin were [Olu Niyi-Awosusi] and [Sana Javed] from [Oddbird] and [Romain Menke]. [cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test [css-url]: https://cssdb.org/#cascade-layers @@ -127,3 +136,9 @@ postcssCascadeLayers({ onConditionalRulesChangingLayerOrder: 'warn' }) // 'warn' [PostCSS Loader]: https://github.com/postcss/postcss-loader [PostCSS Cascade Layers]: https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-cascade-layers [Cascade Layers Specification]: https://www.w3.org/TR/css-cascade-5/#layering +[A Complete Guide to CSS Cascade Layers]: https://css-tricks.com/css-cascade-layers/ +[Olu Niyi-Awosusi]: https://github.com/oluoluoxenfree +[Sana Javed]: https://github.com/sanajaved7 +[Romain Menke]: https://github.com/romainmenke +[Oddbird]: https://github.com/oddbird +[postcss-import]: https://github.com/postcss/postcss-import \ No newline at end of file diff --git a/plugins/postcss-cascade-layers/package.json b/plugins/postcss-cascade-layers/package.json index 0a2a40462..af8b4010e 100644 --- a/plugins/postcss-cascade-layers/package.json +++ b/plugins/postcss-cascade-layers/package.json @@ -1,8 +1,8 @@ { "name": "@csstools/postcss-cascade-layers", - "description": "A base plugin", - "version": "0.0.0", - "author": "Jonathan Neal ", + "description": "Use layers in CSS", + "version": "1.0.0", + "author": "Olu Niyi-Awosusi , Sana Javed , Romain Menke ", "license": "CC0-1.0", "private": true, "engines": { diff --git a/plugins/postcss-cascade-layers/src/index.ts b/plugins/postcss-cascade-layers/src/index.ts index 0252ed7d0..6026784ff 100644 --- a/plugins/postcss-cascade-layers/src/index.ts +++ b/plugins/postcss-cascade-layers/src/index.ts @@ -17,6 +17,7 @@ const creator: PluginCreator = (opts?: pluginOptions) => { const options = Object.assign({ onRevertLayerKeyword: 'warn', onConditionalRulesChangingLayerOrder: 'warn', + onImportLayerRule: 'warn', }, opts); return { @@ -32,6 +33,15 @@ const creator: PluginCreator = (opts?: pluginOptions) => { }); } + if(options.onImportLayerRule){ + root.walkAtRules('import', (atRule) => { + if(atRule.params.includes('layer')){ + atRule.warn(result, 'To use @import with layers, the postcss-import plugin is also required. This plugin alone will not support using the @import at-rule.'); + } + }, + ); + } + splitImportantStyles(root); const model = new Model(); diff --git a/plugins/postcss-cascade-layers/src/options.ts b/plugins/postcss-cascade-layers/src/options.ts index 7620d91e4..2994c8b3c 100644 --- a/plugins/postcss-cascade-layers/src/options.ts +++ b/plugins/postcss-cascade-layers/src/options.ts @@ -1,4 +1,5 @@ export type pluginOptions = { onRevertLayerKeyword: 'warn'|false, onConditionalRulesChangingLayerOrder: 'warn'|false, + onImportLayerRule: 'warn'|false, } diff --git a/plugins/postcss-cascade-layers/test/warnings.css b/plugins/postcss-cascade-layers/test/warnings.css index 18d588bc9..3ba6cd29f 100644 --- a/plugins/postcss-cascade-layers/test/warnings.css +++ b/plugins/postcss-cascade-layers/test/warnings.css @@ -1,3 +1,6 @@ +/* [postcss-cascade-layers]: To use the @import at-rule with layer, the postcss-import plugin is also required. This plugin alone will not support importing layers. */ +@import 'theme.css' layer(utilities); + /* [postcss-cascade-layers]: handling "revert-layer" is unsupported by this plugin and will cause style differences between browser versions. */ @layer { .foo { diff --git a/plugins/postcss-cascade-layers/test/warnings.expect.css b/plugins/postcss-cascade-layers/test/warnings.expect.css index a07cf23bc..f0e09febd 100644 --- a/plugins/postcss-cascade-layers/test/warnings.expect.css +++ b/plugins/postcss-cascade-layers/test/warnings.expect.css @@ -1,3 +1,6 @@ + +@import 'theme.css' layer(utilities);/* [postcss-cascade-layers]: To use the @import at-rule with layer, the postcss-import plugin is also required. This plugin alone will not support importing layers. */ + /* [postcss-cascade-layers]: handling "revert-layer" is unsupported by this plugin and will cause style differences between browser versions. */ .foo { color: revert-layer;