diff --git a/.github/ISSUE_TEMPLATE/css-issue.yml b/.github/ISSUE_TEMPLATE/css-issue.yml index 135995ae7..8107c8b73 100644 --- a/.github/ISSUE_TEMPLATE/css-issue.yml +++ b/.github/ISSUE_TEMPLATE/css-issue.yml @@ -70,8 +70,11 @@ body: - PostCSS Color Hex Alpha - PostCSS Color Mod Function - PostCSS Custom Media Queries + - PostCSS Custom Media Queries Import/Export - PostCSS Custom Properties + - PostCSS Custom Properties Import/Export - PostCSS Custom Selectors + - PostCSS Custom Selectors Import/Export - PostCSS Design Tokens - PostCSS Dir Pseudo Class - PostCSS Double Position Gradients diff --git a/.github/ISSUE_TEMPLATE/plugin-issue.yml b/.github/ISSUE_TEMPLATE/plugin-issue.yml index 2fa9f2915..fa97f0a38 100644 --- a/.github/ISSUE_TEMPLATE/plugin-issue.yml +++ b/.github/ISSUE_TEMPLATE/plugin-issue.yml @@ -72,8 +72,11 @@ body: - PostCSS Color Hex Alpha - PostCSS Color Mod Function - PostCSS Custom Media Queries + - PostCSS Custom Media Queries Import/Export - PostCSS Custom Properties + - PostCSS Custom Properties Import/Export - PostCSS Custom Selectors + - PostCSS Custom Selectors Import/Export - PostCSS Design Tokens - PostCSS Dir Pseudo Class - PostCSS Double Position Gradients diff --git a/.github/labeler.yml b/.github/labeler.yml index c874c1372..90ae46a52 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -216,5 +216,13 @@ - plugins/postcss-trigonometric-functions/** - experimental/postcss-trigonometric-functions/** +"plugins/import-export": + - plugins/postcss-custom-media-queries-import-export/** + - experimental/postcss-custom-media-queries-import-export/** + - plugins/postcss-custom-properties-import-export/** + - experimental/postcss-custom-properties-import-export/** + - plugins/postcss-custom-selectors-import-export/** + - experimental/postcss-custom-selectors-import-export/** + "sites/postcss-preset-env": - sites/postcss-preset-env/** diff --git a/package-lock.json b/package-lock.json index c5371965e..47a4b45d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1923,6 +1923,18 @@ "resolved": "plugins/postcss-conditional-values", "link": true }, + "node_modules/@csstools/postcss-custom-media-import-export": { + "resolved": "plugins/postcss-custom-media-import-export", + "link": true + }, + "node_modules/@csstools/postcss-custom-properties-import-export": { + "resolved": "plugins/postcss-custom-properties-import-export", + "link": true + }, + "node_modules/@csstools/postcss-custom-selectors-import-export": { + "resolved": "plugins/postcss-custom-selectors-import-export", + "link": true + }, "node_modules/@csstools/postcss-design-tokens": { "resolved": "plugins/postcss-design-tokens", "link": true @@ -7392,6 +7404,27 @@ "postcss": "^8.4" } }, + "plugins/postcss-custom-media-import-export": { + "name": "@csstools/postcss-custom-media-import-export", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "devDependencies": { + "postcss-custom-media": "^9.0.1" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, "plugins/postcss-custom-properties": { "version": "13.0.0", "license": "MIT", @@ -7412,6 +7445,25 @@ "postcss": "^8.4" } }, + "plugins/postcss-custom-properties-import-export": { + "name": "@csstools/postcss-custom-properties-import-export", + "version": "1.0.0", + "license": "MIT", + "devDependencies": { + "postcss-custom-properties": "^13.0.0", + "postcss-import": "^15.0.0" + }, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, "plugins/postcss-custom-selectors": { "version": "7.0.0", "license": "MIT", @@ -7429,6 +7481,27 @@ "postcss": "^8.4" } }, + "plugins/postcss-custom-selectors-import-export": { + "name": "@csstools/postcss-custom-selectors-import-export", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "devDependencies": { + "postcss-custom-selectors": "^7.0.0" + }, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, "plugins/postcss-design-tokens": { "name": "@csstools/postcss-design-tokens", "version": "1.2.0", @@ -9282,6 +9355,27 @@ "postcss-value-parser": "^4.2.0" } }, + "@csstools/postcss-custom-media-import-export": { + "version": "file:plugins/postcss-custom-media-import-export", + "requires": { + "postcss-custom-media": "^9.0.1", + "postcss-value-parser": "^4.2.0" + } + }, + "@csstools/postcss-custom-properties-import-export": { + "version": "file:plugins/postcss-custom-properties-import-export", + "requires": { + "postcss-custom-properties": "^13.0.0", + "postcss-import": "^15.0.0" + } + }, + "@csstools/postcss-custom-selectors-import-export": { + "version": "file:plugins/postcss-custom-selectors-import-export", + "requires": { + "postcss-custom-selectors": "^7.0.0", + "postcss-selector-parser": "^6.0.4" + } + }, "@csstools/postcss-design-tokens": { "version": "file:plugins/postcss-design-tokens", "requires": { diff --git a/plugins/postcss-custom-media-import-export/.gitignore b/plugins/postcss-custom-media-import-export/.gitignore new file mode 100644 index 000000000..7172b04f1 --- /dev/null +++ b/plugins/postcss-custom-media-import-export/.gitignore @@ -0,0 +1,6 @@ +node_modules +package-lock.json +yarn.lock +*.result.css +*.result.css.map +dist/* diff --git a/plugins/postcss-custom-media-import-export/.nvmrc b/plugins/postcss-custom-media-import-export/.nvmrc new file mode 100644 index 000000000..f0b10f153 --- /dev/null +++ b/plugins/postcss-custom-media-import-export/.nvmrc @@ -0,0 +1 @@ +v16.13.1 diff --git a/plugins/postcss-custom-media-import-export/.tape.cjs b/plugins/postcss-custom-media-import-export/.tape.cjs new file mode 100644 index 000000000..3312d9165 --- /dev/null +++ b/plugins/postcss-custom-media-import-export/.tape.cjs @@ -0,0 +1,250 @@ +const postcssTape = require('../../packages/postcss-tape/dist/index.cjs'); +const plugin = require('@csstools/postcss-custom-media-import-export'); +const polyfillPlugin = require('postcss-custom-media'); +const fs = require('fs'); + +postcssTape(plugin)({ + 'import': { + message: 'supports { importFrom: { customMedia: { ... } } } usage', + options: { + importFrom: { + customMedia: { + '--mq-a': '(max-width: 30em), (max-height: 30em)', + '--not-mq-a': 'not all and (--mq-a)' + } + } + } + }, + 'import:with-polyfill-plugin': { + message: 'works correctly together with the polyfill', + plugins: [ + plugin({ + importFrom: { + customMedia: { + '--mq-a': '(max-width: 30em), (max-height: 30em)', + '--not-mq-a': 'not all and (--mq-a)' + } + } + }), + polyfillPlugin(), + ] + }, + 'import:imported-styles-override-document-styles:true': { + message: 'supports { importedStylesOverrideDocumentStyles: true } usage', + options: { + importedStylesOverrideDocumentStyles: true, + importFrom: { + customMedia: { + '--mq-a': '(max-width: 30em), (max-height: 30em)', + '--not-mq-a': 'not all and (--mq-a)' + } + } + } + }, + 'import:imported-styles-override-document-styles:false': { + message: 'supports { importedStylesOverrideDocumentStyles: false } usage', + options: { + importedStylesOverrideDocumentStyles: false, + importFrom: { + customMedia: { + '--mq-a': '(max-width: 30em), (max-height: 30em)', + '--not-mq-a': 'not all and (--mq-a)' + } + } + } + }, + 'import:import-fn': { + message: 'supports { importFrom() } usage', + options: { + importFrom() { + return { + customMedia: { + '--mq-a': '(max-width: 30em), (max-height: 30em)', + '--not-mq-a': 'not all and (--mq-a)' + } + }; + } + }, + result: 'import.result.css' + }, + 'import:import-fn-promise': { + message: 'supports { async importFrom() } usage', + options: { + importFrom() { + return new Promise(resolve => { + resolve({ + customMedia: { + '--mq-a': '(max-width: 30em), (max-height: 30em)', + '--not-mq-a': 'not all and (--mq-a)' + } + }) + }); + } + }, + result: 'import.result.css' + }, + 'import:json': { + message: 'supports { importFrom: "test/import-media.json" } usage', + options: { + importFrom: 'test/import-media.json' + }, + result: 'import.result.css' + }, + 'import:js': { + message: 'supports { importFrom: "test/import-media.js" } usage', + options: { + importFrom: 'test/import-media.js' + }, + result: 'import.result.css' + }, + 'import:css': { + message: 'supports { importFrom: "test/import-media.css" } usage', + options: { + importFrom: 'test/import-media.css' + }, + result: 'import.result.css' + }, + 'import:css-from': { + message: 'supports { importFrom: { from: "test/import-media.css" } } usage', + options: { + importFrom: { from: 'test/import-media.css' } + }, + result: 'import.result.css' + }, + 'import:css-from-type': { + message: 'supports { importFrom: [ { from: "test/import-media.css", type: "css" } ] } usage', + options: { + importFrom: [{ from: 'test/import-media.css', type: 'css' }] + }, + result: 'import.result.css' + }, + 'import:empty': { + message: 'supports { importFrom: {} } usage', + options: { + importFrom: {} + } + }, + 'basic:export': { + message: 'supports { exportTo: { customMedia: { ... } } } usage', + options: { + exportTo: (global.__exportMediaObject = global.__exportMediaObject || { + customMedia: null + }) + }, + after() { + if (__exportMediaObject.customMedia['--mq-a'] !== '(max-width: 30em), (max-height: 30em)') { + throw new Error('The exportTo function failed'); + } + } + }, + 'basic:export-fn': { + message: 'supports { exportTo() } usage', + options: { + exportTo(customMedia) { + if (customMedia['--mq-a'] !== '(max-width: 30em), (max-height: 30em)') { + throw new Error('The exportTo function failed'); + } + } + }, + result: 'basic.result.css' + }, + 'basic:export-fn-promise': { + message: 'supports { async exportTo() } usage', + options: { + exportTo(customMedia) { + return new Promise((resolve, reject) => { + if (customMedia['--mq-a'] !== '(max-width: 30em), (max-height: 30em)') { + reject('The exportTo function failed'); + } else { + resolve(); + } + }); + } + }, + result: 'basic.result.css' + }, + 'basic:export-json': { + message: 'supports { exportTo: "test/export-media.json" } usage', + options: { + exportTo: 'test/export-media.json' + }, + before() { + global.__exportMediaString = fs.readFileSync('test/export-media.json', 'utf8'); + }, + after() { + if (global.__exportMediaString !== fs.readFileSync('test/export-media.json', 'utf8')) { + throw new Error('The original file did not match the freshly exported copy'); + } + } + }, + 'basic:export-js': { + message: 'supports { exportTo: "test/export-media.js" } usage', + options: { + exportTo: 'test/export-media.js' + }, + before() { + global.__exportMediaString = fs.readFileSync('test/export-media.js', 'utf8'); + }, + after() { + if (global.__exportMediaString !== fs.readFileSync('test/export-media.js', 'utf8')) { + throw new Error('The original file did not match the freshly exported copy'); + } + } + }, + 'basic:export-mjs': { + message: 'supports { exportTo: "test/export-media.mjs" } usage', + options: { + exportTo: 'test/export-media.mjs' + }, + before() { + global.__exportMediaString = fs.readFileSync('test/export-media.mjs', 'utf8'); + }, + after() { + if (global.__exportMediaString !== fs.readFileSync('test/export-media.mjs', 'utf8')) { + throw new Error('The original file did not match the freshly exported copy'); + } + } + }, + 'basic:export-css': { + message: 'supports { exportTo: "test/export-media.css" } usage', + options: { + exportTo: 'test/export-media.css' + }, + before() { + global.__exportMediaString = fs.readFileSync('test/export-media.css', 'utf8'); + }, + after() { + if (global.__exportMediaString !== fs.readFileSync('test/export-media.css', 'utf8')) { + throw new Error('The original file did not match the freshly exported copy'); + } + } + }, + 'basic:export-css-to': { + message: 'supports { exportTo: { to: "test/export-media.css" } } usage', + options: { + exportTo: { to: 'test/export-media.css' } + }, + before() { + global.__exportMediaString = fs.readFileSync('test/export-media.css', 'utf8'); + }, + after() { + if (global.__exportMediaString !== fs.readFileSync('test/export-media.css', 'utf8')) { + throw new Error('The original file did not match the freshly exported copy'); + } + } + }, + 'basic:export-css-to-type': { + message: 'supports { exportTo: { to: "test/export-media.css", type: "css" } } usage', + options: { + exportTo: { to: 'test/export-media.css', type: 'css' } + }, + before() { + global.__exportMediaString = fs.readFileSync('test/export-media.css', 'utf8'); + }, + after() { + if (global.__exportMediaString !== fs.readFileSync('test/export-media.css', 'utf8')) { + throw new Error('The original file did not match the freshly exported copy'); + } + } + } +}); diff --git a/plugins/postcss-custom-media-import-export/CHANGELOG.md b/plugins/postcss-custom-media-import-export/CHANGELOG.md new file mode 100644 index 000000000..bc8b2132c --- /dev/null +++ b/plugins/postcss-custom-media-import-export/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changes to PostCSS Custom Media Import/Export + +### 1.0.0 (Unreleased) + +- Initial version diff --git a/plugins/postcss-custom-media-import-export/INSTALL.md b/plugins/postcss-custom-media-import-export/INSTALL.md new file mode 100644 index 000000000..c6dc188a9 --- /dev/null +++ b/plugins/postcss-custom-media-import-export/INSTALL.md @@ -0,0 +1,257 @@ +# Installing PostCSS Custom Media Import/Export + +[PostCSS Custom Media Import/Export] runs in all Node environments, with special instructions for: + +- [Node](#node) +- [PostCSS CLI](#postcss-cli) +- [PostCSS Load Config](#postcss-load-config) +- [Webpack](#webpack) +- [Create React App](#create-react-app) +- [Next.js](#nextjs) +- [Gulp](#gulp) +- [Grunt](#grunt) + +## Node + +Add [PostCSS Custom Media Import/Export] to your project: + +```bash +npm install postcss @csstools/postcss-custom-media-import-export --save-dev +``` + +Use it as a [PostCSS] plugin: + +```js +// commonjs +const postcss = require('postcss'); +const postcssCustomMediaImportExport = require('@csstools/postcss-custom-media-import-export'); + +postcss([ + postcssCustomMediaImportExport(/* pluginOptions */) +]).process(YOUR_CSS /*, processOptions */); +``` + +```js +// esm +import postcss from 'postcss'; +import postcssCustomMediaImportExport from '@csstools/postcss-custom-media-import-export'; + +postcss([ + postcssCustomMediaImportExport(/* pluginOptions */) +]).process(YOUR_CSS /*, processOptions */); +``` + +## PostCSS CLI + +Add [PostCSS CLI] to your project: + +```bash +npm install postcss-cli @csstools/postcss-custom-media-import-export --save-dev +``` + +Use [PostCSS Custom Media Import/Export] in your `postcss.config.js` configuration file: + +```js +const postcssCustomMediaImportExport = require('@csstools/postcss-custom-media-import-export'); + +module.exports = { + plugins: [ + postcssCustomMediaImportExport(/* pluginOptions */) + ] +} +``` + +## PostCSS Load Config + +If your framework/CLI supports [`postcss-load-config`](https://github.com/postcss/postcss-load-config). + +```bash +npm install @csstools/postcss-custom-media-import-export --save-dev +``` + +`package.json`: + +```json +{ + "postcss": { + "plugins": { + "@csstools/postcss-custom-media-import-export": {} + } + } +} +``` + +`.postcssrc.json`: + +```json +{ + "plugins": { + "@csstools/postcss-custom-media-import-export": {} + } +} +``` + +_See the [README of `postcss-load-config`](https://github.com/postcss/postcss-load-config#usage) for more usage options._ + +## Webpack + +_Webpack version 5_ + +Add [PostCSS Loader] to your project: + +```bash +npm install postcss-loader @csstools/postcss-custom-media-import-export --save-dev +``` + +Use [PostCSS Custom Media Import/Export] 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-custom-media-import-export", + { + // 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-custom-media-import-export --save-dev +``` + +Use [React App Rewire PostCSS] and [PostCSS Custom Media Import/Export] in your +`config-overrides.js` file: + +```js +const reactAppRewirePostcss = require('react-app-rewire-postcss'); +const postcssCustomMediaImportExport = require('@csstools/postcss-custom-media-import-export'); + +module.exports = config => reactAppRewirePostcss(config, { + plugins: () => [ + postcssCustomMediaImportExport(/* pluginOptions */) + ] +}); +``` + +## Next.js + +Read the instructions on how to [customize the PostCSS configuration in Next.js](https://nextjs.org/docs/advanced-features/customizing-postcss-config) + +```bash +npm install @csstools/postcss-custom-media-import-export --save-dev +``` + +Use [PostCSS Custom Media Import/Export] in your `postcss.config.json` file: + +```json +{ + "plugins": [ + "@csstools/postcss-custom-media-import-export" + ] +} +``` + +```json5 +{ + "plugins": [ + [ + "@csstools/postcss-custom-media-import-export", + { + // Optionally add plugin options + } + ] + ] +} +``` + +## Gulp + +Add [Gulp PostCSS] to your project: + +```bash +npm install gulp-postcss @csstools/postcss-custom-media-import-export --save-dev +``` + +Use [PostCSS Custom Media Import/Export] in your Gulpfile: + +```js +const postcss = require('gulp-postcss'); +const postcssCustomMediaImportExport = require('@csstools/postcss-custom-media-import-export'); + +gulp.task('css', function () { + var plugins = [ + postcssCustomMediaImportExport(/* 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-custom-media-import-export --save-dev +``` + +Use [PostCSS Custom Media Import/Export] in your Gruntfile: + +```js +const postcssCustomMediaImportExport = require('@csstools/postcss-custom-media-import-export'); + +grunt.loadNpmTasks('grunt-postcss'); + +grunt.initConfig({ + postcss: { + options: { + processors: [ + postcssCustomMediaImportExport(/* 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 Custom Media Import/Export]: https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-custom-media-import-export +[React App Rewire PostCSS]: https://github.com/csstools/react-app-rewire-postcss +[React App Rewired]: https://github.com/timarney/react-app-rewired +[Next.js]: https://nextjs.org diff --git a/plugins/postcss-custom-media-import-export/LICENSE.md b/plugins/postcss-custom-media-import-export/LICENSE.md new file mode 100644 index 000000000..6d7047088 --- /dev/null +++ b/plugins/postcss-custom-media-import-export/LICENSE.md @@ -0,0 +1,21 @@ +# The MIT License (MIT) + +Copyright © PostCSS + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/plugins/postcss-custom-media-import-export/README.md b/plugins/postcss-custom-media-import-export/README.md new file mode 100644 index 000000000..7d056e66c --- /dev/null +++ b/plugins/postcss-custom-media-import-export/README.md @@ -0,0 +1,162 @@ +# PostCSS Custom Media Import/Export [PostCSS Logo][PostCSS] + +[npm version][npm-url] [Build Status][cli-url] [Discord][discord] + +[PostCSS Custom Media Import/Export] lets you import or export `@custom-media`'s into or out of your CSS. + +## As a drop in for old versions of `postcss-custom-media` + +```js +// commonjs +const postcss = require('postcss'); +const postcssCustomMediaImportExport = require('@csstools/postcss-custom-media-import-export'); +const postcssCustomMedia = require('postcss-custom-media'); + +postcss([ + // First + postcssCustomMediaImportExport({ + /* pluginOptions */ + importedStylesOverrideDocumentStyles: false, // mimics old `postcss-custom-media` + }), + // Second + postcssCustomMedia() +]).process(YOUR_CSS /*, processOptions */); +``` + +## Usage + +Add [PostCSS Custom Media Import/Export] to your project: + +```bash +npm install postcss @csstools/postcss-custom-media-import-export --save-dev +``` + +Use it as a [PostCSS] plugin: + +```js +const postcss = require('postcss'); +const postcssCustomMediaImportExport = require('@csstools/postcss-custom-media-import-export'); + +postcss([ + postcssCustomMediaImportExport(/* pluginOptions */) +]).process(YOUR_CSS /*, processOptions */); +``` + +[PostCSS Custom Media Import/Export] runs in all Node environments, with special +instructions for: + +- [Node](INSTALL.md#node) +- [PostCSS CLI](INSTALL.md#postcss-cli) +- [PostCSS Load Config](INSTALL.md#postcss-load-config) +- [Webpack](INSTALL.md#webpack) +- [Create React App](INSTALL.md#create-react-app) +- [Next.js](INSTALL.md#nextjs) +- [Gulp](INSTALL.md#gulp) +- [Grunt](INSTALL.md#grunt) + +## Options + +### importFrom + +The `importFrom` option specifies sources where custom media can be imported +from, which might be CSS, JS, and JSON files, functions, and directly passed +objects. + +```js +postcssCustomMediaImportExport({ + importFrom: 'path/to/file.css' // => @custom-selector --small-viewport (max-width: 30em); +}); +``` + +```pcss +@media (--small-viewport) { + /* styles for small viewport */ +} + +/* becomes */ + +@custom-selector --small-viewport (max-width: 30em); + +@media (--small-viewport) { + /* styles for small viewport */ +} +``` + +Multiple sources can be passed into this option, and they will be parsed in the +order they are received. JavaScript files, JSON files, functions, and objects +will need to namespace custom media using the `customMedia` or +`custom-media` key. + +```js +postcssCustomMediaImportExport({ + importFrom: [ + 'path/to/file.css', + 'and/then/this.js', + 'and/then/that.json', + { + customMedia: { '--small-viewport': '(max-width: 30em)' } + }, + () => { + const customMedia = { '--small-viewport': '(max-width: 30em)' }; + + return { customMedia }; + } + ] +}); +``` + +### importedStylesOverrideDocumentStyles + +The `importedStylesOverrideDocumentStyles` option determines if queries added via `importFrom` override queries that exist in your CSS document. +Defaults to `false`. + +```js +postcssCustomMediaImportExport({ + importedStylesOverrideDocumentStyles: true +}); + +### exportTo + +The `exportTo` option specifies destinations where custom media can be exported +to, which might be CSS, JS, and JSON files, functions, and directly passed +objects. + +```js +postcssCustomMediaImportExport({ + exportTo: 'path/to/file.css' // @custom-media --small-viewport (max-width: 30em); +}); +``` + +Multiple destinations can be passed into this option, and they will be parsed +in the order they are received. JavaScript files, JSON files, and objects will +need to namespace custom media using the `customMedia` or +`custom-media` key. + +```js +const cachedObject = { customMedia: {} }; + +postcssCustomMediaImportExport({ + exportTo: [ + 'path/to/file.css', // @custom-media --small-viewport (max-width: 30em); + 'and/then/this.js', // module.exports = { customMedia: { '--small-viewport': '(max-width: 30em)' } } + 'and/then/this.mjs', // export const customMedia = { '--small-viewport': '(max-width: 30em)' } } + 'and/then/that.json', // { "custom-media": { "--small-viewport": "(max-width: 30em)" } } + cachedObject, + customMedia => { + customMedia // { '--small-viewport': '(max-width: 30em)' } + } + ] +}); +``` + +See example exports written to [CSS](test/export-media.css), +[JS](test/export-media.js), [MJS](test/export-media.mjs), and +[JSON](test/export-media.json). + +[cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test + +[discord]: https://discord.gg/bUadyRwkJS +[npm-url]: https://www.npmjs.com/package/@csstools/postcss-custom-media-import-export + +[PostCSS]: https://github.com/postcss/postcss +[PostCSS Custom Media Import/Export]: https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-custom-media-import-export diff --git a/plugins/postcss-custom-media-import-export/docs/README.md b/plugins/postcss-custom-media-import-export/docs/README.md new file mode 100644 index 000000000..18b649092 --- /dev/null +++ b/plugins/postcss-custom-media-import-export/docs/README.md @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + +
+ +[] lets you import or export `@custom-media`'s into or out of your CSS. + +## As a drop in for old versions of `postcss-custom-media` + +```js +// commonjs +const postcss = require('postcss'); +const postcssCustomMediaImportExport = require('@csstools/postcss-custom-media-import-export'); +const postcssCustomMedia = require('postcss-custom-media'); + +postcss([ + // First + postcssCustomMediaImportExport({ + /* pluginOptions */ + importedStylesOverrideDocumentStyles: false, // mimics old `postcss-custom-media` + }), + // Second + postcssCustomMedia() +]).process(YOUR_CSS /*, processOptions */); +``` + + + + + +## Options + +### importFrom + +The `importFrom` option specifies sources where custom media can be imported +from, which might be CSS, JS, and JSON files, functions, and directly passed +objects. + +```js +({ + importFrom: 'path/to/file.css' // => @custom-selector --small-viewport (max-width: 30em); +}); +``` + +```pcss +@media (--small-viewport) { + /* styles for small viewport */ +} + +/* becomes */ + +@custom-selector --small-viewport (max-width: 30em); + +@media (--small-viewport) { + /* styles for small viewport */ +} +``` + +Multiple sources can be passed into this option, and they will be parsed in the +order they are received. JavaScript files, JSON files, functions, and objects +will need to namespace custom media using the `customMedia` or +`custom-media` key. + +```js +({ + importFrom: [ + 'path/to/file.css', + 'and/then/this.js', + 'and/then/that.json', + { + customMedia: { '--small-viewport': '(max-width: 30em)' } + }, + () => { + const customMedia = { '--small-viewport': '(max-width: 30em)' }; + + return { customMedia }; + } + ] +}); +``` + +### importedStylesOverrideDocumentStyles + +The `importedStylesOverrideDocumentStyles` option determines if queries added via `importFrom` override queries that exist in your CSS document. +Defaults to `false`. + +```js +({ + importedStylesOverrideDocumentStyles: true +}); + +### exportTo + +The `exportTo` option specifies destinations where custom media can be exported +to, which might be CSS, JS, and JSON files, functions, and directly passed +objects. + +```js +({ + exportTo: 'path/to/file.css' // @custom-media --small-viewport (max-width: 30em); +}); +``` + +Multiple destinations can be passed into this option, and they will be parsed +in the order they are received. JavaScript files, JSON files, and objects will +need to namespace custom media using the `customMedia` or +`custom-media` key. + +```js +const cachedObject = { customMedia: {} }; + +({ + exportTo: [ + 'path/to/file.css', // @custom-media --small-viewport (max-width: 30em); + 'and/then/this.js', // module.exports = { customMedia: { '--small-viewport': '(max-width: 30em)' } } + 'and/then/this.mjs', // export const customMedia = { '--small-viewport': '(max-width: 30em)' } } + 'and/then/that.json', // { "custom-media": { "--small-viewport": "(max-width: 30em)" } } + cachedObject, + customMedia => { + customMedia // { '--small-viewport': '(max-width: 30em)' } + } + ] +}); +``` + +See example exports written to [CSS](test/export-media.css), +[JS](test/export-media.js), [MJS](test/export-media.mjs), and +[JSON](test/export-media.json). + + diff --git a/plugins/postcss-custom-media-import-export/package.json b/plugins/postcss-custom-media-import-export/package.json new file mode 100644 index 000000000..f72b9e5ab --- /dev/null +++ b/plugins/postcss-custom-media-import-export/package.json @@ -0,0 +1,93 @@ +{ + "name": "@csstools/postcss-custom-media-import-export", + "description": "Import/Export Custom Media Queries in CSS", + "version": "1.0.0", + "contributors": [ + { + "name": "Antonio Laguna", + "email": "antonio@laguna.es", + "url": "https://antonio.laguna.es" + }, + { + "name": "Romain Menke", + "email": "romainmenke@gmail.com" + }, + { + "name": "Jonathan Neal", + "email": "jonathantneal@hotmail.com" + }, + { + "name": "Maxime Thirouin" + } + ], + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "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" + ], + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "peerDependencies": { + "postcss": "^8.4" + }, + "devDependencies": { + "postcss-custom-media": "^9.0.1" + }, + "scripts": { + "build": "rollup -c ../../rollup/default.mjs", + "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.cjs && npm run test:exports", + "test:exports": "node ./test/_import.mjs && node ./test/_require.cjs", + "test:rewrite-expects": "REWRITE_EXPECTS=true node .tape.cjs" + }, + "homepage": "https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-custom-media-import-export#readme", + "repository": { + "type": "git", + "url": "https://github.com/csstools/postcss-plugins.git", + "directory": "plugins/postcss-custom-media-import-export" + }, + "bugs": "https://github.com/csstools/postcss-plugins/issues", + "keywords": [ + "at-rule", + "atrule", + "css", + "custom", + "media", + "postcss", + "postcss-plugin", + "queries", + "query" + ], + "csstools": { + "exportName": "postcssCustomMediaImportExport", + "humanReadableName": "PostCSS Custom Media Import/Export" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/plugins/postcss-custom-media-import-export/src/custom-media-from-root.js b/plugins/postcss-custom-media-import-export/src/custom-media-from-root.js new file mode 100644 index 000000000..6b64a6607 --- /dev/null +++ b/plugins/postcss-custom-media-import-export/src/custom-media-from-root.js @@ -0,0 +1,55 @@ +import valueParser from 'postcss-value-parser'; + +// return custom media from the css root, conditionally removing them +export default (root) => { + // initialize custom media + const customMedias = {}; + + // for each custom selector atrule that is a child of the css root + root.nodes.slice().forEach(node => { + if (node.type !== 'atrule') { + return; + } + + if (node.name.toLowerCase() !== 'custom-media') { + return; + } + + let paramsAst = null; + try { + paramsAst = valueParser(node.params); + } catch (_) { + return; + } + + if (!paramsAst || !paramsAst.nodes || !paramsAst.nodes.length) { + return; + } + + let nameNodeIndex = -1; + for (let i = 0; i < paramsAst.nodes.length; i++) { + const node = paramsAst.nodes[i]; + if (node.type === 'space' || node.type === 'comment') { + continue; + } + + if (node.type === 'word' && node.value.startsWith('--')) { + nameNodeIndex = i; + break; + } + + return; /* invalid starting node */ + } + + if (nameNodeIndex < 0) { + return; + } + + const name = paramsAst.nodes[nameNodeIndex].value.trim(); + const mediaQueries = valueParser.stringify(paramsAst.nodes.slice(nameNodeIndex + 1)).trim(); + + customMedias[name] = mediaQueries; + }); + + return customMedias; +}; diff --git a/plugins/postcss-custom-media-import-export/src/get-custom-media-from-imports.js b/plugins/postcss-custom-media-import-export/src/get-custom-media-from-imports.js new file mode 100644 index 000000000..6a43c5739 --- /dev/null +++ b/plugins/postcss-custom-media-import-export/src/get-custom-media-from-imports.js @@ -0,0 +1,115 @@ +import fs from 'fs'; +import path from 'path'; +import url from 'url'; +import { parse } from 'postcss'; +import getCustomMedia from './custom-media-from-root'; + +/* Get Custom Media from CSS File +/* ========================================================================== */ + +async function getCustomMediaFromCSSFile(from) { + const css = await readFile(url.pathToFileURL(path.resolve(from))); + const root = parse(css, { from }); + + return getCustomMedia(root); +} + +/* Get Custom Media from Object +/* ========================================================================== */ + +function getCustomMediaFromObject(object) { + const customMedia = Object.assign( + {}, + Object(object).customMedia, + Object(object)['custom-media'], + ); + + return customMedia; +} + +/* Get Custom Media from JSON file +/* ========================================================================== */ + +async function getCustomMediaFromJSONFile(from) { + const object = await readJSON(url.pathToFileURL(path.resolve(from))); + + return getCustomMediaFromObject(object); +} + +/* Get Custom Media from JS file +/* ========================================================================== */ + +async function getCustomMediaFromJSFile(from) { + const object = await import(url.pathToFileURL(path.resolve(from)).href); + + if ('default' in object) { + return getCustomMediaFromObject(object.default); + } + + return getCustomMediaFromObject(object); +} + +/* Get Custom Media from Sources +/* ========================================================================== */ + +export default function getCustomMediaFromSources(sources) { + return sources.map(source => { + if (source instanceof Promise) { + return source; + } else if (source instanceof Function) { + return source(); + } + + // read the source as an object + const opts = source === Object(source) ? source : { from: String(source) }; + + // skip objects with custom media + if (Object(opts).customMedia || Object(opts)['custom-media']) { + return opts; + } + + // source pathname + const from = path.resolve(String(opts.from || '')); + + // type of file being read from + const type = (opts.type || path.extname(from).slice(1)).toLowerCase(); + + return { type, from }; + }).reduce(async (customMedia, source) => { + const { type, from } = await source; + + if (type === 'css' || type === 'pcss') { + return Object.assign(await customMedia, await getCustomMediaFromCSSFile(from)); + } + + if (type === 'js' || type === 'cjs') { + return Object.assign(await customMedia, await getCustomMediaFromJSFile(from)); + } + + if (type === 'mjs') { + // Only works when running as a module. + return Object.assign(await customMedia, await getCustomMediaFromJSFile(from)); + } + + if (type === 'json') { + return Object.assign(await customMedia, await getCustomMediaFromJSONFile(from)); + } + + return Object.assign(await customMedia, getCustomMediaFromObject(await source)); + }, {}); +} + +/* Helper utilities +/* ========================================================================== */ + +const readFile = from => new Promise((resolve, reject) => { + fs.readFile(from, 'utf8', (error, result) => { + if (error) { + reject(error); + } else { + resolve(result); + } + }); +}); + +const readJSON = async from => JSON.parse(await readFile(from)); diff --git a/plugins/postcss-custom-media-import-export/src/index.js b/plugins/postcss-custom-media-import-export/src/index.js new file mode 100644 index 000000000..d63d4f1a9 --- /dev/null +++ b/plugins/postcss-custom-media-import-export/src/index.js @@ -0,0 +1,69 @@ +import getCustomMediaFromRoot from './custom-media-from-root'; +import getCustomMediaFromImports from './get-custom-media-from-imports'; +import writeCustomMediaToExports from './write-custom-media-to-exports'; + +const creator = (opts) => { + const importedStylesOverrideDocumentStyles = 'importedStylesOverrideDocumentStyles' in Object(opts) ? Boolean(opts.importedStylesOverrideDocumentStyles) : false; + + // sources to import custom media from + const importFrom = [].concat(Object(opts).importFrom || []); + + // destinations to export custom selectors to + const exportTo = [].concat(Object(opts).exportTo || []); + + // promise any custom media are imported + const customMediaImportsPromise = getCustomMediaFromImports(importFrom); + + return { + postcssPlugin: 'postcss-custom-media-import-export', + Once: async (root, { result, postcss }) => { + const importedMediaQueries = await customMediaImportsPromise; + + let allCustomMediaQueries; + if (importedStylesOverrideDocumentStyles) { + allCustomMediaQueries = Object.assign( + {}, + getCustomMediaFromRoot(root), + importedMediaQueries, + ); + } else { + allCustomMediaQueries = Object.assign( + {}, + importedMediaQueries, + getCustomMediaFromRoot(root), + ); + } + + await writeCustomMediaToExports(allCustomMediaQueries, exportTo); + + if (importedMediaQueries) { + const mediaQueryNames = Object.keys(importedMediaQueries); + // Inserting in reverse order results in the correct order. + mediaQueryNames.reverse(); + + let operator = 'prepend'; + if (importedStylesOverrideDocumentStyles) { + operator = 'append'; + } + + mediaQueryNames.forEach((mediaQueryName) => { + root[operator](postcss.atRule({ + name: 'custom-media', + params: `${mediaQueryName} ${importedMediaQueries[mediaQueryName].toString()}`, + source: { + input: { + from: result.opts.from, + }, + start: { offset: 0, line: 1, column: 1 }, + end: { offset: 0, line: 1, column: 1 }, + }, + })); + }); + } + }, + }; +}; + +creator.postcss = true; + +export default creator; diff --git a/plugins/postcss-custom-media-import-export/src/write-custom-media-to-exports.js b/plugins/postcss-custom-media-import-export/src/write-custom-media-to-exports.js new file mode 100644 index 000000000..52754f3f5 --- /dev/null +++ b/plugins/postcss-custom-media-import-export/src/write-custom-media-to-exports.js @@ -0,0 +1,129 @@ +import fs from 'fs'; +import path from 'path'; + +/* Write Custom Media from CSS File +/* ========================================================================== */ + +async function writeCustomMediaToCssFile(to, customMedia) { + const cssContent = Object.keys(customMedia).reduce((cssLines, name) => { + cssLines.push(`@custom-media ${name} ${customMedia[name]};`); + + return cssLines; + }, []).join('\n'); + const css = `${cssContent}\n`; + + await writeFile(to, css); +} + +/* Write Custom Media from JSON file +/* ========================================================================== */ + +async function writeCustomMediaToJsonFile(to, customMedia) { + const jsonContent = JSON.stringify({ + 'custom-media': customMedia, + }, null, '\t'); + const json = `${jsonContent}\n`; + + await writeFile(to, json); +} + +/* Write Custom Media from Common JS file +/* ========================================================================== */ + +async function writeCustomMediaToCjsFile(to, customMedia) { + const jsContents = Object.keys(customMedia).reduce((jsLines, name) => { + jsLines.push(`\t\t'${escapeForJS(name)}': '${escapeForJS(customMedia[name])}'`); + + return jsLines; + }, []).join(',\n'); + const js = `module.exports = {\n\tcustomMedia: {\n${jsContents}\n\t}\n};\n`; + + await writeFile(to, js); +} + +/* Write Custom Media from Module JS file +/* ========================================================================== */ + +async function writeCustomMediaToMjsFile(to, customMedia) { + const mjsContents = Object.keys(customMedia).reduce((mjsLines, name) => { + mjsLines.push(`\t'${escapeForJS(name)}': '${escapeForJS(customMedia[name])}'`); + + return mjsLines; + }, []).join(',\n'); + const mjs = `export const customMedia = {\n${mjsContents}\n};\n`; + + await writeFile(to, mjs); +} + +/* Write Custom Media to Exports +/* ========================================================================== */ + +export default function writeCustomMediaToExports(customMedia, destinations) { + return Promise.all(destinations.map(async destination => { + if (destination instanceof Function) { + await destination(defaultCustomMediaToJSON(customMedia)); + } else { + // read the destination as an object + const opts = destination === Object(destination) ? destination : { to: String(destination) }; + + // transformer for custom media into a JSON-compatible object + const toJSON = opts.toJSON || defaultCustomMediaToJSON; + + if ('customMedia' in opts) { + // write directly to an object as customMedia + opts.customMedia = toJSON(customMedia); + } else if ('custom-media' in opts) { + // write directly to an object as custom-media + opts['custom-media'] = toJSON(customMedia); + } else { + // destination pathname + const to = String(opts.to || ''); + + // type of file being written to + const type = (opts.type || path.extname(to).slice(1)).toLowerCase(); + + // transformed custom media + const customMediaJSON = toJSON(customMedia); + + if (type === 'css') { + await writeCustomMediaToCssFile(to, customMediaJSON); + } + + if (type === 'js') { + await writeCustomMediaToCjsFile(to, customMediaJSON); + } + + if (type === 'json') { + await writeCustomMediaToJsonFile(to, customMediaJSON); + } + + if (type === 'mjs') { + await writeCustomMediaToMjsFile(to, customMediaJSON); + } + } + } + })); +} + +/* Helper utilities +/* ========================================================================== */ + +const defaultCustomMediaToJSON = customMedia => { + return Object.keys(customMedia).reduce((customMediaJSON, key) => { + customMediaJSON[key] = String(customMedia[key]); + + return customMediaJSON; + }, {}); +}; + +const writeFile = (to, text) => new Promise((resolve, reject) => { + fs.writeFile(to, text, error => { + if (error) { + reject(error); + } else { + resolve(); + } + }); +}); + +const escapeForJS = string => string.replace(/\\([\s\S])|(')/g, '\\$1$2').replace(/\n/g, '\\n').replace(/\r/g, '\\r'); diff --git a/plugins/postcss-custom-media-import-export/test/_import.mjs b/plugins/postcss-custom-media-import-export/test/_import.mjs new file mode 100644 index 000000000..0b023a196 --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/_import.mjs @@ -0,0 +1,6 @@ +import assert from 'assert'; +import plugin from '@csstools/postcss-custom-media-import-export'; +plugin(); + +assert.ok(plugin.postcss, 'should have "postcss flag"'); +assert.equal(typeof plugin, 'function', 'should return a function'); diff --git a/plugins/postcss-custom-media-import-export/test/_require.cjs b/plugins/postcss-custom-media-import-export/test/_require.cjs new file mode 100644 index 000000000..8af6094ba --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/_require.cjs @@ -0,0 +1,6 @@ +const assert = require('assert'); +const plugin = require('@csstools/postcss-custom-media-import-export'); +plugin(); + +assert.ok(plugin.postcss, 'should have "postcss flag"'); +assert.equal(typeof plugin, 'function', 'should return a function'); diff --git a/plugins/postcss-custom-media-import-export/test/basic.css b/plugins/postcss-custom-media-import-export/test/basic.css new file mode 100644 index 000000000..a992cfe2f --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/basic.css @@ -0,0 +1,132 @@ +@custom-media --mq-a (max-width: 30em), (max-height: 30em); +@custom-media --mq-b screen and (max-width: 30em); +@custom-media --not-mq-a not all and (--mq-a); + +@media (--mq-a) { + body { + order: 1; + } +} + +@media (--mq-b) { + body { + order: 1; + } +} + +@media (--mq-a), (--mq-a) { + body { + order: 1; + } +} + +@media not all and (--mq-a) { + body { + order: 2; + } +} + +@media (--not-mq-a) { + body { + order: 1; + } +} + +@media not all and (--not-mq-a) { + body { + order: 2; + } +} + +@custom-media --circular-mq-a (--circular-mq-b); +@custom-media --circular-mq-b (--circular-mq-a); + +@media (--circular-mq-a) { + body { + order: 3; + } +} + +@media (--circular-mq-b) { + body { + order: 4; + } +} + +@media (--unresolved-mq) { + body { + order: 5; + } +} + +@custom-media --min (min-width: 320px); +@custom-media --max (max-width: 640px); + +@media (--min) and (--max) { + body { + order: 6; + } +} + +@custom-media --concat (min-width: 320px) and (max-width: 640px); + +@media (--concat) { + body { + order: 7; + } +} + +@media (--concat) and (min-aspect-ratio: 16/9) { + body { + order: 8; + } +} + +@media ( --mq-a ) { + body { + order: 1000; + } +} + +@media ( --mq-a ) { + body { + order: 1001; + } +} + +@media ( --mq-a ), ( --mq-a ) { + body { + order: 1002; + } +} + +@media ( --mq-a ), ( --mq-a ) { + body { + order: 1003; + } +} + +@media ( --mq-a ), ( --mq-a ) { + body { + order: 1004; + } +} + +@media ( + --mq-a +), +( + --mq-a +) { + body { + order: 1005; + } +} + +@media (trailer--) { + body { + order: 1006; + } +} + +@custom-media trailer-- (min-width: 320px); diff --git a/plugins/postcss-custom-media-import-export/test/basic.export-css-to-type.expect.css b/plugins/postcss-custom-media-import-export/test/basic.export-css-to-type.expect.css new file mode 100644 index 000000000..a992cfe2f --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/basic.export-css-to-type.expect.css @@ -0,0 +1,132 @@ +@custom-media --mq-a (max-width: 30em), (max-height: 30em); +@custom-media --mq-b screen and (max-width: 30em); +@custom-media --not-mq-a not all and (--mq-a); + +@media (--mq-a) { + body { + order: 1; + } +} + +@media (--mq-b) { + body { + order: 1; + } +} + +@media (--mq-a), (--mq-a) { + body { + order: 1; + } +} + +@media not all and (--mq-a) { + body { + order: 2; + } +} + +@media (--not-mq-a) { + body { + order: 1; + } +} + +@media not all and (--not-mq-a) { + body { + order: 2; + } +} + +@custom-media --circular-mq-a (--circular-mq-b); +@custom-media --circular-mq-b (--circular-mq-a); + +@media (--circular-mq-a) { + body { + order: 3; + } +} + +@media (--circular-mq-b) { + body { + order: 4; + } +} + +@media (--unresolved-mq) { + body { + order: 5; + } +} + +@custom-media --min (min-width: 320px); +@custom-media --max (max-width: 640px); + +@media (--min) and (--max) { + body { + order: 6; + } +} + +@custom-media --concat (min-width: 320px) and (max-width: 640px); + +@media (--concat) { + body { + order: 7; + } +} + +@media (--concat) and (min-aspect-ratio: 16/9) { + body { + order: 8; + } +} + +@media ( --mq-a ) { + body { + order: 1000; + } +} + +@media ( --mq-a ) { + body { + order: 1001; + } +} + +@media ( --mq-a ), ( --mq-a ) { + body { + order: 1002; + } +} + +@media ( --mq-a ), ( --mq-a ) { + body { + order: 1003; + } +} + +@media ( --mq-a ), ( --mq-a ) { + body { + order: 1004; + } +} + +@media ( + --mq-a +), +( + --mq-a +) { + body { + order: 1005; + } +} + +@media (trailer--) { + body { + order: 1006; + } +} + +@custom-media trailer-- (min-width: 320px); diff --git a/plugins/postcss-custom-media-import-export/test/basic.export-css-to.expect.css b/plugins/postcss-custom-media-import-export/test/basic.export-css-to.expect.css new file mode 100644 index 000000000..a992cfe2f --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/basic.export-css-to.expect.css @@ -0,0 +1,132 @@ +@custom-media --mq-a (max-width: 30em), (max-height: 30em); +@custom-media --mq-b screen and (max-width: 30em); +@custom-media --not-mq-a not all and (--mq-a); + +@media (--mq-a) { + body { + order: 1; + } +} + +@media (--mq-b) { + body { + order: 1; + } +} + +@media (--mq-a), (--mq-a) { + body { + order: 1; + } +} + +@media not all and (--mq-a) { + body { + order: 2; + } +} + +@media (--not-mq-a) { + body { + order: 1; + } +} + +@media not all and (--not-mq-a) { + body { + order: 2; + } +} + +@custom-media --circular-mq-a (--circular-mq-b); +@custom-media --circular-mq-b (--circular-mq-a); + +@media (--circular-mq-a) { + body { + order: 3; + } +} + +@media (--circular-mq-b) { + body { + order: 4; + } +} + +@media (--unresolved-mq) { + body { + order: 5; + } +} + +@custom-media --min (min-width: 320px); +@custom-media --max (max-width: 640px); + +@media (--min) and (--max) { + body { + order: 6; + } +} + +@custom-media --concat (min-width: 320px) and (max-width: 640px); + +@media (--concat) { + body { + order: 7; + } +} + +@media (--concat) and (min-aspect-ratio: 16/9) { + body { + order: 8; + } +} + +@media ( --mq-a ) { + body { + order: 1000; + } +} + +@media ( --mq-a ) { + body { + order: 1001; + } +} + +@media ( --mq-a ), ( --mq-a ) { + body { + order: 1002; + } +} + +@media ( --mq-a ), ( --mq-a ) { + body { + order: 1003; + } +} + +@media ( --mq-a ), ( --mq-a ) { + body { + order: 1004; + } +} + +@media ( + --mq-a +), +( + --mq-a +) { + body { + order: 1005; + } +} + +@media (trailer--) { + body { + order: 1006; + } +} + +@custom-media trailer-- (min-width: 320px); diff --git a/plugins/postcss-custom-media-import-export/test/basic.export-css.expect.css b/plugins/postcss-custom-media-import-export/test/basic.export-css.expect.css new file mode 100644 index 000000000..a992cfe2f --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/basic.export-css.expect.css @@ -0,0 +1,132 @@ +@custom-media --mq-a (max-width: 30em), (max-height: 30em); +@custom-media --mq-b screen and (max-width: 30em); +@custom-media --not-mq-a not all and (--mq-a); + +@media (--mq-a) { + body { + order: 1; + } +} + +@media (--mq-b) { + body { + order: 1; + } +} + +@media (--mq-a), (--mq-a) { + body { + order: 1; + } +} + +@media not all and (--mq-a) { + body { + order: 2; + } +} + +@media (--not-mq-a) { + body { + order: 1; + } +} + +@media not all and (--not-mq-a) { + body { + order: 2; + } +} + +@custom-media --circular-mq-a (--circular-mq-b); +@custom-media --circular-mq-b (--circular-mq-a); + +@media (--circular-mq-a) { + body { + order: 3; + } +} + +@media (--circular-mq-b) { + body { + order: 4; + } +} + +@media (--unresolved-mq) { + body { + order: 5; + } +} + +@custom-media --min (min-width: 320px); +@custom-media --max (max-width: 640px); + +@media (--min) and (--max) { + body { + order: 6; + } +} + +@custom-media --concat (min-width: 320px) and (max-width: 640px); + +@media (--concat) { + body { + order: 7; + } +} + +@media (--concat) and (min-aspect-ratio: 16/9) { + body { + order: 8; + } +} + +@media ( --mq-a ) { + body { + order: 1000; + } +} + +@media ( --mq-a ) { + body { + order: 1001; + } +} + +@media ( --mq-a ), ( --mq-a ) { + body { + order: 1002; + } +} + +@media ( --mq-a ), ( --mq-a ) { + body { + order: 1003; + } +} + +@media ( --mq-a ), ( --mq-a ) { + body { + order: 1004; + } +} + +@media ( + --mq-a +), +( + --mq-a +) { + body { + order: 1005; + } +} + +@media (trailer--) { + body { + order: 1006; + } +} + +@custom-media trailer-- (min-width: 320px); diff --git a/plugins/postcss-custom-media-import-export/test/basic.export-fn-promise.expect.css b/plugins/postcss-custom-media-import-export/test/basic.export-fn-promise.expect.css new file mode 100644 index 000000000..a992cfe2f --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/basic.export-fn-promise.expect.css @@ -0,0 +1,132 @@ +@custom-media --mq-a (max-width: 30em), (max-height: 30em); +@custom-media --mq-b screen and (max-width: 30em); +@custom-media --not-mq-a not all and (--mq-a); + +@media (--mq-a) { + body { + order: 1; + } +} + +@media (--mq-b) { + body { + order: 1; + } +} + +@media (--mq-a), (--mq-a) { + body { + order: 1; + } +} + +@media not all and (--mq-a) { + body { + order: 2; + } +} + +@media (--not-mq-a) { + body { + order: 1; + } +} + +@media not all and (--not-mq-a) { + body { + order: 2; + } +} + +@custom-media --circular-mq-a (--circular-mq-b); +@custom-media --circular-mq-b (--circular-mq-a); + +@media (--circular-mq-a) { + body { + order: 3; + } +} + +@media (--circular-mq-b) { + body { + order: 4; + } +} + +@media (--unresolved-mq) { + body { + order: 5; + } +} + +@custom-media --min (min-width: 320px); +@custom-media --max (max-width: 640px); + +@media (--min) and (--max) { + body { + order: 6; + } +} + +@custom-media --concat (min-width: 320px) and (max-width: 640px); + +@media (--concat) { + body { + order: 7; + } +} + +@media (--concat) and (min-aspect-ratio: 16/9) { + body { + order: 8; + } +} + +@media ( --mq-a ) { + body { + order: 1000; + } +} + +@media ( --mq-a ) { + body { + order: 1001; + } +} + +@media ( --mq-a ), ( --mq-a ) { + body { + order: 1002; + } +} + +@media ( --mq-a ), ( --mq-a ) { + body { + order: 1003; + } +} + +@media ( --mq-a ), ( --mq-a ) { + body { + order: 1004; + } +} + +@media ( + --mq-a +), +( + --mq-a +) { + body { + order: 1005; + } +} + +@media (trailer--) { + body { + order: 1006; + } +} + +@custom-media trailer-- (min-width: 320px); diff --git a/plugins/postcss-custom-media-import-export/test/basic.export-fn.expect.css b/plugins/postcss-custom-media-import-export/test/basic.export-fn.expect.css new file mode 100644 index 000000000..a992cfe2f --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/basic.export-fn.expect.css @@ -0,0 +1,132 @@ +@custom-media --mq-a (max-width: 30em), (max-height: 30em); +@custom-media --mq-b screen and (max-width: 30em); +@custom-media --not-mq-a not all and (--mq-a); + +@media (--mq-a) { + body { + order: 1; + } +} + +@media (--mq-b) { + body { + order: 1; + } +} + +@media (--mq-a), (--mq-a) { + body { + order: 1; + } +} + +@media not all and (--mq-a) { + body { + order: 2; + } +} + +@media (--not-mq-a) { + body { + order: 1; + } +} + +@media not all and (--not-mq-a) { + body { + order: 2; + } +} + +@custom-media --circular-mq-a (--circular-mq-b); +@custom-media --circular-mq-b (--circular-mq-a); + +@media (--circular-mq-a) { + body { + order: 3; + } +} + +@media (--circular-mq-b) { + body { + order: 4; + } +} + +@media (--unresolved-mq) { + body { + order: 5; + } +} + +@custom-media --min (min-width: 320px); +@custom-media --max (max-width: 640px); + +@media (--min) and (--max) { + body { + order: 6; + } +} + +@custom-media --concat (min-width: 320px) and (max-width: 640px); + +@media (--concat) { + body { + order: 7; + } +} + +@media (--concat) and (min-aspect-ratio: 16/9) { + body { + order: 8; + } +} + +@media ( --mq-a ) { + body { + order: 1000; + } +} + +@media ( --mq-a ) { + body { + order: 1001; + } +} + +@media ( --mq-a ), ( --mq-a ) { + body { + order: 1002; + } +} + +@media ( --mq-a ), ( --mq-a ) { + body { + order: 1003; + } +} + +@media ( --mq-a ), ( --mq-a ) { + body { + order: 1004; + } +} + +@media ( + --mq-a +), +( + --mq-a +) { + body { + order: 1005; + } +} + +@media (trailer--) { + body { + order: 1006; + } +} + +@custom-media trailer-- (min-width: 320px); diff --git a/plugins/postcss-custom-media-import-export/test/basic.export-js.expect.css b/plugins/postcss-custom-media-import-export/test/basic.export-js.expect.css new file mode 100644 index 000000000..a992cfe2f --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/basic.export-js.expect.css @@ -0,0 +1,132 @@ +@custom-media --mq-a (max-width: 30em), (max-height: 30em); +@custom-media --mq-b screen and (max-width: 30em); +@custom-media --not-mq-a not all and (--mq-a); + +@media (--mq-a) { + body { + order: 1; + } +} + +@media (--mq-b) { + body { + order: 1; + } +} + +@media (--mq-a), (--mq-a) { + body { + order: 1; + } +} + +@media not all and (--mq-a) { + body { + order: 2; + } +} + +@media (--not-mq-a) { + body { + order: 1; + } +} + +@media not all and (--not-mq-a) { + body { + order: 2; + } +} + +@custom-media --circular-mq-a (--circular-mq-b); +@custom-media --circular-mq-b (--circular-mq-a); + +@media (--circular-mq-a) { + body { + order: 3; + } +} + +@media (--circular-mq-b) { + body { + order: 4; + } +} + +@media (--unresolved-mq) { + body { + order: 5; + } +} + +@custom-media --min (min-width: 320px); +@custom-media --max (max-width: 640px); + +@media (--min) and (--max) { + body { + order: 6; + } +} + +@custom-media --concat (min-width: 320px) and (max-width: 640px); + +@media (--concat) { + body { + order: 7; + } +} + +@media (--concat) and (min-aspect-ratio: 16/9) { + body { + order: 8; + } +} + +@media ( --mq-a ) { + body { + order: 1000; + } +} + +@media ( --mq-a ) { + body { + order: 1001; + } +} + +@media ( --mq-a ), ( --mq-a ) { + body { + order: 1002; + } +} + +@media ( --mq-a ), ( --mq-a ) { + body { + order: 1003; + } +} + +@media ( --mq-a ), ( --mq-a ) { + body { + order: 1004; + } +} + +@media ( + --mq-a +), +( + --mq-a +) { + body { + order: 1005; + } +} + +@media (trailer--) { + body { + order: 1006; + } +} + +@custom-media trailer-- (min-width: 320px); diff --git a/plugins/postcss-custom-media-import-export/test/basic.export-json.expect.css b/plugins/postcss-custom-media-import-export/test/basic.export-json.expect.css new file mode 100644 index 000000000..a992cfe2f --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/basic.export-json.expect.css @@ -0,0 +1,132 @@ +@custom-media --mq-a (max-width: 30em), (max-height: 30em); +@custom-media --mq-b screen and (max-width: 30em); +@custom-media --not-mq-a not all and (--mq-a); + +@media (--mq-a) { + body { + order: 1; + } +} + +@media (--mq-b) { + body { + order: 1; + } +} + +@media (--mq-a), (--mq-a) { + body { + order: 1; + } +} + +@media not all and (--mq-a) { + body { + order: 2; + } +} + +@media (--not-mq-a) { + body { + order: 1; + } +} + +@media not all and (--not-mq-a) { + body { + order: 2; + } +} + +@custom-media --circular-mq-a (--circular-mq-b); +@custom-media --circular-mq-b (--circular-mq-a); + +@media (--circular-mq-a) { + body { + order: 3; + } +} + +@media (--circular-mq-b) { + body { + order: 4; + } +} + +@media (--unresolved-mq) { + body { + order: 5; + } +} + +@custom-media --min (min-width: 320px); +@custom-media --max (max-width: 640px); + +@media (--min) and (--max) { + body { + order: 6; + } +} + +@custom-media --concat (min-width: 320px) and (max-width: 640px); + +@media (--concat) { + body { + order: 7; + } +} + +@media (--concat) and (min-aspect-ratio: 16/9) { + body { + order: 8; + } +} + +@media ( --mq-a ) { + body { + order: 1000; + } +} + +@media ( --mq-a ) { + body { + order: 1001; + } +} + +@media ( --mq-a ), ( --mq-a ) { + body { + order: 1002; + } +} + +@media ( --mq-a ), ( --mq-a ) { + body { + order: 1003; + } +} + +@media ( --mq-a ), ( --mq-a ) { + body { + order: 1004; + } +} + +@media ( + --mq-a +), +( + --mq-a +) { + body { + order: 1005; + } +} + +@media (trailer--) { + body { + order: 1006; + } +} + +@custom-media trailer-- (min-width: 320px); diff --git a/plugins/postcss-custom-media-import-export/test/basic.export-mjs.expect.css b/plugins/postcss-custom-media-import-export/test/basic.export-mjs.expect.css new file mode 100644 index 000000000..a992cfe2f --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/basic.export-mjs.expect.css @@ -0,0 +1,132 @@ +@custom-media --mq-a (max-width: 30em), (max-height: 30em); +@custom-media --mq-b screen and (max-width: 30em); +@custom-media --not-mq-a not all and (--mq-a); + +@media (--mq-a) { + body { + order: 1; + } +} + +@media (--mq-b) { + body { + order: 1; + } +} + +@media (--mq-a), (--mq-a) { + body { + order: 1; + } +} + +@media not all and (--mq-a) { + body { + order: 2; + } +} + +@media (--not-mq-a) { + body { + order: 1; + } +} + +@media not all and (--not-mq-a) { + body { + order: 2; + } +} + +@custom-media --circular-mq-a (--circular-mq-b); +@custom-media --circular-mq-b (--circular-mq-a); + +@media (--circular-mq-a) { + body { + order: 3; + } +} + +@media (--circular-mq-b) { + body { + order: 4; + } +} + +@media (--unresolved-mq) { + body { + order: 5; + } +} + +@custom-media --min (min-width: 320px); +@custom-media --max (max-width: 640px); + +@media (--min) and (--max) { + body { + order: 6; + } +} + +@custom-media --concat (min-width: 320px) and (max-width: 640px); + +@media (--concat) { + body { + order: 7; + } +} + +@media (--concat) and (min-aspect-ratio: 16/9) { + body { + order: 8; + } +} + +@media ( --mq-a ) { + body { + order: 1000; + } +} + +@media ( --mq-a ) { + body { + order: 1001; + } +} + +@media ( --mq-a ), ( --mq-a ) { + body { + order: 1002; + } +} + +@media ( --mq-a ), ( --mq-a ) { + body { + order: 1003; + } +} + +@media ( --mq-a ), ( --mq-a ) { + body { + order: 1004; + } +} + +@media ( + --mq-a +), +( + --mq-a +) { + body { + order: 1005; + } +} + +@media (trailer--) { + body { + order: 1006; + } +} + +@custom-media trailer-- (min-width: 320px); diff --git a/plugins/postcss-custom-media-import-export/test/basic.export.expect.css b/plugins/postcss-custom-media-import-export/test/basic.export.expect.css new file mode 100644 index 000000000..a992cfe2f --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/basic.export.expect.css @@ -0,0 +1,132 @@ +@custom-media --mq-a (max-width: 30em), (max-height: 30em); +@custom-media --mq-b screen and (max-width: 30em); +@custom-media --not-mq-a not all and (--mq-a); + +@media (--mq-a) { + body { + order: 1; + } +} + +@media (--mq-b) { + body { + order: 1; + } +} + +@media (--mq-a), (--mq-a) { + body { + order: 1; + } +} + +@media not all and (--mq-a) { + body { + order: 2; + } +} + +@media (--not-mq-a) { + body { + order: 1; + } +} + +@media not all and (--not-mq-a) { + body { + order: 2; + } +} + +@custom-media --circular-mq-a (--circular-mq-b); +@custom-media --circular-mq-b (--circular-mq-a); + +@media (--circular-mq-a) { + body { + order: 3; + } +} + +@media (--circular-mq-b) { + body { + order: 4; + } +} + +@media (--unresolved-mq) { + body { + order: 5; + } +} + +@custom-media --min (min-width: 320px); +@custom-media --max (max-width: 640px); + +@media (--min) and (--max) { + body { + order: 6; + } +} + +@custom-media --concat (min-width: 320px) and (max-width: 640px); + +@media (--concat) { + body { + order: 7; + } +} + +@media (--concat) and (min-aspect-ratio: 16/9) { + body { + order: 8; + } +} + +@media ( --mq-a ) { + body { + order: 1000; + } +} + +@media ( --mq-a ) { + body { + order: 1001; + } +} + +@media ( --mq-a ), ( --mq-a ) { + body { + order: 1002; + } +} + +@media ( --mq-a ), ( --mq-a ) { + body { + order: 1003; + } +} + +@media ( --mq-a ), ( --mq-a ) { + body { + order: 1004; + } +} + +@media ( + --mq-a +), +( + --mq-a +) { + body { + order: 1005; + } +} + +@media (trailer--) { + body { + order: 1006; + } +} + +@custom-media trailer-- (min-width: 320px); diff --git a/plugins/postcss-custom-media-import-export/test/basic.import.expect.css b/plugins/postcss-custom-media-import-export/test/basic.import.expect.css new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/postcss-custom-media-import-export/test/examples/.gitkeep b/plugins/postcss-custom-media-import-export/test/examples/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/postcss-custom-media-import-export/test/export-media.css b/plugins/postcss-custom-media-import-export/test/export-media.css new file mode 100644 index 000000000..f51e88c82 --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/export-media.css @@ -0,0 +1,8 @@ +@custom-media --mq-a (max-width: 30em), (max-height: 30em); +@custom-media --mq-b screen and (max-width: 30em); +@custom-media --not-mq-a not all and (--mq-a); +@custom-media --circular-mq-a (--circular-mq-b); +@custom-media --circular-mq-b (--circular-mq-a); +@custom-media --min (min-width: 320px); +@custom-media --max (max-width: 640px); +@custom-media --concat (min-width: 320px) and (max-width: 640px); diff --git a/plugins/postcss-custom-media-import-export/test/export-media.js b/plugins/postcss-custom-media-import-export/test/export-media.js new file mode 100644 index 000000000..acccd8408 --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/export-media.js @@ -0,0 +1,12 @@ +module.exports = { + customMedia: { + '--mq-a': '(max-width: 30em), (max-height: 30em)', + '--mq-b': 'screen and (max-width: 30em)', + '--not-mq-a': 'not all and (--mq-a)', + '--circular-mq-a': '(--circular-mq-b)', + '--circular-mq-b': '(--circular-mq-a)', + '--min': '(min-width: 320px)', + '--max': '(max-width: 640px)', + '--concat': '(min-width: 320px) and (max-width: 640px)' + } +}; diff --git a/plugins/postcss-custom-media-import-export/test/export-media.json b/plugins/postcss-custom-media-import-export/test/export-media.json new file mode 100644 index 000000000..729bde28e --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/export-media.json @@ -0,0 +1,12 @@ +{ + "custom-media": { + "--mq-a": "(max-width: 30em), (max-height: 30em)", + "--mq-b": "screen and (max-width: 30em)", + "--not-mq-a": "not all and (--mq-a)", + "--circular-mq-a": "(--circular-mq-b)", + "--circular-mq-b": "(--circular-mq-a)", + "--min": "(min-width: 320px)", + "--max": "(max-width: 640px)", + "--concat": "(min-width: 320px) and (max-width: 640px)" + } +} diff --git a/plugins/postcss-custom-media-import-export/test/export-media.mjs b/plugins/postcss-custom-media-import-export/test/export-media.mjs new file mode 100644 index 000000000..ea36519d8 --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/export-media.mjs @@ -0,0 +1,10 @@ +export const customMedia = { + '--mq-a': '(max-width: 30em), (max-height: 30em)', + '--mq-b': 'screen and (max-width: 30em)', + '--not-mq-a': 'not all and (--mq-a)', + '--circular-mq-a': '(--circular-mq-b)', + '--circular-mq-b': '(--circular-mq-a)', + '--min': '(min-width: 320px)', + '--max': '(max-width: 640px)', + '--concat': '(min-width: 320px) and (max-width: 640px)' +}; diff --git a/plugins/postcss-custom-media-import-export/test/import-css.css b/plugins/postcss-custom-media-import-export/test/import-css.css new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/postcss-custom-media-import-export/test/import-media.css b/plugins/postcss-custom-media-import-export/test/import-media.css new file mode 100644 index 000000000..e788f32ae --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/import-media.css @@ -0,0 +1,2 @@ +@custom-media --mq-a (max-width: 30em), (max-height: 30em); +@custom-media --not-mq-a not all and (--mq-a); diff --git a/plugins/postcss-custom-media-import-export/test/import-media.js b/plugins/postcss-custom-media-import-export/test/import-media.js new file mode 100644 index 000000000..3f2e0401a --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/import-media.js @@ -0,0 +1,6 @@ +module.exports = { + customMedia: { + '--mq-a': '(max-width: 30em), (max-height: 30em)', + '--not-mq-a': 'not all and (--mq-a)' + } +} diff --git a/plugins/postcss-custom-media-import-export/test/import-media.json b/plugins/postcss-custom-media-import-export/test/import-media.json new file mode 100644 index 000000000..807d8dfdd --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/import-media.json @@ -0,0 +1,6 @@ +{ + "customMedia": { + "--mq-a": "(max-width: 30em), (max-height: 30em)", + "--not-mq-a": "not all and (--mq-a)" + } +} diff --git a/plugins/postcss-custom-media-import-export/test/import.css b/plugins/postcss-custom-media-import-export/test/import.css new file mode 100644 index 000000000..f37220e74 --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/import.css @@ -0,0 +1,29 @@ +@media (--mq-a) { + body { + order: 1; + } +} + +@media (--mq-a), (--mq-a) { + body { + order: 1; + } +} + +@media not all and (--mq-a) { + body { + order: 2; + } +} + +@media (--not-mq-a) { + body { + order: 1; + } +} + +@media not all and (--not-mq-a) { + body { + order: 2; + } +} diff --git a/plugins/postcss-custom-media-import-export/test/import.css-from-type.expect.css b/plugins/postcss-custom-media-import-export/test/import.css-from-type.expect.css new file mode 100644 index 000000000..6bbae6719 --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/import.css-from-type.expect.css @@ -0,0 +1,33 @@ +@custom-media --mq-a (max-width: 30em), (max-height: 30em); + +@custom-media --not-mq-a not all and (--mq-a); + +@media (--mq-a) { + body { + order: 1; + } +} + +@media (--mq-a), (--mq-a) { + body { + order: 1; + } +} + +@media not all and (--mq-a) { + body { + order: 2; + } +} + +@media (--not-mq-a) { + body { + order: 1; + } +} + +@media not all and (--not-mq-a) { + body { + order: 2; + } +} diff --git a/plugins/postcss-custom-media-import-export/test/import.css-from.expect.css b/plugins/postcss-custom-media-import-export/test/import.css-from.expect.css new file mode 100644 index 000000000..6bbae6719 --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/import.css-from.expect.css @@ -0,0 +1,33 @@ +@custom-media --mq-a (max-width: 30em), (max-height: 30em); + +@custom-media --not-mq-a not all and (--mq-a); + +@media (--mq-a) { + body { + order: 1; + } +} + +@media (--mq-a), (--mq-a) { + body { + order: 1; + } +} + +@media not all and (--mq-a) { + body { + order: 2; + } +} + +@media (--not-mq-a) { + body { + order: 1; + } +} + +@media not all and (--not-mq-a) { + body { + order: 2; + } +} diff --git a/plugins/postcss-custom-media-import-export/test/import.css.expect.css b/plugins/postcss-custom-media-import-export/test/import.css.expect.css new file mode 100644 index 000000000..6bbae6719 --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/import.css.expect.css @@ -0,0 +1,33 @@ +@custom-media --mq-a (max-width: 30em), (max-height: 30em); + +@custom-media --not-mq-a not all and (--mq-a); + +@media (--mq-a) { + body { + order: 1; + } +} + +@media (--mq-a), (--mq-a) { + body { + order: 1; + } +} + +@media not all and (--mq-a) { + body { + order: 2; + } +} + +@media (--not-mq-a) { + body { + order: 1; + } +} + +@media not all and (--not-mq-a) { + body { + order: 2; + } +} diff --git a/plugins/postcss-custom-media-import-export/test/import.empty.expect.css b/plugins/postcss-custom-media-import-export/test/import.empty.expect.css new file mode 100644 index 000000000..f37220e74 --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/import.empty.expect.css @@ -0,0 +1,29 @@ +@media (--mq-a) { + body { + order: 1; + } +} + +@media (--mq-a), (--mq-a) { + body { + order: 1; + } +} + +@media not all and (--mq-a) { + body { + order: 2; + } +} + +@media (--not-mq-a) { + body { + order: 1; + } +} + +@media not all and (--not-mq-a) { + body { + order: 2; + } +} diff --git a/plugins/postcss-custom-media-import-export/test/import.expect.css b/plugins/postcss-custom-media-import-export/test/import.expect.css new file mode 100644 index 000000000..6bbae6719 --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/import.expect.css @@ -0,0 +1,33 @@ +@custom-media --mq-a (max-width: 30em), (max-height: 30em); + +@custom-media --not-mq-a not all and (--mq-a); + +@media (--mq-a) { + body { + order: 1; + } +} + +@media (--mq-a), (--mq-a) { + body { + order: 1; + } +} + +@media not all and (--mq-a) { + body { + order: 2; + } +} + +@media (--not-mq-a) { + body { + order: 1; + } +} + +@media not all and (--not-mq-a) { + body { + order: 2; + } +} diff --git a/plugins/postcss-custom-media-import-export/test/import.import-fn-promise.expect.css b/plugins/postcss-custom-media-import-export/test/import.import-fn-promise.expect.css new file mode 100644 index 000000000..6bbae6719 --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/import.import-fn-promise.expect.css @@ -0,0 +1,33 @@ +@custom-media --mq-a (max-width: 30em), (max-height: 30em); + +@custom-media --not-mq-a not all and (--mq-a); + +@media (--mq-a) { + body { + order: 1; + } +} + +@media (--mq-a), (--mq-a) { + body { + order: 1; + } +} + +@media not all and (--mq-a) { + body { + order: 2; + } +} + +@media (--not-mq-a) { + body { + order: 1; + } +} + +@media not all and (--not-mq-a) { + body { + order: 2; + } +} diff --git a/plugins/postcss-custom-media-import-export/test/import.import-fn.expect.css b/plugins/postcss-custom-media-import-export/test/import.import-fn.expect.css new file mode 100644 index 000000000..6bbae6719 --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/import.import-fn.expect.css @@ -0,0 +1,33 @@ +@custom-media --mq-a (max-width: 30em), (max-height: 30em); + +@custom-media --not-mq-a not all and (--mq-a); + +@media (--mq-a) { + body { + order: 1; + } +} + +@media (--mq-a), (--mq-a) { + body { + order: 1; + } +} + +@media not all and (--mq-a) { + body { + order: 2; + } +} + +@media (--not-mq-a) { + body { + order: 1; + } +} + +@media not all and (--not-mq-a) { + body { + order: 2; + } +} diff --git a/plugins/postcss-custom-media-import-export/test/import.imported-styles-override-document-styles.false.expect.css b/plugins/postcss-custom-media-import-export/test/import.imported-styles-override-document-styles.false.expect.css new file mode 100644 index 000000000..6bbae6719 --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/import.imported-styles-override-document-styles.false.expect.css @@ -0,0 +1,33 @@ +@custom-media --mq-a (max-width: 30em), (max-height: 30em); + +@custom-media --not-mq-a not all and (--mq-a); + +@media (--mq-a) { + body { + order: 1; + } +} + +@media (--mq-a), (--mq-a) { + body { + order: 1; + } +} + +@media not all and (--mq-a) { + body { + order: 2; + } +} + +@media (--not-mq-a) { + body { + order: 1; + } +} + +@media not all and (--not-mq-a) { + body { + order: 2; + } +} diff --git a/plugins/postcss-custom-media-import-export/test/import.imported-styles-override-document-styles.true.expect.css b/plugins/postcss-custom-media-import-export/test/import.imported-styles-override-document-styles.true.expect.css new file mode 100644 index 000000000..5b32dc079 --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/import.imported-styles-override-document-styles.true.expect.css @@ -0,0 +1,33 @@ +@media (--mq-a) { + body { + order: 1; + } +} + +@media (--mq-a), (--mq-a) { + body { + order: 1; + } +} + +@media not all and (--mq-a) { + body { + order: 2; + } +} + +@media (--not-mq-a) { + body { + order: 1; + } +} + +@media not all and (--not-mq-a) { + body { + order: 2; + } +} + +@custom-media --not-mq-a not all and (--mq-a); + +@custom-media --mq-a (max-width: 30em), (max-height: 30em) diff --git a/plugins/postcss-custom-media-import-export/test/import.js.expect.css b/plugins/postcss-custom-media-import-export/test/import.js.expect.css new file mode 100644 index 000000000..6bbae6719 --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/import.js.expect.css @@ -0,0 +1,33 @@ +@custom-media --mq-a (max-width: 30em), (max-height: 30em); + +@custom-media --not-mq-a not all and (--mq-a); + +@media (--mq-a) { + body { + order: 1; + } +} + +@media (--mq-a), (--mq-a) { + body { + order: 1; + } +} + +@media not all and (--mq-a) { + body { + order: 2; + } +} + +@media (--not-mq-a) { + body { + order: 1; + } +} + +@media not all and (--not-mq-a) { + body { + order: 2; + } +} diff --git a/plugins/postcss-custom-media-import-export/test/import.json.expect.css b/plugins/postcss-custom-media-import-export/test/import.json.expect.css new file mode 100644 index 000000000..6bbae6719 --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/import.json.expect.css @@ -0,0 +1,33 @@ +@custom-media --mq-a (max-width: 30em), (max-height: 30em); + +@custom-media --not-mq-a not all and (--mq-a); + +@media (--mq-a) { + body { + order: 1; + } +} + +@media (--mq-a), (--mq-a) { + body { + order: 1; + } +} + +@media not all and (--mq-a) { + body { + order: 2; + } +} + +@media (--not-mq-a) { + body { + order: 1; + } +} + +@media not all and (--not-mq-a) { + body { + order: 2; + } +} diff --git a/plugins/postcss-custom-media-import-export/test/import.plugin.expect.css b/plugins/postcss-custom-media-import-export/test/import.plugin.expect.css new file mode 100644 index 000000000..0bc2bbf3b --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/import.plugin.expect.css @@ -0,0 +1,29 @@ +@media (max-width: 30em),(max-height: 30em) { + body { + order: 1; + } +} + +@media (max-width: 30em),(max-height: 30em), (max-width: 30em), (max-height: 30em) { + body { + order: 1; + } +} + +@media not all and (max-width: 30em),not all and (max-height: 30em) { + body { + order: 2; + } +} + +@media not all and (max-width: 30em),not all and (max-height: 30em) { + body { + order: 1; + } +} + +@media all and (max-width: 30em),all and (max-height: 30em) { + body { + order: 2; + } +} diff --git a/plugins/postcss-custom-media-import-export/test/import.with-polyfill-plugin.expect.css b/plugins/postcss-custom-media-import-export/test/import.with-polyfill-plugin.expect.css new file mode 100644 index 000000000..e28c85d2a --- /dev/null +++ b/plugins/postcss-custom-media-import-export/test/import.with-polyfill-plugin.expect.css @@ -0,0 +1,95 @@ +@media (max-width: 30em),(max-height: 30em) { + body { + order: 1; + } +} + +@media (max-width: 30em),(max-height: 30em),(max-width: 30em),(max-height: 30em) { + body { + order: 1; + } +} + +@media (max-width: 30em),(max-height: 30em) { + +@media not all and (max-color:2147477350) { + body { + order: 2; + } +} +} + +@media not all and (max-width: 30em),not all and (max-height: 30em) { + +@media not all and (color:2147477350) { + body { + order: 2; + } +} +} + +@media (max-width: 30em),(max-height: 30em) { + +@media not all and (max-color:2147477350) { + body { + order: 1; + } +} +} + +@media not all and (max-width: 30em),not all and (max-height: 30em) { + +@media not all and (color:2147477350) { + body { + order: 1; + } +} +} + +@media (max-width: 30em),(max-height: 30em) { + +@media not all and (max-color:2147477350) { + +@media not all and (max-color:2147477350) { + body { + order: 2; + } +} +} +} + +@media not all and (max-width: 30em),not all and (max-height: 30em) { + +@media not all and (color:2147477350) { + +@media not all and (max-color:2147477350) { + body { + order: 2; + } +} +} +} + +@media (max-width: 30em),(max-height: 30em) { + +@media all and (max-color:2147477350) { + +@media not all and (color:2147477350) { + body { + order: 2; + } +} +} +} + +@media not all and (max-width: 30em),not all and (max-height: 30em) { + +@media all and (color:2147477350) { + +@media not all and (color:2147477350) { + body { + order: 2; + } +} +} +} diff --git a/plugins/postcss-custom-properties-import-export/.gitignore b/plugins/postcss-custom-properties-import-export/.gitignore new file mode 100644 index 000000000..e5835f4f9 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/.gitignore @@ -0,0 +1,14 @@ +node_modules +dist +package-lock.json +yarn.lock +browser.js +browser.min.js +*.log* +*.result.css +*.result.css.map +!.editorconfig +!.gitignore +!.rollup.js +!.tape.js +!.travis.yml diff --git a/plugins/postcss-custom-properties-import-export/.nvmrc b/plugins/postcss-custom-properties-import-export/.nvmrc new file mode 100644 index 000000000..f0b10f153 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/.nvmrc @@ -0,0 +1 @@ +v16.13.1 diff --git a/plugins/postcss-custom-properties-import-export/.tape.cjs b/plugins/postcss-custom-properties-import-export/.tape.cjs new file mode 100644 index 000000000..166d4f387 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/.tape.cjs @@ -0,0 +1,23 @@ +const postcssTape = require('../../packages/postcss-tape/dist/index.cjs'); +const plugin = require('@csstools/postcss-custom-properties-import-export'); + +postcssTape(plugin)({ + 'basic:import-cjs': { + message: 'supports { importFrom: "test/import-properties{-2}?.cjs" } usage', + options: { + importFrom: [ + 'test/import-properties.cjs', + 'test/import-properties-2.cjs' + ] + }, + }, + 'basic:import-css-js': { + message: 'supports { importFrom: "test/import-properties{-2}?.{css|js}" } usage', + options: { + importFrom: [ + 'test/import-properties.js', + 'test/import-properties-2.css' + ] + }, + } +}); diff --git a/plugins/postcss-custom-properties-import-export/.tape.mjs b/plugins/postcss-custom-properties-import-export/.tape.mjs new file mode 100644 index 000000000..09d68a303 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/.tape.mjs @@ -0,0 +1,372 @@ +import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import plugin from '@csstools/postcss-custom-properties-import-export'; +import polyfillPlugin from 'postcss-custom-properties'; +import { strict as assert } from 'assert'; +import postcssImport from 'postcss-import'; +import fs from 'fs'; + +postcssTape(plugin)({ + 'basic:import': { + message: 'supports { importFrom: { customProperties: { ... } } } usage', + options: { + importFrom: { + customProperties: { + '--color': 'rgb(255, 0, 0)', + '--color-2': 'yellow', + '--ref-color': 'var(--color)', + '--margin': '0 10px 20px 30px', + '--z-index': 10 + } + } + } + }, + 'basic:import-with-polyfill-plugin': { + message: 'works correctly together with the polyfill', + plugins: [ + plugin({ + importFrom: { + customProperties: { + '--color': 'rgb(255, 0, 0)', + '--color-2': 'yellow', + '--ref-color': 'var(--color)', + '--margin': '0 10px 20px 30px', + '--z-index': 10 + } + } + }), + polyfillPlugin(), + ] + }, + 'basic:import-imported-styles-override-document-styles:true': { + message: 'supports { importedStylesOverrideDocumentStyles: true } usage', + options: { + importedStylesOverrideDocumentStyles: true, + importFrom: { + customProperties: { + '--color': 'rgb(255, 0, 0)', + '--color-2': 'yellow', + '--ref-color': 'var(--color)', + '--margin': '0 10px 20px 30px', + '--z-index': 10 + } + } + } + }, + 'basic:import-imported-styles-override-document-styles:false': { + message: 'supports { importedStylesOverrideDocumentStyles: false } usage', + options: { + importedStylesOverrideDocumentStyles: false, + importFrom: { + customProperties: { + '--color': 'rgb(255, 0, 0)', + '--color-2': 'yellow', + '--ref-color': 'var(--color)', + '--margin': '0 10px 20px 30px', + '--z-index': 10 + } + } + } + }, + 'basic:import-fn': { + message: 'supports { importFrom() } usage', + options: { + importFrom() { + return { + customProperties: { + '--color': 'rgb(255, 0, 0)', + '--color-2': 'yellow', + '--ref-color': 'var(--color)', + '--margin': '0 10px 20px 30px', + '--z-index': 10 + } + }; + } + }, + }, + 'basic:import-fn-promise': { + message: 'supports { async importFrom() } usage', + options: { + importFrom() { + return new Promise(resolve => { + resolve({ + customProperties: { + '--color': 'rgb(255, 0, 0)', + '--color-2': 'yellow', + '--ref-color': 'var(--color)', + '--z-index': 10 + } + }) + }); + } + }, + }, + 'basic:import-json': { + message: 'supports { importFrom: "test/import-properties.json" } usage', + options: { + importFrom: 'test/import-properties.json' + }, + }, + 'basic:import-cjs': { + message: 'supports { importFrom: "test/import-properties{-2}?.cjs" } usage', + options: { + importFrom: [ + 'test/import-properties.cjs', + 'test/import-properties-2.cjs' + ] + }, + }, + 'basic:import-mjs': { + message: 'supports { importFrom: "test/import-properties{-2}?.mjs" } usage', + options: { + importFrom: [ + 'test/import-properties.mjs', + 'test/import-properties-2.mjs' + ] + }, + }, + 'basic:import-css': { + message: 'supports { importFrom: "test/import-properties{-2}?.css" } usage', + options: { + importFrom: [ + 'test/import-properties.css', + 'test/import-properties-2.css' + ] + }, + }, + 'basic:import-css-js': { + message: 'supports { importFrom: "test/import-properties{-2}?.{css|js}" } usage', + options: { + importFrom: [ + 'test/import-properties.js', + 'test/import-properties-2.css' + ] + }, + }, + 'basic:import-css-pcss': { + message: 'supports { importFrom: "test/import-properties.{p}?css" } usage', + options: { + importFrom: [ + 'test/import-properties.pcss', + 'test/import-properties-2.css' + ] + }, + }, + 'basic:import-css-from': { + message: 'supports { importFrom: { from: "test/import-properties.css" } } usage', + options: { + importFrom: [ + { from: 'test/import-properties.css' }, + { from: 'test/import-properties-2.css' } + ] + }, + }, + 'basic:import-css-from-type': { + message: 'supports { importFrom: [ { from: "test/import-properties.css", type: "css" } ] } usage', + options: { + importFrom: [ + { from: 'test/import-properties.css', type: 'css' }, + { from: 'test/import-properties-2.css', type: 'css' } + ] + }, + }, + 'basic:import-override': { + message: 'importFrom with { preserve: false } should override importFrom properties', + options: { + preserve: false, + importFrom: { + customProperties: { + '--color': 'rgb(0, 0, 0)', + '--color-2': 'yellow', + '--ref-color': 'var(--color)', + '--margin': '0 10px 20px 30px', + '--shadow-color': 'rgb(0,0,0)', + '--z-index': 10 + } + } + }, + }, + 'basic:import-override:inverse': { + message: 'importFrom with { preserve: false, importedStylesOverrideDocumentStyles: true } should override root properties', + options: { + preserve: false, + importedStylesOverrideDocumentStyles: true, + importFrom: { + customProperties: { + '--color': 'rgb(0, 0, 0)', + '--color-2': 'yellow', + '--ref-color': 'var(--color)', + '--margin': '0 10px 20px 30px', + '--shadow-color': 'rgb(0,0,0)', + '--z-index': 10 + } + } + }, + }, + 'basic:export': { + message: 'supports { exportTo: { customProperties: { ... } } } usage', + options: { + exportTo: (global.__exportPropertiesObject = global.__exportPropertiesObject || { + customProperties: null + }) + }, + after() { + if (__exportPropertiesObject.customProperties['--color'] !== 'rgb(255, 0, 0)') { + throw new Error('The exportTo function failed'); + } + } + }, + 'basic:export-fn': { + message: 'supports { exportTo() } usage', + options: { + exportTo(customProperties) { + if (customProperties['--color'] !== 'rgb(255, 0, 0)') { + throw new Error('The exportTo function failed'); + } + } + }, + }, + 'basic:export-fn-promise': { + message: 'supports { async exportTo() } usage', + options: { + exportTo(customProperties) { + return new Promise((resolve, reject) => { + if (customProperties['--color'] !== 'rgb(255, 0, 0)') { + reject('The exportTo function failed'); + } else { + resolve(); + } + }); + } + }, + }, + 'basic:export-scss': { + message: 'supports { exportTo: "test/export-properties.scss" } usage', + options: { + exportTo: 'test/export-properties.scss' + }, + before() { + try { + global.__exportPropertiesString = fs.readFileSync('test/export-properties.scss', 'utf8'); + fs.rmSync('test/export-properties.scss'); + } catch (_) { + // ignore + } + }, + after() { + assert.strictEqual(global.__exportPropertiesString, fs.readFileSync('test/export-properties.scss', 'utf8')); + } + }, + 'basic:export-json': { + message: 'supports { exportTo: "test/export-properties.json" } usage', + options: { + exportTo: 'test/export-properties.json' + }, + before() { + try { + global.__exportPropertiesString = fs.readFileSync('test/export-properties.json', 'utf8'); + fs.rmSync('test/export-properties.json'); + } catch (_) { + // ignore + } + }, + after() { + assert.strictEqual(global.__exportPropertiesString, fs.readFileSync('test/export-properties.json', 'utf8')); + } + }, + 'basic:export-js': { + message: 'supports { exportTo: "test/export-properties.js" } usage', + options: { + exportTo: 'test/export-properties.js' + }, + before() { + try { + global.__exportPropertiesString = fs.readFileSync('test/export-properties.js', 'utf8'); + fs.rmSync('test/export-properties.js'); + } catch (_) { + // ignore + } + }, + after() { + assert.strictEqual(global.__exportPropertiesString, fs.readFileSync('test/export-properties.js', 'utf8')); + } + }, + 'basic:export-mjs': { + message: 'supports { exportTo: "test/export-properties.mjs" } usage', + options: { + exportTo: 'test/export-properties.mjs' + }, + before() { + try { + global.__exportPropertiesString = fs.readFileSync('test/export-properties.mjs', 'utf8'); + fs.rmSync('test/export-properties.mjs'); + } catch (_) { + // ignore + } + }, + after() { + assert.strictEqual(global.__exportPropertiesString, fs.readFileSync('test/export-properties.mjs', 'utf8')); + } + }, + 'basic:export-css': { + message: 'supports { exportTo: "test/export-properties.css" } usage', + options: { + exportTo: 'test/export-properties.css' + }, + before() { + try { + global.__exportPropertiesString = fs.readFileSync('test/export-properties.css', 'utf8'); + fs.rmSync('test/export-properties.css'); + } catch (_) { + // ignore + } + }, + after() { + assert.strictEqual(global.__exportPropertiesString, fs.readFileSync('test/export-properties.css', 'utf8')); + } + }, + 'basic:export-css-to': { + message: 'supports { exportTo: { to: "test/export-properties.css" } } usage', + options: { + exportTo: { to: 'test/export-properties.css' } + }, + before() { + try { + global.__exportPropertiesString = fs.readFileSync('test/export-properties.css', 'utf8'); + fs.rmSync('test/export-properties.css'); + } catch (_) { + // ignore + } + }, + after() { + assert.strictEqual(global.__exportPropertiesString, fs.readFileSync('test/export-properties.css', 'utf8')); + } + }, + 'basic:export-css-to-type': { + message: 'supports { exportTo: { to: "test/export-properties.css", type: "css" } } usage', + options: { + exportTo: { to: 'test/export-properties.css', type: 'css' } + }, + before() { + try { + global.__exportPropertiesString = fs.readFileSync('test/export-properties.css', 'utf8'); + fs.rmSync('test/export-properties.css'); + } catch (_) { + // ignore + } + }, + after() { + assert.strictEqual(global.__exportPropertiesString, fs.readFileSync('test/export-properties.css', 'utf8')); + } + }, + 'basic:import-is-empty': { + message: 'supports { importFrom: {} } usage', + options: { + importFrom: {}, + disableDeprecationNotice: true + } + }, + 'import': { + message: 'supports "postcss-import"', + plugins: [postcssImport(), plugin()] + } +}); diff --git a/plugins/postcss-custom-properties-import-export/CHANGELOG.md b/plugins/postcss-custom-properties-import-export/CHANGELOG.md new file mode 100644 index 000000000..df73cd762 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changes to PostCSS Custom Properties Import/Export + +### 1.0.0 (Unreleased) + +- Initial version diff --git a/plugins/postcss-custom-properties-import-export/INSTALL.md b/plugins/postcss-custom-properties-import-export/INSTALL.md new file mode 100644 index 000000000..b215f4bbf --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/INSTALL.md @@ -0,0 +1,257 @@ +# Installing PostCSS Custom Properties Import/Export + +[PostCSS Custom Properties Import/Export] runs in all Node environments, with special instructions for: + +- [Node](#node) +- [PostCSS CLI](#postcss-cli) +- [PostCSS Load Config](#postcss-load-config) +- [Webpack](#webpack) +- [Create React App](#create-react-app) +- [Next.js](#nextjs) +- [Gulp](#gulp) +- [Grunt](#grunt) + +## Node + +Add [PostCSS Custom Properties Import/Export] to your project: + +```bash +npm install postcss @csstools/postcss-custom-properties-import-export --save-dev +``` + +Use it as a [PostCSS] plugin: + +```js +// commonjs +const postcss = require('postcss'); +const postcssCustomPropertiesImportExport = require('@csstools/postcss-custom-properties-import-export'); + +postcss([ + postcssCustomPropertiesImportExport(/* pluginOptions */) +]).process(YOUR_CSS /*, processOptions */); +``` + +```js +// esm +import postcss from 'postcss'; +import postcssCustomPropertiesImportExport from '@csstools/postcss-custom-properties-import-export'; + +postcss([ + postcssCustomPropertiesImportExport(/* pluginOptions */) +]).process(YOUR_CSS /*, processOptions */); +``` + +## PostCSS CLI + +Add [PostCSS CLI] to your project: + +```bash +npm install postcss-cli @csstools/postcss-custom-properties-import-export --save-dev +``` + +Use [PostCSS Custom Properties Import/Export] in your `postcss.config.js` configuration file: + +```js +const postcssCustomPropertiesImportExport = require('@csstools/postcss-custom-properties-import-export'); + +module.exports = { + plugins: [ + postcssCustomPropertiesImportExport(/* pluginOptions */) + ] +} +``` + +## PostCSS Load Config + +If your framework/CLI supports [`postcss-load-config`](https://github.com/postcss/postcss-load-config). + +```bash +npm install @csstools/postcss-custom-properties-import-export --save-dev +``` + +`package.json`: + +```json +{ + "postcss": { + "plugins": { + "@csstools/postcss-custom-properties-import-export": {} + } + } +} +``` + +`.postcssrc.json`: + +```json +{ + "plugins": { + "@csstools/postcss-custom-properties-import-export": {} + } +} +``` + +_See the [README of `postcss-load-config`](https://github.com/postcss/postcss-load-config#usage) for more usage options._ + +## Webpack + +_Webpack version 5_ + +Add [PostCSS Loader] to your project: + +```bash +npm install postcss-loader @csstools/postcss-custom-properties-import-export --save-dev +``` + +Use [PostCSS Custom Properties Import/Export] 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-custom-properties-import-export", + { + // 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-custom-properties-import-export --save-dev +``` + +Use [React App Rewire PostCSS] and [PostCSS Custom Properties Import/Export] in your +`config-overrides.js` file: + +```js +const reactAppRewirePostcss = require('react-app-rewire-postcss'); +const postcssCustomPropertiesImportExport = require('@csstools/postcss-custom-properties-import-export'); + +module.exports = config => reactAppRewirePostcss(config, { + plugins: () => [ + postcssCustomPropertiesImportExport(/* pluginOptions */) + ] +}); +``` + +## Next.js + +Read the instructions on how to [customize the PostCSS configuration in Next.js](https://nextjs.org/docs/advanced-features/customizing-postcss-config) + +```bash +npm install @csstools/postcss-custom-properties-import-export --save-dev +``` + +Use [PostCSS Custom Properties Import/Export] in your `postcss.config.json` file: + +```json +{ + "plugins": [ + "@csstools/postcss-custom-properties-import-export" + ] +} +``` + +```json5 +{ + "plugins": [ + [ + "@csstools/postcss-custom-properties-import-export", + { + // Optionally add plugin options + } + ] + ] +} +``` + +## Gulp + +Add [Gulp PostCSS] to your project: + +```bash +npm install gulp-postcss @csstools/postcss-custom-properties-import-export --save-dev +``` + +Use [PostCSS Custom Properties Import/Export] in your Gulpfile: + +```js +const postcss = require('gulp-postcss'); +const postcssCustomPropertiesImportExport = require('@csstools/postcss-custom-properties-import-export'); + +gulp.task('css', function () { + var plugins = [ + postcssCustomPropertiesImportExport(/* 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-custom-properties-import-export --save-dev +``` + +Use [PostCSS Custom Properties Import/Export] in your Gruntfile: + +```js +const postcssCustomPropertiesImportExport = require('@csstools/postcss-custom-properties-import-export'); + +grunt.loadNpmTasks('grunt-postcss'); + +grunt.initConfig({ + postcss: { + options: { + processors: [ + postcssCustomPropertiesImportExport(/* 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 Custom Properties Import/Export]: https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-custom-properties-import-export +[React App Rewire PostCSS]: https://github.com/csstools/react-app-rewire-postcss +[React App Rewired]: https://github.com/timarney/react-app-rewired +[Next.js]: https://nextjs.org diff --git a/plugins/postcss-custom-properties-import-export/LICENSE.md b/plugins/postcss-custom-properties-import-export/LICENSE.md new file mode 100644 index 000000000..6d7047088 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/LICENSE.md @@ -0,0 +1,21 @@ +# The MIT License (MIT) + +Copyright © PostCSS + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/plugins/postcss-custom-properties-import-export/README.md b/plugins/postcss-custom-properties-import-export/README.md new file mode 100644 index 000000000..bf23b2773 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/README.md @@ -0,0 +1,164 @@ +# PostCSS Custom Properties Import/Export [PostCSS Logo][PostCSS] + +[npm version][npm-url] [Build Status][cli-url] [Discord][discord] + +[PostCSS Custom Properties Import/Export] lets you import or export CSS custom properties (`--foo: pink;`) into or out of your CSS. + +## As a drop in for old versions of `postcss-custom-properties` + +⚠️ `postcss-custom-properties` no longer removes any custom properties. +If you inject a lot of properties they will all be added to your final CSS. +Use a separate CSS minifier/optimizer to remove unused custom properties. + +```js +// commonjs +const postcss = require('postcss'); +const postcssCustomPropertiesImportExport = require('@csstools/postcss-custom-properties-import-export'); +const postcssCustomProperties = require('postcss-custom-properties'); + +postcss([ + // First + postcssCustomPropertiesImportExport({ + /* pluginOptions */ + importedStylesOverrideDocumentStyles: true, // mimics old `postcss-custom-properties` + }), + // Second + postcssCustomProperties() +]).process(YOUR_CSS /*, processOptions */); +``` + +## Usage + +Add [PostCSS Custom Properties Import/Export] to your project: + +```bash +npm install postcss @csstools/postcss-custom-properties-import-export --save-dev +``` + +Use it as a [PostCSS] plugin: + +```js +const postcss = require('postcss'); +const postcssCustomPropertiesImportExport = require('@csstools/postcss-custom-properties-import-export'); + +postcss([ + postcssCustomPropertiesImportExport(/* pluginOptions */) +]).process(YOUR_CSS /*, processOptions */); +``` + +[PostCSS Custom Properties Import/Export] runs in all Node environments, with special +instructions for: + +- [Node](INSTALL.md#node) +- [PostCSS CLI](INSTALL.md#postcss-cli) +- [PostCSS Load Config](INSTALL.md#postcss-load-config) +- [Webpack](INSTALL.md#webpack) +- [Create React App](INSTALL.md#create-react-app) +- [Next.js](INSTALL.md#nextjs) +- [Gulp](INSTALL.md#gulp) +- [Grunt](INSTALL.md#grunt) + +## Options + +### importFrom + +The `importFrom` option specifies sources where custom properties can be +imported from, which might be CSS, JS, and JSON files, functions, and directly +passed objects. + +```js +postcssCustomPropertiesImportExport({ + importFrom: 'path/to/file.css' // => :root { --color: var(rgb(245 20 255)); } +}); +``` + +```pcss +article { + color: var(--color); +} + +/* becomes */ + +:root { + --color: var(rgb(245 20 255)); +} + +article { + color: var(--color); +} +``` + +Multiple sources can be passed into this option, and they will be parsed in the +order they are received. JavaScript files, JSON files, functions, and objects +will need to namespace custom properties using the `customProperties` or +`custom-properties` key. + +```js +postcssCustomPropertiesImportExport({ + importFrom: [ + 'path/to/file.css', + 'and/then/this.js', + 'and/then/that.json', + { + customProperties: { '--color': 'var(rgb(245 20 255))' } + }, + () => { + const customProperties = { '--color': 'var(rgb(245 20 255))' }; + + return { customProperties }; + } + ] +}); +``` + +### importedStylesOverrideDocumentStyles + +The `importedStylesOverrideDocumentStyles` option determines if properties added via `importFrom` override properties that exist in your CSS document. +Defaults to `false`. + +```js +postcssCustomPropertiesImportExport({ + importedStylesOverrideDocumentStyles: true +}); + +### exportTo + +The `exportTo` option specifies destinations where custom properties can be +exported to, which might be CSS, JS, and JSON files, functions, and directly +passed objects. + +```js +postcssCustomPropertiesImportExport({ + exportTo: 'path/to/file.css' // :root { --color: var(rgb(245 20 255)); } +}); +``` + +Multiple destinations can be passed into this option, and they will be parsed +in the order they are received. JavaScript files, JSON files, and objects will +need to namespace custom properties using the `customProperties` or +`custom-properties` key. + +```js +const cachedObject = { customProperties: {} }; + +postcssCustomPropertiesImportExport({ + exportTo: [ + 'path/to/file.css', // :root { --color: var(rgb(245 20 255)); } + 'and/then/this.js', // module.exports = { customProperties: { '--color': 'var(rgb(245 20 255))' } } + 'and/then/this.mjs', // export const customProperties = { '--color': 'var(rgb(245 20 255))' } } + 'and/then/that.json', // { "custom-properties": { "--color": "var(rgb(245 20 255))" } } + cachedObject, + (customProperties) => { + customProperties // { '--color': 'var(rgb(245 20 255))' } + } + ] +}); +``` + +[cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test + +[discord]: https://discord.gg/bUadyRwkJS +[npm-url]: https://www.npmjs.com/package/@csstools/postcss-custom-properties-import-export + +[PostCSS]: https://github.com/postcss/postcss +[PostCSS Custom Properties Import/Export]: https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-custom-properties-import-export diff --git a/plugins/postcss-custom-properties-import-export/docs/README.md b/plugins/postcss-custom-properties-import-export/docs/README.md new file mode 100644 index 000000000..3df3822e4 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/docs/README.md @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + +
+ +[] lets you import or export CSS custom properties (`--foo: pink;`) into or out of your CSS. + +## As a drop in for old versions of `postcss-custom-properties` + +⚠️ `postcss-custom-properties` no longer removes any custom properties. +If you inject a lot of properties they will all be added to your final CSS. +Use a separate CSS minifier/optimizer to remove unused custom properties. + +```js +// commonjs +const postcss = require('postcss'); +const postcssCustomPropertiesImportExport = require('@csstools/postcss-custom-properties-import-export'); +const postcssCustomProperties = require('postcss-custom-properties'); + +postcss([ + // First + postcssCustomPropertiesImportExport({ + /* pluginOptions */ + importedStylesOverrideDocumentStyles: true, // mimics old `postcss-custom-properties` + }), + // Second + postcssCustomProperties() +]).process(YOUR_CSS /*, processOptions */); +``` + + + + + +## Options + +### importFrom + +The `importFrom` option specifies sources where custom properties can be +imported from, which might be CSS, JS, and JSON files, functions, and directly +passed objects. + +```js +({ + importFrom: 'path/to/file.css' // => :root { --color: var(rgb(245 20 255)); } +}); +``` + +```pcss +article { + color: var(--color); +} + +/* becomes */ + +:root { + --color: var(rgb(245 20 255)); +} + +article { + color: var(--color); +} +``` + +Multiple sources can be passed into this option, and they will be parsed in the +order they are received. JavaScript files, JSON files, functions, and objects +will need to namespace custom properties using the `customProperties` or +`custom-properties` key. + +```js +({ + importFrom: [ + 'path/to/file.css', + 'and/then/this.js', + 'and/then/that.json', + { + customProperties: { '--color': 'var(rgb(245 20 255))' } + }, + () => { + const customProperties = { '--color': 'var(rgb(245 20 255))' }; + + return { customProperties }; + } + ] +}); +``` + +### importedStylesOverrideDocumentStyles + +The `importedStylesOverrideDocumentStyles` option determines if properties added via `importFrom` override properties that exist in your CSS document. +Defaults to `false`. + +```js +({ + importedStylesOverrideDocumentStyles: true +}); + +### exportTo + +The `exportTo` option specifies destinations where custom properties can be +exported to, which might be CSS, JS, and JSON files, functions, and directly +passed objects. + +```js +({ + exportTo: 'path/to/file.css' // :root { --color: var(rgb(245 20 255)); } +}); +``` + +Multiple destinations can be passed into this option, and they will be parsed +in the order they are received. JavaScript files, JSON files, and objects will +need to namespace custom properties using the `customProperties` or +`custom-properties` key. + +```js +const cachedObject = { customProperties: {} }; + +({ + exportTo: [ + 'path/to/file.css', // :root { --color: var(rgb(245 20 255)); } + 'and/then/this.js', // module.exports = { customProperties: { '--color': 'var(rgb(245 20 255))' } } + 'and/then/this.mjs', // export const customProperties = { '--color': 'var(rgb(245 20 255))' } } + 'and/then/that.json', // { "custom-properties": { "--color": "var(rgb(245 20 255))" } } + cachedObject, + (customProperties) => { + customProperties // { '--color': 'var(rgb(245 20 255))' } + } + ] +}); +``` + + diff --git a/plugins/postcss-custom-properties-import-export/package.json b/plugins/postcss-custom-properties-import-export/package.json new file mode 100644 index 000000000..59f149cab --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/package.json @@ -0,0 +1,91 @@ +{ + "name": "@csstools/postcss-custom-properties-import-export", + "description": "Import/Export Custom Properties in CSS", + "version": "1.0.0", + "contributors": [ + { + "name": "Antonio Laguna", + "email": "antonio@laguna.es", + "url": "https://antonio.laguna.es" + }, + { + "name": "Romain Menke", + "email": "romainmenke@gmail.com" + }, + { + "name": "Jonathan Neal", + "email": "jonathantneal@hotmail.com" + }, + { + "name": "Maxime Thirouin" + } + ], + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "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", + "index.d.ts" + ], + "peerDependencies": { + "postcss": "^8.4" + }, + "devDependencies": { + "postcss-custom-properties": "^13.0.0", + "postcss-import": "^15.0.0" + }, + "scripts": { + "build": "rollup -c ../../rollup/default.mjs", + "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 && node .tape.cjs && npm run test:exports", + "test:exports": "node ./test/_import.mjs && node ./test/_require.cjs", + "test:rewrite-expects": "REWRITE_EXPECTS=true node .tape.mjs" + }, + "homepage": "https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-custom-properties-import-export#readme", + "repository": { + "type": "git", + "url": "https://github.com/csstools/postcss-plugins.git", + "directory": "plugins/postcss-custom-properties-import-export" + }, + "bugs": "https://github.com/csstools/postcss-plugins/issues", + "keywords": [ + "css", + "custom", + "declarations", + "postcss", + "postcss-plugin", + "properties", + "variables", + "vars" + ], + "csstools": { + "exportName": "postcssCustomPropertiesImportExport", + "humanReadableName": "PostCSS Custom Properties Import/Export" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/plugins/postcss-custom-properties-import-export/src/index.ts b/plugins/postcss-custom-properties-import-export/src/index.ts new file mode 100644 index 000000000..96ff55c0c --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/src/index.ts @@ -0,0 +1,119 @@ +import getCustomPropertiesFromImports from './lib/get-custom-properties-from-imports'; +import getCustomPropertiesFromRoot from './lib/get-custom-properties-from-root'; +import writeCustomPropertiesToExports from './lib/write-custom-properties-to-exports'; +import type { ImportOptions, ExportOptions } from './lib/options'; +import type { PluginCreator } from 'postcss'; + +export interface PluginOptions { + /** Specifies sources where Custom Properties can be imported from, which might be CSS, JS, and JSON files, functions, and directly passed objects. */ + importFrom?: ImportOptions | Array + + /** Specifies destinations where Custom Properties can be exported to, which might be CSS, JS, and JSON files, functions, and directly passed objects. */ + exportTo?: ExportOptions | Array + + /** Specifies if `importFrom` properties or `:root` properties have priority. */ + importedStylesOverrideDocumentStyles?: boolean +} + +const creator: PluginCreator = (opts?: PluginOptions) => { + const importedStylesOverrideDocumentStyles = 'importedStylesOverrideDocumentStyles' in Object(opts) ? Boolean(opts.importedStylesOverrideDocumentStyles) : false; + + // sources to import custom selectors from + let importFrom: Array = []; + if (Array.isArray(opts?.importFrom)) { + importFrom = opts.importFrom; + } else if (opts?.importFrom) { + importFrom = [opts.importFrom]; + } + + // destinations to export custom selectors to + let exportTo: Array = []; + if (Array.isArray(opts?.exportTo)) { + exportTo = opts.exportTo; + } else if (opts?.exportTo) { + exportTo = [opts.exportTo]; + } + + // promise any custom selectors are imported + const customPropertiesPromise = getCustomPropertiesFromImports(importFrom); + + return { + postcssPlugin: 'postcss-custom-properties-import-export', + prepare () { + const customPropertiesForInsertion: Map = new Map(); + const customPropertiesForExport: Map = new Map(); + + return { + Once: async (root, { result, postcss }) => { + const importedCustomerProperties = (await customPropertiesPromise).entries(); + const rootCustomProperties = getCustomPropertiesFromRoot(root).entries(); + + for (const [name, value] of importedCustomerProperties) { + customPropertiesForInsertion.set(name, value); + } + + if (importedStylesOverrideDocumentStyles) { + for (const [name, value] of [...rootCustomProperties, ...importedCustomerProperties]) { + customPropertiesForExport.set(name, value); + } + } else { + for (const [name, value] of [...importedCustomerProperties, ...rootCustomProperties]) { + customPropertiesForExport.set(name, value); + } + } + + await writeCustomPropertiesToExports(customPropertiesForExport, exportTo); + + if (customPropertiesForInsertion.size > 0) { + const propertyNames = Array.from(customPropertiesForInsertion.keys()); + // Inserting in reverse order results in the correct order. + propertyNames.reverse(); + + let operator = 'prepend'; + if (importedStylesOverrideDocumentStyles) { + operator = 'append'; + } + + const rootRule = postcss.rule({ + selector: ':root', + source: { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + input: { + from: result.opts.from, + }, + start: { offset: 0, line: 1, column: 1 }, + end: { offset: 0, line: 1, column: 1 }, + }, + }); + + root[operator](rootRule); + + propertyNames.forEach((propertyName) => { + const decl = postcss.decl({ + prop: propertyName, + value: customPropertiesForInsertion.get(propertyName), + }); + + decl.source = { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + input: { + from: result.opts.from, + }, + start: { offset: 0, line: 1, column: 1 }, + end: { offset: 0, line: 1, column: 1 }, + }; + + rootRule.append(decl); + }); + } + }, + }; + }, + }; +}; + +creator.postcss = true; + +export default creator; diff --git a/plugins/postcss-custom-properties-import-export/src/lib/get-custom-properties-from-imports.ts b/plugins/postcss-custom-properties-import-export/src/lib/get-custom-properties-from-imports.ts new file mode 100644 index 000000000..a1f354e03 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/src/lib/get-custom-properties-from-imports.ts @@ -0,0 +1,150 @@ +import getCustomPropertiesFromRoot from './get-custom-properties-from-root'; +import path from 'path'; +import { pathToFileURL } from 'url'; +import type { ImportCustomProperties, ImportOptions } from './options'; +import { parse } from 'postcss'; +import { promises as fsp } from 'fs'; + +/* Get Custom Properties from CSS File +/* ========================================================================== */ + +async function getCustomPropertiesFromCSSFile(from) { + const css = await fsp.readFile(pathToFileURL(path.resolve(from))); + const root = parse(css, { from : from.toString() }); + + return getCustomPropertiesFromRoot(root); +} + +/* Get Custom Properties from Object +/* ========================================================================== */ + +function getCustomPropertiesFromObject(object: ImportCustomProperties): Map { + const out: Map = new Map(); + + if ('customProperties' in object) { + for (const [name, value] of Object.entries(object.customProperties)) { + out.set(name, value.toString()); + } + } + + if ('custom-properties' in object) { + for (const [name, value] of Object.entries(object['custom-properties'])) { + out.set(name, value.toString()); + } + } + + return out; +} + +/* Get Custom Properties from JSON file +/* ========================================================================== */ + +async function getCustomPropertiesFromJSONFile(from): Promise> { + const object = await readJSON(pathToFileURL(path.resolve(from))); + + return getCustomPropertiesFromObject(object); +} + +/* Get Custom Properties from JS file +/* ========================================================================== */ + +async function getCustomPropertiesFromJSFile(from): Promise> { + const object = await import(pathToFileURL(path.resolve(from)).href); + + if ('default' in object) { + return getCustomPropertiesFromObject(object.default); + } + + return getCustomPropertiesFromObject(object); +} + +/* Get Custom Properties from Imports +/* ========================================================================== */ + +export default async function getCustomPropertiesFromImports(sources: Array): Promise> { + const sourceData = (await Promise.all(sources.map(async (source) => { + if (source instanceof Promise) { + source = await source; + } else if (source instanceof Function) { + source = await source(); + } + + if (typeof source === 'string') { + const fromPath = path.resolve(source); + const type = path.extname(fromPath).slice(1).toLowerCase(); + + return { + type: type, + from: fromPath, + }; + } + + if ('customProperties' in source && Object(source.customProperties) === source.customProperties) { + return source; + } + + if ('custom-properties' in source && Object(source['custom-properties']) === source['custom-properties']) { + return source; + } + + if ('from' in source) { + const fromPath = path.resolve(source.from); + let type = source.type; + if (!type) { + type = path.extname(fromPath).slice(1).toLowerCase(); + } + return { + type: type, + from: fromPath, + }; + } + + if (Object.keys(source).length === 0) { + // Empty `importFrom` object. + return null; + } + + return null; + }))).filter((x) => { + return !!x; + }); + + const data: Array> = await Promise.all(sourceData.map(async (partialData) => { + if (('type' in partialData) && ('from' in partialData)) { + if (partialData.type === 'css' || partialData.type === 'pcss') { + return await getCustomPropertiesFromCSSFile(partialData.from); + } + + if (partialData.type === 'js' || partialData.type === 'cjs') { + return await getCustomPropertiesFromJSFile(partialData.from); + } + + if (partialData.type === 'mjs') { + // Only works when running as a module. + return await getCustomPropertiesFromJSFile(partialData.from); + } + + if (partialData.type === 'json') { + return await getCustomPropertiesFromJSONFile(partialData.from); + } + + throw new Error('Invalid source type: ' + partialData.type); + } + + return getCustomPropertiesFromObject(partialData); + })); + + const out: Map = new Map(); + data.forEach((partialData) => { + for (const [name, value] of partialData.entries()) { + out.set(name, value); + } + }); + + return out; +} + +/* Helper utilities +/* ========================================================================== */ + +const readJSON = async from => JSON.parse((await fsp.readFile(from)).toString()); diff --git a/plugins/postcss-custom-properties-import-export/src/lib/get-custom-properties-from-root.ts b/plugins/postcss-custom-properties-import-export/src/lib/get-custom-properties-from-root.ts new file mode 100644 index 000000000..4de0dafe9 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/src/lib/get-custom-properties-from-root.ts @@ -0,0 +1,47 @@ +// return custom selectors from the css root, conditionally removing them +export default function getCustomPropertiesFromRoot(root): Map { + // initialize custom selectors + const customPropertiesFromHtmlElement: Map = new Map(); + const customPropertiesFromRootPseudo: Map = new Map(); + const out: Map = new Map(); + + // for each html or :root rule + root.nodes.slice().forEach(rule => { + const customPropertiesObject = isHtmlRule(rule) + ? customPropertiesFromHtmlElement + : isRootRule(rule) + ? customPropertiesFromRootPseudo + : null; + + // for each custom property + if (customPropertiesObject) { + rule.nodes.slice().forEach(decl => { + if (decl.variable) { + const { prop } = decl; + + // write the parsed value to the custom property + customPropertiesObject.set(prop, decl.value); + } + }); + } + }); + + for (const [name, value] of customPropertiesFromHtmlElement.entries()) { + out.set(name, value); + } + + for (const [name, value] of customPropertiesFromRootPseudo.entries()) { + out.set(name, value); + } + + // return all custom properties, preferring :root properties over html properties + return out; +} + +// match html and :root rules +const htmlSelectorRegExp = /^html$/i; +const rootSelectorRegExp = /^:root$/i; + +// whether the node is an html or :root rule +const isHtmlRule = node => node.type === 'rule' && node.selector.split(',').some(item => htmlSelectorRegExp.test(item)) && Object(node.nodes).length; +const isRootRule = node => node.type === 'rule' && node.selector.split(',').some(item => rootSelectorRegExp.test(item)) && Object(node.nodes).length; diff --git a/plugins/postcss-custom-properties-import-export/src/lib/options.ts b/plugins/postcss-custom-properties-import-export/src/lib/options.ts new file mode 100644 index 000000000..6db403f7f --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/src/lib/options.ts @@ -0,0 +1,14 @@ + +export type ImportFromSource = { from: string, type?: string } | string; +export type ImportCustomProperties = { customProperties?: Record, 'custom-properties'?: Record }; +export type ImportAsFunction = () => ImportFromSource | ImportCustomProperties +export type ImportAsPromise = Promise +export type ImportAsFunctionPromise = () => Promise +export type ImportOptions = ImportFromSource | ImportCustomProperties | ImportAsFunction | ImportAsPromise | ImportAsFunctionPromise; + +export type ExportJSONFunction = (customProperties?: Record) => Record; +export type ExportToSource = { to: string, type?: string, toJSON: ExportJSONFunction } | string; +export type ExportCustomProperties = { customProperties?: Record, 'custom-properties'?: Record, toJSON: ExportJSONFunction }; +export type ExportAsFunction = (ExportCustomProperties) => void +export type ExportAsFunctionPromise = (ExportCustomProperties) => Promise +export type ExportOptions = ExportToSource | ExportCustomProperties | ExportAsFunction | ExportAsFunctionPromise; diff --git a/plugins/postcss-custom-properties-import-export/src/lib/write-custom-properties-to-exports.ts b/plugins/postcss-custom-properties-import-export/src/lib/write-custom-properties-to-exports.ts new file mode 100644 index 000000000..61a9948d5 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/src/lib/write-custom-properties-to-exports.ts @@ -0,0 +1,157 @@ +import { promises as fsp } from 'fs'; +import path from 'path'; +import type { ExportOptions } from './options'; + +/* Write Custom Properties to CSS File +/* ========================================================================== */ + +async function writeCustomPropertiesToCssFile(to, customProperties) { + const cssContent = Object.keys(customProperties).reduce((cssLines, name) => { + cssLines.push(`\t${name}: ${customProperties[name]};`); + + return cssLines; + }, []).join('\n'); + const css = `:root {\n${cssContent}\n}\n`; + + await fsp.writeFile(to, css); +} + +/* Write Custom Properties to SCSS File +/* ========================================================================== */ + +async function writeCustomPropertiesToScssFile(to, customProperties) { + const scssContent = Object.keys(customProperties).reduce((scssLines, name) => { + const scssName = name.replace('--', '$'); + scssLines.push(`${scssName}: ${customProperties[name]};`); + + return scssLines; + }, []).join('\n'); + const scss = `${scssContent}\n`; + + await fsp.writeFile(to, scss); +} + +/* Write Custom Properties to JSON file +/* ========================================================================== */ + +async function writeCustomPropertiesToJsonFile(to, customProperties) { + const jsonContent = JSON.stringify({ + 'custom-properties': customProperties, + }, null, ' '); + const json = `${jsonContent}\n`; + + await fsp.writeFile(to, json); +} + +/* Write Custom Properties to Common JS file +/* ========================================================================== */ + +async function writeCustomPropertiesToCjsFile(to, customProperties) { + const jsContents = Object.keys(customProperties).reduce((jsLines, name) => { + jsLines.push(`\t\t'${escapeForJS(name)}': '${escapeForJS(customProperties[name])}'`); + + return jsLines; + }, []).join(',\n'); + const js = `module.exports = {\n\tcustomProperties: {\n${jsContents}\n\t}\n};\n`; + + await fsp.writeFile(to, js); +} + +/* Write Custom Properties to Module JS file +/* ========================================================================== */ + +async function writeCustomPropertiesToMjsFile(to, customProperties) { + const mjsContents = Object.keys(customProperties).reduce((mjsLines, name) => { + mjsLines.push(`\t'${escapeForJS(name)}': '${escapeForJS(customProperties[name])}'`); + + return mjsLines; + }, []).join(',\n'); + const mjs = `export const customProperties = {\n${mjsContents}\n};\n`; + + await fsp.writeFile(to, mjs); +} + +/* Write Custom Properties to Exports +/* ========================================================================== */ + +export default function writeCustomPropertiesToExports(customProperties, destinations: Array) { + return Promise.all(destinations.map(async destination => { + if (destination instanceof Function) { + await destination(defaultCustomPropertiesToJSONObject(customProperties)); + return; + } + + if (typeof destination === 'string') { + const toPath = path.resolve(destination); + const type = path.extname(toPath).slice(1).toLowerCase(); + + await writePropertiesToFile(toPath, type, defaultCustomPropertiesToJSONObject(customProperties)); + return; + } + + // transformer for Custom Properties into a JSON-compatible object + let customPropertiesAsJSONObject = {}; + if ('toJSON' in destination) { + customPropertiesAsJSONObject = destination.toJSON(defaultCustomPropertiesToJSONObject(customProperties)); + } else { + customPropertiesAsJSONObject = defaultCustomPropertiesToJSONObject(customProperties); + } + + if ('to' in destination) { + const toPath = path.resolve(destination.to); + let type = destination.type; + if (!type) { + type = path.extname(toPath).slice(1).toLowerCase(); + } + + await writePropertiesToFile(toPath, type, customPropertiesAsJSONObject); + return; + } + + if ('customProperties' in destination) { + // write directly to an object as customProperties + destination.customProperties = customPropertiesAsJSONObject; + } else if ('custom-properties' in destination) { + // write directly to an object as custom-properties + destination['custom-properties'] = customPropertiesAsJSONObject; + } + })); +} + +async function writePropertiesToFile(to: string, type: string, customProperties: Record) { + if (type === 'css') { + await writeCustomPropertiesToCssFile(to, customProperties); + } + + if (type === 'scss') { + await writeCustomPropertiesToScssFile(to, customProperties); + } + + if (type === 'js') { + await writeCustomPropertiesToCjsFile(to, customProperties); + } + + if (type === 'json') { + await writeCustomPropertiesToJsonFile(to, customProperties); + } + + if (type === 'mjs') { + await writeCustomPropertiesToMjsFile(to, customProperties); + } +} + +/* Helper utilities +/* ========================================================================== */ + +function defaultCustomPropertiesToJSONObject(customProperties: Map): Record { + const out = {}; + for (const [name, value] of customProperties.entries()) { + out[name] = value.toString(); + } + + return out; +} + +const escapeForJS = (string) => { + return string.replace(/\\([\s\S])|(')/g, '\\$1$2').replace(/\n/g, '\\n').replace(/\r/g, '\\r'); +}; diff --git a/plugins/postcss-custom-properties-import-export/test/_import.mjs b/plugins/postcss-custom-properties-import-export/test/_import.mjs new file mode 100644 index 000000000..2f8dedcfd --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/_import.mjs @@ -0,0 +1,17 @@ +import assert from 'assert'; +import plugin from '@csstools/postcss-custom-properties-import-export'; +import postcss from 'postcss'; +import fs from 'fs'; + +plugin(); + +assert.ok(plugin.postcss, 'should have "postcss flag"'); +assert.equal(typeof plugin, 'function', 'should return a function'); + +postcss([plugin({ importFrom: 'test/import-properties.cjs' })]). + process(fs.readFileSync('test/basic.css', 'utf8'), {from: 'test/basic.css'}). + then(); + +postcss([plugin({ importFrom: 'test/import-properties.mjs' })]). + process(fs.readFileSync('test/basic.css', 'utf8'), {from: 'test/basic.css'}). + then(); diff --git a/plugins/postcss-custom-properties-import-export/test/_require.cjs b/plugins/postcss-custom-properties-import-export/test/_require.cjs new file mode 100644 index 000000000..91bcaac37 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/_require.cjs @@ -0,0 +1,13 @@ +const assert = require('assert'); +const plugin = require('@csstools/postcss-custom-properties-import-export'); +const postcss = require('postcss'); +const fs = require('fs'); + +plugin(); + +assert.ok(plugin.postcss, 'should have "postcss flag"'); +assert.equal(typeof plugin, 'function', 'should return a function'); + +postcss([plugin({ importFrom: 'test/import-properties.cjs' })]). + process(fs.readFileSync('test/basic.css', 'utf8'), {from: 'test/basic.css'}). + then(); diff --git a/plugins/postcss-custom-properties-import-export/test/basic.css b/plugins/postcss-custom-properties-import-export/test/basic.css new file mode 100644 index 000000000..dc9fd373e --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/basic.css @@ -0,0 +1,147 @@ +html { + --ref-color: skip; +} + +:root { + --color: rgb(255, 0, 0); + --color-h: 0; + --color-s: 100%; + --color-l: 50%; + --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l)); + --ref-color: var(--color); + --circular: var(--circular-2); + --circular-2: var(--circular); + --margin: 0 10px 20px 30px; + --shadow-color: rgb(255,0,0); + --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15)); + --font-family: "Open Sans", sans-serif; + --url-1: url("/my/path"); + --url-2: url('/my/path'); + --url-3: url(/my/path); + --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; + color: var(--color); +} + +:root, +[data-theme=light] { + --theme-color: #053; +} + +.test { + --skip: gray; + color: var(--override, var(--color)); +} + +.test--color_spacing { + box-shadow: inset 0 -3px 0 var(--color); +} + +.test--preserve_whitespaces { + margin: var(--margin); +} + +.test--complex_values { + box-shadow: var(--shadow); +} + +.test--comma_separated_values { + font-family: var(--font-family); +} + +.test--fallback { + color: var(--color-2, blue); +} + +.test--color_w_var { + color: var(--ref-color); +} + +.test--color_w_vars { + color: var(--color-hsl); +} + +.test--circular_var { + color: var(--circular); +} + +.test--z-index { + z-index: var(--z-index); +} + +.test--nested-fallback { + z-index: var(--xxx, var(--yyy, 1)); +} + +.text--calc { + width: calc((100% - var(--xxx, 1px)) + var(--yyy, 10px)); +} + +.test--linear-gradient { + background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%); +} + +.test--loose-formatting { + color: var( + --color, + blue + )/*rtl:red*/; +} + +.test--combined-selector { + color: var(--theme-color); +} + +.test--variable-with-url { + order: 1; + background: var(--url-1); +} + +.test--variable-with-url { + order: 2; + background: var(--url-2); +} + + +.test--variable-with-url { + order: 3; + background: var(--url-3); +} + + +.test--variable-with-url { + order: 4; + background: var(--url-4); +} + +.no-prototype-collisions { + color: var(toString); +} + +.test-unicode { + color: var(--✅-size); +} + +.test { + font-family: var(--font, "Helvetica Neue", Arial, sans-serif); +} + +.ignores-declarations-that-have-an-exact-fallback-a { + left: 1rem; + left: var(--does-not-exist, 1rem); +} + +.ignores-declarations-that-have-an-exact-fallback-b { + right: 2em; + right: var(--✅-size); +} + +.does-not-ignore-declarations-that-have-an-exact-override-a { + left: var(--does-not-exist, 1rem); + left: 1rem; +} + +.does-not-ignore-declarations-that-have-an-exact-override-b { + right: var(--✅-size); + right: 2em; +} diff --git a/plugins/postcss-custom-properties-import-export/test/basic.export-css-to-type.expect.css b/plugins/postcss-custom-properties-import-export/test/basic.export-css-to-type.expect.css new file mode 100644 index 000000000..dc9fd373e --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/basic.export-css-to-type.expect.css @@ -0,0 +1,147 @@ +html { + --ref-color: skip; +} + +:root { + --color: rgb(255, 0, 0); + --color-h: 0; + --color-s: 100%; + --color-l: 50%; + --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l)); + --ref-color: var(--color); + --circular: var(--circular-2); + --circular-2: var(--circular); + --margin: 0 10px 20px 30px; + --shadow-color: rgb(255,0,0); + --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15)); + --font-family: "Open Sans", sans-serif; + --url-1: url("/my/path"); + --url-2: url('/my/path'); + --url-3: url(/my/path); + --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; + color: var(--color); +} + +:root, +[data-theme=light] { + --theme-color: #053; +} + +.test { + --skip: gray; + color: var(--override, var(--color)); +} + +.test--color_spacing { + box-shadow: inset 0 -3px 0 var(--color); +} + +.test--preserve_whitespaces { + margin: var(--margin); +} + +.test--complex_values { + box-shadow: var(--shadow); +} + +.test--comma_separated_values { + font-family: var(--font-family); +} + +.test--fallback { + color: var(--color-2, blue); +} + +.test--color_w_var { + color: var(--ref-color); +} + +.test--color_w_vars { + color: var(--color-hsl); +} + +.test--circular_var { + color: var(--circular); +} + +.test--z-index { + z-index: var(--z-index); +} + +.test--nested-fallback { + z-index: var(--xxx, var(--yyy, 1)); +} + +.text--calc { + width: calc((100% - var(--xxx, 1px)) + var(--yyy, 10px)); +} + +.test--linear-gradient { + background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%); +} + +.test--loose-formatting { + color: var( + --color, + blue + )/*rtl:red*/; +} + +.test--combined-selector { + color: var(--theme-color); +} + +.test--variable-with-url { + order: 1; + background: var(--url-1); +} + +.test--variable-with-url { + order: 2; + background: var(--url-2); +} + + +.test--variable-with-url { + order: 3; + background: var(--url-3); +} + + +.test--variable-with-url { + order: 4; + background: var(--url-4); +} + +.no-prototype-collisions { + color: var(toString); +} + +.test-unicode { + color: var(--✅-size); +} + +.test { + font-family: var(--font, "Helvetica Neue", Arial, sans-serif); +} + +.ignores-declarations-that-have-an-exact-fallback-a { + left: 1rem; + left: var(--does-not-exist, 1rem); +} + +.ignores-declarations-that-have-an-exact-fallback-b { + right: 2em; + right: var(--✅-size); +} + +.does-not-ignore-declarations-that-have-an-exact-override-a { + left: var(--does-not-exist, 1rem); + left: 1rem; +} + +.does-not-ignore-declarations-that-have-an-exact-override-b { + right: var(--✅-size); + right: 2em; +} diff --git a/plugins/postcss-custom-properties-import-export/test/basic.export-css-to.expect.css b/plugins/postcss-custom-properties-import-export/test/basic.export-css-to.expect.css new file mode 100644 index 000000000..dc9fd373e --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/basic.export-css-to.expect.css @@ -0,0 +1,147 @@ +html { + --ref-color: skip; +} + +:root { + --color: rgb(255, 0, 0); + --color-h: 0; + --color-s: 100%; + --color-l: 50%; + --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l)); + --ref-color: var(--color); + --circular: var(--circular-2); + --circular-2: var(--circular); + --margin: 0 10px 20px 30px; + --shadow-color: rgb(255,0,0); + --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15)); + --font-family: "Open Sans", sans-serif; + --url-1: url("/my/path"); + --url-2: url('/my/path'); + --url-3: url(/my/path); + --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; + color: var(--color); +} + +:root, +[data-theme=light] { + --theme-color: #053; +} + +.test { + --skip: gray; + color: var(--override, var(--color)); +} + +.test--color_spacing { + box-shadow: inset 0 -3px 0 var(--color); +} + +.test--preserve_whitespaces { + margin: var(--margin); +} + +.test--complex_values { + box-shadow: var(--shadow); +} + +.test--comma_separated_values { + font-family: var(--font-family); +} + +.test--fallback { + color: var(--color-2, blue); +} + +.test--color_w_var { + color: var(--ref-color); +} + +.test--color_w_vars { + color: var(--color-hsl); +} + +.test--circular_var { + color: var(--circular); +} + +.test--z-index { + z-index: var(--z-index); +} + +.test--nested-fallback { + z-index: var(--xxx, var(--yyy, 1)); +} + +.text--calc { + width: calc((100% - var(--xxx, 1px)) + var(--yyy, 10px)); +} + +.test--linear-gradient { + background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%); +} + +.test--loose-formatting { + color: var( + --color, + blue + )/*rtl:red*/; +} + +.test--combined-selector { + color: var(--theme-color); +} + +.test--variable-with-url { + order: 1; + background: var(--url-1); +} + +.test--variable-with-url { + order: 2; + background: var(--url-2); +} + + +.test--variable-with-url { + order: 3; + background: var(--url-3); +} + + +.test--variable-with-url { + order: 4; + background: var(--url-4); +} + +.no-prototype-collisions { + color: var(toString); +} + +.test-unicode { + color: var(--✅-size); +} + +.test { + font-family: var(--font, "Helvetica Neue", Arial, sans-serif); +} + +.ignores-declarations-that-have-an-exact-fallback-a { + left: 1rem; + left: var(--does-not-exist, 1rem); +} + +.ignores-declarations-that-have-an-exact-fallback-b { + right: 2em; + right: var(--✅-size); +} + +.does-not-ignore-declarations-that-have-an-exact-override-a { + left: var(--does-not-exist, 1rem); + left: 1rem; +} + +.does-not-ignore-declarations-that-have-an-exact-override-b { + right: var(--✅-size); + right: 2em; +} diff --git a/plugins/postcss-custom-properties-import-export/test/basic.export-css.expect.css b/plugins/postcss-custom-properties-import-export/test/basic.export-css.expect.css new file mode 100644 index 000000000..dc9fd373e --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/basic.export-css.expect.css @@ -0,0 +1,147 @@ +html { + --ref-color: skip; +} + +:root { + --color: rgb(255, 0, 0); + --color-h: 0; + --color-s: 100%; + --color-l: 50%; + --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l)); + --ref-color: var(--color); + --circular: var(--circular-2); + --circular-2: var(--circular); + --margin: 0 10px 20px 30px; + --shadow-color: rgb(255,0,0); + --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15)); + --font-family: "Open Sans", sans-serif; + --url-1: url("/my/path"); + --url-2: url('/my/path'); + --url-3: url(/my/path); + --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; + color: var(--color); +} + +:root, +[data-theme=light] { + --theme-color: #053; +} + +.test { + --skip: gray; + color: var(--override, var(--color)); +} + +.test--color_spacing { + box-shadow: inset 0 -3px 0 var(--color); +} + +.test--preserve_whitespaces { + margin: var(--margin); +} + +.test--complex_values { + box-shadow: var(--shadow); +} + +.test--comma_separated_values { + font-family: var(--font-family); +} + +.test--fallback { + color: var(--color-2, blue); +} + +.test--color_w_var { + color: var(--ref-color); +} + +.test--color_w_vars { + color: var(--color-hsl); +} + +.test--circular_var { + color: var(--circular); +} + +.test--z-index { + z-index: var(--z-index); +} + +.test--nested-fallback { + z-index: var(--xxx, var(--yyy, 1)); +} + +.text--calc { + width: calc((100% - var(--xxx, 1px)) + var(--yyy, 10px)); +} + +.test--linear-gradient { + background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%); +} + +.test--loose-formatting { + color: var( + --color, + blue + )/*rtl:red*/; +} + +.test--combined-selector { + color: var(--theme-color); +} + +.test--variable-with-url { + order: 1; + background: var(--url-1); +} + +.test--variable-with-url { + order: 2; + background: var(--url-2); +} + + +.test--variable-with-url { + order: 3; + background: var(--url-3); +} + + +.test--variable-with-url { + order: 4; + background: var(--url-4); +} + +.no-prototype-collisions { + color: var(toString); +} + +.test-unicode { + color: var(--✅-size); +} + +.test { + font-family: var(--font, "Helvetica Neue", Arial, sans-serif); +} + +.ignores-declarations-that-have-an-exact-fallback-a { + left: 1rem; + left: var(--does-not-exist, 1rem); +} + +.ignores-declarations-that-have-an-exact-fallback-b { + right: 2em; + right: var(--✅-size); +} + +.does-not-ignore-declarations-that-have-an-exact-override-a { + left: var(--does-not-exist, 1rem); + left: 1rem; +} + +.does-not-ignore-declarations-that-have-an-exact-override-b { + right: var(--✅-size); + right: 2em; +} diff --git a/plugins/postcss-custom-properties-import-export/test/basic.export-fn-promise.expect.css b/plugins/postcss-custom-properties-import-export/test/basic.export-fn-promise.expect.css new file mode 100644 index 000000000..dc9fd373e --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/basic.export-fn-promise.expect.css @@ -0,0 +1,147 @@ +html { + --ref-color: skip; +} + +:root { + --color: rgb(255, 0, 0); + --color-h: 0; + --color-s: 100%; + --color-l: 50%; + --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l)); + --ref-color: var(--color); + --circular: var(--circular-2); + --circular-2: var(--circular); + --margin: 0 10px 20px 30px; + --shadow-color: rgb(255,0,0); + --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15)); + --font-family: "Open Sans", sans-serif; + --url-1: url("/my/path"); + --url-2: url('/my/path'); + --url-3: url(/my/path); + --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; + color: var(--color); +} + +:root, +[data-theme=light] { + --theme-color: #053; +} + +.test { + --skip: gray; + color: var(--override, var(--color)); +} + +.test--color_spacing { + box-shadow: inset 0 -3px 0 var(--color); +} + +.test--preserve_whitespaces { + margin: var(--margin); +} + +.test--complex_values { + box-shadow: var(--shadow); +} + +.test--comma_separated_values { + font-family: var(--font-family); +} + +.test--fallback { + color: var(--color-2, blue); +} + +.test--color_w_var { + color: var(--ref-color); +} + +.test--color_w_vars { + color: var(--color-hsl); +} + +.test--circular_var { + color: var(--circular); +} + +.test--z-index { + z-index: var(--z-index); +} + +.test--nested-fallback { + z-index: var(--xxx, var(--yyy, 1)); +} + +.text--calc { + width: calc((100% - var(--xxx, 1px)) + var(--yyy, 10px)); +} + +.test--linear-gradient { + background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%); +} + +.test--loose-formatting { + color: var( + --color, + blue + )/*rtl:red*/; +} + +.test--combined-selector { + color: var(--theme-color); +} + +.test--variable-with-url { + order: 1; + background: var(--url-1); +} + +.test--variable-with-url { + order: 2; + background: var(--url-2); +} + + +.test--variable-with-url { + order: 3; + background: var(--url-3); +} + + +.test--variable-with-url { + order: 4; + background: var(--url-4); +} + +.no-prototype-collisions { + color: var(toString); +} + +.test-unicode { + color: var(--✅-size); +} + +.test { + font-family: var(--font, "Helvetica Neue", Arial, sans-serif); +} + +.ignores-declarations-that-have-an-exact-fallback-a { + left: 1rem; + left: var(--does-not-exist, 1rem); +} + +.ignores-declarations-that-have-an-exact-fallback-b { + right: 2em; + right: var(--✅-size); +} + +.does-not-ignore-declarations-that-have-an-exact-override-a { + left: var(--does-not-exist, 1rem); + left: 1rem; +} + +.does-not-ignore-declarations-that-have-an-exact-override-b { + right: var(--✅-size); + right: 2em; +} diff --git a/plugins/postcss-custom-properties-import-export/test/basic.export-fn.expect.css b/plugins/postcss-custom-properties-import-export/test/basic.export-fn.expect.css new file mode 100644 index 000000000..dc9fd373e --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/basic.export-fn.expect.css @@ -0,0 +1,147 @@ +html { + --ref-color: skip; +} + +:root { + --color: rgb(255, 0, 0); + --color-h: 0; + --color-s: 100%; + --color-l: 50%; + --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l)); + --ref-color: var(--color); + --circular: var(--circular-2); + --circular-2: var(--circular); + --margin: 0 10px 20px 30px; + --shadow-color: rgb(255,0,0); + --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15)); + --font-family: "Open Sans", sans-serif; + --url-1: url("/my/path"); + --url-2: url('/my/path'); + --url-3: url(/my/path); + --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; + color: var(--color); +} + +:root, +[data-theme=light] { + --theme-color: #053; +} + +.test { + --skip: gray; + color: var(--override, var(--color)); +} + +.test--color_spacing { + box-shadow: inset 0 -3px 0 var(--color); +} + +.test--preserve_whitespaces { + margin: var(--margin); +} + +.test--complex_values { + box-shadow: var(--shadow); +} + +.test--comma_separated_values { + font-family: var(--font-family); +} + +.test--fallback { + color: var(--color-2, blue); +} + +.test--color_w_var { + color: var(--ref-color); +} + +.test--color_w_vars { + color: var(--color-hsl); +} + +.test--circular_var { + color: var(--circular); +} + +.test--z-index { + z-index: var(--z-index); +} + +.test--nested-fallback { + z-index: var(--xxx, var(--yyy, 1)); +} + +.text--calc { + width: calc((100% - var(--xxx, 1px)) + var(--yyy, 10px)); +} + +.test--linear-gradient { + background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%); +} + +.test--loose-formatting { + color: var( + --color, + blue + )/*rtl:red*/; +} + +.test--combined-selector { + color: var(--theme-color); +} + +.test--variable-with-url { + order: 1; + background: var(--url-1); +} + +.test--variable-with-url { + order: 2; + background: var(--url-2); +} + + +.test--variable-with-url { + order: 3; + background: var(--url-3); +} + + +.test--variable-with-url { + order: 4; + background: var(--url-4); +} + +.no-prototype-collisions { + color: var(toString); +} + +.test-unicode { + color: var(--✅-size); +} + +.test { + font-family: var(--font, "Helvetica Neue", Arial, sans-serif); +} + +.ignores-declarations-that-have-an-exact-fallback-a { + left: 1rem; + left: var(--does-not-exist, 1rem); +} + +.ignores-declarations-that-have-an-exact-fallback-b { + right: 2em; + right: var(--✅-size); +} + +.does-not-ignore-declarations-that-have-an-exact-override-a { + left: var(--does-not-exist, 1rem); + left: 1rem; +} + +.does-not-ignore-declarations-that-have-an-exact-override-b { + right: var(--✅-size); + right: 2em; +} diff --git a/plugins/postcss-custom-properties-import-export/test/basic.export-js.expect.css b/plugins/postcss-custom-properties-import-export/test/basic.export-js.expect.css new file mode 100644 index 000000000..dc9fd373e --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/basic.export-js.expect.css @@ -0,0 +1,147 @@ +html { + --ref-color: skip; +} + +:root { + --color: rgb(255, 0, 0); + --color-h: 0; + --color-s: 100%; + --color-l: 50%; + --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l)); + --ref-color: var(--color); + --circular: var(--circular-2); + --circular-2: var(--circular); + --margin: 0 10px 20px 30px; + --shadow-color: rgb(255,0,0); + --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15)); + --font-family: "Open Sans", sans-serif; + --url-1: url("/my/path"); + --url-2: url('/my/path'); + --url-3: url(/my/path); + --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; + color: var(--color); +} + +:root, +[data-theme=light] { + --theme-color: #053; +} + +.test { + --skip: gray; + color: var(--override, var(--color)); +} + +.test--color_spacing { + box-shadow: inset 0 -3px 0 var(--color); +} + +.test--preserve_whitespaces { + margin: var(--margin); +} + +.test--complex_values { + box-shadow: var(--shadow); +} + +.test--comma_separated_values { + font-family: var(--font-family); +} + +.test--fallback { + color: var(--color-2, blue); +} + +.test--color_w_var { + color: var(--ref-color); +} + +.test--color_w_vars { + color: var(--color-hsl); +} + +.test--circular_var { + color: var(--circular); +} + +.test--z-index { + z-index: var(--z-index); +} + +.test--nested-fallback { + z-index: var(--xxx, var(--yyy, 1)); +} + +.text--calc { + width: calc((100% - var(--xxx, 1px)) + var(--yyy, 10px)); +} + +.test--linear-gradient { + background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%); +} + +.test--loose-formatting { + color: var( + --color, + blue + )/*rtl:red*/; +} + +.test--combined-selector { + color: var(--theme-color); +} + +.test--variable-with-url { + order: 1; + background: var(--url-1); +} + +.test--variable-with-url { + order: 2; + background: var(--url-2); +} + + +.test--variable-with-url { + order: 3; + background: var(--url-3); +} + + +.test--variable-with-url { + order: 4; + background: var(--url-4); +} + +.no-prototype-collisions { + color: var(toString); +} + +.test-unicode { + color: var(--✅-size); +} + +.test { + font-family: var(--font, "Helvetica Neue", Arial, sans-serif); +} + +.ignores-declarations-that-have-an-exact-fallback-a { + left: 1rem; + left: var(--does-not-exist, 1rem); +} + +.ignores-declarations-that-have-an-exact-fallback-b { + right: 2em; + right: var(--✅-size); +} + +.does-not-ignore-declarations-that-have-an-exact-override-a { + left: var(--does-not-exist, 1rem); + left: 1rem; +} + +.does-not-ignore-declarations-that-have-an-exact-override-b { + right: var(--✅-size); + right: 2em; +} diff --git a/plugins/postcss-custom-properties-import-export/test/basic.export-json.expect.css b/plugins/postcss-custom-properties-import-export/test/basic.export-json.expect.css new file mode 100644 index 000000000..dc9fd373e --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/basic.export-json.expect.css @@ -0,0 +1,147 @@ +html { + --ref-color: skip; +} + +:root { + --color: rgb(255, 0, 0); + --color-h: 0; + --color-s: 100%; + --color-l: 50%; + --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l)); + --ref-color: var(--color); + --circular: var(--circular-2); + --circular-2: var(--circular); + --margin: 0 10px 20px 30px; + --shadow-color: rgb(255,0,0); + --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15)); + --font-family: "Open Sans", sans-serif; + --url-1: url("/my/path"); + --url-2: url('/my/path'); + --url-3: url(/my/path); + --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; + color: var(--color); +} + +:root, +[data-theme=light] { + --theme-color: #053; +} + +.test { + --skip: gray; + color: var(--override, var(--color)); +} + +.test--color_spacing { + box-shadow: inset 0 -3px 0 var(--color); +} + +.test--preserve_whitespaces { + margin: var(--margin); +} + +.test--complex_values { + box-shadow: var(--shadow); +} + +.test--comma_separated_values { + font-family: var(--font-family); +} + +.test--fallback { + color: var(--color-2, blue); +} + +.test--color_w_var { + color: var(--ref-color); +} + +.test--color_w_vars { + color: var(--color-hsl); +} + +.test--circular_var { + color: var(--circular); +} + +.test--z-index { + z-index: var(--z-index); +} + +.test--nested-fallback { + z-index: var(--xxx, var(--yyy, 1)); +} + +.text--calc { + width: calc((100% - var(--xxx, 1px)) + var(--yyy, 10px)); +} + +.test--linear-gradient { + background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%); +} + +.test--loose-formatting { + color: var( + --color, + blue + )/*rtl:red*/; +} + +.test--combined-selector { + color: var(--theme-color); +} + +.test--variable-with-url { + order: 1; + background: var(--url-1); +} + +.test--variable-with-url { + order: 2; + background: var(--url-2); +} + + +.test--variable-with-url { + order: 3; + background: var(--url-3); +} + + +.test--variable-with-url { + order: 4; + background: var(--url-4); +} + +.no-prototype-collisions { + color: var(toString); +} + +.test-unicode { + color: var(--✅-size); +} + +.test { + font-family: var(--font, "Helvetica Neue", Arial, sans-serif); +} + +.ignores-declarations-that-have-an-exact-fallback-a { + left: 1rem; + left: var(--does-not-exist, 1rem); +} + +.ignores-declarations-that-have-an-exact-fallback-b { + right: 2em; + right: var(--✅-size); +} + +.does-not-ignore-declarations-that-have-an-exact-override-a { + left: var(--does-not-exist, 1rem); + left: 1rem; +} + +.does-not-ignore-declarations-that-have-an-exact-override-b { + right: var(--✅-size); + right: 2em; +} diff --git a/plugins/postcss-custom-properties-import-export/test/basic.export-mjs.expect.css b/plugins/postcss-custom-properties-import-export/test/basic.export-mjs.expect.css new file mode 100644 index 000000000..dc9fd373e --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/basic.export-mjs.expect.css @@ -0,0 +1,147 @@ +html { + --ref-color: skip; +} + +:root { + --color: rgb(255, 0, 0); + --color-h: 0; + --color-s: 100%; + --color-l: 50%; + --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l)); + --ref-color: var(--color); + --circular: var(--circular-2); + --circular-2: var(--circular); + --margin: 0 10px 20px 30px; + --shadow-color: rgb(255,0,0); + --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15)); + --font-family: "Open Sans", sans-serif; + --url-1: url("/my/path"); + --url-2: url('/my/path'); + --url-3: url(/my/path); + --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; + color: var(--color); +} + +:root, +[data-theme=light] { + --theme-color: #053; +} + +.test { + --skip: gray; + color: var(--override, var(--color)); +} + +.test--color_spacing { + box-shadow: inset 0 -3px 0 var(--color); +} + +.test--preserve_whitespaces { + margin: var(--margin); +} + +.test--complex_values { + box-shadow: var(--shadow); +} + +.test--comma_separated_values { + font-family: var(--font-family); +} + +.test--fallback { + color: var(--color-2, blue); +} + +.test--color_w_var { + color: var(--ref-color); +} + +.test--color_w_vars { + color: var(--color-hsl); +} + +.test--circular_var { + color: var(--circular); +} + +.test--z-index { + z-index: var(--z-index); +} + +.test--nested-fallback { + z-index: var(--xxx, var(--yyy, 1)); +} + +.text--calc { + width: calc((100% - var(--xxx, 1px)) + var(--yyy, 10px)); +} + +.test--linear-gradient { + background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%); +} + +.test--loose-formatting { + color: var( + --color, + blue + )/*rtl:red*/; +} + +.test--combined-selector { + color: var(--theme-color); +} + +.test--variable-with-url { + order: 1; + background: var(--url-1); +} + +.test--variable-with-url { + order: 2; + background: var(--url-2); +} + + +.test--variable-with-url { + order: 3; + background: var(--url-3); +} + + +.test--variable-with-url { + order: 4; + background: var(--url-4); +} + +.no-prototype-collisions { + color: var(toString); +} + +.test-unicode { + color: var(--✅-size); +} + +.test { + font-family: var(--font, "Helvetica Neue", Arial, sans-serif); +} + +.ignores-declarations-that-have-an-exact-fallback-a { + left: 1rem; + left: var(--does-not-exist, 1rem); +} + +.ignores-declarations-that-have-an-exact-fallback-b { + right: 2em; + right: var(--✅-size); +} + +.does-not-ignore-declarations-that-have-an-exact-override-a { + left: var(--does-not-exist, 1rem); + left: 1rem; +} + +.does-not-ignore-declarations-that-have-an-exact-override-b { + right: var(--✅-size); + right: 2em; +} diff --git a/plugins/postcss-custom-properties-import-export/test/basic.export-scss.expect.css b/plugins/postcss-custom-properties-import-export/test/basic.export-scss.expect.css new file mode 100644 index 000000000..dc9fd373e --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/basic.export-scss.expect.css @@ -0,0 +1,147 @@ +html { + --ref-color: skip; +} + +:root { + --color: rgb(255, 0, 0); + --color-h: 0; + --color-s: 100%; + --color-l: 50%; + --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l)); + --ref-color: var(--color); + --circular: var(--circular-2); + --circular-2: var(--circular); + --margin: 0 10px 20px 30px; + --shadow-color: rgb(255,0,0); + --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15)); + --font-family: "Open Sans", sans-serif; + --url-1: url("/my/path"); + --url-2: url('/my/path'); + --url-3: url(/my/path); + --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; + color: var(--color); +} + +:root, +[data-theme=light] { + --theme-color: #053; +} + +.test { + --skip: gray; + color: var(--override, var(--color)); +} + +.test--color_spacing { + box-shadow: inset 0 -3px 0 var(--color); +} + +.test--preserve_whitespaces { + margin: var(--margin); +} + +.test--complex_values { + box-shadow: var(--shadow); +} + +.test--comma_separated_values { + font-family: var(--font-family); +} + +.test--fallback { + color: var(--color-2, blue); +} + +.test--color_w_var { + color: var(--ref-color); +} + +.test--color_w_vars { + color: var(--color-hsl); +} + +.test--circular_var { + color: var(--circular); +} + +.test--z-index { + z-index: var(--z-index); +} + +.test--nested-fallback { + z-index: var(--xxx, var(--yyy, 1)); +} + +.text--calc { + width: calc((100% - var(--xxx, 1px)) + var(--yyy, 10px)); +} + +.test--linear-gradient { + background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%); +} + +.test--loose-formatting { + color: var( + --color, + blue + )/*rtl:red*/; +} + +.test--combined-selector { + color: var(--theme-color); +} + +.test--variable-with-url { + order: 1; + background: var(--url-1); +} + +.test--variable-with-url { + order: 2; + background: var(--url-2); +} + + +.test--variable-with-url { + order: 3; + background: var(--url-3); +} + + +.test--variable-with-url { + order: 4; + background: var(--url-4); +} + +.no-prototype-collisions { + color: var(toString); +} + +.test-unicode { + color: var(--✅-size); +} + +.test { + font-family: var(--font, "Helvetica Neue", Arial, sans-serif); +} + +.ignores-declarations-that-have-an-exact-fallback-a { + left: 1rem; + left: var(--does-not-exist, 1rem); +} + +.ignores-declarations-that-have-an-exact-fallback-b { + right: 2em; + right: var(--✅-size); +} + +.does-not-ignore-declarations-that-have-an-exact-override-a { + left: var(--does-not-exist, 1rem); + left: 1rem; +} + +.does-not-ignore-declarations-that-have-an-exact-override-b { + right: var(--✅-size); + right: 2em; +} diff --git a/plugins/postcss-custom-properties-import-export/test/basic.export.expect.css b/plugins/postcss-custom-properties-import-export/test/basic.export.expect.css new file mode 100644 index 000000000..dc9fd373e --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/basic.export.expect.css @@ -0,0 +1,147 @@ +html { + --ref-color: skip; +} + +:root { + --color: rgb(255, 0, 0); + --color-h: 0; + --color-s: 100%; + --color-l: 50%; + --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l)); + --ref-color: var(--color); + --circular: var(--circular-2); + --circular-2: var(--circular); + --margin: 0 10px 20px 30px; + --shadow-color: rgb(255,0,0); + --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15)); + --font-family: "Open Sans", sans-serif; + --url-1: url("/my/path"); + --url-2: url('/my/path'); + --url-3: url(/my/path); + --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; + color: var(--color); +} + +:root, +[data-theme=light] { + --theme-color: #053; +} + +.test { + --skip: gray; + color: var(--override, var(--color)); +} + +.test--color_spacing { + box-shadow: inset 0 -3px 0 var(--color); +} + +.test--preserve_whitespaces { + margin: var(--margin); +} + +.test--complex_values { + box-shadow: var(--shadow); +} + +.test--comma_separated_values { + font-family: var(--font-family); +} + +.test--fallback { + color: var(--color-2, blue); +} + +.test--color_w_var { + color: var(--ref-color); +} + +.test--color_w_vars { + color: var(--color-hsl); +} + +.test--circular_var { + color: var(--circular); +} + +.test--z-index { + z-index: var(--z-index); +} + +.test--nested-fallback { + z-index: var(--xxx, var(--yyy, 1)); +} + +.text--calc { + width: calc((100% - var(--xxx, 1px)) + var(--yyy, 10px)); +} + +.test--linear-gradient { + background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%); +} + +.test--loose-formatting { + color: var( + --color, + blue + )/*rtl:red*/; +} + +.test--combined-selector { + color: var(--theme-color); +} + +.test--variable-with-url { + order: 1; + background: var(--url-1); +} + +.test--variable-with-url { + order: 2; + background: var(--url-2); +} + + +.test--variable-with-url { + order: 3; + background: var(--url-3); +} + + +.test--variable-with-url { + order: 4; + background: var(--url-4); +} + +.no-prototype-collisions { + color: var(toString); +} + +.test-unicode { + color: var(--✅-size); +} + +.test { + font-family: var(--font, "Helvetica Neue", Arial, sans-serif); +} + +.ignores-declarations-that-have-an-exact-fallback-a { + left: 1rem; + left: var(--does-not-exist, 1rem); +} + +.ignores-declarations-that-have-an-exact-fallback-b { + right: 2em; + right: var(--✅-size); +} + +.does-not-ignore-declarations-that-have-an-exact-override-a { + left: var(--does-not-exist, 1rem); + left: 1rem; +} + +.does-not-ignore-declarations-that-have-an-exact-override-b { + right: var(--✅-size); + right: 2em; +} diff --git a/plugins/postcss-custom-properties-import-export/test/basic.import-cjs.expect.css b/plugins/postcss-custom-properties-import-export/test/basic.import-cjs.expect.css new file mode 100644 index 000000000..4f0cd5aa0 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/basic.import-cjs.expect.css @@ -0,0 +1,154 @@ +:root { + --color-2: yellow; + --color: rgb(255, 0, 0); + --z-index: 10; + --ref-color: var(--color); +} + +html { + --ref-color: skip; +} + +:root { + --color: rgb(255, 0, 0); + --color-h: 0; + --color-s: 100%; + --color-l: 50%; + --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l)); + --ref-color: var(--color); + --circular: var(--circular-2); + --circular-2: var(--circular); + --margin: 0 10px 20px 30px; + --shadow-color: rgb(255,0,0); + --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15)); + --font-family: "Open Sans", sans-serif; + --url-1: url("/my/path"); + --url-2: url('/my/path'); + --url-3: url(/my/path); + --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; + color: var(--color); +} + +:root, +[data-theme=light] { + --theme-color: #053; +} + +.test { + --skip: gray; + color: var(--override, var(--color)); +} + +.test--color_spacing { + box-shadow: inset 0 -3px 0 var(--color); +} + +.test--preserve_whitespaces { + margin: var(--margin); +} + +.test--complex_values { + box-shadow: var(--shadow); +} + +.test--comma_separated_values { + font-family: var(--font-family); +} + +.test--fallback { + color: var(--color-2, blue); +} + +.test--color_w_var { + color: var(--ref-color); +} + +.test--color_w_vars { + color: var(--color-hsl); +} + +.test--circular_var { + color: var(--circular); +} + +.test--z-index { + z-index: var(--z-index); +} + +.test--nested-fallback { + z-index: var(--xxx, var(--yyy, 1)); +} + +.text--calc { + width: calc((100% - var(--xxx, 1px)) + var(--yyy, 10px)); +} + +.test--linear-gradient { + background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%); +} + +.test--loose-formatting { + color: var( + --color, + blue + )/*rtl:red*/; +} + +.test--combined-selector { + color: var(--theme-color); +} + +.test--variable-with-url { + order: 1; + background: var(--url-1); +} + +.test--variable-with-url { + order: 2; + background: var(--url-2); +} + + +.test--variable-with-url { + order: 3; + background: var(--url-3); +} + + +.test--variable-with-url { + order: 4; + background: var(--url-4); +} + +.no-prototype-collisions { + color: var(toString); +} + +.test-unicode { + color: var(--✅-size); +} + +.test { + font-family: var(--font, "Helvetica Neue", Arial, sans-serif); +} + +.ignores-declarations-that-have-an-exact-fallback-a { + left: 1rem; + left: var(--does-not-exist, 1rem); +} + +.ignores-declarations-that-have-an-exact-fallback-b { + right: 2em; + right: var(--✅-size); +} + +.does-not-ignore-declarations-that-have-an-exact-override-a { + left: var(--does-not-exist, 1rem); + left: 1rem; +} + +.does-not-ignore-declarations-that-have-an-exact-override-b { + right: var(--✅-size); + right: 2em; +} diff --git a/plugins/postcss-custom-properties-import-export/test/basic.import-css-from-type.expect.css b/plugins/postcss-custom-properties-import-export/test/basic.import-css-from-type.expect.css new file mode 100644 index 000000000..4f0cd5aa0 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/basic.import-css-from-type.expect.css @@ -0,0 +1,154 @@ +:root { + --color-2: yellow; + --color: rgb(255, 0, 0); + --z-index: 10; + --ref-color: var(--color); +} + +html { + --ref-color: skip; +} + +:root { + --color: rgb(255, 0, 0); + --color-h: 0; + --color-s: 100%; + --color-l: 50%; + --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l)); + --ref-color: var(--color); + --circular: var(--circular-2); + --circular-2: var(--circular); + --margin: 0 10px 20px 30px; + --shadow-color: rgb(255,0,0); + --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15)); + --font-family: "Open Sans", sans-serif; + --url-1: url("/my/path"); + --url-2: url('/my/path'); + --url-3: url(/my/path); + --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; + color: var(--color); +} + +:root, +[data-theme=light] { + --theme-color: #053; +} + +.test { + --skip: gray; + color: var(--override, var(--color)); +} + +.test--color_spacing { + box-shadow: inset 0 -3px 0 var(--color); +} + +.test--preserve_whitespaces { + margin: var(--margin); +} + +.test--complex_values { + box-shadow: var(--shadow); +} + +.test--comma_separated_values { + font-family: var(--font-family); +} + +.test--fallback { + color: var(--color-2, blue); +} + +.test--color_w_var { + color: var(--ref-color); +} + +.test--color_w_vars { + color: var(--color-hsl); +} + +.test--circular_var { + color: var(--circular); +} + +.test--z-index { + z-index: var(--z-index); +} + +.test--nested-fallback { + z-index: var(--xxx, var(--yyy, 1)); +} + +.text--calc { + width: calc((100% - var(--xxx, 1px)) + var(--yyy, 10px)); +} + +.test--linear-gradient { + background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%); +} + +.test--loose-formatting { + color: var( + --color, + blue + )/*rtl:red*/; +} + +.test--combined-selector { + color: var(--theme-color); +} + +.test--variable-with-url { + order: 1; + background: var(--url-1); +} + +.test--variable-with-url { + order: 2; + background: var(--url-2); +} + + +.test--variable-with-url { + order: 3; + background: var(--url-3); +} + + +.test--variable-with-url { + order: 4; + background: var(--url-4); +} + +.no-prototype-collisions { + color: var(toString); +} + +.test-unicode { + color: var(--✅-size); +} + +.test { + font-family: var(--font, "Helvetica Neue", Arial, sans-serif); +} + +.ignores-declarations-that-have-an-exact-fallback-a { + left: 1rem; + left: var(--does-not-exist, 1rem); +} + +.ignores-declarations-that-have-an-exact-fallback-b { + right: 2em; + right: var(--✅-size); +} + +.does-not-ignore-declarations-that-have-an-exact-override-a { + left: var(--does-not-exist, 1rem); + left: 1rem; +} + +.does-not-ignore-declarations-that-have-an-exact-override-b { + right: var(--✅-size); + right: 2em; +} diff --git a/plugins/postcss-custom-properties-import-export/test/basic.import-css-from.expect.css b/plugins/postcss-custom-properties-import-export/test/basic.import-css-from.expect.css new file mode 100644 index 000000000..4f0cd5aa0 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/basic.import-css-from.expect.css @@ -0,0 +1,154 @@ +:root { + --color-2: yellow; + --color: rgb(255, 0, 0); + --z-index: 10; + --ref-color: var(--color); +} + +html { + --ref-color: skip; +} + +:root { + --color: rgb(255, 0, 0); + --color-h: 0; + --color-s: 100%; + --color-l: 50%; + --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l)); + --ref-color: var(--color); + --circular: var(--circular-2); + --circular-2: var(--circular); + --margin: 0 10px 20px 30px; + --shadow-color: rgb(255,0,0); + --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15)); + --font-family: "Open Sans", sans-serif; + --url-1: url("/my/path"); + --url-2: url('/my/path'); + --url-3: url(/my/path); + --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; + color: var(--color); +} + +:root, +[data-theme=light] { + --theme-color: #053; +} + +.test { + --skip: gray; + color: var(--override, var(--color)); +} + +.test--color_spacing { + box-shadow: inset 0 -3px 0 var(--color); +} + +.test--preserve_whitespaces { + margin: var(--margin); +} + +.test--complex_values { + box-shadow: var(--shadow); +} + +.test--comma_separated_values { + font-family: var(--font-family); +} + +.test--fallback { + color: var(--color-2, blue); +} + +.test--color_w_var { + color: var(--ref-color); +} + +.test--color_w_vars { + color: var(--color-hsl); +} + +.test--circular_var { + color: var(--circular); +} + +.test--z-index { + z-index: var(--z-index); +} + +.test--nested-fallback { + z-index: var(--xxx, var(--yyy, 1)); +} + +.text--calc { + width: calc((100% - var(--xxx, 1px)) + var(--yyy, 10px)); +} + +.test--linear-gradient { + background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%); +} + +.test--loose-formatting { + color: var( + --color, + blue + )/*rtl:red*/; +} + +.test--combined-selector { + color: var(--theme-color); +} + +.test--variable-with-url { + order: 1; + background: var(--url-1); +} + +.test--variable-with-url { + order: 2; + background: var(--url-2); +} + + +.test--variable-with-url { + order: 3; + background: var(--url-3); +} + + +.test--variable-with-url { + order: 4; + background: var(--url-4); +} + +.no-prototype-collisions { + color: var(toString); +} + +.test-unicode { + color: var(--✅-size); +} + +.test { + font-family: var(--font, "Helvetica Neue", Arial, sans-serif); +} + +.ignores-declarations-that-have-an-exact-fallback-a { + left: 1rem; + left: var(--does-not-exist, 1rem); +} + +.ignores-declarations-that-have-an-exact-fallback-b { + right: 2em; + right: var(--✅-size); +} + +.does-not-ignore-declarations-that-have-an-exact-override-a { + left: var(--does-not-exist, 1rem); + left: 1rem; +} + +.does-not-ignore-declarations-that-have-an-exact-override-b { + right: var(--✅-size); + right: 2em; +} diff --git a/plugins/postcss-custom-properties-import-export/test/basic.import-css-js.expect.css b/plugins/postcss-custom-properties-import-export/test/basic.import-css-js.expect.css new file mode 100644 index 000000000..4f0cd5aa0 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/basic.import-css-js.expect.css @@ -0,0 +1,154 @@ +:root { + --color-2: yellow; + --color: rgb(255, 0, 0); + --z-index: 10; + --ref-color: var(--color); +} + +html { + --ref-color: skip; +} + +:root { + --color: rgb(255, 0, 0); + --color-h: 0; + --color-s: 100%; + --color-l: 50%; + --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l)); + --ref-color: var(--color); + --circular: var(--circular-2); + --circular-2: var(--circular); + --margin: 0 10px 20px 30px; + --shadow-color: rgb(255,0,0); + --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15)); + --font-family: "Open Sans", sans-serif; + --url-1: url("/my/path"); + --url-2: url('/my/path'); + --url-3: url(/my/path); + --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; + color: var(--color); +} + +:root, +[data-theme=light] { + --theme-color: #053; +} + +.test { + --skip: gray; + color: var(--override, var(--color)); +} + +.test--color_spacing { + box-shadow: inset 0 -3px 0 var(--color); +} + +.test--preserve_whitespaces { + margin: var(--margin); +} + +.test--complex_values { + box-shadow: var(--shadow); +} + +.test--comma_separated_values { + font-family: var(--font-family); +} + +.test--fallback { + color: var(--color-2, blue); +} + +.test--color_w_var { + color: var(--ref-color); +} + +.test--color_w_vars { + color: var(--color-hsl); +} + +.test--circular_var { + color: var(--circular); +} + +.test--z-index { + z-index: var(--z-index); +} + +.test--nested-fallback { + z-index: var(--xxx, var(--yyy, 1)); +} + +.text--calc { + width: calc((100% - var(--xxx, 1px)) + var(--yyy, 10px)); +} + +.test--linear-gradient { + background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%); +} + +.test--loose-formatting { + color: var( + --color, + blue + )/*rtl:red*/; +} + +.test--combined-selector { + color: var(--theme-color); +} + +.test--variable-with-url { + order: 1; + background: var(--url-1); +} + +.test--variable-with-url { + order: 2; + background: var(--url-2); +} + + +.test--variable-with-url { + order: 3; + background: var(--url-3); +} + + +.test--variable-with-url { + order: 4; + background: var(--url-4); +} + +.no-prototype-collisions { + color: var(toString); +} + +.test-unicode { + color: var(--✅-size); +} + +.test { + font-family: var(--font, "Helvetica Neue", Arial, sans-serif); +} + +.ignores-declarations-that-have-an-exact-fallback-a { + left: 1rem; + left: var(--does-not-exist, 1rem); +} + +.ignores-declarations-that-have-an-exact-fallback-b { + right: 2em; + right: var(--✅-size); +} + +.does-not-ignore-declarations-that-have-an-exact-override-a { + left: var(--does-not-exist, 1rem); + left: 1rem; +} + +.does-not-ignore-declarations-that-have-an-exact-override-b { + right: var(--✅-size); + right: 2em; +} diff --git a/plugins/postcss-custom-properties-import-export/test/basic.import-css-pcss.expect.css b/plugins/postcss-custom-properties-import-export/test/basic.import-css-pcss.expect.css new file mode 100644 index 000000000..4f0cd5aa0 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/basic.import-css-pcss.expect.css @@ -0,0 +1,154 @@ +:root { + --color-2: yellow; + --color: rgb(255, 0, 0); + --z-index: 10; + --ref-color: var(--color); +} + +html { + --ref-color: skip; +} + +:root { + --color: rgb(255, 0, 0); + --color-h: 0; + --color-s: 100%; + --color-l: 50%; + --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l)); + --ref-color: var(--color); + --circular: var(--circular-2); + --circular-2: var(--circular); + --margin: 0 10px 20px 30px; + --shadow-color: rgb(255,0,0); + --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15)); + --font-family: "Open Sans", sans-serif; + --url-1: url("/my/path"); + --url-2: url('/my/path'); + --url-3: url(/my/path); + --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; + color: var(--color); +} + +:root, +[data-theme=light] { + --theme-color: #053; +} + +.test { + --skip: gray; + color: var(--override, var(--color)); +} + +.test--color_spacing { + box-shadow: inset 0 -3px 0 var(--color); +} + +.test--preserve_whitespaces { + margin: var(--margin); +} + +.test--complex_values { + box-shadow: var(--shadow); +} + +.test--comma_separated_values { + font-family: var(--font-family); +} + +.test--fallback { + color: var(--color-2, blue); +} + +.test--color_w_var { + color: var(--ref-color); +} + +.test--color_w_vars { + color: var(--color-hsl); +} + +.test--circular_var { + color: var(--circular); +} + +.test--z-index { + z-index: var(--z-index); +} + +.test--nested-fallback { + z-index: var(--xxx, var(--yyy, 1)); +} + +.text--calc { + width: calc((100% - var(--xxx, 1px)) + var(--yyy, 10px)); +} + +.test--linear-gradient { + background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%); +} + +.test--loose-formatting { + color: var( + --color, + blue + )/*rtl:red*/; +} + +.test--combined-selector { + color: var(--theme-color); +} + +.test--variable-with-url { + order: 1; + background: var(--url-1); +} + +.test--variable-with-url { + order: 2; + background: var(--url-2); +} + + +.test--variable-with-url { + order: 3; + background: var(--url-3); +} + + +.test--variable-with-url { + order: 4; + background: var(--url-4); +} + +.no-prototype-collisions { + color: var(toString); +} + +.test-unicode { + color: var(--✅-size); +} + +.test { + font-family: var(--font, "Helvetica Neue", Arial, sans-serif); +} + +.ignores-declarations-that-have-an-exact-fallback-a { + left: 1rem; + left: var(--does-not-exist, 1rem); +} + +.ignores-declarations-that-have-an-exact-fallback-b { + right: 2em; + right: var(--✅-size); +} + +.does-not-ignore-declarations-that-have-an-exact-override-a { + left: var(--does-not-exist, 1rem); + left: 1rem; +} + +.does-not-ignore-declarations-that-have-an-exact-override-b { + right: var(--✅-size); + right: 2em; +} diff --git a/plugins/postcss-custom-properties-import-export/test/basic.import-css.expect.css b/plugins/postcss-custom-properties-import-export/test/basic.import-css.expect.css new file mode 100644 index 000000000..4f0cd5aa0 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/basic.import-css.expect.css @@ -0,0 +1,154 @@ +:root { + --color-2: yellow; + --color: rgb(255, 0, 0); + --z-index: 10; + --ref-color: var(--color); +} + +html { + --ref-color: skip; +} + +:root { + --color: rgb(255, 0, 0); + --color-h: 0; + --color-s: 100%; + --color-l: 50%; + --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l)); + --ref-color: var(--color); + --circular: var(--circular-2); + --circular-2: var(--circular); + --margin: 0 10px 20px 30px; + --shadow-color: rgb(255,0,0); + --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15)); + --font-family: "Open Sans", sans-serif; + --url-1: url("/my/path"); + --url-2: url('/my/path'); + --url-3: url(/my/path); + --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; + color: var(--color); +} + +:root, +[data-theme=light] { + --theme-color: #053; +} + +.test { + --skip: gray; + color: var(--override, var(--color)); +} + +.test--color_spacing { + box-shadow: inset 0 -3px 0 var(--color); +} + +.test--preserve_whitespaces { + margin: var(--margin); +} + +.test--complex_values { + box-shadow: var(--shadow); +} + +.test--comma_separated_values { + font-family: var(--font-family); +} + +.test--fallback { + color: var(--color-2, blue); +} + +.test--color_w_var { + color: var(--ref-color); +} + +.test--color_w_vars { + color: var(--color-hsl); +} + +.test--circular_var { + color: var(--circular); +} + +.test--z-index { + z-index: var(--z-index); +} + +.test--nested-fallback { + z-index: var(--xxx, var(--yyy, 1)); +} + +.text--calc { + width: calc((100% - var(--xxx, 1px)) + var(--yyy, 10px)); +} + +.test--linear-gradient { + background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%); +} + +.test--loose-formatting { + color: var( + --color, + blue + )/*rtl:red*/; +} + +.test--combined-selector { + color: var(--theme-color); +} + +.test--variable-with-url { + order: 1; + background: var(--url-1); +} + +.test--variable-with-url { + order: 2; + background: var(--url-2); +} + + +.test--variable-with-url { + order: 3; + background: var(--url-3); +} + + +.test--variable-with-url { + order: 4; + background: var(--url-4); +} + +.no-prototype-collisions { + color: var(toString); +} + +.test-unicode { + color: var(--✅-size); +} + +.test { + font-family: var(--font, "Helvetica Neue", Arial, sans-serif); +} + +.ignores-declarations-that-have-an-exact-fallback-a { + left: 1rem; + left: var(--does-not-exist, 1rem); +} + +.ignores-declarations-that-have-an-exact-fallback-b { + right: 2em; + right: var(--✅-size); +} + +.does-not-ignore-declarations-that-have-an-exact-override-a { + left: var(--does-not-exist, 1rem); + left: 1rem; +} + +.does-not-ignore-declarations-that-have-an-exact-override-b { + right: var(--✅-size); + right: 2em; +} diff --git a/plugins/postcss-custom-properties-import-export/test/basic.import-fn-promise.expect.css b/plugins/postcss-custom-properties-import-export/test/basic.import-fn-promise.expect.css new file mode 100644 index 000000000..998feb186 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/basic.import-fn-promise.expect.css @@ -0,0 +1,154 @@ +:root { + --z-index: 10; + --ref-color: var(--color); + --color-2: yellow; + --color: rgb(255, 0, 0); +} + +html { + --ref-color: skip; +} + +:root { + --color: rgb(255, 0, 0); + --color-h: 0; + --color-s: 100%; + --color-l: 50%; + --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l)); + --ref-color: var(--color); + --circular: var(--circular-2); + --circular-2: var(--circular); + --margin: 0 10px 20px 30px; + --shadow-color: rgb(255,0,0); + --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15)); + --font-family: "Open Sans", sans-serif; + --url-1: url("/my/path"); + --url-2: url('/my/path'); + --url-3: url(/my/path); + --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; + color: var(--color); +} + +:root, +[data-theme=light] { + --theme-color: #053; +} + +.test { + --skip: gray; + color: var(--override, var(--color)); +} + +.test--color_spacing { + box-shadow: inset 0 -3px 0 var(--color); +} + +.test--preserve_whitespaces { + margin: var(--margin); +} + +.test--complex_values { + box-shadow: var(--shadow); +} + +.test--comma_separated_values { + font-family: var(--font-family); +} + +.test--fallback { + color: var(--color-2, blue); +} + +.test--color_w_var { + color: var(--ref-color); +} + +.test--color_w_vars { + color: var(--color-hsl); +} + +.test--circular_var { + color: var(--circular); +} + +.test--z-index { + z-index: var(--z-index); +} + +.test--nested-fallback { + z-index: var(--xxx, var(--yyy, 1)); +} + +.text--calc { + width: calc((100% - var(--xxx, 1px)) + var(--yyy, 10px)); +} + +.test--linear-gradient { + background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%); +} + +.test--loose-formatting { + color: var( + --color, + blue + )/*rtl:red*/; +} + +.test--combined-selector { + color: var(--theme-color); +} + +.test--variable-with-url { + order: 1; + background: var(--url-1); +} + +.test--variable-with-url { + order: 2; + background: var(--url-2); +} + + +.test--variable-with-url { + order: 3; + background: var(--url-3); +} + + +.test--variable-with-url { + order: 4; + background: var(--url-4); +} + +.no-prototype-collisions { + color: var(toString); +} + +.test-unicode { + color: var(--✅-size); +} + +.test { + font-family: var(--font, "Helvetica Neue", Arial, sans-serif); +} + +.ignores-declarations-that-have-an-exact-fallback-a { + left: 1rem; + left: var(--does-not-exist, 1rem); +} + +.ignores-declarations-that-have-an-exact-fallback-b { + right: 2em; + right: var(--✅-size); +} + +.does-not-ignore-declarations-that-have-an-exact-override-a { + left: var(--does-not-exist, 1rem); + left: 1rem; +} + +.does-not-ignore-declarations-that-have-an-exact-override-b { + right: var(--✅-size); + right: 2em; +} diff --git a/plugins/postcss-custom-properties-import-export/test/basic.import-fn.expect.css b/plugins/postcss-custom-properties-import-export/test/basic.import-fn.expect.css new file mode 100644 index 000000000..d3cc2c38c --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/basic.import-fn.expect.css @@ -0,0 +1,155 @@ +:root { + --z-index: 10; + --margin: 0 10px 20px 30px; + --ref-color: var(--color); + --color-2: yellow; + --color: rgb(255, 0, 0); +} + +html { + --ref-color: skip; +} + +:root { + --color: rgb(255, 0, 0); + --color-h: 0; + --color-s: 100%; + --color-l: 50%; + --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l)); + --ref-color: var(--color); + --circular: var(--circular-2); + --circular-2: var(--circular); + --margin: 0 10px 20px 30px; + --shadow-color: rgb(255,0,0); + --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15)); + --font-family: "Open Sans", sans-serif; + --url-1: url("/my/path"); + --url-2: url('/my/path'); + --url-3: url(/my/path); + --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; + color: var(--color); +} + +:root, +[data-theme=light] { + --theme-color: #053; +} + +.test { + --skip: gray; + color: var(--override, var(--color)); +} + +.test--color_spacing { + box-shadow: inset 0 -3px 0 var(--color); +} + +.test--preserve_whitespaces { + margin: var(--margin); +} + +.test--complex_values { + box-shadow: var(--shadow); +} + +.test--comma_separated_values { + font-family: var(--font-family); +} + +.test--fallback { + color: var(--color-2, blue); +} + +.test--color_w_var { + color: var(--ref-color); +} + +.test--color_w_vars { + color: var(--color-hsl); +} + +.test--circular_var { + color: var(--circular); +} + +.test--z-index { + z-index: var(--z-index); +} + +.test--nested-fallback { + z-index: var(--xxx, var(--yyy, 1)); +} + +.text--calc { + width: calc((100% - var(--xxx, 1px)) + var(--yyy, 10px)); +} + +.test--linear-gradient { + background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%); +} + +.test--loose-formatting { + color: var( + --color, + blue + )/*rtl:red*/; +} + +.test--combined-selector { + color: var(--theme-color); +} + +.test--variable-with-url { + order: 1; + background: var(--url-1); +} + +.test--variable-with-url { + order: 2; + background: var(--url-2); +} + + +.test--variable-with-url { + order: 3; + background: var(--url-3); +} + + +.test--variable-with-url { + order: 4; + background: var(--url-4); +} + +.no-prototype-collisions { + color: var(toString); +} + +.test-unicode { + color: var(--✅-size); +} + +.test { + font-family: var(--font, "Helvetica Neue", Arial, sans-serif); +} + +.ignores-declarations-that-have-an-exact-fallback-a { + left: 1rem; + left: var(--does-not-exist, 1rem); +} + +.ignores-declarations-that-have-an-exact-fallback-b { + right: 2em; + right: var(--✅-size); +} + +.does-not-ignore-declarations-that-have-an-exact-override-a { + left: var(--does-not-exist, 1rem); + left: 1rem; +} + +.does-not-ignore-declarations-that-have-an-exact-override-b { + right: var(--✅-size); + right: 2em; +} diff --git a/plugins/postcss-custom-properties-import-export/test/basic.import-imported-styles-override-document-styles.false.expect.css b/plugins/postcss-custom-properties-import-export/test/basic.import-imported-styles-override-document-styles.false.expect.css new file mode 100644 index 000000000..d3cc2c38c --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/basic.import-imported-styles-override-document-styles.false.expect.css @@ -0,0 +1,155 @@ +:root { + --z-index: 10; + --margin: 0 10px 20px 30px; + --ref-color: var(--color); + --color-2: yellow; + --color: rgb(255, 0, 0); +} + +html { + --ref-color: skip; +} + +:root { + --color: rgb(255, 0, 0); + --color-h: 0; + --color-s: 100%; + --color-l: 50%; + --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l)); + --ref-color: var(--color); + --circular: var(--circular-2); + --circular-2: var(--circular); + --margin: 0 10px 20px 30px; + --shadow-color: rgb(255,0,0); + --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15)); + --font-family: "Open Sans", sans-serif; + --url-1: url("/my/path"); + --url-2: url('/my/path'); + --url-3: url(/my/path); + --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; + color: var(--color); +} + +:root, +[data-theme=light] { + --theme-color: #053; +} + +.test { + --skip: gray; + color: var(--override, var(--color)); +} + +.test--color_spacing { + box-shadow: inset 0 -3px 0 var(--color); +} + +.test--preserve_whitespaces { + margin: var(--margin); +} + +.test--complex_values { + box-shadow: var(--shadow); +} + +.test--comma_separated_values { + font-family: var(--font-family); +} + +.test--fallback { + color: var(--color-2, blue); +} + +.test--color_w_var { + color: var(--ref-color); +} + +.test--color_w_vars { + color: var(--color-hsl); +} + +.test--circular_var { + color: var(--circular); +} + +.test--z-index { + z-index: var(--z-index); +} + +.test--nested-fallback { + z-index: var(--xxx, var(--yyy, 1)); +} + +.text--calc { + width: calc((100% - var(--xxx, 1px)) + var(--yyy, 10px)); +} + +.test--linear-gradient { + background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%); +} + +.test--loose-formatting { + color: var( + --color, + blue + )/*rtl:red*/; +} + +.test--combined-selector { + color: var(--theme-color); +} + +.test--variable-with-url { + order: 1; + background: var(--url-1); +} + +.test--variable-with-url { + order: 2; + background: var(--url-2); +} + + +.test--variable-with-url { + order: 3; + background: var(--url-3); +} + + +.test--variable-with-url { + order: 4; + background: var(--url-4); +} + +.no-prototype-collisions { + color: var(toString); +} + +.test-unicode { + color: var(--✅-size); +} + +.test { + font-family: var(--font, "Helvetica Neue", Arial, sans-serif); +} + +.ignores-declarations-that-have-an-exact-fallback-a { + left: 1rem; + left: var(--does-not-exist, 1rem); +} + +.ignores-declarations-that-have-an-exact-fallback-b { + right: 2em; + right: var(--✅-size); +} + +.does-not-ignore-declarations-that-have-an-exact-override-a { + left: var(--does-not-exist, 1rem); + left: 1rem; +} + +.does-not-ignore-declarations-that-have-an-exact-override-b { + right: var(--✅-size); + right: 2em; +} diff --git a/plugins/postcss-custom-properties-import-export/test/basic.import-imported-styles-override-document-styles.true.expect.css b/plugins/postcss-custom-properties-import-export/test/basic.import-imported-styles-override-document-styles.true.expect.css new file mode 100644 index 000000000..9669ff981 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/basic.import-imported-styles-override-document-styles.true.expect.css @@ -0,0 +1,155 @@ +html { + --ref-color: skip; +} + +:root { + --color: rgb(255, 0, 0); + --color-h: 0; + --color-s: 100%; + --color-l: 50%; + --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l)); + --ref-color: var(--color); + --circular: var(--circular-2); + --circular-2: var(--circular); + --margin: 0 10px 20px 30px; + --shadow-color: rgb(255,0,0); + --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15)); + --font-family: "Open Sans", sans-serif; + --url-1: url("/my/path"); + --url-2: url('/my/path'); + --url-3: url(/my/path); + --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; + color: var(--color); +} + +:root, +[data-theme=light] { + --theme-color: #053; +} + +.test { + --skip: gray; + color: var(--override, var(--color)); +} + +.test--color_spacing { + box-shadow: inset 0 -3px 0 var(--color); +} + +.test--preserve_whitespaces { + margin: var(--margin); +} + +.test--complex_values { + box-shadow: var(--shadow); +} + +.test--comma_separated_values { + font-family: var(--font-family); +} + +.test--fallback { + color: var(--color-2, blue); +} + +.test--color_w_var { + color: var(--ref-color); +} + +.test--color_w_vars { + color: var(--color-hsl); +} + +.test--circular_var { + color: var(--circular); +} + +.test--z-index { + z-index: var(--z-index); +} + +.test--nested-fallback { + z-index: var(--xxx, var(--yyy, 1)); +} + +.text--calc { + width: calc((100% - var(--xxx, 1px)) + var(--yyy, 10px)); +} + +.test--linear-gradient { + background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%); +} + +.test--loose-formatting { + color: var( + --color, + blue + )/*rtl:red*/; +} + +.test--combined-selector { + color: var(--theme-color); +} + +.test--variable-with-url { + order: 1; + background: var(--url-1); +} + +.test--variable-with-url { + order: 2; + background: var(--url-2); +} + + +.test--variable-with-url { + order: 3; + background: var(--url-3); +} + + +.test--variable-with-url { + order: 4; + background: var(--url-4); +} + +.no-prototype-collisions { + color: var(toString); +} + +.test-unicode { + color: var(--✅-size); +} + +.test { + font-family: var(--font, "Helvetica Neue", Arial, sans-serif); +} + +.ignores-declarations-that-have-an-exact-fallback-a { + left: 1rem; + left: var(--does-not-exist, 1rem); +} + +.ignores-declarations-that-have-an-exact-fallback-b { + right: 2em; + right: var(--✅-size); +} + +.does-not-ignore-declarations-that-have-an-exact-override-a { + left: var(--does-not-exist, 1rem); + left: 1rem; +} + +.does-not-ignore-declarations-that-have-an-exact-override-b { + right: var(--✅-size); + right: 2em; +} + +:root { + --z-index: 10; + --margin: 0 10px 20px 30px; + --ref-color: var(--color); + --color-2: yellow; + --color: rgb(255, 0, 0); +} diff --git a/plugins/postcss-custom-properties-import-export/test/basic.import-is-empty.expect.css b/plugins/postcss-custom-properties-import-export/test/basic.import-is-empty.expect.css new file mode 100644 index 000000000..dc9fd373e --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/basic.import-is-empty.expect.css @@ -0,0 +1,147 @@ +html { + --ref-color: skip; +} + +:root { + --color: rgb(255, 0, 0); + --color-h: 0; + --color-s: 100%; + --color-l: 50%; + --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l)); + --ref-color: var(--color); + --circular: var(--circular-2); + --circular-2: var(--circular); + --margin: 0 10px 20px 30px; + --shadow-color: rgb(255,0,0); + --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15)); + --font-family: "Open Sans", sans-serif; + --url-1: url("/my/path"); + --url-2: url('/my/path'); + --url-3: url(/my/path); + --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; + color: var(--color); +} + +:root, +[data-theme=light] { + --theme-color: #053; +} + +.test { + --skip: gray; + color: var(--override, var(--color)); +} + +.test--color_spacing { + box-shadow: inset 0 -3px 0 var(--color); +} + +.test--preserve_whitespaces { + margin: var(--margin); +} + +.test--complex_values { + box-shadow: var(--shadow); +} + +.test--comma_separated_values { + font-family: var(--font-family); +} + +.test--fallback { + color: var(--color-2, blue); +} + +.test--color_w_var { + color: var(--ref-color); +} + +.test--color_w_vars { + color: var(--color-hsl); +} + +.test--circular_var { + color: var(--circular); +} + +.test--z-index { + z-index: var(--z-index); +} + +.test--nested-fallback { + z-index: var(--xxx, var(--yyy, 1)); +} + +.text--calc { + width: calc((100% - var(--xxx, 1px)) + var(--yyy, 10px)); +} + +.test--linear-gradient { + background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%); +} + +.test--loose-formatting { + color: var( + --color, + blue + )/*rtl:red*/; +} + +.test--combined-selector { + color: var(--theme-color); +} + +.test--variable-with-url { + order: 1; + background: var(--url-1); +} + +.test--variable-with-url { + order: 2; + background: var(--url-2); +} + + +.test--variable-with-url { + order: 3; + background: var(--url-3); +} + + +.test--variable-with-url { + order: 4; + background: var(--url-4); +} + +.no-prototype-collisions { + color: var(toString); +} + +.test-unicode { + color: var(--✅-size); +} + +.test { + font-family: var(--font, "Helvetica Neue", Arial, sans-serif); +} + +.ignores-declarations-that-have-an-exact-fallback-a { + left: 1rem; + left: var(--does-not-exist, 1rem); +} + +.ignores-declarations-that-have-an-exact-fallback-b { + right: 2em; + right: var(--✅-size); +} + +.does-not-ignore-declarations-that-have-an-exact-override-a { + left: var(--does-not-exist, 1rem); + left: 1rem; +} + +.does-not-ignore-declarations-that-have-an-exact-override-b { + right: var(--✅-size); + right: 2em; +} diff --git a/plugins/postcss-custom-properties-import-export/test/basic.import-json.expect.css b/plugins/postcss-custom-properties-import-export/test/basic.import-json.expect.css new file mode 100644 index 000000000..998feb186 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/basic.import-json.expect.css @@ -0,0 +1,154 @@ +:root { + --z-index: 10; + --ref-color: var(--color); + --color-2: yellow; + --color: rgb(255, 0, 0); +} + +html { + --ref-color: skip; +} + +:root { + --color: rgb(255, 0, 0); + --color-h: 0; + --color-s: 100%; + --color-l: 50%; + --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l)); + --ref-color: var(--color); + --circular: var(--circular-2); + --circular-2: var(--circular); + --margin: 0 10px 20px 30px; + --shadow-color: rgb(255,0,0); + --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15)); + --font-family: "Open Sans", sans-serif; + --url-1: url("/my/path"); + --url-2: url('/my/path'); + --url-3: url(/my/path); + --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; + color: var(--color); +} + +:root, +[data-theme=light] { + --theme-color: #053; +} + +.test { + --skip: gray; + color: var(--override, var(--color)); +} + +.test--color_spacing { + box-shadow: inset 0 -3px 0 var(--color); +} + +.test--preserve_whitespaces { + margin: var(--margin); +} + +.test--complex_values { + box-shadow: var(--shadow); +} + +.test--comma_separated_values { + font-family: var(--font-family); +} + +.test--fallback { + color: var(--color-2, blue); +} + +.test--color_w_var { + color: var(--ref-color); +} + +.test--color_w_vars { + color: var(--color-hsl); +} + +.test--circular_var { + color: var(--circular); +} + +.test--z-index { + z-index: var(--z-index); +} + +.test--nested-fallback { + z-index: var(--xxx, var(--yyy, 1)); +} + +.text--calc { + width: calc((100% - var(--xxx, 1px)) + var(--yyy, 10px)); +} + +.test--linear-gradient { + background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%); +} + +.test--loose-formatting { + color: var( + --color, + blue + )/*rtl:red*/; +} + +.test--combined-selector { + color: var(--theme-color); +} + +.test--variable-with-url { + order: 1; + background: var(--url-1); +} + +.test--variable-with-url { + order: 2; + background: var(--url-2); +} + + +.test--variable-with-url { + order: 3; + background: var(--url-3); +} + + +.test--variable-with-url { + order: 4; + background: var(--url-4); +} + +.no-prototype-collisions { + color: var(toString); +} + +.test-unicode { + color: var(--✅-size); +} + +.test { + font-family: var(--font, "Helvetica Neue", Arial, sans-serif); +} + +.ignores-declarations-that-have-an-exact-fallback-a { + left: 1rem; + left: var(--does-not-exist, 1rem); +} + +.ignores-declarations-that-have-an-exact-fallback-b { + right: 2em; + right: var(--✅-size); +} + +.does-not-ignore-declarations-that-have-an-exact-override-a { + left: var(--does-not-exist, 1rem); + left: 1rem; +} + +.does-not-ignore-declarations-that-have-an-exact-override-b { + right: var(--✅-size); + right: 2em; +} diff --git a/plugins/postcss-custom-properties-import-export/test/basic.import-mjs.expect.css b/plugins/postcss-custom-properties-import-export/test/basic.import-mjs.expect.css new file mode 100644 index 000000000..4f0cd5aa0 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/basic.import-mjs.expect.css @@ -0,0 +1,154 @@ +:root { + --color-2: yellow; + --color: rgb(255, 0, 0); + --z-index: 10; + --ref-color: var(--color); +} + +html { + --ref-color: skip; +} + +:root { + --color: rgb(255, 0, 0); + --color-h: 0; + --color-s: 100%; + --color-l: 50%; + --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l)); + --ref-color: var(--color); + --circular: var(--circular-2); + --circular-2: var(--circular); + --margin: 0 10px 20px 30px; + --shadow-color: rgb(255,0,0); + --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15)); + --font-family: "Open Sans", sans-serif; + --url-1: url("/my/path"); + --url-2: url('/my/path'); + --url-3: url(/my/path); + --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; + color: var(--color); +} + +:root, +[data-theme=light] { + --theme-color: #053; +} + +.test { + --skip: gray; + color: var(--override, var(--color)); +} + +.test--color_spacing { + box-shadow: inset 0 -3px 0 var(--color); +} + +.test--preserve_whitespaces { + margin: var(--margin); +} + +.test--complex_values { + box-shadow: var(--shadow); +} + +.test--comma_separated_values { + font-family: var(--font-family); +} + +.test--fallback { + color: var(--color-2, blue); +} + +.test--color_w_var { + color: var(--ref-color); +} + +.test--color_w_vars { + color: var(--color-hsl); +} + +.test--circular_var { + color: var(--circular); +} + +.test--z-index { + z-index: var(--z-index); +} + +.test--nested-fallback { + z-index: var(--xxx, var(--yyy, 1)); +} + +.text--calc { + width: calc((100% - var(--xxx, 1px)) + var(--yyy, 10px)); +} + +.test--linear-gradient { + background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%); +} + +.test--loose-formatting { + color: var( + --color, + blue + )/*rtl:red*/; +} + +.test--combined-selector { + color: var(--theme-color); +} + +.test--variable-with-url { + order: 1; + background: var(--url-1); +} + +.test--variable-with-url { + order: 2; + background: var(--url-2); +} + + +.test--variable-with-url { + order: 3; + background: var(--url-3); +} + + +.test--variable-with-url { + order: 4; + background: var(--url-4); +} + +.no-prototype-collisions { + color: var(toString); +} + +.test-unicode { + color: var(--✅-size); +} + +.test { + font-family: var(--font, "Helvetica Neue", Arial, sans-serif); +} + +.ignores-declarations-that-have-an-exact-fallback-a { + left: 1rem; + left: var(--does-not-exist, 1rem); +} + +.ignores-declarations-that-have-an-exact-fallback-b { + right: 2em; + right: var(--✅-size); +} + +.does-not-ignore-declarations-that-have-an-exact-override-a { + left: var(--does-not-exist, 1rem); + left: 1rem; +} + +.does-not-ignore-declarations-that-have-an-exact-override-b { + right: var(--✅-size); + right: 2em; +} diff --git a/plugins/postcss-custom-properties-import-export/test/basic.import-override.expect.css b/plugins/postcss-custom-properties-import-export/test/basic.import-override.expect.css new file mode 100644 index 000000000..3e1d78f8e --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/basic.import-override.expect.css @@ -0,0 +1,156 @@ +:root { + --z-index: 10; + --shadow-color: rgb(0,0,0); + --margin: 0 10px 20px 30px; + --ref-color: var(--color); + --color-2: yellow; + --color: rgb(0, 0, 0); +} + +html { + --ref-color: skip; +} + +:root { + --color: rgb(255, 0, 0); + --color-h: 0; + --color-s: 100%; + --color-l: 50%; + --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l)); + --ref-color: var(--color); + --circular: var(--circular-2); + --circular-2: var(--circular); + --margin: 0 10px 20px 30px; + --shadow-color: rgb(255,0,0); + --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15)); + --font-family: "Open Sans", sans-serif; + --url-1: url("/my/path"); + --url-2: url('/my/path'); + --url-3: url(/my/path); + --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; + color: var(--color); +} + +:root, +[data-theme=light] { + --theme-color: #053; +} + +.test { + --skip: gray; + color: var(--override, var(--color)); +} + +.test--color_spacing { + box-shadow: inset 0 -3px 0 var(--color); +} + +.test--preserve_whitespaces { + margin: var(--margin); +} + +.test--complex_values { + box-shadow: var(--shadow); +} + +.test--comma_separated_values { + font-family: var(--font-family); +} + +.test--fallback { + color: var(--color-2, blue); +} + +.test--color_w_var { + color: var(--ref-color); +} + +.test--color_w_vars { + color: var(--color-hsl); +} + +.test--circular_var { + color: var(--circular); +} + +.test--z-index { + z-index: var(--z-index); +} + +.test--nested-fallback { + z-index: var(--xxx, var(--yyy, 1)); +} + +.text--calc { + width: calc((100% - var(--xxx, 1px)) + var(--yyy, 10px)); +} + +.test--linear-gradient { + background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%); +} + +.test--loose-formatting { + color: var( + --color, + blue + )/*rtl:red*/; +} + +.test--combined-selector { + color: var(--theme-color); +} + +.test--variable-with-url { + order: 1; + background: var(--url-1); +} + +.test--variable-with-url { + order: 2; + background: var(--url-2); +} + + +.test--variable-with-url { + order: 3; + background: var(--url-3); +} + + +.test--variable-with-url { + order: 4; + background: var(--url-4); +} + +.no-prototype-collisions { + color: var(toString); +} + +.test-unicode { + color: var(--✅-size); +} + +.test { + font-family: var(--font, "Helvetica Neue", Arial, sans-serif); +} + +.ignores-declarations-that-have-an-exact-fallback-a { + left: 1rem; + left: var(--does-not-exist, 1rem); +} + +.ignores-declarations-that-have-an-exact-fallback-b { + right: 2em; + right: var(--✅-size); +} + +.does-not-ignore-declarations-that-have-an-exact-override-a { + left: var(--does-not-exist, 1rem); + left: 1rem; +} + +.does-not-ignore-declarations-that-have-an-exact-override-b { + right: var(--✅-size); + right: 2em; +} diff --git a/plugins/postcss-custom-properties-import-export/test/basic.import-override.inverse.expect.css b/plugins/postcss-custom-properties-import-export/test/basic.import-override.inverse.expect.css new file mode 100644 index 000000000..383fd0036 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/basic.import-override.inverse.expect.css @@ -0,0 +1,156 @@ +html { + --ref-color: skip; +} + +:root { + --color: rgb(255, 0, 0); + --color-h: 0; + --color-s: 100%; + --color-l: 50%; + --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l)); + --ref-color: var(--color); + --circular: var(--circular-2); + --circular-2: var(--circular); + --margin: 0 10px 20px 30px; + --shadow-color: rgb(255,0,0); + --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15)); + --font-family: "Open Sans", sans-serif; + --url-1: url("/my/path"); + --url-2: url('/my/path'); + --url-3: url(/my/path); + --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; + color: var(--color); +} + +:root, +[data-theme=light] { + --theme-color: #053; +} + +.test { + --skip: gray; + color: var(--override, var(--color)); +} + +.test--color_spacing { + box-shadow: inset 0 -3px 0 var(--color); +} + +.test--preserve_whitespaces { + margin: var(--margin); +} + +.test--complex_values { + box-shadow: var(--shadow); +} + +.test--comma_separated_values { + font-family: var(--font-family); +} + +.test--fallback { + color: var(--color-2, blue); +} + +.test--color_w_var { + color: var(--ref-color); +} + +.test--color_w_vars { + color: var(--color-hsl); +} + +.test--circular_var { + color: var(--circular); +} + +.test--z-index { + z-index: var(--z-index); +} + +.test--nested-fallback { + z-index: var(--xxx, var(--yyy, 1)); +} + +.text--calc { + width: calc((100% - var(--xxx, 1px)) + var(--yyy, 10px)); +} + +.test--linear-gradient { + background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%); +} + +.test--loose-formatting { + color: var( + --color, + blue + )/*rtl:red*/; +} + +.test--combined-selector { + color: var(--theme-color); +} + +.test--variable-with-url { + order: 1; + background: var(--url-1); +} + +.test--variable-with-url { + order: 2; + background: var(--url-2); +} + + +.test--variable-with-url { + order: 3; + background: var(--url-3); +} + + +.test--variable-with-url { + order: 4; + background: var(--url-4); +} + +.no-prototype-collisions { + color: var(toString); +} + +.test-unicode { + color: var(--✅-size); +} + +.test { + font-family: var(--font, "Helvetica Neue", Arial, sans-serif); +} + +.ignores-declarations-that-have-an-exact-fallback-a { + left: 1rem; + left: var(--does-not-exist, 1rem); +} + +.ignores-declarations-that-have-an-exact-fallback-b { + right: 2em; + right: var(--✅-size); +} + +.does-not-ignore-declarations-that-have-an-exact-override-a { + left: var(--does-not-exist, 1rem); + left: 1rem; +} + +.does-not-ignore-declarations-that-have-an-exact-override-b { + right: var(--✅-size); + right: 2em; +} + +:root { + --z-index: 10; + --shadow-color: rgb(0,0,0); + --margin: 0 10px 20px 30px; + --ref-color: var(--color); + --color-2: yellow; + --color: rgb(0, 0, 0); +} diff --git a/plugins/postcss-custom-properties-import-export/test/basic.import-with-polyfill-plugin.expect.css b/plugins/postcss-custom-properties-import-export/test/basic.import-with-polyfill-plugin.expect.css new file mode 100644 index 000000000..c6836c647 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/basic.import-with-polyfill-plugin.expect.css @@ -0,0 +1,178 @@ +:root { + --z-index: 10; + --margin: 0 10px 20px 30px; + --ref-color: var(--color); + --color-2: yellow; + --color: rgb(255, 0, 0); +} + +html { + --ref-color: skip; +} + +:root { + --color: rgb(255, 0, 0); + --color-h: 0; + --color-s: 100%; + --color-l: 50%; + --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l)); + --ref-color: var(--color); + --circular: var(--circular-2); + --circular-2: var(--circular); + --margin: 0 10px 20px 30px; + --shadow-color: rgb(255,0,0); + --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15)); + --font-family: "Open Sans", sans-serif; + --url-1: url("/my/path"); + --url-2: url('/my/path'); + --url-3: url(/my/path); + --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; + color: rgb(255, 0, 0); + color: var(--color); +} + +:root, +[data-theme=light] { + --theme-color: #053; +} + +.test { + --skip: gray; + color: rgb(255, 0, 0); + color: var(--override, var(--color)); +} + +.test--color_spacing { + box-shadow: inset 0 -3px 0 rgb(255, 0, 0); + box-shadow: inset 0 -3px 0 var(--color); +} + +.test--preserve_whitespaces { + margin: 0 10px 20px 30px; + margin: var(--margin); +} + +.test--complex_values { + box-shadow: 0 6px 14px 0 color(rgb(255,0,0) a(.15)); + box-shadow: var(--shadow); +} + +.test--comma_separated_values { + font-family: "Open Sans", sans-serif; + font-family: var(--font-family); +} + +.test--fallback { + color: yellow; + color: var(--color-2, blue); +} + +.test--color_w_var { + color: rgb(255, 0, 0); + color: var(--ref-color); +} + +.test--color_w_vars { + color: hsl(0, 100%, 50%); + color: var(--color-hsl); +} + +.test--circular_var { + color: var(--circular); +} + +.test--z-index { + z-index: 10; + z-index: var(--z-index); +} + +.test--nested-fallback { + z-index: 1; + z-index: var(--xxx, var(--yyy, 1)); +} + +.text--calc { + width: calc((100% - 1px) + 10px); + width: calc((100% - var(--xxx, 1px)) + var(--yyy, 10px)); +} + +.test--linear-gradient { + background-image: linear-gradient(to right, rgb(255, 0, 0) 0%, rgb(255, 0, 0) 100%); + background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%); +} + +.test--loose-formatting { + color: rgb(255, 0, 0)/*rtl:red*/; + color: var( + --color, + blue + )/*rtl:red*/; +} + +.test--combined-selector { + color: #053; + color: var(--theme-color); +} + +.test--variable-with-url { + order: 1; + background: url("/my/path"); + background: var(--url-1); +} + +.test--variable-with-url { + order: 2; + background: url('/my/path'); + background: var(--url-2); +} + + +.test--variable-with-url { + order: 3; + background: url(/my/path); + background: var(--url-3); +} + + +.test--variable-with-url { + order: 4; + background: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + background: var(--url-4); +} + +.no-prototype-collisions { + color: var(toString); +} + +.test-unicode { + color: 2em; + color: var(--✅-size); +} + +.test { + font-family: "Helvetica Neue", Arial, sans-serif; + font-family: var(--font, "Helvetica Neue", Arial, sans-serif); +} + +.ignores-declarations-that-have-an-exact-fallback-a { + left: 1rem; + left: var(--does-not-exist, 1rem); +} + +.ignores-declarations-that-have-an-exact-fallback-b { + right: 2em; + right: var(--✅-size); +} + +.does-not-ignore-declarations-that-have-an-exact-override-a { + left: 1rem; + left: var(--does-not-exist, 1rem); + left: 1rem; +} + +.does-not-ignore-declarations-that-have-an-exact-override-b { + right: 2em; + right: var(--✅-size); + right: 2em; +} diff --git a/plugins/postcss-custom-properties-import-export/test/basic.import.expect.css b/plugins/postcss-custom-properties-import-export/test/basic.import.expect.css new file mode 100644 index 000000000..d3cc2c38c --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/basic.import.expect.css @@ -0,0 +1,155 @@ +:root { + --z-index: 10; + --margin: 0 10px 20px 30px; + --ref-color: var(--color); + --color-2: yellow; + --color: rgb(255, 0, 0); +} + +html { + --ref-color: skip; +} + +:root { + --color: rgb(255, 0, 0); + --color-h: 0; + --color-s: 100%; + --color-l: 50%; + --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l)); + --ref-color: var(--color); + --circular: var(--circular-2); + --circular-2: var(--circular); + --margin: 0 10px 20px 30px; + --shadow-color: rgb(255,0,0); + --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15)); + --font-family: "Open Sans", sans-serif; + --url-1: url("/my/path"); + --url-2: url('/my/path'); + --url-3: url(/my/path); + --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; + color: var(--color); +} + +:root, +[data-theme=light] { + --theme-color: #053; +} + +.test { + --skip: gray; + color: var(--override, var(--color)); +} + +.test--color_spacing { + box-shadow: inset 0 -3px 0 var(--color); +} + +.test--preserve_whitespaces { + margin: var(--margin); +} + +.test--complex_values { + box-shadow: var(--shadow); +} + +.test--comma_separated_values { + font-family: var(--font-family); +} + +.test--fallback { + color: var(--color-2, blue); +} + +.test--color_w_var { + color: var(--ref-color); +} + +.test--color_w_vars { + color: var(--color-hsl); +} + +.test--circular_var { + color: var(--circular); +} + +.test--z-index { + z-index: var(--z-index); +} + +.test--nested-fallback { + z-index: var(--xxx, var(--yyy, 1)); +} + +.text--calc { + width: calc((100% - var(--xxx, 1px)) + var(--yyy, 10px)); +} + +.test--linear-gradient { + background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%); +} + +.test--loose-formatting { + color: var( + --color, + blue + )/*rtl:red*/; +} + +.test--combined-selector { + color: var(--theme-color); +} + +.test--variable-with-url { + order: 1; + background: var(--url-1); +} + +.test--variable-with-url { + order: 2; + background: var(--url-2); +} + + +.test--variable-with-url { + order: 3; + background: var(--url-3); +} + + +.test--variable-with-url { + order: 4; + background: var(--url-4); +} + +.no-prototype-collisions { + color: var(toString); +} + +.test-unicode { + color: var(--✅-size); +} + +.test { + font-family: var(--font, "Helvetica Neue", Arial, sans-serif); +} + +.ignores-declarations-that-have-an-exact-fallback-a { + left: 1rem; + left: var(--does-not-exist, 1rem); +} + +.ignores-declarations-that-have-an-exact-fallback-b { + right: 2em; + right: var(--✅-size); +} + +.does-not-ignore-declarations-that-have-an-exact-override-a { + left: var(--does-not-exist, 1rem); + left: 1rem; +} + +.does-not-ignore-declarations-that-have-an-exact-override-b { + right: var(--✅-size); + right: 2em; +} diff --git a/plugins/postcss-custom-properties-import-export/test/examples/.gitkeep b/plugins/postcss-custom-properties-import-export/test/examples/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/postcss-custom-properties-import-export/test/export-properties.css b/plugins/postcss-custom-properties-import-export/test/export-properties.css new file mode 100644 index 000000000..6c5bc15ff --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/export-properties.css @@ -0,0 +1,20 @@ +:root { + --ref-color: var(--color); + --color: rgb(255, 0, 0); + --color-h: 0; + --color-s: 100%; + --color-l: 50%; + --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l)); + --circular: var(--circular-2); + --circular-2: var(--circular); + --margin: 0 10px 20px 30px; + --shadow-color: rgb(255,0,0); + --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15)); + --font-family: "Open Sans", sans-serif; + --url-1: url("/my/path"); + --url-2: url('/my/path'); + --url-3: url(/my/path); + --url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); + --✅-size: 2em; + --theme-color: #053; +} diff --git a/plugins/postcss-custom-properties-import-export/test/export-properties.js b/plugins/postcss-custom-properties-import-export/test/export-properties.js new file mode 100644 index 000000000..4a881c6cd --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/export-properties.js @@ -0,0 +1,22 @@ +module.exports = { + customProperties: { + '--ref-color': 'var(--color)', + '--color': 'rgb(255, 0, 0)', + '--color-h': '0', + '--color-s': '100%', + '--color-l': '50%', + '--color-hsl': 'hsl(var(--color-h), var(--color-s), var(--color-l))', + '--circular': 'var(--circular-2)', + '--circular-2': 'var(--circular)', + '--margin': '0 10px 20px 30px', + '--shadow-color': 'rgb(255,0,0)', + '--shadow': '0 6px 14px 0 color(var(--shadow-color) a(.15))', + '--font-family': '"Open Sans", sans-serif', + '--url-1': 'url("/my/path")', + '--url-2': 'url(\'/my/path\')', + '--url-3': 'url(/my/path)', + '--url-4': 'url(data:image/png;bm90LWFuZC1pbWFnZQ==)', + '--✅-size': '2em', + '--theme-color': '#053' + } +}; diff --git a/plugins/postcss-custom-properties-import-export/test/export-properties.json b/plugins/postcss-custom-properties-import-export/test/export-properties.json new file mode 100644 index 000000000..723f042ba --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/export-properties.json @@ -0,0 +1,22 @@ +{ + "custom-properties": { + "--ref-color": "var(--color)", + "--color": "rgb(255, 0, 0)", + "--color-h": "0", + "--color-s": "100%", + "--color-l": "50%", + "--color-hsl": "hsl(var(--color-h), var(--color-s), var(--color-l))", + "--circular": "var(--circular-2)", + "--circular-2": "var(--circular)", + "--margin": "0 10px 20px 30px", + "--shadow-color": "rgb(255,0,0)", + "--shadow": "0 6px 14px 0 color(var(--shadow-color) a(.15))", + "--font-family": "\"Open Sans\", sans-serif", + "--url-1": "url(\"/my/path\")", + "--url-2": "url('/my/path')", + "--url-3": "url(/my/path)", + "--url-4": "url(data:image/png;bm90LWFuZC1pbWFnZQ==)", + "--✅-size": "2em", + "--theme-color": "#053" + } +} diff --git a/plugins/postcss-custom-properties-import-export/test/export-properties.mjs b/plugins/postcss-custom-properties-import-export/test/export-properties.mjs new file mode 100644 index 000000000..30e030b94 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/export-properties.mjs @@ -0,0 +1,20 @@ +export const customProperties = { + '--ref-color': 'var(--color)', + '--color': 'rgb(255, 0, 0)', + '--color-h': '0', + '--color-s': '100%', + '--color-l': '50%', + '--color-hsl': 'hsl(var(--color-h), var(--color-s), var(--color-l))', + '--circular': 'var(--circular-2)', + '--circular-2': 'var(--circular)', + '--margin': '0 10px 20px 30px', + '--shadow-color': 'rgb(255,0,0)', + '--shadow': '0 6px 14px 0 color(var(--shadow-color) a(.15))', + '--font-family': '"Open Sans", sans-serif', + '--url-1': 'url("/my/path")', + '--url-2': 'url(\'/my/path\')', + '--url-3': 'url(/my/path)', + '--url-4': 'url(data:image/png;bm90LWFuZC1pbWFnZQ==)', + '--✅-size': '2em', + '--theme-color': '#053' +}; diff --git a/plugins/postcss-custom-properties-import-export/test/export-properties.scss b/plugins/postcss-custom-properties-import-export/test/export-properties.scss new file mode 100644 index 000000000..a920224bb --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/export-properties.scss @@ -0,0 +1,18 @@ +$ref-color: var(--color); +$color: rgb(255, 0, 0); +$color-h: 0; +$color-s: 100%; +$color-l: 50%; +$color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l)); +$circular: var(--circular-2); +$circular-2: var(--circular); +$margin: 0 10px 20px 30px; +$shadow-color: rgb(255,0,0); +$shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15)); +$font-family: "Open Sans", sans-serif; +$url-1: url("/my/path"); +$url-2: url('/my/path'); +$url-3: url(/my/path); +$url-4: url(data:image/png;bm90LWFuZC1pbWFnZQ==); +$✅-size: 2em; +$theme-color: #053; diff --git a/plugins/postcss-custom-properties-import-export/test/import-properties-2.cjs b/plugins/postcss-custom-properties-import-export/test/import-properties-2.cjs new file mode 100644 index 000000000..a9ac86761 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/import-properties-2.cjs @@ -0,0 +1,6 @@ +module.exports = { + customProperties: { + '--color': 'rgb(255, 0, 0)', + '--color-2': 'yellow', + }, +}; diff --git a/plugins/postcss-custom-properties-import-export/test/import-properties-2.css b/plugins/postcss-custom-properties-import-export/test/import-properties-2.css new file mode 100644 index 000000000..3f814966a --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/import-properties-2.css @@ -0,0 +1,4 @@ +:root { + --color: rgb(255, 0, 0); + --color-2: yellow; +} diff --git a/plugins/postcss-custom-properties-import-export/test/import-properties-2.js b/plugins/postcss-custom-properties-import-export/test/import-properties-2.js new file mode 100644 index 000000000..a9ac86761 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/import-properties-2.js @@ -0,0 +1,6 @@ +module.exports = { + customProperties: { + '--color': 'rgb(255, 0, 0)', + '--color-2': 'yellow', + }, +}; diff --git a/plugins/postcss-custom-properties-import-export/test/import-properties-2.mjs b/plugins/postcss-custom-properties-import-export/test/import-properties-2.mjs new file mode 100644 index 000000000..22617d2ef --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/import-properties-2.mjs @@ -0,0 +1,6 @@ +export default { + customProperties: { + '--color': 'rgb(255, 0, 0)', + '--color-2': 'yellow', + }, +}; diff --git a/plugins/postcss-custom-properties-import-export/test/import-properties.cjs b/plugins/postcss-custom-properties-import-export/test/import-properties.cjs new file mode 100644 index 000000000..4b2551bb6 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/import-properties.cjs @@ -0,0 +1,6 @@ +module.exports = { + customProperties: { + '--ref-color': 'var(--color)', + '--z-index': 10, + }, +}; diff --git a/plugins/postcss-custom-properties-import-export/test/import-properties.css b/plugins/postcss-custom-properties-import-export/test/import-properties.css new file mode 100644 index 000000000..a01ea3e89 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/import-properties.css @@ -0,0 +1,4 @@ +:root { + --ref-color: var(--color); + --z-index: 10; +} diff --git a/plugins/postcss-custom-properties-import-export/test/import-properties.js b/plugins/postcss-custom-properties-import-export/test/import-properties.js new file mode 100644 index 000000000..4b2551bb6 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/import-properties.js @@ -0,0 +1,6 @@ +module.exports = { + customProperties: { + '--ref-color': 'var(--color)', + '--z-index': 10, + }, +}; diff --git a/plugins/postcss-custom-properties-import-export/test/import-properties.json b/plugins/postcss-custom-properties-import-export/test/import-properties.json new file mode 100644 index 000000000..d4125fc5d --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/import-properties.json @@ -0,0 +1,8 @@ +{ + "custom-properties": { + "--color": "rgb(255, 0, 0)", + "--color-2": "yellow", + "--ref-color": "var(--color)", + "--z-index": 10 + } +} diff --git a/plugins/postcss-custom-properties-import-export/test/import-properties.mjs b/plugins/postcss-custom-properties-import-export/test/import-properties.mjs new file mode 100644 index 000000000..78c3b75a2 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/import-properties.mjs @@ -0,0 +1,6 @@ +export default { + customProperties: { + '--ref-color': 'var(--color)', + '--z-index': 10, + }, +}; diff --git a/plugins/postcss-custom-properties-import-export/test/import-properties.pcss b/plugins/postcss-custom-properties-import-export/test/import-properties.pcss new file mode 100644 index 000000000..a01ea3e89 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/import-properties.pcss @@ -0,0 +1,4 @@ +:root { + --ref-color: var(--color); + --z-index: 10; +} diff --git a/plugins/postcss-custom-properties-import-export/test/import.css b/plugins/postcss-custom-properties-import-export/test/import.css new file mode 100644 index 000000000..64636398d --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/import.css @@ -0,0 +1,5 @@ +@import "./imported-file.css"; + +a { + color: var(--color); +} diff --git a/plugins/postcss-custom-properties-import-export/test/import.expect.css b/plugins/postcss-custom-properties-import-export/test/import.expect.css new file mode 100644 index 000000000..03939d1a2 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/import.expect.css @@ -0,0 +1,7 @@ +:root { + --color: blue; +} + +a { + color: var(--color); +} diff --git a/plugins/postcss-custom-properties-import-export/test/imported-file.css b/plugins/postcss-custom-properties-import-export/test/imported-file.css new file mode 100644 index 000000000..eb0ff0d59 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/test/imported-file.css @@ -0,0 +1,3 @@ +:root { + --color: blue; +} diff --git a/plugins/postcss-custom-properties-import-export/tsconfig.json b/plugins/postcss-custom-properties-import-export/tsconfig.json new file mode 100644 index 000000000..2e428a8c2 --- /dev/null +++ b/plugins/postcss-custom-properties-import-export/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "declarationDir": ".", + "module": "es2020" + }, + "include": ["./src/**/*"], + "exclude": ["dist"], +} diff --git a/plugins/postcss-custom-selectors-import-export/.gitignore b/plugins/postcss-custom-selectors-import-export/.gitignore new file mode 100644 index 000000000..7172b04f1 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/.gitignore @@ -0,0 +1,6 @@ +node_modules +package-lock.json +yarn.lock +*.result.css +*.result.css.map +dist/* diff --git a/plugins/postcss-custom-selectors-import-export/.nvmrc b/plugins/postcss-custom-selectors-import-export/.nvmrc new file mode 100644 index 000000000..f0b10f153 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/.nvmrc @@ -0,0 +1 @@ +v16.13.1 diff --git a/plugins/postcss-custom-selectors-import-export/.tape.cjs b/plugins/postcss-custom-selectors-import-export/.tape.cjs new file mode 100644 index 000000000..4d0f0d15a --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/.tape.cjs @@ -0,0 +1,244 @@ +const postcssTape = require('../../packages/postcss-tape/dist/index.cjs'); +const plugin = require('@csstools/postcss-custom-selectors-import-export'); + +postcssTape(plugin)({ + 'basic': { + message: 'does nothing when no options are set', + }, + 'basic-import': { + message: 'supports { importFrom: { customSelectors: { ... } } } usage', + options: { + importFrom: { + customSelectors: { + ':--heading': 'h1, h2, h3', + ':--text': ':--heading, p', + } + } + } + }, + 'basic-import:imported-styles-override-document-styles:true': { + message: 'supports { importedStylesOverrideDocumentStyles: true, importFrom: { customSelectors: { ... } } } usage', + options: { + importedStylesOverrideDocumentStyles: true, + importFrom: { + customSelectors: { + ':--heading': 'h1, h2, h3', + ':--text': ':--heading, p', + } + }, + exportTo(customProperties) { + if (customProperties[':--text'] !== ':--heading, p') { + throw new Error('Incorrect value in exportTo ' + customProperties[':--text']); + } + } + } + }, + 'basic-import:imported-styles-override-document-styles:false': { + message: 'supports { importedStylesOverrideDocumentStyles: false, importFrom: { customSelectors: { ... } } } usage', + options: { + importedStylesOverrideDocumentStyles: false, + importFrom: { + customSelectors: { + ':--heading': 'h1, h2, h3', + ':--text': ':--heading, p', + } + }, + exportTo(customProperties) { + if (customProperties[':--text'] !== 'p, b, strong, i, em, quote, cite') { + throw new Error('Incorrect value in exportTo ' + customProperties[':--text']); + } + } + } + }, + 'basic-import:fn': { + message: 'supports { importFrom() } usage', + options: { + importFrom() { + return { + customSelectors: { + ':--heading': 'h1, h2, h3', + ':--text': ':--heading, p', + } + }; + } + }, + }, + 'basic-import:fn-promise': { + message: 'supports { async importFrom() } usage', + options: { + importFrom() { + return new Promise(resolve => { + resolve({ + customSelectors: { + ':--heading': 'h1, h2, h3', + ':--text': ':--heading, p', + } + }) + }); + } + }, + }, + 'basic-import:json': { + message: 'supports { importFrom: "test/import-selectors.json" } usage', + options: { + importFrom: 'test/import-selectors.json' + }, + }, + 'basic-import:js': { + message: 'supports { importFrom: "test/import-selectors.js" } usage', + options: { + importFrom: 'test/import-selectors.js' + }, + }, + 'basic-import:css': { + message: 'supports { importFrom: "test/import-selectors.css" } usage', + options: { + importFrom: 'test/import-selectors.css' + }, + }, + 'basic-import:css-from': { + message: 'supports { importFrom: { from: "test/import-selectors.css" } } usage', + options: { + importFrom: { from: 'test/import-selectors.css' } + }, + }, + 'basic-import:css-from-multiple-files': { + message: 'supports { importFrom: ["test/empty.css", "test/import-selectors.css"] } usage', + options: { + importFrom: ["test/empty.css", "test/import-selectors.css"] + }, + }, + 'basic-import:css-from-type': { + message: 'supports { importFrom: [ { from: "test/import-selectors.css", type: "css" } ] } usage', + options: { + importFrom: [{ from: 'test/import-selectors.css', type: 'css' }] + }, + }, + 'basic-import:empty': { + message: 'supports { importFrom: {} } usage', + options: { + importFrom: {} + } + }, + 'basic:export': { + message: 'supports { exportTo: { customSelectors: { ... } } } usage', + options: { + exportTo: (global.__exportSelectorObject = global.__exportSelectorObject || { + customSelectors: null + }) + }, + after() { + if (__exportSelectorObject.customSelectors[':--foo'] !== '.foo') { + throw new Error('The exportTo function failed'); + } + } + }, + 'basic:export-fn': { + message: 'supports { exportTo() } usage', + options: { + exportTo(customProperties) { + if (customProperties[':--foo'] !== '.foo') { + throw new Error('The exportTo function failed'); + } + } + }, + }, + 'basic:export-fn-promise': { + message: 'supports { async exportTo() } usage', + options: { + exportTo(customProperties) { + return new Promise((resolve, reject) => { + if (customProperties[':--foo'] !== '.foo') { + reject('The exportTo function failed'); + } else { + resolve(); + } + }); + } + }, + }, + 'basic:export-json': { + message: 'supports { exportTo: "test/export-selectors.json" } usage', + options: { + exportTo: 'test/export-selectors.json' + }, + before() { + global.__exportSelectorsString = require('fs').readFileSync('test/export-selectors.json', 'utf8'); + }, + after() { + if (global.__exportSelectorsString !== require('fs').readFileSync('test/export-selectors.json', 'utf8')) { + throw new Error('The original file did not match the freshly exported copy'); + } + }, + }, + 'basic:export-js': { + message: 'supports { exportTo: "test/export-selectors.js" } usage', + options: { + exportTo: 'test/export-selectors.js' + }, + before() { + global.__exportSelectorsString = require('fs').readFileSync('test/export-selectors.js', 'utf8'); + }, + after() { + if (global.__exportSelectorsString !== require('fs').readFileSync('test/export-selectors.js', 'utf8')) { + throw new Error('The original file did not match the freshly exported copy'); + } + }, + }, + 'basic:export-mjs': { + message: 'supports { exportTo: "test/export-selectors.mjs" } usage', + options: { + exportTo: 'test/export-selectors.mjs' + }, + before() { + global.__exportSelectorsString = require('fs').readFileSync('test/export-selectors.mjs', 'utf8'); + }, + after() { + if (global.__exportSelectorsString !== require('fs').readFileSync('test/export-selectors.mjs', 'utf8')) { + throw new Error('The original file did not match the freshly exported copy'); + } + }, + }, + 'basic:export-css': { + message: 'supports { exportTo: "test/export-selectors.css" } usage', + options: { + exportTo: 'test/export-selectors.css' + }, + before() { + global.__exportSelectorsString = require('fs').readFileSync('test/export-selectors.css', 'utf8'); + }, + after() { + if (global.__exportSelectorsString !== require('fs').readFileSync('test/export-selectors.css', 'utf8')) { + throw new Error('The original file did not match the freshly exported copy'); + } + }, + }, + 'basic:export-css-to': { + message: 'supports { exportTo: { to: "test/export-selectors.css" } } usage', + options: { + exportTo: { to: 'test/export-selectors.css' } + }, + before() { + global.__exportSelectorsString = require('fs').readFileSync('test/export-selectors.css', 'utf8'); + }, + after() { + if (global.__exportSelectorsString !== require('fs').readFileSync('test/export-selectors.css', 'utf8')) { + throw new Error('The original file did not match the freshly exported copy'); + } + }, + }, + 'basic:export-css-to-type': { + message: 'supports { exportTo: { to: "test/export-selectors.css", type: "css" } } usage', + options: { + exportTo: { to: 'test/export-selectors.css', type: 'css' } + }, + before() { + global.__exportSelectorsString = require('fs').readFileSync('test/export-selectors.css', 'utf8'); + }, + after() { + if (global.__exportSelectorsString !== require('fs').readFileSync('test/export-selectors.css', 'utf8')) { + throw new Error('The original file did not match the freshly exported copy'); + } + }, + } +}); diff --git a/plugins/postcss-custom-selectors-import-export/.tape.mjs b/plugins/postcss-custom-selectors-import-export/.tape.mjs new file mode 100644 index 000000000..e71d51823 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/.tape.mjs @@ -0,0 +1,266 @@ +import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import plugin from '@csstools/postcss-custom-selectors-import-export'; +import polyfillPlugin from 'postcss-custom-selectors'; +import fs from 'fs'; + +postcssTape(plugin)({ + 'basic': { + message: 'does nothing when no options are set', + }, + 'basic-import': { + message: 'supports { importFrom: { customSelectors: { ... } } } usage', + options: { + importFrom: { + customSelectors: { + ':--heading': 'h1, h2, h3', + ':--text': ':--heading, p', + } + } + } + }, + 'basic-import:with-polyfill-plugin': { + message: 'works correctly together with the polyfill', + plugins: [ + plugin({ + importFrom: { + customSelectors: { + ':--heading': 'h1, h2, h3', + ':--text': ':--heading, p', + } + } + }), + polyfillPlugin(), + ] + }, + 'basic-import:imported-styles-override-document-styles:true': { + message: 'supports { importedStylesOverrideDocumentStyles: true, importFrom: { customSelectors: { ... } } } usage', + options: { + importedStylesOverrideDocumentStyles: true, + importFrom: { + customSelectors: { + ':--heading': 'h1, h2, h3', + ':--text': ':--heading, p', + } + }, + exportTo(customProperties) { + if (customProperties[':--text'] !== ':--heading, p') { + throw new Error('Incorrect value in exportTo ' + customProperties[':--text']); + } + } + } + }, + 'basic-import:imported-styles-override-document-styles:false': { + message: 'supports { importedStylesOverrideDocumentStyles: false, importFrom: { customSelectors: { ... } } } usage', + options: { + importedStylesOverrideDocumentStyles: false, + importFrom: { + customSelectors: { + ':--heading': 'h1, h2, h3', + ':--text': ':--heading, p', + } + }, + exportTo(customProperties) { + if (customProperties[':--text'] !== 'p, b, strong, i, em, quote, cite') { + throw new Error('Incorrect value in exportTo ' + customProperties[':--text']); + } + } + } + }, + 'basic-import:fn': { + message: 'supports { importFrom() } usage', + options: { + importFrom() { + return { + customSelectors: { + ':--heading': 'h1, h2, h3', + ':--text': ':--heading, p', + } + }; + } + }, + }, + 'basic-import:fn-promise': { + message: 'supports { async importFrom() } usage', + options: { + importFrom() { + return new Promise(resolve => { + resolve({ + customSelectors: { + ':--heading': 'h1, h2, h3', + ':--text': ':--heading, p', + } + }) + }); + } + }, + }, + 'basic-import:json': { + message: 'supports { importFrom: "test/import-selectors.json" } usage', + options: { + importFrom: 'test/import-selectors.json' + }, + }, + 'basic-import:js': { + message: 'supports { importFrom: "test/import-selectors.js" } usage', + options: { + importFrom: 'test/import-selectors.js' + }, + }, + 'basic-import:mjs': { + message: 'supports { importFrom: "test/import-selectors.mjs" } usage', + options: { + importFrom: 'test/import-selectors.mjs' + }, + }, + 'basic-import:css': { + message: 'supports { importFrom: "test/import-selectors.css" } usage', + options: { + importFrom: 'test/import-selectors.css' + }, + }, + 'basic-import:css-from': { + message: 'supports { importFrom: { from: "test/import-selectors.css" } } usage', + options: { + importFrom: { from: 'test/import-selectors.css' } + }, + }, + 'basic-import:css-from-multiple-files': { + message: 'supports { importFrom: ["test/empty.css", "test/import-selectors.css"] } usage', + options: { + importFrom: ["test/empty.css", "test/import-selectors.css"] + }, + }, + 'basic-import:css-from-type': { + message: 'supports { importFrom: [ { from: "test/import-selectors.css", type: "css" } ] } usage', + options: { + importFrom: [{ from: 'test/import-selectors.css', type: 'css' }] + }, + }, + 'basic-import:empty': { + message: 'supports { importFrom: {} } usage', + options: { + importFrom: {} + } + }, + 'basic:export': { + message: 'supports { exportTo: { customSelectors: { ... } } } usage', + options: { + exportTo: (global.__exportSelectorObject = global.__exportSelectorObject || { + customSelectors: null + }) + }, + after() { + if (__exportSelectorObject.customSelectors[':--foo'] !== '.foo') { + throw new Error('The exportTo function failed'); + } + } + }, + 'basic:export-fn': { + message: 'supports { exportTo() } usage', + options: { + exportTo(customProperties) { + if (customProperties[':--foo'] !== '.foo') { + throw new Error('The exportTo function failed'); + } + } + }, + }, + 'basic:export-fn-promise': { + message: 'supports { async exportTo() } usage', + options: { + exportTo(customProperties) { + return new Promise((resolve, reject) => { + if (customProperties[':--foo'] !== '.foo') { + reject('The exportTo function failed'); + } else { + resolve(); + } + }); + } + }, + }, + 'basic:export-json': { + message: 'supports { exportTo: "test/export-selectors.json" } usage', + options: { + exportTo: 'test/export-selectors.json' + }, + before() { + global.__exportSelectorsString = fs.readFileSync('test/export-selectors.json', 'utf8'); + }, + after() { + if (global.__exportSelectorsString !== fs.readFileSync('test/export-selectors.json', 'utf8')) { + throw new Error('The original file did not match the freshly exported copy'); + } + }, + }, + 'basic:export-js': { + message: 'supports { exportTo: "test/export-selectors.js" } usage', + options: { + exportTo: 'test/export-selectors.js' + }, + before() { + global.__exportSelectorsString = fs.readFileSync('test/export-selectors.js', 'utf8'); + }, + after() { + if (global.__exportSelectorsString !== fs.readFileSync('test/export-selectors.js', 'utf8')) { + throw new Error('The original file did not match the freshly exported copy'); + } + }, + }, + 'basic:export-mjs': { + message: 'supports { exportTo: "test/export-selectors.mjs" } usage', + options: { + exportTo: 'test/export-selectors.mjs' + }, + before() { + global.__exportSelectorsString = fs.readFileSync('test/export-selectors.mjs', 'utf8'); + }, + after() { + if (global.__exportSelectorsString !== fs.readFileSync('test/export-selectors.mjs', 'utf8')) { + throw new Error('The original file did not match the freshly exported copy'); + } + }, + }, + 'basic:export-css': { + message: 'supports { exportTo: "test/export-selectors.css" } usage', + options: { + exportTo: 'test/export-selectors.css' + }, + before() { + global.__exportSelectorsString = fs.readFileSync('test/export-selectors.css', 'utf8'); + }, + after() { + if (global.__exportSelectorsString !== fs.readFileSync('test/export-selectors.css', 'utf8')) { + throw new Error('The original file did not match the freshly exported copy'); + } + }, + }, + 'basic:export-css-to': { + message: 'supports { exportTo: { to: "test/export-selectors.css" } } usage', + options: { + exportTo: { to: 'test/export-selectors.css' } + }, + before() { + global.__exportSelectorsString = fs.readFileSync('test/export-selectors.css', 'utf8'); + }, + after() { + if (global.__exportSelectorsString !== fs.readFileSync('test/export-selectors.css', 'utf8')) { + throw new Error('The original file did not match the freshly exported copy'); + } + }, + }, + 'basic:export-css-to-type': { + message: 'supports { exportTo: { to: "test/export-selectors.css", type: "css" } } usage', + options: { + exportTo: { to: 'test/export-selectors.css', type: 'css' } + }, + before() { + global.__exportSelectorsString = fs.readFileSync('test/export-selectors.css', 'utf8'); + }, + after() { + if (global.__exportSelectorsString !== fs.readFileSync('test/export-selectors.css', 'utf8')) { + throw new Error('The original file did not match the freshly exported copy'); + } + }, + } +}); diff --git a/plugins/postcss-custom-selectors-import-export/CHANGELOG.md b/plugins/postcss-custom-selectors-import-export/CHANGELOG.md new file mode 100644 index 000000000..50e5e4e99 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changes to PostCSS Custom Selectors Import/Export + +### 1.0.0 (Unreleased) + +- Initial version diff --git a/plugins/postcss-custom-selectors-import-export/INSTALL.md b/plugins/postcss-custom-selectors-import-export/INSTALL.md new file mode 100644 index 000000000..2f0d0317e --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/INSTALL.md @@ -0,0 +1,257 @@ +# Installing PostCSS Custom Selectors Import/Export + +[PostCSS Custom Selectors Import/Export] runs in all Node environments, with special instructions for: + +- [Node](#node) +- [PostCSS CLI](#postcss-cli) +- [PostCSS Load Config](#postcss-load-config) +- [Webpack](#webpack) +- [Create React App](#create-react-app) +- [Next.js](#nextjs) +- [Gulp](#gulp) +- [Grunt](#grunt) + +## Node + +Add [PostCSS Custom Selectors Import/Export] to your project: + +```bash +npm install postcss @csstools/postcss-custom-selectors-import-export --save-dev +``` + +Use it as a [PostCSS] plugin: + +```js +// commonjs +const postcss = require('postcss'); +const postcssCustomSelectorsImportExport = require('@csstools/postcss-custom-selectors-import-export'); + +postcss([ + postcssCustomSelectorsImportExport(/* pluginOptions */) +]).process(YOUR_CSS /*, processOptions */); +``` + +```js +// esm +import postcss from 'postcss'; +import postcssCustomSelectorsImportExport from '@csstools/postcss-custom-selectors-import-export'; + +postcss([ + postcssCustomSelectorsImportExport(/* pluginOptions */) +]).process(YOUR_CSS /*, processOptions */); +``` + +## PostCSS CLI + +Add [PostCSS CLI] to your project: + +```bash +npm install postcss-cli @csstools/postcss-custom-selectors-import-export --save-dev +``` + +Use [PostCSS Custom Selectors Import/Export] in your `postcss.config.js` configuration file: + +```js +const postcssCustomSelectorsImportExport = require('@csstools/postcss-custom-selectors-import-export'); + +module.exports = { + plugins: [ + postcssCustomSelectorsImportExport(/* pluginOptions */) + ] +} +``` + +## PostCSS Load Config + +If your framework/CLI supports [`postcss-load-config`](https://github.com/postcss/postcss-load-config). + +```bash +npm install @csstools/postcss-custom-selectors-import-export --save-dev +``` + +`package.json`: + +```json +{ + "postcss": { + "plugins": { + "@csstools/postcss-custom-selectors-import-export": {} + } + } +} +``` + +`.postcssrc.json`: + +```json +{ + "plugins": { + "@csstools/postcss-custom-selectors-import-export": {} + } +} +``` + +_See the [README of `postcss-load-config`](https://github.com/postcss/postcss-load-config#usage) for more usage options._ + +## Webpack + +_Webpack version 5_ + +Add [PostCSS Loader] to your project: + +```bash +npm install postcss-loader @csstools/postcss-custom-selectors-import-export --save-dev +``` + +Use [PostCSS Custom Selectors Import/Export] 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-custom-selectors-import-export", + { + // 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-custom-selectors-import-export --save-dev +``` + +Use [React App Rewire PostCSS] and [PostCSS Custom Selectors Import/Export] in your +`config-overrides.js` file: + +```js +const reactAppRewirePostcss = require('react-app-rewire-postcss'); +const postcssCustomSelectorsImportExport = require('@csstools/postcss-custom-selectors-import-export'); + +module.exports = config => reactAppRewirePostcss(config, { + plugins: () => [ + postcssCustomSelectorsImportExport(/* pluginOptions */) + ] +}); +``` + +## Next.js + +Read the instructions on how to [customize the PostCSS configuration in Next.js](https://nextjs.org/docs/advanced-features/customizing-postcss-config) + +```bash +npm install @csstools/postcss-custom-selectors-import-export --save-dev +``` + +Use [PostCSS Custom Selectors Import/Export] in your `postcss.config.json` file: + +```json +{ + "plugins": [ + "@csstools/postcss-custom-selectors-import-export" + ] +} +``` + +```json5 +{ + "plugins": [ + [ + "@csstools/postcss-custom-selectors-import-export", + { + // Optionally add plugin options + } + ] + ] +} +``` + +## Gulp + +Add [Gulp PostCSS] to your project: + +```bash +npm install gulp-postcss @csstools/postcss-custom-selectors-import-export --save-dev +``` + +Use [PostCSS Custom Selectors Import/Export] in your Gulpfile: + +```js +const postcss = require('gulp-postcss'); +const postcssCustomSelectorsImportExport = require('@csstools/postcss-custom-selectors-import-export'); + +gulp.task('css', function () { + var plugins = [ + postcssCustomSelectorsImportExport(/* 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-custom-selectors-import-export --save-dev +``` + +Use [PostCSS Custom Selectors Import/Export] in your Gruntfile: + +```js +const postcssCustomSelectorsImportExport = require('@csstools/postcss-custom-selectors-import-export'); + +grunt.loadNpmTasks('grunt-postcss'); + +grunt.initConfig({ + postcss: { + options: { + processors: [ + postcssCustomSelectorsImportExport(/* 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 Custom Selectors Import/Export]: https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-custom-selectors-import-export +[React App Rewire PostCSS]: https://github.com/csstools/react-app-rewire-postcss +[React App Rewired]: https://github.com/timarney/react-app-rewired +[Next.js]: https://nextjs.org diff --git a/plugins/postcss-custom-selectors-import-export/LICENSE.md b/plugins/postcss-custom-selectors-import-export/LICENSE.md new file mode 100644 index 000000000..6d7047088 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/LICENSE.md @@ -0,0 +1,21 @@ +# The MIT License (MIT) + +Copyright © PostCSS + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/plugins/postcss-custom-selectors-import-export/README.md b/plugins/postcss-custom-selectors-import-export/README.md new file mode 100644 index 000000000..ed5382e8a --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/README.md @@ -0,0 +1,158 @@ +# PostCSS Custom Selectors Import/Export [PostCSS Logo][PostCSS] + +[npm version][npm-url] [Build Status][cli-url] [Discord][discord] + +[PostCSS Custom Selectors Import/Export] lets you import or export `@custom-selector`'s into or out of your CSS. + +## As a drop in for old versions of `postcss-custom-selectors` + +```js +// commonjs +const postcss = require('postcss'); +const postcssCustomSelectorsImportExport = require('@csstools/postcss-custom-selectors-import-export'); +const postcssCustomSelectors = require('postcss-custom-selectors'); + +postcss([ + // First + postcssCustomSelectorsImportExport({ + /* pluginOptions */ + importedStylesOverrideDocumentStyles: false, // mimics old `postcss-custom-selectors` + }), + // Second + postcssCustomSelectors() +]).process(YOUR_CSS /*, processOptions */); +``` + +## Usage + +Add [PostCSS Custom Selectors Import/Export] to your project: + +```bash +npm install postcss @csstools/postcss-custom-selectors-import-export --save-dev +``` + +Use it as a [PostCSS] plugin: + +```js +const postcss = require('postcss'); +const postcssCustomSelectorsImportExport = require('@csstools/postcss-custom-selectors-import-export'); + +postcss([ + postcssCustomSelectorsImportExport(/* pluginOptions */) +]).process(YOUR_CSS /*, processOptions */); +``` + +[PostCSS Custom Selectors Import/Export] runs in all Node environments, with special +instructions for: + +- [Node](INSTALL.md#node) +- [PostCSS CLI](INSTALL.md#postcss-cli) +- [PostCSS Load Config](INSTALL.md#postcss-load-config) +- [Webpack](INSTALL.md#webpack) +- [Create React App](INSTALL.md#create-react-app) +- [Next.js](INSTALL.md#nextjs) +- [Gulp](INSTALL.md#gulp) +- [Grunt](INSTALL.md#grunt) + +## Options + +### importFrom + +The `importFrom` option specifies sources where custom selectors can be +imported from, which might be CSS, JS, and JSON files, functions, and directly +passed objects. + +```js +postcssCustomSelectorsImportExport({ + importFrom: 'path/to/file.css' // => @custom-selector :--heading h1, h2, h3; +}); +``` + +```pcss +article :--heading + p { + margin-top: 0; +} + +/* becomes */ + +@custom-selector :--heading h1, h2, h3; + +article :--heading+p { + margin-top: 0; +} +``` + +Multiple sources can be passed into this option, and they will be parsed in the +order they are received. JavaScript files, JSON files, functions, and objects +will need to namespace custom selectors using the `customSelectors` or +`custom-selectors` key. + +```js +postcssCustomSelectorsImportExport({ + importFrom: [ + 'path/to/file.css', + 'and/then/this.js', + 'and/then/that.json', + { + customSelectors: { ':--heading': 'h1, h2, h3' } + }, + () => { + const customSelectors = { ':--heading': 'h1, h2, h3' }; + + return { customSelectors }; + } + ] +}); +``` + +### importedStylesOverrideDocumentStyles + +The `importedStylesOverrideDocumentStyles` option determines if selectors added via `importFrom` override selectors that exist in your CSS document. +Defaults to `false`. + +```js +postcssCustomSelectorsImportExport({ + importedStylesOverrideDocumentStyles: true +}); + +### exportTo + +The `exportTo` option specifies destinations where custom selectors can be +exported to, which might be CSS, JS, and JSON files, functions, and directly +passed objects. + +```js +postcssCustomSelectorsImportExport({ + exportTo: 'path/to/file.css' // @custom-selector :--heading h1, h2, h3; +}); +``` + +Multiple destinations can be passed into this option, and they will be parsed +in the order they are received. JavaScript files, JSON files, and objects will +need to namespace custom selectors using the `customSelectors` or +`custom-Selectors` key. + +```js +const cachedObject = { customSelectors: {} }; + +postcssCustomSelectorsImportExport({ + exportTo: [ + 'path/to/file.css', // @custom-selector :--heading h1, h2, h3; + 'and/then/this.js', // module.exports = { customSelectors: { ':--heading': 'h1, h2, h3' } } + 'and/then/this.mjs', // export const customSelectors = { ':--heading': 'h1, h2, h3' } } + 'and/then/that.json', // { "custom-selectors": { ":--heading": "h1, h2, h3" } } + cachedObject, + (customSelectors) => { + customSelectors // { ':--heading': 'h1, h2, h3' } + } + ] +}); +``` + +[cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test + +[discord]: https://discord.gg/bUadyRwkJS +[npm-url]: https://www.npmjs.com/package/@csstools/postcss-custom-selectors-import-export + +[PostCSS]: https://github.com/postcss/postcss +[PostCSS Custom Selectors Import/Export]: https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-custom-selectors-import-export diff --git a/plugins/postcss-custom-selectors-import-export/docs/README.md b/plugins/postcss-custom-selectors-import-export/docs/README.md new file mode 100644 index 000000000..0f0ef1624 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/docs/README.md @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + +
+ +[] lets you import or export `@custom-selector`'s into or out of your CSS. + +## As a drop in for old versions of `postcss-custom-selectors` + +```js +// commonjs +const postcss = require('postcss'); +const postcssCustomSelectorsImportExport = require('@csstools/postcss-custom-selectors-import-export'); +const postcssCustomSelectors = require('postcss-custom-selectors'); + +postcss([ + // First + postcssCustomSelectorsImportExport({ + /* pluginOptions */ + importedStylesOverrideDocumentStyles: false, // mimics old `postcss-custom-selectors` + }), + // Second + postcssCustomSelectors() +]).process(YOUR_CSS /*, processOptions */); +``` + + + + + +## Options + +### importFrom + +The `importFrom` option specifies sources where custom selectors can be +imported from, which might be CSS, JS, and JSON files, functions, and directly +passed objects. + +```js +({ + importFrom: 'path/to/file.css' // => @custom-selector :--heading h1, h2, h3; +}); +``` + +```pcss +article :--heading + p { + margin-top: 0; +} + +/* becomes */ + +@custom-selector :--heading h1, h2, h3; + +article :--heading+p { + margin-top: 0; +} +``` + +Multiple sources can be passed into this option, and they will be parsed in the +order they are received. JavaScript files, JSON files, functions, and objects +will need to namespace custom selectors using the `customSelectors` or +`custom-selectors` key. + +```js +({ + importFrom: [ + 'path/to/file.css', + 'and/then/this.js', + 'and/then/that.json', + { + customSelectors: { ':--heading': 'h1, h2, h3' } + }, + () => { + const customSelectors = { ':--heading': 'h1, h2, h3' }; + + return { customSelectors }; + } + ] +}); +``` + +### importedStylesOverrideDocumentStyles + +The `importedStylesOverrideDocumentStyles` option determines if selectors added via `importFrom` override selectors that exist in your CSS document. +Defaults to `false`. + +```js +({ + importedStylesOverrideDocumentStyles: true +}); + +### exportTo + +The `exportTo` option specifies destinations where custom selectors can be +exported to, which might be CSS, JS, and JSON files, functions, and directly +passed objects. + +```js +({ + exportTo: 'path/to/file.css' // @custom-selector :--heading h1, h2, h3; +}); +``` + +Multiple destinations can be passed into this option, and they will be parsed +in the order they are received. JavaScript files, JSON files, and objects will +need to namespace custom selectors using the `customSelectors` or +`custom-Selectors` key. + +```js +const cachedObject = { customSelectors: {} }; + +({ + exportTo: [ + 'path/to/file.css', // @custom-selector :--heading h1, h2, h3; + 'and/then/this.js', // module.exports = { customSelectors: { ':--heading': 'h1, h2, h3' } } + 'and/then/this.mjs', // export const customSelectors = { ':--heading': 'h1, h2, h3' } } + 'and/then/that.json', // { "custom-selectors": { ":--heading": "h1, h2, h3" } } + cachedObject, + (customSelectors) => { + customSelectors // { ':--heading': 'h1, h2, h3' } + } + ] +}); +``` + + diff --git a/plugins/postcss-custom-selectors-import-export/package.json b/plugins/postcss-custom-selectors-import-export/package.json new file mode 100644 index 000000000..43259f05d --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/package.json @@ -0,0 +1,96 @@ +{ + "name": "@csstools/postcss-custom-selectors-import-export", + "description": "Import/Export Custom Selectors in CSS", + "version": "1.0.0", + "contributors": [ + { + "name": "Antonio Laguna", + "email": "antonio@laguna.es", + "url": "https://antonio.laguna.es" + }, + { + "name": "Romain Menke", + "email": "romainmenke@gmail.com" + }, + { + "name": "Jonathan Neal", + "email": "jonathantneal@hotmail.com" + }, + { + "name": "Maxime Thirouin" + }, + { + "name": "yisi" + } + ], + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "main": "dist/index.cjs", + "module": "dist/index.mjs", + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.cjs", + "default": "./dist/index.mjs" + } + }, + "files": [ + "CHANGELOG.md", + "LICENSE.md", + "README.md", + "dist" + ], + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "peerDependencies": { + "postcss": "^8.4" + }, + "devDependencies": { + "postcss-custom-selectors": "^7.0.0" + }, + "scripts": { + "build": "rollup -c ../../rollup/default.mjs", + "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.cjs && node .tape.mjs && npm run test:exports", + "test:exports": "node ./test/_import.mjs && node ./test/_require.cjs", + "test:rewrite-expects": "REWRITE_EXPECTS=true node .tape.mjs && REWRITE_EXPECTS=true node .tape.cjs" + }, + "homepage": "https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-custom-selectors-import-export#readme", + "repository": { + "type": "git", + "url": "https://github.com/csstools/postcss-plugins.git", + "directory": "plugins/postcss-custom-selectors-import-export" + }, + "bugs": "https://github.com/csstools/postcss-plugins/issues", + "keywords": [ + "at-rule", + "atrule", + "css", + "custom", + "declarative", + "extensions", + "postcss", + "postcss-plugin", + "rule", + "selectors" + ], + "csstools": { + "exportName": "postcssCustomSelectorsImportExport", + "humanReadableName": "PostCSS Custom Selectors Import/Export" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/plugins/postcss-custom-selectors-import-export/src/custom-selectors-from-root.js b/plugins/postcss-custom-selectors-import-export/src/custom-selectors-from-root.js new file mode 100644 index 000000000..12579e384 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/src/custom-selectors-from-root.js @@ -0,0 +1,32 @@ +import parser from 'postcss-selector-parser'; + +// return custom selectors from the css root, conditionally removing them +export default (root) => { + // initialize custom selectors + const customSelectors = {}; + + // for each custom selector atrule that is a child of the css root + root.walkAtRules((node) => { + if (node.name.toLowerCase() !== 'custom-selector') { + return; + } + + if (!node.params || !node.params.includes(':--')) { + return; + } + + const source = node.params.trim(); + + const selectorAST = parser().astSync(source); + const nameNode = selectorAST?.nodes?.[0]?.nodes?.[0]; + if (!nameNode || nameNode.type !== 'pseudo' || !nameNode.value.startsWith(':--')) { + return; + } + + const name = nameNode.toString(); + + customSelectors[name] = source.slice(name.length).trim(); + }); + + return customSelectors; +}; diff --git a/plugins/postcss-custom-selectors-import-export/src/export-to.js b/plugins/postcss-custom-selectors-import-export/src/export-to.js new file mode 100644 index 000000000..3420fe074 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/src/export-to.js @@ -0,0 +1,129 @@ +import fs from 'fs'; +import path from 'path'; + +/* Import Custom Selectors from CSS File +/* ========================================================================== */ + +async function exportCustomSelectorsToCssFile(to, customSelectors) { + const cssContent = Object.keys(customSelectors).reduce((cssLines, name) => { + cssLines.push(`@custom-selector ${name} ${customSelectors[name]};`); + + return cssLines; + }, []).join('\n'); + const css = `${cssContent}\n`; + + await writeFile(to, css); +} + +/* Import Custom Selectors from JSON file +/* ========================================================================== */ + +async function exportCustomSelectorsToJsonFile(to, customSelectors) { + const jsonContent = JSON.stringify({ + 'custom-selectors': customSelectors, + }, null, '\t'); + const json = `${jsonContent}\n`; + + await writeFile(to, json); +} + +/* Import Custom Selectors from Common JS file +/* ========================================================================== */ + +async function exportCustomSelectorsToCjsFile(to, customSelectors) { + const jsContents = Object.keys(customSelectors).reduce((jsLines, name) => { + jsLines.push(`\t\t'${escapeForJS(name)}': '${escapeForJS(customSelectors[name])}'`); + + return jsLines; + }, []).join(',\n'); + const js = `module.exports = {\n\tcustomSelectors: {\n${jsContents}\n\t}\n};\n`; + + await writeFile(to, js); +} + +/* Import Custom Selectors from Module JS file +/* ========================================================================== */ + +async function exportCustomSelectorsToMjsFile(to, customSelectors) { + const mjsContents = Object.keys(customSelectors).reduce((mjsLines, name) => { + mjsLines.push(`\t'${escapeForJS(name)}': '${escapeForJS(customSelectors[name])}'`); + + return mjsLines; + }, []).join(',\n'); + const mjs = `export const customSelectors = {\n${mjsContents}\n};\n`; + + await writeFile(to, mjs); +} + +/* Export Custom Selectors to Destinations +/* ========================================================================== */ + +export default function exportCustomSelectorsToDestinations(customSelectors, destinations) { + return Promise.all(destinations.map(async destination => { + if (destination instanceof Function) { + await destination(defaultCustomSelectorsToJSON(customSelectors)); + } else { + // read the destination as an object + const opts = destination === Object(destination) ? destination : { to: String(destination) }; + + // transformer for custom selectors into a JSON-compatible object + const toJSON = opts.toJSON || defaultCustomSelectorsToJSON; + + if ('customSelectors' in opts) { + // write directly to an object as customSelectors + opts.customSelectors = toJSON(customSelectors); + } else if ('custom-selectors' in opts) { + // write directly to an object as custom-selectors + opts['custom-selectors'] = toJSON(customSelectors); + } else { + // destination pathname + const to = String(opts.to || ''); + + // type of file being written to + const type = (opts.type || path.extname(opts.to).slice(1)).toLowerCase(); + + // transformed custom selectors + const customSelectorsJSON = toJSON(customSelectors); + + if (type === 'css') { + await exportCustomSelectorsToCssFile(to, customSelectorsJSON); + } + + if (type === 'js') { + await exportCustomSelectorsToCjsFile(to, customSelectorsJSON); + } + + if (type === 'json') { + await exportCustomSelectorsToJsonFile(to, customSelectorsJSON); + } + + if (type === 'mjs') { + await exportCustomSelectorsToMjsFile(to, customSelectorsJSON); + } + } + } + })); +} + +/* Helper utilities +/* ========================================================================== */ + +const defaultCustomSelectorsToJSON = customSelectors => { + return Object.keys(customSelectors).reduce((customSelectorsJSON, key) => { + customSelectorsJSON[key] = String(customSelectors[key]); + + return customSelectorsJSON; + }, {}); +}; + +const writeFile = (to, text) => new Promise((resolve, reject) => { + fs.writeFile(to, text, error => { + if (error) { + reject(error); + } else { + resolve(); + } + }); +}); + +const escapeForJS = string => string.replace(/\\([\s\S])|(')/g, '\\$1$2').replace(/\n/g, '\\n').replace(/\r/g, '\\r'); diff --git a/plugins/postcss-custom-selectors-import-export/src/import-from.js b/plugins/postcss-custom-selectors-import-export/src/import-from.js new file mode 100644 index 000000000..915fdf28b --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/src/import-from.js @@ -0,0 +1,126 @@ +import fs from 'fs'; +import path from 'path'; +import url from 'url'; +import postcss from 'postcss'; +import getCustomSelectors from './custom-selectors-from-root'; + +/* Import Custom Selectors from CSS AST +/* ========================================================================== */ + +function importCustomSelectorsFromCSSAST(root) { + return getCustomSelectors(root); +} + +/* Import Custom Selectors from CSS File +/* ========================================================================== */ + +async function importCustomSelectorsFromCSSFile(from) { + const css = await readFile(url.pathToFileURL(path.resolve(from))); + const root = postcss.parse(css, { from: path.resolve(from) }); + + return importCustomSelectorsFromCSSAST(root); +} + +/* Import Custom Selectors from Object +/* ========================================================================== */ + +function importCustomSelectorsFromObject(object) { + const customSelectors = Object.assign( + {}, + Object(object).customSelectors || Object(object)['custom-selectors'], + ); + + return customSelectors; +} + +/* Import Custom Selectors from JSON file +/* ========================================================================== */ + +async function importCustomSelectorsFromJSONFile(from) { + const object = await readJSON(url.pathToFileURL(path.resolve(from))); + + return importCustomSelectorsFromObject(object); +} + +/* Import Custom Selectors from JS file +/* ========================================================================== */ + +async function importCustomSelectorsFromJSFile(from) { + const object = await import(url.pathToFileURL(path.resolve(from)).href); + + if ('default' in object) { + return importCustomSelectorsFromObject(object.default); + } + + return importCustomSelectorsFromObject(object); +} + +/* Import Custom Selectors from Sources +/* ========================================================================== */ + +export default function importCustomSelectorsFromSources(sources) { + return sources.map(source => { + if (source instanceof Promise) { + return source; + } else if (source instanceof Function) { + return source(); + } + + // read the source as an object + const opts = source === Object(source) ? source : { from: String(source) }; + + // skip objects with custom selectors + if (Object(opts).customSelectors || Object(opts)['custom-selectors']) { + return opts; + } + + // source pathname + const from = String(opts.from || ''); + + // type of file being read from + const type = (opts.type || path.extname(from).slice(1)).toLowerCase(); + + return { type, from }; + }).reduce(async (customSelectorsPromise, source) => { + const customSelectors = await customSelectorsPromise; + const { type, from } = await source; + + if (type === 'ast') { + return Object.assign(customSelectors, importCustomSelectorsFromCSSAST(from)); + } + + if (type === 'css' || type === 'pcss') { + return Object.assign(customSelectors, await importCustomSelectorsFromCSSFile(from)); + } + + if (type === 'js' || type === 'cjs') { + return Object.assign(customSelectors, await importCustomSelectorsFromJSFile(from)); + } + + if (type === 'mjs') { + // Only works when running as a module. + return Object.assign(customSelectors, await importCustomSelectorsFromJSFile(from)); + } + + if (type === 'json') { + return Object.assign(customSelectors, await importCustomSelectorsFromJSONFile(from)); + } + + return Object.assign(customSelectors, importCustomSelectorsFromObject(await source)); + }, Promise.resolve({})); +} + +/* Helper utilities +/* ========================================================================== */ + +const readFile = from => new Promise((resolve, reject) => { + fs.readFile(from, 'utf8', (error, result) => { + if (error) { + reject(error); + } else { + resolve(result); + } + }); +}); + +const readJSON = async from => JSON.parse(await readFile(from)); diff --git a/plugins/postcss-custom-selectors-import-export/src/index.js b/plugins/postcss-custom-selectors-import-export/src/index.js new file mode 100644 index 000000000..9783c5f9d --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/src/index.js @@ -0,0 +1,69 @@ +import getCustomSelectors from './custom-selectors-from-root'; +import importCustomSelectorsFromSources from './import-from'; +import exportCustomSelectorsToDestinations from './export-to'; + +const creator = (opts) => { + const importedStylesOverrideDocumentStyles = 'importedStylesOverrideDocumentStyles' in Object(opts) ? Boolean(opts.importedStylesOverrideDocumentStyles) : false; + + // sources to import custom selectors from + const importFrom = [].concat(Object(opts).importFrom || []); + + // destinations to export custom selectors to + const exportTo = [].concat(Object(opts).exportTo || []); + + // promise any custom selectors are imported + const customSelectorsPromise = importCustomSelectorsFromSources(importFrom); + + return { + postcssPlugin: 'postcss-custom-selectors-import-export', + Once: async (root, { result, postcss }) => { + const importedSelectors = await customSelectorsPromise; + + let allCustomSelectors; + if (importedStylesOverrideDocumentStyles) { + allCustomSelectors = Object.assign( + {}, + getCustomSelectors(root), + importedSelectors, + ); + } else { + allCustomSelectors = Object.assign( + {}, + importedSelectors, + getCustomSelectors(root), + ); + } + + await exportCustomSelectorsToDestinations(allCustomSelectors, exportTo); + + if (importedSelectors) { + const selectorNames = Object.keys(importedSelectors); + // Inserting in reverse order results in the correct order. + selectorNames.reverse(); + + let operator = 'prepend'; + if (importedStylesOverrideDocumentStyles) { + operator = 'append'; + } + + selectorNames.forEach((selectorName) => { + root[operator](postcss.atRule({ + name: 'custom-selector', + params: `${selectorName} ${importedSelectors[selectorName].toString()}`, + source: { + input: { + from: result.opts.from, + }, + start: { offset: 0, line: 1, column: 1 }, + end: { offset: 0, line: 1, column: 1 }, + }, + })); + }); + } + }, + }; +}; + +creator.postcss = true; + +export default creator; diff --git a/plugins/postcss-custom-selectors-import-export/test/_import.mjs b/plugins/postcss-custom-selectors-import-export/test/_import.mjs new file mode 100644 index 000000000..3b174fff6 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/_import.mjs @@ -0,0 +1,6 @@ +import assert from 'assert'; +import plugin from '@csstools/postcss-custom-selectors-import-export'; +plugin(); + +assert.ok(plugin.postcss, 'should have "postcss flag"'); +assert.equal(typeof plugin, 'function', 'should return a function'); diff --git a/plugins/postcss-custom-selectors-import-export/test/_require.cjs b/plugins/postcss-custom-selectors-import-export/test/_require.cjs new file mode 100644 index 000000000..30faeabc0 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/_require.cjs @@ -0,0 +1,6 @@ +const assert = require('assert'); +const plugin = require('@csstools/postcss-custom-selectors-import-export'); +plugin(); + +assert.ok(plugin.postcss, 'should have "postcss flag"'); +assert.equal(typeof plugin, 'function', 'should return a function'); diff --git a/plugins/postcss-custom-selectors-import-export/test/basic-import.css b/plugins/postcss-custom-selectors-import-export/test/basic-import.css new file mode 100644 index 000000000..1d1630362 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/basic-import.css @@ -0,0 +1,10 @@ +article :--heading+p { + margin-top: 0; +} + +@custom-selector :--text p, b, strong, i, em, quote, cite; +@custom-selector :--other .other; + +.anything_else { + margin-bottom: 0; +} diff --git a/plugins/postcss-custom-selectors-import-export/test/basic-import.css-from-multiple-files.expect.css b/plugins/postcss-custom-selectors-import-export/test/basic-import.css-from-multiple-files.expect.css new file mode 100644 index 000000000..681d94276 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/basic-import.css-from-multiple-files.expect.css @@ -0,0 +1,14 @@ +@custom-selector :--heading h1, h2, h3; + +@custom-selector :--text :--heading, p; + +article :--heading+p { + margin-top: 0; +} + +@custom-selector :--text p, b, strong, i, em, quote, cite; +@custom-selector :--other .other; + +.anything_else { + margin-bottom: 0; +} diff --git a/plugins/postcss-custom-selectors-import-export/test/basic-import.css-from-type.expect.css b/plugins/postcss-custom-selectors-import-export/test/basic-import.css-from-type.expect.css new file mode 100644 index 000000000..681d94276 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/basic-import.css-from-type.expect.css @@ -0,0 +1,14 @@ +@custom-selector :--heading h1, h2, h3; + +@custom-selector :--text :--heading, p; + +article :--heading+p { + margin-top: 0; +} + +@custom-selector :--text p, b, strong, i, em, quote, cite; +@custom-selector :--other .other; + +.anything_else { + margin-bottom: 0; +} diff --git a/plugins/postcss-custom-selectors-import-export/test/basic-import.css-from.expect.css b/plugins/postcss-custom-selectors-import-export/test/basic-import.css-from.expect.css new file mode 100644 index 000000000..681d94276 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/basic-import.css-from.expect.css @@ -0,0 +1,14 @@ +@custom-selector :--heading h1, h2, h3; + +@custom-selector :--text :--heading, p; + +article :--heading+p { + margin-top: 0; +} + +@custom-selector :--text p, b, strong, i, em, quote, cite; +@custom-selector :--other .other; + +.anything_else { + margin-bottom: 0; +} diff --git a/plugins/postcss-custom-selectors-import-export/test/basic-import.css.expect.css b/plugins/postcss-custom-selectors-import-export/test/basic-import.css.expect.css new file mode 100644 index 000000000..681d94276 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/basic-import.css.expect.css @@ -0,0 +1,14 @@ +@custom-selector :--heading h1, h2, h3; + +@custom-selector :--text :--heading, p; + +article :--heading+p { + margin-top: 0; +} + +@custom-selector :--text p, b, strong, i, em, quote, cite; +@custom-selector :--other .other; + +.anything_else { + margin-bottom: 0; +} diff --git a/plugins/postcss-custom-selectors-import-export/test/basic-import.empty.expect.css b/plugins/postcss-custom-selectors-import-export/test/basic-import.empty.expect.css new file mode 100644 index 000000000..1d1630362 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/basic-import.empty.expect.css @@ -0,0 +1,10 @@ +article :--heading+p { + margin-top: 0; +} + +@custom-selector :--text p, b, strong, i, em, quote, cite; +@custom-selector :--other .other; + +.anything_else { + margin-bottom: 0; +} diff --git a/plugins/postcss-custom-selectors-import-export/test/basic-import.expect.css b/plugins/postcss-custom-selectors-import-export/test/basic-import.expect.css new file mode 100644 index 000000000..681d94276 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/basic-import.expect.css @@ -0,0 +1,14 @@ +@custom-selector :--heading h1, h2, h3; + +@custom-selector :--text :--heading, p; + +article :--heading+p { + margin-top: 0; +} + +@custom-selector :--text p, b, strong, i, em, quote, cite; +@custom-selector :--other .other; + +.anything_else { + margin-bottom: 0; +} diff --git a/plugins/postcss-custom-selectors-import-export/test/basic-import.fn-promise.expect.css b/plugins/postcss-custom-selectors-import-export/test/basic-import.fn-promise.expect.css new file mode 100644 index 000000000..681d94276 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/basic-import.fn-promise.expect.css @@ -0,0 +1,14 @@ +@custom-selector :--heading h1, h2, h3; + +@custom-selector :--text :--heading, p; + +article :--heading+p { + margin-top: 0; +} + +@custom-selector :--text p, b, strong, i, em, quote, cite; +@custom-selector :--other .other; + +.anything_else { + margin-bottom: 0; +} diff --git a/plugins/postcss-custom-selectors-import-export/test/basic-import.fn.expect.css b/plugins/postcss-custom-selectors-import-export/test/basic-import.fn.expect.css new file mode 100644 index 000000000..681d94276 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/basic-import.fn.expect.css @@ -0,0 +1,14 @@ +@custom-selector :--heading h1, h2, h3; + +@custom-selector :--text :--heading, p; + +article :--heading+p { + margin-top: 0; +} + +@custom-selector :--text p, b, strong, i, em, quote, cite; +@custom-selector :--other .other; + +.anything_else { + margin-bottom: 0; +} diff --git a/plugins/postcss-custom-selectors-import-export/test/basic-import.imported-styles-override-document-styles.false.expect.css b/plugins/postcss-custom-selectors-import-export/test/basic-import.imported-styles-override-document-styles.false.expect.css new file mode 100644 index 000000000..681d94276 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/basic-import.imported-styles-override-document-styles.false.expect.css @@ -0,0 +1,14 @@ +@custom-selector :--heading h1, h2, h3; + +@custom-selector :--text :--heading, p; + +article :--heading+p { + margin-top: 0; +} + +@custom-selector :--text p, b, strong, i, em, quote, cite; +@custom-selector :--other .other; + +.anything_else { + margin-bottom: 0; +} diff --git a/plugins/postcss-custom-selectors-import-export/test/basic-import.imported-styles-override-document-styles.true.expect.css b/plugins/postcss-custom-selectors-import-export/test/basic-import.imported-styles-override-document-styles.true.expect.css new file mode 100644 index 000000000..16184231b --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/basic-import.imported-styles-override-document-styles.true.expect.css @@ -0,0 +1,14 @@ +article :--heading+p { + margin-top: 0; +} + +@custom-selector :--text p, b, strong, i, em, quote, cite; +@custom-selector :--other .other; + +.anything_else { + margin-bottom: 0; +} + +@custom-selector :--text :--heading, p; + +@custom-selector :--heading h1, h2, h3 diff --git a/plugins/postcss-custom-selectors-import-export/test/basic-import.js.expect.css b/plugins/postcss-custom-selectors-import-export/test/basic-import.js.expect.css new file mode 100644 index 000000000..681d94276 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/basic-import.js.expect.css @@ -0,0 +1,14 @@ +@custom-selector :--heading h1, h2, h3; + +@custom-selector :--text :--heading, p; + +article :--heading+p { + margin-top: 0; +} + +@custom-selector :--text p, b, strong, i, em, quote, cite; +@custom-selector :--other .other; + +.anything_else { + margin-bottom: 0; +} diff --git a/plugins/postcss-custom-selectors-import-export/test/basic-import.json.expect.css b/plugins/postcss-custom-selectors-import-export/test/basic-import.json.expect.css new file mode 100644 index 000000000..681d94276 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/basic-import.json.expect.css @@ -0,0 +1,14 @@ +@custom-selector :--heading h1, h2, h3; + +@custom-selector :--text :--heading, p; + +article :--heading+p { + margin-top: 0; +} + +@custom-selector :--text p, b, strong, i, em, quote, cite; +@custom-selector :--other .other; + +.anything_else { + margin-bottom: 0; +} diff --git a/plugins/postcss-custom-selectors-import-export/test/basic-import.mjs.expect.css b/plugins/postcss-custom-selectors-import-export/test/basic-import.mjs.expect.css new file mode 100644 index 000000000..681d94276 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/basic-import.mjs.expect.css @@ -0,0 +1,14 @@ +@custom-selector :--heading h1, h2, h3; + +@custom-selector :--text :--heading, p; + +article :--heading+p { + margin-top: 0; +} + +@custom-selector :--text p, b, strong, i, em, quote, cite; +@custom-selector :--other .other; + +.anything_else { + margin-bottom: 0; +} diff --git a/plugins/postcss-custom-selectors-import-export/test/basic-import.with-polyfill-plugin.expect.css b/plugins/postcss-custom-selectors-import-export/test/basic-import.with-polyfill-plugin.expect.css new file mode 100644 index 000000000..1d14a4a59 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/basic-import.with-polyfill-plugin.expect.css @@ -0,0 +1,7 @@ +article :is(h1, h2, h3)+p { + margin-top: 0; +} + +.anything_else { + margin-bottom: 0; +} diff --git a/plugins/postcss-custom-selectors-import-export/test/basic.css b/plugins/postcss-custom-selectors-import-export/test/basic.css new file mode 100644 index 000000000..e3ce21368 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/basic.css @@ -0,0 +1,126 @@ +@custom-selector :--foo .bar, .baz; + +.foo:--foo { + margin-top: 16px; +} + +@custom-selector :--any-heading h1, h2, h3, h4, h5, h6; + +:--any-heading + p {} + +@custom-selector :--foobar .foo, .bar; +@custom-selector :--baz .baz; +@custom-selector :--fizzbuzz .fizz, .buzz; +@custom-selector :--button-types + .btn-primary, + .btn-success, + .btn-info, + .btn-warning, + .btn-danger; + +:--foobar > :--baz {} +:--fizzbuzz > :--foobar {} +:--button-types, :--button-types:active {} + +@custom-selector :--commented-foo + /* comment */ + .foo, + .bar > .baz; + +:--commented-foo + p { + display: block; +} + +@custom-selector :--pseudo ::before, ::after; + +.foo > a:--pseudo img { + display: block; +} + +@custom-selector :--foo .foo; + +:--foo, :--foo.bar { + color: white; +} + +:--foo :--foo:hover { + color: white; +} + +@custom-selector :--fo-----o h1, h2, h3; +@custom-selector :--ba-----r h4, h5, h6; + +.fo--oo > :--fo-----o { + margin: auto; +} + +:--ba-----r:hover .ba--z { + display: block; +} + +/* comment */ + +article :--heading + p { + margin-top: 0; +} + +@custom-selector :--multiline + .foo, + .bar > .baz; + +:--multiline { + display: block; +} + +/* should works with collapsed custom selectors */ + +@custom-selector :--button button, .button; +@custom-selector :--enter :hover, :focus; + +:--button:--enter {} + +@custom-selector :--any-foobar .foo, .bar; + +:--any-foobar h1 { + margin-top: 16px; +} + +main :--foo + p { + margin-top: 16px; +} + +@custom-selector :--foobar .foo; + +:--foobar { + order: 1000; +} +a, :--foobar { + order: 1001; +} +b,:--foobar { + order: 1002; +} + +@custom-selector :--foobaz .foo.baz; + +:--foobaz { + order: 1010; +} +a, :--foobaz { + order: 1011; +} +b,:--foobaz { + order: 1012; +} + +@custom-selector :--foobazz .foo .baz; + +:--foobazz { + order: 1020; +} +a, :--foobazz { + order: 1021; +} +b,:--foobazz { + order: 1022; +} diff --git a/plugins/postcss-custom-selectors-import-export/test/basic.expect.css b/plugins/postcss-custom-selectors-import-export/test/basic.expect.css new file mode 100644 index 000000000..e3ce21368 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/basic.expect.css @@ -0,0 +1,126 @@ +@custom-selector :--foo .bar, .baz; + +.foo:--foo { + margin-top: 16px; +} + +@custom-selector :--any-heading h1, h2, h3, h4, h5, h6; + +:--any-heading + p {} + +@custom-selector :--foobar .foo, .bar; +@custom-selector :--baz .baz; +@custom-selector :--fizzbuzz .fizz, .buzz; +@custom-selector :--button-types + .btn-primary, + .btn-success, + .btn-info, + .btn-warning, + .btn-danger; + +:--foobar > :--baz {} +:--fizzbuzz > :--foobar {} +:--button-types, :--button-types:active {} + +@custom-selector :--commented-foo + /* comment */ + .foo, + .bar > .baz; + +:--commented-foo + p { + display: block; +} + +@custom-selector :--pseudo ::before, ::after; + +.foo > a:--pseudo img { + display: block; +} + +@custom-selector :--foo .foo; + +:--foo, :--foo.bar { + color: white; +} + +:--foo :--foo:hover { + color: white; +} + +@custom-selector :--fo-----o h1, h2, h3; +@custom-selector :--ba-----r h4, h5, h6; + +.fo--oo > :--fo-----o { + margin: auto; +} + +:--ba-----r:hover .ba--z { + display: block; +} + +/* comment */ + +article :--heading + p { + margin-top: 0; +} + +@custom-selector :--multiline + .foo, + .bar > .baz; + +:--multiline { + display: block; +} + +/* should works with collapsed custom selectors */ + +@custom-selector :--button button, .button; +@custom-selector :--enter :hover, :focus; + +:--button:--enter {} + +@custom-selector :--any-foobar .foo, .bar; + +:--any-foobar h1 { + margin-top: 16px; +} + +main :--foo + p { + margin-top: 16px; +} + +@custom-selector :--foobar .foo; + +:--foobar { + order: 1000; +} +a, :--foobar { + order: 1001; +} +b,:--foobar { + order: 1002; +} + +@custom-selector :--foobaz .foo.baz; + +:--foobaz { + order: 1010; +} +a, :--foobaz { + order: 1011; +} +b,:--foobaz { + order: 1012; +} + +@custom-selector :--foobazz .foo .baz; + +:--foobazz { + order: 1020; +} +a, :--foobazz { + order: 1021; +} +b,:--foobazz { + order: 1022; +} diff --git a/plugins/postcss-custom-selectors-import-export/test/basic.export-css-to-type.expect.css b/plugins/postcss-custom-selectors-import-export/test/basic.export-css-to-type.expect.css new file mode 100644 index 000000000..e3ce21368 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/basic.export-css-to-type.expect.css @@ -0,0 +1,126 @@ +@custom-selector :--foo .bar, .baz; + +.foo:--foo { + margin-top: 16px; +} + +@custom-selector :--any-heading h1, h2, h3, h4, h5, h6; + +:--any-heading + p {} + +@custom-selector :--foobar .foo, .bar; +@custom-selector :--baz .baz; +@custom-selector :--fizzbuzz .fizz, .buzz; +@custom-selector :--button-types + .btn-primary, + .btn-success, + .btn-info, + .btn-warning, + .btn-danger; + +:--foobar > :--baz {} +:--fizzbuzz > :--foobar {} +:--button-types, :--button-types:active {} + +@custom-selector :--commented-foo + /* comment */ + .foo, + .bar > .baz; + +:--commented-foo + p { + display: block; +} + +@custom-selector :--pseudo ::before, ::after; + +.foo > a:--pseudo img { + display: block; +} + +@custom-selector :--foo .foo; + +:--foo, :--foo.bar { + color: white; +} + +:--foo :--foo:hover { + color: white; +} + +@custom-selector :--fo-----o h1, h2, h3; +@custom-selector :--ba-----r h4, h5, h6; + +.fo--oo > :--fo-----o { + margin: auto; +} + +:--ba-----r:hover .ba--z { + display: block; +} + +/* comment */ + +article :--heading + p { + margin-top: 0; +} + +@custom-selector :--multiline + .foo, + .bar > .baz; + +:--multiline { + display: block; +} + +/* should works with collapsed custom selectors */ + +@custom-selector :--button button, .button; +@custom-selector :--enter :hover, :focus; + +:--button:--enter {} + +@custom-selector :--any-foobar .foo, .bar; + +:--any-foobar h1 { + margin-top: 16px; +} + +main :--foo + p { + margin-top: 16px; +} + +@custom-selector :--foobar .foo; + +:--foobar { + order: 1000; +} +a, :--foobar { + order: 1001; +} +b,:--foobar { + order: 1002; +} + +@custom-selector :--foobaz .foo.baz; + +:--foobaz { + order: 1010; +} +a, :--foobaz { + order: 1011; +} +b,:--foobaz { + order: 1012; +} + +@custom-selector :--foobazz .foo .baz; + +:--foobazz { + order: 1020; +} +a, :--foobazz { + order: 1021; +} +b,:--foobazz { + order: 1022; +} diff --git a/plugins/postcss-custom-selectors-import-export/test/basic.export-css-to.expect.css b/plugins/postcss-custom-selectors-import-export/test/basic.export-css-to.expect.css new file mode 100644 index 000000000..e3ce21368 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/basic.export-css-to.expect.css @@ -0,0 +1,126 @@ +@custom-selector :--foo .bar, .baz; + +.foo:--foo { + margin-top: 16px; +} + +@custom-selector :--any-heading h1, h2, h3, h4, h5, h6; + +:--any-heading + p {} + +@custom-selector :--foobar .foo, .bar; +@custom-selector :--baz .baz; +@custom-selector :--fizzbuzz .fizz, .buzz; +@custom-selector :--button-types + .btn-primary, + .btn-success, + .btn-info, + .btn-warning, + .btn-danger; + +:--foobar > :--baz {} +:--fizzbuzz > :--foobar {} +:--button-types, :--button-types:active {} + +@custom-selector :--commented-foo + /* comment */ + .foo, + .bar > .baz; + +:--commented-foo + p { + display: block; +} + +@custom-selector :--pseudo ::before, ::after; + +.foo > a:--pseudo img { + display: block; +} + +@custom-selector :--foo .foo; + +:--foo, :--foo.bar { + color: white; +} + +:--foo :--foo:hover { + color: white; +} + +@custom-selector :--fo-----o h1, h2, h3; +@custom-selector :--ba-----r h4, h5, h6; + +.fo--oo > :--fo-----o { + margin: auto; +} + +:--ba-----r:hover .ba--z { + display: block; +} + +/* comment */ + +article :--heading + p { + margin-top: 0; +} + +@custom-selector :--multiline + .foo, + .bar > .baz; + +:--multiline { + display: block; +} + +/* should works with collapsed custom selectors */ + +@custom-selector :--button button, .button; +@custom-selector :--enter :hover, :focus; + +:--button:--enter {} + +@custom-selector :--any-foobar .foo, .bar; + +:--any-foobar h1 { + margin-top: 16px; +} + +main :--foo + p { + margin-top: 16px; +} + +@custom-selector :--foobar .foo; + +:--foobar { + order: 1000; +} +a, :--foobar { + order: 1001; +} +b,:--foobar { + order: 1002; +} + +@custom-selector :--foobaz .foo.baz; + +:--foobaz { + order: 1010; +} +a, :--foobaz { + order: 1011; +} +b,:--foobaz { + order: 1012; +} + +@custom-selector :--foobazz .foo .baz; + +:--foobazz { + order: 1020; +} +a, :--foobazz { + order: 1021; +} +b,:--foobazz { + order: 1022; +} diff --git a/plugins/postcss-custom-selectors-import-export/test/basic.export-css.expect.css b/plugins/postcss-custom-selectors-import-export/test/basic.export-css.expect.css new file mode 100644 index 000000000..e3ce21368 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/basic.export-css.expect.css @@ -0,0 +1,126 @@ +@custom-selector :--foo .bar, .baz; + +.foo:--foo { + margin-top: 16px; +} + +@custom-selector :--any-heading h1, h2, h3, h4, h5, h6; + +:--any-heading + p {} + +@custom-selector :--foobar .foo, .bar; +@custom-selector :--baz .baz; +@custom-selector :--fizzbuzz .fizz, .buzz; +@custom-selector :--button-types + .btn-primary, + .btn-success, + .btn-info, + .btn-warning, + .btn-danger; + +:--foobar > :--baz {} +:--fizzbuzz > :--foobar {} +:--button-types, :--button-types:active {} + +@custom-selector :--commented-foo + /* comment */ + .foo, + .bar > .baz; + +:--commented-foo + p { + display: block; +} + +@custom-selector :--pseudo ::before, ::after; + +.foo > a:--pseudo img { + display: block; +} + +@custom-selector :--foo .foo; + +:--foo, :--foo.bar { + color: white; +} + +:--foo :--foo:hover { + color: white; +} + +@custom-selector :--fo-----o h1, h2, h3; +@custom-selector :--ba-----r h4, h5, h6; + +.fo--oo > :--fo-----o { + margin: auto; +} + +:--ba-----r:hover .ba--z { + display: block; +} + +/* comment */ + +article :--heading + p { + margin-top: 0; +} + +@custom-selector :--multiline + .foo, + .bar > .baz; + +:--multiline { + display: block; +} + +/* should works with collapsed custom selectors */ + +@custom-selector :--button button, .button; +@custom-selector :--enter :hover, :focus; + +:--button:--enter {} + +@custom-selector :--any-foobar .foo, .bar; + +:--any-foobar h1 { + margin-top: 16px; +} + +main :--foo + p { + margin-top: 16px; +} + +@custom-selector :--foobar .foo; + +:--foobar { + order: 1000; +} +a, :--foobar { + order: 1001; +} +b,:--foobar { + order: 1002; +} + +@custom-selector :--foobaz .foo.baz; + +:--foobaz { + order: 1010; +} +a, :--foobaz { + order: 1011; +} +b,:--foobaz { + order: 1012; +} + +@custom-selector :--foobazz .foo .baz; + +:--foobazz { + order: 1020; +} +a, :--foobazz { + order: 1021; +} +b,:--foobazz { + order: 1022; +} diff --git a/plugins/postcss-custom-selectors-import-export/test/basic.export-fn-promise.expect.css b/plugins/postcss-custom-selectors-import-export/test/basic.export-fn-promise.expect.css new file mode 100644 index 000000000..e3ce21368 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/basic.export-fn-promise.expect.css @@ -0,0 +1,126 @@ +@custom-selector :--foo .bar, .baz; + +.foo:--foo { + margin-top: 16px; +} + +@custom-selector :--any-heading h1, h2, h3, h4, h5, h6; + +:--any-heading + p {} + +@custom-selector :--foobar .foo, .bar; +@custom-selector :--baz .baz; +@custom-selector :--fizzbuzz .fizz, .buzz; +@custom-selector :--button-types + .btn-primary, + .btn-success, + .btn-info, + .btn-warning, + .btn-danger; + +:--foobar > :--baz {} +:--fizzbuzz > :--foobar {} +:--button-types, :--button-types:active {} + +@custom-selector :--commented-foo + /* comment */ + .foo, + .bar > .baz; + +:--commented-foo + p { + display: block; +} + +@custom-selector :--pseudo ::before, ::after; + +.foo > a:--pseudo img { + display: block; +} + +@custom-selector :--foo .foo; + +:--foo, :--foo.bar { + color: white; +} + +:--foo :--foo:hover { + color: white; +} + +@custom-selector :--fo-----o h1, h2, h3; +@custom-selector :--ba-----r h4, h5, h6; + +.fo--oo > :--fo-----o { + margin: auto; +} + +:--ba-----r:hover .ba--z { + display: block; +} + +/* comment */ + +article :--heading + p { + margin-top: 0; +} + +@custom-selector :--multiline + .foo, + .bar > .baz; + +:--multiline { + display: block; +} + +/* should works with collapsed custom selectors */ + +@custom-selector :--button button, .button; +@custom-selector :--enter :hover, :focus; + +:--button:--enter {} + +@custom-selector :--any-foobar .foo, .bar; + +:--any-foobar h1 { + margin-top: 16px; +} + +main :--foo + p { + margin-top: 16px; +} + +@custom-selector :--foobar .foo; + +:--foobar { + order: 1000; +} +a, :--foobar { + order: 1001; +} +b,:--foobar { + order: 1002; +} + +@custom-selector :--foobaz .foo.baz; + +:--foobaz { + order: 1010; +} +a, :--foobaz { + order: 1011; +} +b,:--foobaz { + order: 1012; +} + +@custom-selector :--foobazz .foo .baz; + +:--foobazz { + order: 1020; +} +a, :--foobazz { + order: 1021; +} +b,:--foobazz { + order: 1022; +} diff --git a/plugins/postcss-custom-selectors-import-export/test/basic.export-fn.expect.css b/plugins/postcss-custom-selectors-import-export/test/basic.export-fn.expect.css new file mode 100644 index 000000000..e3ce21368 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/basic.export-fn.expect.css @@ -0,0 +1,126 @@ +@custom-selector :--foo .bar, .baz; + +.foo:--foo { + margin-top: 16px; +} + +@custom-selector :--any-heading h1, h2, h3, h4, h5, h6; + +:--any-heading + p {} + +@custom-selector :--foobar .foo, .bar; +@custom-selector :--baz .baz; +@custom-selector :--fizzbuzz .fizz, .buzz; +@custom-selector :--button-types + .btn-primary, + .btn-success, + .btn-info, + .btn-warning, + .btn-danger; + +:--foobar > :--baz {} +:--fizzbuzz > :--foobar {} +:--button-types, :--button-types:active {} + +@custom-selector :--commented-foo + /* comment */ + .foo, + .bar > .baz; + +:--commented-foo + p { + display: block; +} + +@custom-selector :--pseudo ::before, ::after; + +.foo > a:--pseudo img { + display: block; +} + +@custom-selector :--foo .foo; + +:--foo, :--foo.bar { + color: white; +} + +:--foo :--foo:hover { + color: white; +} + +@custom-selector :--fo-----o h1, h2, h3; +@custom-selector :--ba-----r h4, h5, h6; + +.fo--oo > :--fo-----o { + margin: auto; +} + +:--ba-----r:hover .ba--z { + display: block; +} + +/* comment */ + +article :--heading + p { + margin-top: 0; +} + +@custom-selector :--multiline + .foo, + .bar > .baz; + +:--multiline { + display: block; +} + +/* should works with collapsed custom selectors */ + +@custom-selector :--button button, .button; +@custom-selector :--enter :hover, :focus; + +:--button:--enter {} + +@custom-selector :--any-foobar .foo, .bar; + +:--any-foobar h1 { + margin-top: 16px; +} + +main :--foo + p { + margin-top: 16px; +} + +@custom-selector :--foobar .foo; + +:--foobar { + order: 1000; +} +a, :--foobar { + order: 1001; +} +b,:--foobar { + order: 1002; +} + +@custom-selector :--foobaz .foo.baz; + +:--foobaz { + order: 1010; +} +a, :--foobaz { + order: 1011; +} +b,:--foobaz { + order: 1012; +} + +@custom-selector :--foobazz .foo .baz; + +:--foobazz { + order: 1020; +} +a, :--foobazz { + order: 1021; +} +b,:--foobazz { + order: 1022; +} diff --git a/plugins/postcss-custom-selectors-import-export/test/basic.export-js.expect.css b/plugins/postcss-custom-selectors-import-export/test/basic.export-js.expect.css new file mode 100644 index 000000000..e3ce21368 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/basic.export-js.expect.css @@ -0,0 +1,126 @@ +@custom-selector :--foo .bar, .baz; + +.foo:--foo { + margin-top: 16px; +} + +@custom-selector :--any-heading h1, h2, h3, h4, h5, h6; + +:--any-heading + p {} + +@custom-selector :--foobar .foo, .bar; +@custom-selector :--baz .baz; +@custom-selector :--fizzbuzz .fizz, .buzz; +@custom-selector :--button-types + .btn-primary, + .btn-success, + .btn-info, + .btn-warning, + .btn-danger; + +:--foobar > :--baz {} +:--fizzbuzz > :--foobar {} +:--button-types, :--button-types:active {} + +@custom-selector :--commented-foo + /* comment */ + .foo, + .bar > .baz; + +:--commented-foo + p { + display: block; +} + +@custom-selector :--pseudo ::before, ::after; + +.foo > a:--pseudo img { + display: block; +} + +@custom-selector :--foo .foo; + +:--foo, :--foo.bar { + color: white; +} + +:--foo :--foo:hover { + color: white; +} + +@custom-selector :--fo-----o h1, h2, h3; +@custom-selector :--ba-----r h4, h5, h6; + +.fo--oo > :--fo-----o { + margin: auto; +} + +:--ba-----r:hover .ba--z { + display: block; +} + +/* comment */ + +article :--heading + p { + margin-top: 0; +} + +@custom-selector :--multiline + .foo, + .bar > .baz; + +:--multiline { + display: block; +} + +/* should works with collapsed custom selectors */ + +@custom-selector :--button button, .button; +@custom-selector :--enter :hover, :focus; + +:--button:--enter {} + +@custom-selector :--any-foobar .foo, .bar; + +:--any-foobar h1 { + margin-top: 16px; +} + +main :--foo + p { + margin-top: 16px; +} + +@custom-selector :--foobar .foo; + +:--foobar { + order: 1000; +} +a, :--foobar { + order: 1001; +} +b,:--foobar { + order: 1002; +} + +@custom-selector :--foobaz .foo.baz; + +:--foobaz { + order: 1010; +} +a, :--foobaz { + order: 1011; +} +b,:--foobaz { + order: 1012; +} + +@custom-selector :--foobazz .foo .baz; + +:--foobazz { + order: 1020; +} +a, :--foobazz { + order: 1021; +} +b,:--foobazz { + order: 1022; +} diff --git a/plugins/postcss-custom-selectors-import-export/test/basic.export-json.expect.css b/plugins/postcss-custom-selectors-import-export/test/basic.export-json.expect.css new file mode 100644 index 000000000..e3ce21368 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/basic.export-json.expect.css @@ -0,0 +1,126 @@ +@custom-selector :--foo .bar, .baz; + +.foo:--foo { + margin-top: 16px; +} + +@custom-selector :--any-heading h1, h2, h3, h4, h5, h6; + +:--any-heading + p {} + +@custom-selector :--foobar .foo, .bar; +@custom-selector :--baz .baz; +@custom-selector :--fizzbuzz .fizz, .buzz; +@custom-selector :--button-types + .btn-primary, + .btn-success, + .btn-info, + .btn-warning, + .btn-danger; + +:--foobar > :--baz {} +:--fizzbuzz > :--foobar {} +:--button-types, :--button-types:active {} + +@custom-selector :--commented-foo + /* comment */ + .foo, + .bar > .baz; + +:--commented-foo + p { + display: block; +} + +@custom-selector :--pseudo ::before, ::after; + +.foo > a:--pseudo img { + display: block; +} + +@custom-selector :--foo .foo; + +:--foo, :--foo.bar { + color: white; +} + +:--foo :--foo:hover { + color: white; +} + +@custom-selector :--fo-----o h1, h2, h3; +@custom-selector :--ba-----r h4, h5, h6; + +.fo--oo > :--fo-----o { + margin: auto; +} + +:--ba-----r:hover .ba--z { + display: block; +} + +/* comment */ + +article :--heading + p { + margin-top: 0; +} + +@custom-selector :--multiline + .foo, + .bar > .baz; + +:--multiline { + display: block; +} + +/* should works with collapsed custom selectors */ + +@custom-selector :--button button, .button; +@custom-selector :--enter :hover, :focus; + +:--button:--enter {} + +@custom-selector :--any-foobar .foo, .bar; + +:--any-foobar h1 { + margin-top: 16px; +} + +main :--foo + p { + margin-top: 16px; +} + +@custom-selector :--foobar .foo; + +:--foobar { + order: 1000; +} +a, :--foobar { + order: 1001; +} +b,:--foobar { + order: 1002; +} + +@custom-selector :--foobaz .foo.baz; + +:--foobaz { + order: 1010; +} +a, :--foobaz { + order: 1011; +} +b,:--foobaz { + order: 1012; +} + +@custom-selector :--foobazz .foo .baz; + +:--foobazz { + order: 1020; +} +a, :--foobazz { + order: 1021; +} +b,:--foobazz { + order: 1022; +} diff --git a/plugins/postcss-custom-selectors-import-export/test/basic.export-mjs.expect.css b/plugins/postcss-custom-selectors-import-export/test/basic.export-mjs.expect.css new file mode 100644 index 000000000..e3ce21368 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/basic.export-mjs.expect.css @@ -0,0 +1,126 @@ +@custom-selector :--foo .bar, .baz; + +.foo:--foo { + margin-top: 16px; +} + +@custom-selector :--any-heading h1, h2, h3, h4, h5, h6; + +:--any-heading + p {} + +@custom-selector :--foobar .foo, .bar; +@custom-selector :--baz .baz; +@custom-selector :--fizzbuzz .fizz, .buzz; +@custom-selector :--button-types + .btn-primary, + .btn-success, + .btn-info, + .btn-warning, + .btn-danger; + +:--foobar > :--baz {} +:--fizzbuzz > :--foobar {} +:--button-types, :--button-types:active {} + +@custom-selector :--commented-foo + /* comment */ + .foo, + .bar > .baz; + +:--commented-foo + p { + display: block; +} + +@custom-selector :--pseudo ::before, ::after; + +.foo > a:--pseudo img { + display: block; +} + +@custom-selector :--foo .foo; + +:--foo, :--foo.bar { + color: white; +} + +:--foo :--foo:hover { + color: white; +} + +@custom-selector :--fo-----o h1, h2, h3; +@custom-selector :--ba-----r h4, h5, h6; + +.fo--oo > :--fo-----o { + margin: auto; +} + +:--ba-----r:hover .ba--z { + display: block; +} + +/* comment */ + +article :--heading + p { + margin-top: 0; +} + +@custom-selector :--multiline + .foo, + .bar > .baz; + +:--multiline { + display: block; +} + +/* should works with collapsed custom selectors */ + +@custom-selector :--button button, .button; +@custom-selector :--enter :hover, :focus; + +:--button:--enter {} + +@custom-selector :--any-foobar .foo, .bar; + +:--any-foobar h1 { + margin-top: 16px; +} + +main :--foo + p { + margin-top: 16px; +} + +@custom-selector :--foobar .foo; + +:--foobar { + order: 1000; +} +a, :--foobar { + order: 1001; +} +b,:--foobar { + order: 1002; +} + +@custom-selector :--foobaz .foo.baz; + +:--foobaz { + order: 1010; +} +a, :--foobaz { + order: 1011; +} +b,:--foobaz { + order: 1012; +} + +@custom-selector :--foobazz .foo .baz; + +:--foobazz { + order: 1020; +} +a, :--foobazz { + order: 1021; +} +b,:--foobazz { + order: 1022; +} diff --git a/plugins/postcss-custom-selectors-import-export/test/basic.export.expect.css b/plugins/postcss-custom-selectors-import-export/test/basic.export.expect.css new file mode 100644 index 000000000..e3ce21368 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/basic.export.expect.css @@ -0,0 +1,126 @@ +@custom-selector :--foo .bar, .baz; + +.foo:--foo { + margin-top: 16px; +} + +@custom-selector :--any-heading h1, h2, h3, h4, h5, h6; + +:--any-heading + p {} + +@custom-selector :--foobar .foo, .bar; +@custom-selector :--baz .baz; +@custom-selector :--fizzbuzz .fizz, .buzz; +@custom-selector :--button-types + .btn-primary, + .btn-success, + .btn-info, + .btn-warning, + .btn-danger; + +:--foobar > :--baz {} +:--fizzbuzz > :--foobar {} +:--button-types, :--button-types:active {} + +@custom-selector :--commented-foo + /* comment */ + .foo, + .bar > .baz; + +:--commented-foo + p { + display: block; +} + +@custom-selector :--pseudo ::before, ::after; + +.foo > a:--pseudo img { + display: block; +} + +@custom-selector :--foo .foo; + +:--foo, :--foo.bar { + color: white; +} + +:--foo :--foo:hover { + color: white; +} + +@custom-selector :--fo-----o h1, h2, h3; +@custom-selector :--ba-----r h4, h5, h6; + +.fo--oo > :--fo-----o { + margin: auto; +} + +:--ba-----r:hover .ba--z { + display: block; +} + +/* comment */ + +article :--heading + p { + margin-top: 0; +} + +@custom-selector :--multiline + .foo, + .bar > .baz; + +:--multiline { + display: block; +} + +/* should works with collapsed custom selectors */ + +@custom-selector :--button button, .button; +@custom-selector :--enter :hover, :focus; + +:--button:--enter {} + +@custom-selector :--any-foobar .foo, .bar; + +:--any-foobar h1 { + margin-top: 16px; +} + +main :--foo + p { + margin-top: 16px; +} + +@custom-selector :--foobar .foo; + +:--foobar { + order: 1000; +} +a, :--foobar { + order: 1001; +} +b,:--foobar { + order: 1002; +} + +@custom-selector :--foobaz .foo.baz; + +:--foobaz { + order: 1010; +} +a, :--foobaz { + order: 1011; +} +b,:--foobaz { + order: 1012; +} + +@custom-selector :--foobazz .foo .baz; + +:--foobazz { + order: 1020; +} +a, :--foobazz { + order: 1021; +} +b,:--foobazz { + order: 1022; +} diff --git a/plugins/postcss-custom-selectors-import-export/test/empty.css b/plugins/postcss-custom-selectors-import-export/test/empty.css new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/postcss-custom-selectors-import-export/test/examples/.gitkeep b/plugins/postcss-custom-selectors-import-export/test/examples/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/postcss-custom-selectors-import-export/test/export-selectors.css b/plugins/postcss-custom-selectors-import-export/test/export-selectors.css new file mode 100644 index 000000000..b129f1329 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/export-selectors.css @@ -0,0 +1,22 @@ +@custom-selector :--foo .foo; +@custom-selector :--any-heading h1, h2, h3, h4, h5, h6; +@custom-selector :--foobar .foo; +@custom-selector :--baz .baz; +@custom-selector :--fizzbuzz .fizz, .buzz; +@custom-selector :--button-types .btn-primary, + .btn-success, + .btn-info, + .btn-warning, + .btn-danger; +@custom-selector :--commented-foo .foo, + .bar > .baz; +@custom-selector :--pseudo ::before, ::after; +@custom-selector :--fo-----o h1, h2, h3; +@custom-selector :--ba-----r h4, h5, h6; +@custom-selector :--multiline .foo, + .bar > .baz; +@custom-selector :--button button, .button; +@custom-selector :--enter :hover, :focus; +@custom-selector :--any-foobar .foo, .bar; +@custom-selector :--foobaz .foo.baz; +@custom-selector :--foobazz .foo .baz; diff --git a/plugins/postcss-custom-selectors-import-export/test/export-selectors.js b/plugins/postcss-custom-selectors-import-export/test/export-selectors.js new file mode 100644 index 000000000..060a3658a --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/export-selectors.js @@ -0,0 +1,20 @@ +module.exports = { + customSelectors: { + ':--foo': '.foo', + ':--any-heading': 'h1, h2, h3, h4, h5, h6', + ':--foobar': '.foo', + ':--baz': '.baz', + ':--fizzbuzz': '.fizz, .buzz', + ':--button-types': '.btn-primary,\n .btn-success,\n .btn-info,\n .btn-warning,\n .btn-danger', + ':--commented-foo': '.foo,\n .bar > .baz', + ':--pseudo': '::before, ::after', + ':--fo-----o': 'h1, h2, h3', + ':--ba-----r': 'h4, h5, h6', + ':--multiline': '.foo,\n .bar > .baz', + ':--button': 'button, .button', + ':--enter': ':hover, :focus', + ':--any-foobar': '.foo, .bar', + ':--foobaz': '.foo.baz', + ':--foobazz': '.foo .baz' + } +}; diff --git a/plugins/postcss-custom-selectors-import-export/test/export-selectors.json b/plugins/postcss-custom-selectors-import-export/test/export-selectors.json new file mode 100644 index 000000000..c7967c5e9 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/export-selectors.json @@ -0,0 +1,20 @@ +{ + "custom-selectors": { + ":--foo": ".foo", + ":--any-heading": "h1, h2, h3, h4, h5, h6", + ":--foobar": ".foo", + ":--baz": ".baz", + ":--fizzbuzz": ".fizz, .buzz", + ":--button-types": ".btn-primary,\n\t.btn-success,\n\t.btn-info,\n\t.btn-warning,\n\t.btn-danger", + ":--commented-foo": ".foo,\n\t.bar > .baz", + ":--pseudo": "::before, ::after", + ":--fo-----o": "h1, h2, h3", + ":--ba-----r": "h4, h5, h6", + ":--multiline": ".foo,\n\t.bar > .baz", + ":--button": "button, .button", + ":--enter": ":hover, :focus", + ":--any-foobar": ".foo, .bar", + ":--foobaz": ".foo.baz", + ":--foobazz": ".foo .baz" + } +} diff --git a/plugins/postcss-custom-selectors-import-export/test/export-selectors.mjs b/plugins/postcss-custom-selectors-import-export/test/export-selectors.mjs new file mode 100644 index 000000000..a73ac9bc5 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/export-selectors.mjs @@ -0,0 +1,18 @@ +export const customSelectors = { + ':--foo': '.foo', + ':--any-heading': 'h1, h2, h3, h4, h5, h6', + ':--foobar': '.foo', + ':--baz': '.baz', + ':--fizzbuzz': '.fizz, .buzz', + ':--button-types': '.btn-primary,\n .btn-success,\n .btn-info,\n .btn-warning,\n .btn-danger', + ':--commented-foo': '.foo,\n .bar > .baz', + ':--pseudo': '::before, ::after', + ':--fo-----o': 'h1, h2, h3', + ':--ba-----r': 'h4, h5, h6', + ':--multiline': '.foo,\n .bar > .baz', + ':--button': 'button, .button', + ':--enter': ':hover, :focus', + ':--any-foobar': '.foo, .bar', + ':--foobaz': '.foo.baz', + ':--foobazz': '.foo .baz' +}; diff --git a/plugins/postcss-custom-selectors-import-export/test/import-selectors.css b/plugins/postcss-custom-selectors-import-export/test/import-selectors.css new file mode 100644 index 000000000..ef3ea5baf --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/import-selectors.css @@ -0,0 +1,2 @@ +@custom-selector :--heading h1, h2, h3; +@custom-selector :--text :--heading, p; diff --git a/plugins/postcss-custom-selectors-import-export/test/import-selectors.js b/plugins/postcss-custom-selectors-import-export/test/import-selectors.js new file mode 100644 index 000000000..47cdbc57a --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/import-selectors.js @@ -0,0 +1,6 @@ +module.exports = { + customSelectors: { + ':--heading': 'h1, h2, h3', + ':--text': ':--heading, p', + }, +}; diff --git a/plugins/postcss-custom-selectors-import-export/test/import-selectors.json b/plugins/postcss-custom-selectors-import-export/test/import-selectors.json new file mode 100644 index 000000000..5a162f940 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/import-selectors.json @@ -0,0 +1,6 @@ +{ + "custom-selectors": { + ":--heading": "h1, h2, h3", + ":--text": ":--heading, p" + } +} diff --git a/plugins/postcss-custom-selectors-import-export/test/import-selectors.mjs b/plugins/postcss-custom-selectors-import-export/test/import-selectors.mjs new file mode 100644 index 000000000..c989ccae2 --- /dev/null +++ b/plugins/postcss-custom-selectors-import-export/test/import-selectors.mjs @@ -0,0 +1,6 @@ +export default { + customSelectors: { + ':--heading': 'h1, h2, h3', + ':--text': ':--heading, p', + }, +};