From 3644abec6eed2d738cb69620efc74c80073ee3ac Mon Sep 17 00:00:00 2001 From: romainmenke Date: Sat, 29 Jan 2022 15:57:35 +0100 Subject: [PATCH 1/7] postcss-preset-env: filter by implementation status --- plugin-packs/postcss-preset-env/.tape.mjs | 7 + plugin-packs/postcss-preset-env/CHANGELOG.md | 1 + plugin-packs/postcss-preset-env/README.md | 12 + plugin-packs/postcss-preset-env/package.json | 2 +- .../plugins.mjs} | 0 plugin-packs/postcss-preset-env/src/index.js | 214 ++-------- .../lib/feature-is-inserted-or-has-plugin.mjs | 21 + .../src/lib/feature-is-less.mjs | 24 ++ .../src/lib/format-feature.mjs | 67 +++ ...> get-options-for-browsers-by-feature.mjs} | 8 +- .../src/lib/get-transformed-insertions.js | 14 - .../src/lib/get-transformed-insertions.mjs | 46 +++ .../get-unsupported-browsers-by-feature.js | 43 -- .../get-unsupported-browsers-by-feature.mjs | 46 +++ ...on-order.js => ids-by-execution-order.mjs} | 0 .../src/lib/list-features.mjs | 109 +++++ .../src/lib/prepare-features-list.mjs | 21 + .../src/lib/shared-options.mjs | 26 ++ .../postcss-preset-env/src/lib/stage.mjs | 33 ++ .../features-list.mjs} | 4 +- .../src/{lib/log-helper.js => log/helper.mjs} | 1 + .../postcss-preset-env/src/own-keys/keys.mjs | 4 + ...y.js => postcss-system-ui-font-family.mjs} | 0 .../plugin-id-help.mjs} | 3 +- .../plugins-by-id.mjs} | 80 ++-- .../plugins.mjs} | 2 +- .../write-to-exports.mjs} | 0 .../lib/feature-is-inserted-or-has-plugin.mjs | 32 ++ .../src/test/lib/feature-is-less.mjs | 120 ++++++ .../src/test/lib/format-staged-feature.mjs | 114 ++++++ .../get-unsupported-browsers-by-feature.mjs | 180 ++++++++ .../test/lib/list-features/client-side.mjs | 95 +++++ .../test/lib/list-features/cssdb-fixture.mjs | 54 +++ .../test/lib/list-features/list-features.mjs | 5 + .../src/test/lib/list-features/no-options.mjs | 49 +++ .../src/test/lib/list-features/preserve.mjs | 110 +++++ .../src/test/lib/list-features/stage-0.mjs | 63 +++ .../list-features/vendor-implementations.mjs | 50 +++ .../src/test/lib/prepare-features-list.mjs | 138 +++++++ .../postcss-preset-env/src/test/lib/stage.mjs | 90 ++++ .../src/test/lib/transformed-insertions.mjs | 256 ++++++++++++ .../src/test/log/test-logger.mjs | 14 + .../postcss-preset-env/src/test/test.mjs | 9 + .../postcss-preset-env/src/util/clamp.mjs | 3 + .../src/util/int-or-zero.mjs | 8 + .../postcss-preset-env/stryker.conf.json | 19 + .../test/basic.nesting.true.expect.css | 2 +- .../test/basic.vendors-2.expect.css | 386 ++++++++++++++++++ 48 files changed, 2289 insertions(+), 296 deletions(-) rename plugin-packs/postcss-preset-env/src/{lib/plugins-with-client-side.js => client-side-polyfills/plugins.mjs} (100%) create mode 100644 plugin-packs/postcss-preset-env/src/lib/feature-is-inserted-or-has-plugin.mjs create mode 100644 plugin-packs/postcss-preset-env/src/lib/feature-is-less.mjs create mode 100644 plugin-packs/postcss-preset-env/src/lib/format-feature.mjs rename plugin-packs/postcss-preset-env/src/lib/{get-options-for-browsers-by-feature.js => get-options-for-browsers-by-feature.mjs} (91%) delete mode 100644 plugin-packs/postcss-preset-env/src/lib/get-transformed-insertions.js create mode 100644 plugin-packs/postcss-preset-env/src/lib/get-transformed-insertions.mjs delete mode 100644 plugin-packs/postcss-preset-env/src/lib/get-unsupported-browsers-by-feature.js create mode 100644 plugin-packs/postcss-preset-env/src/lib/get-unsupported-browsers-by-feature.mjs rename plugin-packs/postcss-preset-env/src/lib/{ids-by-execution-order.js => ids-by-execution-order.mjs} (100%) create mode 100644 plugin-packs/postcss-preset-env/src/lib/list-features.mjs create mode 100644 plugin-packs/postcss-preset-env/src/lib/prepare-features-list.mjs create mode 100644 plugin-packs/postcss-preset-env/src/lib/shared-options.mjs create mode 100644 plugin-packs/postcss-preset-env/src/lib/stage.mjs rename plugin-packs/postcss-preset-env/src/{lib/log-features-list.js => log/features-list.mjs} (86%) rename plugin-packs/postcss-preset-env/src/{lib/log-helper.js => log/helper.mjs} (99%) create mode 100644 plugin-packs/postcss-preset-env/src/own-keys/keys.mjs rename plugin-packs/postcss-preset-env/src/patch/{postcss-system-ui-font-family.js => postcss-system-ui-font-family.mjs} (100%) rename plugin-packs/postcss-preset-env/src/{lib/plugin-id-help.js => plugins/plugin-id-help.mjs} (99%) rename plugin-packs/postcss-preset-env/src/{lib/plugins-by-id.js => plugins/plugins-by-id.mjs} (69%) rename plugin-packs/postcss-preset-env/src/{lib/plugins-with-side-effects.js => side-effects/plugins.mjs} (96%) rename plugin-packs/postcss-preset-env/src/{lib/write-to-exports.js => side-effects/write-to-exports.mjs} (100%) create mode 100644 plugin-packs/postcss-preset-env/src/test/lib/feature-is-inserted-or-has-plugin.mjs create mode 100644 plugin-packs/postcss-preset-env/src/test/lib/feature-is-less.mjs create mode 100644 plugin-packs/postcss-preset-env/src/test/lib/format-staged-feature.mjs create mode 100644 plugin-packs/postcss-preset-env/src/test/lib/get-unsupported-browsers-by-feature.mjs create mode 100644 plugin-packs/postcss-preset-env/src/test/lib/list-features/client-side.mjs create mode 100644 plugin-packs/postcss-preset-env/src/test/lib/list-features/cssdb-fixture.mjs create mode 100644 plugin-packs/postcss-preset-env/src/test/lib/list-features/list-features.mjs create mode 100644 plugin-packs/postcss-preset-env/src/test/lib/list-features/no-options.mjs create mode 100644 plugin-packs/postcss-preset-env/src/test/lib/list-features/preserve.mjs create mode 100644 plugin-packs/postcss-preset-env/src/test/lib/list-features/stage-0.mjs create mode 100644 plugin-packs/postcss-preset-env/src/test/lib/list-features/vendor-implementations.mjs create mode 100644 plugin-packs/postcss-preset-env/src/test/lib/prepare-features-list.mjs create mode 100644 plugin-packs/postcss-preset-env/src/test/lib/stage.mjs create mode 100644 plugin-packs/postcss-preset-env/src/test/lib/transformed-insertions.mjs create mode 100644 plugin-packs/postcss-preset-env/src/test/log/test-logger.mjs create mode 100644 plugin-packs/postcss-preset-env/src/test/test.mjs create mode 100644 plugin-packs/postcss-preset-env/src/util/clamp.mjs create mode 100644 plugin-packs/postcss-preset-env/src/util/int-or-zero.mjs create mode 100644 plugin-packs/postcss-preset-env/stryker.conf.json create mode 100644 plugin-packs/postcss-preset-env/test/basic.vendors-2.expect.css diff --git a/plugin-packs/postcss-preset-env/.tape.mjs b/plugin-packs/postcss-preset-env/.tape.mjs index c98f4d49e..e45b7be13 100644 --- a/plugin-packs/postcss-preset-env/.tape.mjs +++ b/plugin-packs/postcss-preset-env/.tape.mjs @@ -96,6 +96,13 @@ postcssTape(plugin)({ stage: 0 } }, + 'basic:vendors-2': { + message: 'supports { minimumVendorImplementations: 2, enableClientSidePolyfills: false } usage', + options: { + minimumVendorImplementations: 2, + enableClientSidePolyfills: false + } + }, 'basic:nesting:true': { message: 'supports { stage: false, features: { "nesting-rules": true } } usage', options: { diff --git a/plugin-packs/postcss-preset-env/CHANGELOG.md b/plugin-packs/postcss-preset-env/CHANGELOG.md index 7934ebeca..3d9a5bc36 100644 --- a/plugin-packs/postcss-preset-env/CHANGELOG.md +++ b/plugin-packs/postcss-preset-env/CHANGELOG.md @@ -10,6 +10,7 @@ - Added `@csstools/postcss-font-format-keywords`
[Check the plugin README](https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-font-format-keywords#readme) for usage details. - Added `debug` [option](https://github.com/csstools/postcss-plugins/tree/main/plugin-packs/postcss-preset-env#debug) that enables extra debugging information while processing the CSS. - Added `enableClientSidePolyfills` [option](https://github.com/csstools/postcss-plugins/tree/main/plugin-packs/postcss-preset-env#enableclientsidepolyfills) that allows you to control every single plugin that requires a browser library to fully work. Defaults to `true` so they're enabled by default. +- Added `minimumVendorImplementations` [option](https://github.com/csstools/postcss-plugins/tree/main/plugin-packs/postcss-preset-env#minimumvendorimplementations) that allows you to enable/disable plugins based on their implementation status in browsers. - Fix sourcemaps for `image-set()` function. - Updated `cssdb` to `6.0.2` (major). - Updated `postcss-custom-properties` to `12.1.3` (patch). diff --git a/plugin-packs/postcss-preset-env/README.md b/plugin-packs/postcss-preset-env/README.md index 19518ba6e..2e7ee0f1c 100644 --- a/plugin-packs/postcss-preset-env/README.md +++ b/plugin-packs/postcss-preset-env/README.md @@ -145,6 +145,18 @@ if you intended to exclusively use the [`features`](#features) option. Without any configuration options, [PostCSS Preset Env] enables **Stage 2** features. +### minimumVendorImplementations + +The `minimumVendorImplementations` option determines which CSS features to polyfill, based their implementation status. +This can be used to enable plugins that are available in browsers regardless of the [spec status](#stage). + +```js +postcssPresetEnv({ minimumVendorImplementations: 2 }) +``` + +`minimumVendorImplementations` can be `0` (no vendor has implemented it) through `3` (all major vendors). +A value of `2` is recommended when you want to use only those features that are stable. + ### features The `features` option enables or disables specific polyfills by ID. Passing diff --git a/plugin-packs/postcss-preset-env/package.json b/plugin-packs/postcss-preset-env/package.json index c48b6f155..dc73a1543 100644 --- a/plugin-packs/postcss-preset-env/package.json +++ b/plugin-packs/postcss-preset-env/package.json @@ -20,7 +20,7 @@ "lint": "eslint ./src --ext .js --ext .ts --ext .mjs --no-error-on-unmatched-pattern", "prepublishOnly": "npm run clean && npm run build && npm run test", "stryker": "stryker run --logLevel error", - "test": "node .tape.mjs && npm run test:exports", + "test": "node .tape.mjs && node ./src/test/test.mjs && npm run test:exports", "test:rewrite-expects": "REWRITE_EXPECTS=true node .tape.mjs", "test:exports": "node ./test/_import.mjs && node ./test/_require.cjs" }, diff --git a/plugin-packs/postcss-preset-env/src/lib/plugins-with-client-side.js b/plugin-packs/postcss-preset-env/src/client-side-polyfills/plugins.mjs similarity index 100% rename from plugin-packs/postcss-preset-env/src/lib/plugins-with-client-side.js rename to plugin-packs/postcss-preset-env/src/client-side-polyfills/plugins.mjs diff --git a/plugin-packs/postcss-preset-env/src/index.js b/plugin-packs/postcss-preset-env/src/index.js index 08554bb49..ba8c0d6f8 100644 --- a/plugin-packs/postcss-preset-env/src/index.js +++ b/plugin-packs/postcss-preset-env/src/index.js @@ -1,174 +1,31 @@ import autoprefixer from 'autoprefixer'; -import browserslist from 'browserslist'; import cssdb from 'cssdb'; -import { pluginsById as plugins } from './lib/plugins-by-id'; -import getTransformedInsertions from './lib/get-transformed-insertions'; -import getUnsupportedBrowsersByFeature from './lib/get-unsupported-browsers-by-feature'; -import idsByExecutionOrder from './lib/ids-by-execution-order'; -import writeToExports from './lib/write-to-exports'; -import getOptionsForBrowsersByFeature from './lib/get-options-for-browsers-by-feature'; -import { pluginIdHelp } from './lib/plugin-id-help'; -import { pluginHasSideEffects } from './lib/plugins-with-side-effects'; -import { log, dumpLogs, resetLogger } from './lib/log-helper'; -import { featuresWithClientSide } from './lib/plugins-with-client-side'; -import logFeaturesList from './lib/log-features-list'; - -const DEFAULT_STAGE = 2; -const OUT_OF_RANGE_STAGE = 5; - -const plugin = opts => { +import writeToExports from './side-effects/write-to-exports.mjs'; +import { pluginIdHelp } from './plugins/plugin-id-help.mjs'; +import { dumpLogs, resetLogger } from './log/helper.mjs'; +import logFeaturesList from './log/features-list.mjs'; +import { listFeatures } from './lib/list-features.mjs'; +import { initializeSharedOptions } from './lib/shared-options.mjs'; + +const plugin = (opts) => { // initialize options const options = Object(opts); - const features = Object(options.features); - const enableClientSidePolyfills = 'enableClientSidePolyfills' in options ? options.enableClientSidePolyfills : true; - const featureNamesInOptions = Object.keys(features); - const insertBefore = Object(options.insertBefore); - const insertAfter = Object(options.insertAfter); + const featureNamesInOptions = Object.keys(Object(options.features)); const browsers = options.browsers; - let stage = DEFAULT_STAGE; - - resetLogger(); - - if (typeof options.stage !== 'undefined') { - if (options.stage === false) { - stage = OUT_OF_RANGE_STAGE; - } else { - stage = Math.min(parseInt(options.stage, 10), OUT_OF_RANGE_STAGE) || 0; - } - } - - if (stage === OUT_OF_RANGE_STAGE) { - log('Stage has been disabled, features will be handled via the "features" option.'); - } else { - log(`Using features from Stage ${stage}`); - } - - const autoprefixerOptions = options.autoprefixer; - const sharedOpts = initializeSharedOpts(options); - const stagedAutoprefixer = autoprefixerOptions === false - ? () => {} - : autoprefixer(Object.assign({ overrideBrowserslist: browsers }, autoprefixerOptions)); - - // polyfillable features (those with an available postcss plugin) - const polyfillableFeatures = cssdb.concat( - // additional features to be inserted before cssdb features - getTransformedInsertions(insertBefore, 'insertBefore'), - // additional features to be inserted after cssdb features - getTransformedInsertions(insertAfter, 'insertAfter'), - ).filter( - // inserted features or features with an available postcss plugin - feature => feature.insertBefore || feature.id in plugins, - ).sort( - // features sorted by execution order and then insertion order - (a, b) => idsByExecutionOrder.indexOf(a.id) - idsByExecutionOrder.indexOf(b.id) || (a.insertBefore ? -1 : b.insertBefore ? 1 : 0) || (a.insertAfter ? 1 : b.insertAfter ? -1 : 0), - ).map( - // polyfillable features as an object - feature => { - // target browsers for the polyfill - const unsupportedBrowsers = getUnsupportedBrowsersByFeature(feature); - - return feature.insertBefore || feature.insertAfter ? { - browsers: unsupportedBrowsers, - plugin: feature.plugin, - id: `${feature.insertBefore ? 'before' : 'after'}-${feature.id}`, - stage: OUT_OF_RANGE_STAGE + 1, // So they always match - } : { - browsers: unsupportedBrowsers, - plugin: plugins[feature.id], - id: feature.id, - stage: feature.stage, - }; - }, - ); - - // staged features (those at or above the selected stage) - const stagedFeatures = polyfillableFeatures.filter(feature => { - const isAllowedStage = feature.stage >= stage; - const isAllowedByType = enableClientSidePolyfills || !featuresWithClientSide.includes(feature.id); - const isDisabled = features[feature.id] === false; - const isAllowedFeature = features[feature.id] ? features[feature.id] : isAllowedStage && isAllowedByType; - - if (isDisabled) { - log(` ${feature.id} has been disabled by options`); - } else if (!isAllowedStage) { - if (isAllowedFeature) { - log(` ${feature.id} has been enabled by options`); - } else { - log(` ${feature.id} with stage ${feature.stage} has been disabled`); - } - } else if (!isAllowedByType) { - log(` ${feature.id} has been disabled by "enableClientSidePolyfills: false".`); - } - - return isAllowedFeature; - }).map( - feature => { - let options; - let plugin; - - options = getOptionsForBrowsersByFeature(browsers, feature, cssdb); - - if (features[feature.id] === true) { - // if the plugin is enabled - options = sharedOpts ? Object.assign({}, options, sharedOpts) : undefined; - } else { - options = sharedOpts - // if the plugin has shared options and individual options - ? Object.assign({}, options, sharedOpts, features[feature.id]) - // if the plugin has individual options - : Object.assign({}, options, features[feature.id]); - } - - if (feature.plugin.postcss) { - plugin = feature.plugin(options); - } else { - plugin = feature.plugin; - } - - return { - browsers: feature.browsers, - plugin: plugin, - pluginOptions: options, - id: feature.id, - }; - }, - ); + const sharedOptions = initializeSharedOptions(options); - // browsers supported by the configuration - const supportedBrowsers = browserslist(browsers, { ignoreUnknownVersions: true }); - - // - features supported by the stage - // - features with `true` or with options - // - required for the browsers - // - having "importFrom" or "exportTo" options - const supportedFeatures = stagedFeatures.filter((feature) => { - if (feature.id in features) { - return features[feature.id]; - } - - if (pluginHasSideEffects(feature)) { - return true; - } - - const unsupportedBrowsers = browserslist(feature.browsers, { - ignoreUnknownVersions: true, - }); - - const needsPolyfill = supportedBrowsers.some(supportedBrowser => { - return unsupportedBrowsers.some(unsupportedBrowser => unsupportedBrowser === supportedBrowser); - }); - - if (!needsPolyfill) { - log(`${feature.id} disabled due to browser support`); - } - - return needsPolyfill; + const features = listFeatures(cssdb, options, sharedOptions); + const plugins = features.map((feature) => { + return feature.plugin; }); - const usedPlugins = supportedFeatures.map(feature => feature.plugin); - usedPlugins.push(stagedAutoprefixer); + if (options.autoprefixer !== false) { + plugins.push( + autoprefixer(Object.assign({ overrideBrowserslist: browsers }, options.autoprefixer)), + ); + } - logFeaturesList(supportedFeatures, options); + logFeaturesList(features, options); const internalPlugin = () => { return { @@ -180,8 +37,11 @@ const plugin = opts => { dumpLogs(result); } + // Always reset the logger, if when debug is false + resetLogger(); + if (options.exportTo) { - writeToExports(sharedOpts.exportTo, opts.exportTo); + writeToExports(sharedOptions.exportTo, opts.exportTo); } }, }; @@ -191,36 +51,10 @@ const plugin = opts => { return { postcssPlugin: 'postcss-preset-env', - plugins: [...usedPlugins, internalPlugin()], + plugins: [...plugins, internalPlugin()], }; }; -const initializeSharedOpts = opts => { - if ('importFrom' in opts || 'exportTo' in opts || 'preserve' in opts) { - const sharedOpts = {}; - - if ('importFrom' in opts) { - sharedOpts.importFrom = opts.importFrom; - } - - if ('exportTo' in opts) { - sharedOpts.exportTo = { - customMedia: {}, - customProperties: {}, - customSelectors: {}, - }; - } - - if ('preserve' in opts) { - sharedOpts.preserve = opts.preserve; - } - - return sharedOpts; - } - - return false; -}; - plugin.postcss = true; export default plugin; diff --git a/plugin-packs/postcss-preset-env/src/lib/feature-is-inserted-or-has-plugin.mjs b/plugin-packs/postcss-preset-env/src/lib/feature-is-inserted-or-has-plugin.mjs new file mode 100644 index 000000000..b1d567d49 --- /dev/null +++ b/plugin-packs/postcss-preset-env/src/lib/feature-is-inserted-or-has-plugin.mjs @@ -0,0 +1,21 @@ +import { pluginsById } from '../plugins/plugins-by-id.mjs'; +import { insertAfterKey, insertBeforeKey } from '../own-keys/keys.mjs'; + +export function featureIsInsertedOrHasAPlugin(feature) { + if (feature[insertBeforeKey]) { + // inserted; + return true; + } + + if (feature[insertAfterKey]) { + // inserted; + return true; + } + + if (pluginsById.has(feature.id)) { + // plugin exists in postcss-preset-env + return true; + } + + return false; +} diff --git a/plugin-packs/postcss-preset-env/src/lib/feature-is-less.mjs b/plugin-packs/postcss-preset-env/src/lib/feature-is-less.mjs new file mode 100644 index 000000000..20acf8ada --- /dev/null +++ b/plugin-packs/postcss-preset-env/src/lib/feature-is-less.mjs @@ -0,0 +1,24 @@ +import idsByExecutionOrder from './ids-by-execution-order.mjs'; +import { clamp } from '../util/clamp.mjs'; +import { insertAfterKey, insertBeforeKey, insertOrderKey } from '../own-keys/keys.mjs'; + +// features sorted by execution order and then insertion order +export function featureIsLess(a, b) { + if (a.id === b.id) { + if ((a[insertBeforeKey] && b[insertBeforeKey]) || (a[insertAfterKey] && b[insertAfterKey])) { + return clamp(-1, a[insertOrderKey] - b[insertOrderKey], 1); + } + + if (a[insertBeforeKey] || b[insertAfterKey]) { + return -1; + } + + if (a[insertAfterKey] || b[insertBeforeKey]) { + return 1; + } + + return 0; + } + + return clamp(-1, idsByExecutionOrder.indexOf(a.id) - idsByExecutionOrder.indexOf(b.id), 1); +} diff --git a/plugin-packs/postcss-preset-env/src/lib/format-feature.mjs b/plugin-packs/postcss-preset-env/src/lib/format-feature.mjs new file mode 100644 index 000000000..a3acf5ead --- /dev/null +++ b/plugin-packs/postcss-preset-env/src/lib/format-feature.mjs @@ -0,0 +1,67 @@ +import getOptionsForBrowsersByFeature from './get-options-for-browsers-by-feature.mjs'; +import getUnsupportedBrowsersByFeature from './get-unsupported-browsers-by-feature.mjs'; +import { OUT_OF_RANGE_STAGE } from './stage.mjs'; +import { insertAfterKey, insertBeforeKey, pluginKey } from '../own-keys/keys.mjs'; +import { pluginsById } from '../plugins/plugins-by-id.mjs'; + +export function formatPolyfillableFeature(feature) { + // target browsers for the polyfill + const unsupportedBrowsers = getUnsupportedBrowsersByFeature(feature); + if (feature[insertBeforeKey] || feature[insertAfterKey]) { + let id = feature.id; + if (feature.insertBefore) { + id = `before-${id}`; + } else { + id = `after-${id}`; + } + + return { + browsers: unsupportedBrowsers, + vendors_implementations: feature.vendors_implementations, + plugin: feature[pluginKey], + id: id, + stage: OUT_OF_RANGE_STAGE + 1, // So they always match + }; + } + + return { + browsers: unsupportedBrowsers, + vendors_implementations: feature.vendors_implementations, + plugin: pluginsById.get(feature.id), + id: feature.id, + stage: feature.stage, + }; +} + +export function formatStagedFeature(cssdbList, browsers, features, feature, sharedOptions) { + let options; + let plugin; + + options = getOptionsForBrowsersByFeature(browsers, feature, cssdbList); + + if (features[feature.id] === true) { + if (sharedOptions) { + options = Object.assign({}, options, sharedOptions); + } + } else { + if (sharedOptions) { + options = Object.assign({}, options, sharedOptions, features[feature.id]); + } else { + options = Object.assign({}, options, features[feature.id]); + } + } + + if (feature.plugin.postcss) { + plugin = feature.plugin(options); + } else { + plugin = feature.plugin; + } + + return { + browsers: feature.browsers, + vendors_implementations: feature.vendors_implementations, + plugin: plugin, + pluginOptions: options, + id: feature.id, + }; +} diff --git a/plugin-packs/postcss-preset-env/src/lib/get-options-for-browsers-by-feature.js b/plugin-packs/postcss-preset-env/src/lib/get-options-for-browsers-by-feature.mjs similarity index 91% rename from plugin-packs/postcss-preset-env/src/lib/get-options-for-browsers-by-feature.js rename to plugin-packs/postcss-preset-env/src/lib/get-options-for-browsers-by-feature.mjs index bc7a59a82..91a903cd3 100644 --- a/plugin-packs/postcss-preset-env/src/lib/get-options-for-browsers-by-feature.js +++ b/plugin-packs/postcss-preset-env/src/lib/get-options-for-browsers-by-feature.mjs @@ -1,9 +1,9 @@ import browserslist from 'browserslist'; -import getUnsupportedBrowsersByFeature from './get-unsupported-browsers-by-feature'; -import { log } from './log-helper'; +import getUnsupportedBrowsersByFeature from './get-unsupported-browsers-by-feature.mjs'; +import { log } from '../log/helper.mjs'; // add extra options for certain browsers by feature -export default function getOptionsForBrowsersByFeature(browsers, feature, cssdb) { +export default function getOptionsForBrowsersByFeature(browsers, feature, cssdbList) { const supportedBrowsers = browserslist(browsers, { ignoreUnknownVersions: true }); switch (feature.id) { @@ -18,7 +18,7 @@ export default function getOptionsForBrowsersByFeature(browsers, feature, cssdb) // Nesting rules can transform selectors to use :is pseudo. // This is more spec compliant but it's not supported by all browsers. // If we can't use :is pseudo according to preset-env options, we add an extra option to avoid :is pseudo. - const feature = cssdb.find(feature => feature.id === 'is-pseudo-class'); + const feature = cssdbList.find(feature => feature.id === 'is-pseudo-class'); if (needsOptionFor(feature, supportedBrowsers)) { log('Disabling :is on "nesting-rules" due to lack of browser support.'); diff --git a/plugin-packs/postcss-preset-env/src/lib/get-transformed-insertions.js b/plugin-packs/postcss-preset-env/src/lib/get-transformed-insertions.js deleted file mode 100644 index 7080d3e99..000000000 --- a/plugin-packs/postcss-preset-env/src/lib/get-transformed-insertions.js +++ /dev/null @@ -1,14 +0,0 @@ -// return a list of features to be inserted before or after cssdb features -export default function getTransformedInsertions(insertions, placement) { - return Object.keys(insertions).map( - id => [].concat(insertions[id]).map( - plugin => ({ - [placement]: true, - plugin, - id, - }), - ), - ).reduce( - (array, feature) => array.concat(feature), [], - ); -} diff --git a/plugin-packs/postcss-preset-env/src/lib/get-transformed-insertions.mjs b/plugin-packs/postcss-preset-env/src/lib/get-transformed-insertions.mjs new file mode 100644 index 000000000..7008595c1 --- /dev/null +++ b/plugin-packs/postcss-preset-env/src/lib/get-transformed-insertions.mjs @@ -0,0 +1,46 @@ +import { insertAfterKey, insertBeforeKey, insertOrderKey, pluginKey } from '../own-keys/keys.mjs'; + +// return a list of features to be inserted before or after cssdb features +export default function getTransformedInsertions(cssdbList, insertions, placement) { + if (placement !== 'insertBefore' && placement !== 'insertAfter') { + return []; + } + + const insertPlacementKey = placement === 'insertBefore' ? insertBeforeKey : insertAfterKey; + + const out = []; + + for (const featureId in insertions) { + if (!Object.hasOwnProperty.call(insertions, featureId)) { + continue; + } + + const cssdbFeature = cssdbList.find((feature) => { + return feature.id === featureId; + }); + + if (!cssdbFeature) { + continue; + } + + let pluginsToInsert = insertions[featureId]; + if (!Array.isArray(pluginsToInsert)) { + pluginsToInsert = [pluginsToInsert]; + } + + for (let i = 0; i < pluginsToInsert.length; i++) { + out.push({ + id: featureId, + // TODO : verify current behavior. + // At the moment it is unclear if inserted plugins have logic around browser lists. + // Adding this ensures they do, which might be a breaking change. + ...JSON.parse(JSON.stringify(cssdbFeature)), // deep copy + [pluginKey]: pluginsToInsert[i], + [insertOrderKey]: i, + [insertPlacementKey]: true, + }); + } + } + + return out; +} diff --git a/plugin-packs/postcss-preset-env/src/lib/get-unsupported-browsers-by-feature.js b/plugin-packs/postcss-preset-env/src/lib/get-unsupported-browsers-by-feature.js deleted file mode 100644 index 13584081c..000000000 --- a/plugin-packs/postcss-preset-env/src/lib/get-unsupported-browsers-by-feature.js +++ /dev/null @@ -1,43 +0,0 @@ -const browsers = [ - 'ie', - 'edge', - 'firefox', - 'chrome', - 'safari', - 'opera', - 'ios_saf', - 'android', - 'op_mob', - 'and_chr', - 'and_ff', - 'and_uc', - 'samsung', - 'and_qq', - 'baidu', - 'kaios', -]; - -// return a list of browsers that do not support the feature -export default function getUnsupportedBrowsersByFeature(feature) { - // if feature support can be determined - if (feature.browser_support) { - const query = []; - - browsers.forEach(browser => { - const browserSupport = feature.browser_support[browser]; - // If the version is something like TP we can't do < TP - const isValid = typeof browserSupport === 'string' && browserSupport.match(/^[0-9|.]+$/) !== null; - - if (isValid) { - query.push(`${browser} < ${feature.browser_support[browser]}`); - } else { - query.push(`${browser} >= 1`); - } - }); - - return query; - } else { - // otherwise, return that the feature does not work in any browser - return [ '> 0%' ]; - } -} diff --git a/plugin-packs/postcss-preset-env/src/lib/get-unsupported-browsers-by-feature.mjs b/plugin-packs/postcss-preset-env/src/lib/get-unsupported-browsers-by-feature.mjs new file mode 100644 index 000000000..3936a19bc --- /dev/null +++ b/plugin-packs/postcss-preset-env/src/lib/get-unsupported-browsers-by-feature.mjs @@ -0,0 +1,46 @@ +const browsers = [ + 'ie', + 'edge', + 'firefox', + 'chrome', + 'safari', + 'opera', + 'ios_saf', + 'android', + 'op_mob', + 'and_chr', + 'and_ff', + 'and_uc', + 'samsung', + 'and_qq', + 'baidu', + 'kaios', +]; + +// return a list of browsers that do not support the feature +export default function getUnsupportedBrowsersByFeature(feature) { + if (!feature) { + return []; + } + + if (!('browser_support' in feature)) { + // the feature does not work in any browser (yet) + return [ '> 0%' ]; + } + + const query = []; + + browsers.forEach(browser => { + const browserSupport = feature.browser_support[browser]; + // Browser support info must be a valid version. + // TP or Beta versions are considered unsupported. + const isValid = (typeof browserSupport === 'string') && (/^[0-9|.]+$/.test(browserSupport)); + if (isValid) { + query.push(`${browser} < ${feature.browser_support[browser]}`); + } else { + query.push(`${browser} >= 1`); + } + }); + + return query; +} diff --git a/plugin-packs/postcss-preset-env/src/lib/ids-by-execution-order.js b/plugin-packs/postcss-preset-env/src/lib/ids-by-execution-order.mjs similarity index 100% rename from plugin-packs/postcss-preset-env/src/lib/ids-by-execution-order.js rename to plugin-packs/postcss-preset-env/src/lib/ids-by-execution-order.mjs diff --git a/plugin-packs/postcss-preset-env/src/lib/list-features.mjs b/plugin-packs/postcss-preset-env/src/lib/list-features.mjs new file mode 100644 index 000000000..f015474c1 --- /dev/null +++ b/plugin-packs/postcss-preset-env/src/lib/list-features.mjs @@ -0,0 +1,109 @@ +import browserslist from 'browserslist'; +import { pluginHasSideEffects } from '../side-effects/plugins.mjs'; +import { log, resetLogger } from '../log/helper.mjs'; +import { featuresWithClientSide } from '../client-side-polyfills/plugins.mjs'; +import { stageFromOptions } from './stage.mjs'; +import { prepareFeaturesList } from './prepare-features-list.mjs'; +import { formatPolyfillableFeature, formatStagedFeature } from './format-feature.mjs'; +import { clamp } from '../util/clamp.mjs'; +import { intOrZero } from '../util/int-or-zero.mjs'; + +export function listFeatures(cssdbList, options, sharedOptions) { + // initialize options + const features = Object(options.features); + const enableClientSidePolyfills = 'enableClientSidePolyfills' in options ? options.enableClientSidePolyfills : true; + const insertBefore = Object(options.insertBefore); + const insertAfter = Object(options.insertAfter); + const browsers = options.browsers; + + const minimumVendorImplementations = clamp( + 0, // 0 equals not setting this options + intOrZero(options.minimumVendorImplementations), + 3, // There are currently only 3 vendors that are tracked (Blink, Webkit, Gecko) + ); + + resetLogger(); + + if (minimumVendorImplementations > 0) { + log(`Using features with ${minimumVendorImplementations} or more vendor implementations`); + } + + const stage = stageFromOptions(options); + + // polyfillable features (those with an available postcss plugin) + const polyfillableFeatures = prepareFeaturesList(cssdbList, insertBefore, insertAfter).map((feature) => { + return formatPolyfillableFeature(feature); + }); + + // vendor implemented features (those implemented by at least N vendors) + const vendorImplementedFeatures = polyfillableFeatures.filter((feature) => { + if (minimumVendorImplementations === 0) { + return true; + } + + if ( minimumVendorImplementations < feature.vendors_implementations ) { + return true; + } + + log(` ${feature.id} with ${feature.vendors_implementations} vendor implementations has been disabled`); + return false; + }); + + // staged features (those at or above the selected stage) + const stagedFeatures = vendorImplementedFeatures.filter((feature) => { + // TODO : this filter needs to be split up. + const isAllowedStage = feature.stage >= stage; + const isAllowedByType = enableClientSidePolyfills || !featuresWithClientSide.includes(feature.id); + const isDisabled = features[feature.id] === false; + const isAllowedFeature = features[feature.id] ? features[feature.id] : isAllowedStage && isAllowedByType; + + if (isDisabled) { + log(` ${feature.id} has been disabled by options`); + } else if (!isAllowedStage) { + if (isAllowedFeature) { + log(` ${feature.id} has been enabled by options`); + } else { + log(` ${feature.id} with stage ${feature.stage} has been disabled`); + } + } else if (!isAllowedByType) { + log(` ${feature.id} has been disabled by "enableClientSidePolyfills: false".`); + } + + return isAllowedFeature; + }).map((feature) => { + return formatStagedFeature(cssdbList, browsers, features, feature, sharedOptions); + }); + + // browsers supported by the configuration + const supportedBrowsers = browserslist(browsers, { ignoreUnknownVersions: true }); + + // - features supported by the stage + // - features with `true` or with options + // - required for the browsers + // - having "importFrom" or "exportTo" options + const supportedFeatures = stagedFeatures.filter((feature) => { + if (feature.id in features) { + return features[feature.id]; + } + + if (pluginHasSideEffects(feature)) { + return true; + } + + const unsupportedBrowsers = browserslist(feature.browsers, { + ignoreUnknownVersions: true, + }); + + const needsPolyfill = supportedBrowsers.some(supportedBrowser => { + return unsupportedBrowsers.some(unsupportedBrowser => unsupportedBrowser === supportedBrowser); + }); + + if (!needsPolyfill) { + log(`${feature.id} disabled due to browser support`); + } + + return needsPolyfill; + }); + + return supportedFeatures; +} diff --git a/plugin-packs/postcss-preset-env/src/lib/prepare-features-list.mjs b/plugin-packs/postcss-preset-env/src/lib/prepare-features-list.mjs new file mode 100644 index 000000000..868a23379 --- /dev/null +++ b/plugin-packs/postcss-preset-env/src/lib/prepare-features-list.mjs @@ -0,0 +1,21 @@ +import getTransformedInsertions from './get-transformed-insertions.mjs'; +import { featureIsLess } from './feature-is-less.mjs'; +import { featureIsInsertedOrHasAPlugin } from './feature-is-inserted-or-has-plugin.mjs'; + +export function prepareFeaturesList(cssdbList, insertBefore, insertAfter) { + // All features from cssdb + inserted features features + const allKnownFeatures = cssdbList.concat( + // additional features to be inserted before cssdb features + getTransformedInsertions(cssdbList, insertBefore, 'insertBefore'), + // additional features to be inserted after cssdb features + getTransformedInsertions(cssdbList, insertAfter, 'insertAfter'), + ); + + // polyfillable features (those with an available postcss plugin) + return allKnownFeatures.filter((feature) => { + return featureIsInsertedOrHasAPlugin(feature); + }).sort((a, b) => { + // features sorted by execution order and then insertion order + return featureIsLess(a, b); + }); +} diff --git a/plugin-packs/postcss-preset-env/src/lib/shared-options.mjs b/plugin-packs/postcss-preset-env/src/lib/shared-options.mjs new file mode 100644 index 000000000..44bcd9111 --- /dev/null +++ b/plugin-packs/postcss-preset-env/src/lib/shared-options.mjs @@ -0,0 +1,26 @@ + +export function initializeSharedOptions(opts) { + if ('importFrom' in opts || 'exportTo' in opts || 'preserve' in opts) { + const sharedOptions = {}; + + if ('importFrom' in opts) { + sharedOptions.importFrom = opts.importFrom; + } + + if ('exportTo' in opts) { + sharedOptions.exportTo = { + customMedia: {}, + customProperties: {}, + customSelectors: {}, + }; + } + + if ('preserve' in opts) { + sharedOptions.preserve = opts.preserve; + } + + return sharedOptions; + } + + return false; +} diff --git a/plugin-packs/postcss-preset-env/src/lib/stage.mjs b/plugin-packs/postcss-preset-env/src/lib/stage.mjs new file mode 100644 index 000000000..e35faed7f --- /dev/null +++ b/plugin-packs/postcss-preset-env/src/lib/stage.mjs @@ -0,0 +1,33 @@ +import { log } from '../log/helper.mjs'; +import { clamp } from '../util/clamp.mjs'; + +export const DEFAULT_STAGE = 2; +export const OUT_OF_RANGE_STAGE = 5; + +export function stageFromOptions(options) { + let stage = DEFAULT_STAGE; + + if (typeof options.stage === 'undefined') { + log(`Using features from Stage ${stage} (default)`); + return stage; + } + + if (options.stage === false) { + stage = OUT_OF_RANGE_STAGE; + } else { + let parsedStage = parseInt(options.stage, 10); + if (Number.isNaN(parsedStage)) { + parsedStage = 0; + } + + stage = clamp(0, parsedStage, OUT_OF_RANGE_STAGE); + } + + if (stage === OUT_OF_RANGE_STAGE) { + log('Stage has been disabled, features will be handled via the "features" option.'); + } else { + log(`Using features from Stage ${stage}`); + } + + return stage; +} diff --git a/plugin-packs/postcss-preset-env/src/lib/log-features-list.js b/plugin-packs/postcss-preset-env/src/log/features-list.mjs similarity index 86% rename from plugin-packs/postcss-preset-env/src/lib/log-features-list.js rename to plugin-packs/postcss-preset-env/src/log/features-list.mjs index 7db2a880c..b603d85a9 100644 --- a/plugin-packs/postcss-preset-env/src/lib/log-features-list.js +++ b/plugin-packs/postcss-preset-env/src/log/features-list.mjs @@ -1,5 +1,5 @@ -import { log } from './log-helper'; -import { clientSideDocumentation } from './plugins-with-client-side'; +import { log } from './helper.mjs'; +import { clientSideDocumentation } from '../client-side-polyfills/plugins.mjs'; export default function logFeaturesList(supportedFeatures, options) { if (options.debug) { diff --git a/plugin-packs/postcss-preset-env/src/lib/log-helper.js b/plugin-packs/postcss-preset-env/src/log/helper.mjs similarity index 99% rename from plugin-packs/postcss-preset-env/src/lib/log-helper.js rename to plugin-packs/postcss-preset-env/src/log/helper.mjs index f3d6c7e21..858687717 100644 --- a/plugin-packs/postcss-preset-env/src/lib/log-helper.js +++ b/plugin-packs/postcss-preset-env/src/log/helper.mjs @@ -15,3 +15,4 @@ export function dumpLogs(result) { resetLogger(); } + diff --git a/plugin-packs/postcss-preset-env/src/own-keys/keys.mjs b/plugin-packs/postcss-preset-env/src/own-keys/keys.mjs new file mode 100644 index 000000000..315caf5ad --- /dev/null +++ b/plugin-packs/postcss-preset-env/src/own-keys/keys.mjs @@ -0,0 +1,4 @@ +export const insertBeforeKey = Symbol('insertBefore'); +export const insertAfterKey = Symbol('insertAfter'); +export const insertOrderKey = Symbol('insertOrder'); +export const pluginKey = Symbol('plugin'); diff --git a/plugin-packs/postcss-preset-env/src/patch/postcss-system-ui-font-family.js b/plugin-packs/postcss-preset-env/src/patch/postcss-system-ui-font-family.mjs similarity index 100% rename from plugin-packs/postcss-preset-env/src/patch/postcss-system-ui-font-family.js rename to plugin-packs/postcss-preset-env/src/patch/postcss-system-ui-font-family.mjs diff --git a/plugin-packs/postcss-preset-env/src/lib/plugin-id-help.js b/plugin-packs/postcss-preset-env/src/plugins/plugin-id-help.mjs similarity index 99% rename from plugin-packs/postcss-preset-env/src/lib/plugin-id-help.js rename to plugin-packs/postcss-preset-env/src/plugins/plugin-id-help.mjs index 32e2081ce..0c6ab71d1 100644 --- a/plugin-packs/postcss-preset-env/src/lib/plugin-id-help.js +++ b/plugin-packs/postcss-preset-env/src/plugins/plugin-id-help.mjs @@ -1,8 +1,9 @@ -import { idsToPackageNames, packageNamesToIds } from './plugins-by-id'; +import { idsToPackageNames, packageNamesToIds } from './plugins-by-id.mjs'; export function pluginIdHelp(featureNamesInOptions, root, result) { const featureNames = Object.keys(idsToPackageNames); const packageNames = Object.keys(packageNamesToIds); + featureNamesInOptions.forEach((featureName) => { if (featureNames.includes(featureName)) { return; diff --git a/plugin-packs/postcss-preset-env/src/lib/plugins-by-id.js b/plugin-packs/postcss-preset-env/src/plugins/plugins-by-id.mjs similarity index 69% rename from plugin-packs/postcss-preset-env/src/lib/plugins-by-id.js rename to plugin-packs/postcss-preset-env/src/plugins/plugins-by-id.mjs index 204d695aa..a9e1d57d7 100644 --- a/plugin-packs/postcss-preset-env/src/lib/plugins-by-id.js +++ b/plugin-packs/postcss-preset-env/src/plugins/plugins-by-id.mjs @@ -12,7 +12,7 @@ import postcssEnvFunction from 'postcss-env-function'; import postcssFocusVisible from 'postcss-focus-visible'; import postcssFocusWithin from 'postcss-focus-within'; import postcssFontVariant from 'postcss-font-variant'; -import postcssFontFamilySystemUi from '../patch/postcss-system-ui-font-family'; +import postcssFontFamilySystemUi from '../patch/postcss-system-ui-font-family.mjs'; import postcssGapProperties from 'postcss-gap-properties'; import postcssHasPseudo from 'css-has-pseudo'; import postcssImageSetPolyfill from 'postcss-image-set-function'; @@ -84,41 +84,43 @@ export const idsToPackageNames = (() => { })(); // postcss plugins ordered by id -export const pluginsById = { - 'all-property': postcssInitial, - 'any-link-pseudo-class': postcssPseudoClassAnyLink, - 'blank-pseudo-class': postcssBlankPseudo, - 'break-properties': postcssPageBreak, - 'case-insensitive-attributes': postcssAttributeCaseInsensitive, - 'clamp': postcssClamp, - 'color-functional-notation': postcssColorFunctionalNotation, - 'custom-media-queries': postcssCustomMedia, - 'custom-properties': postcssCustomProperties, - 'custom-selectors': postcssCustomSelectors, - 'dir-pseudo-class': postcssDirPseudoClass, - 'display-two-values': postcssNormalizeDisplayValues, - 'double-position-gradients': postcssDoublePositionGradients, - 'environment-variables': postcssEnvFunction, - 'focus-visible-pseudo-class': postcssFocusVisible, - 'focus-within-pseudo-class': postcssFocusWithin, - 'font-format-keywords': postcssFontFormatKeywords, - 'font-variant-property': postcssFontVariant, - 'gap-properties': postcssGapProperties, - 'hwb-function': postcssHWBFunction, - 'has-pseudo-class': postcssHasPseudo, - 'hexadecimal-alpha-notation': postcssColorHexAlpha, - 'image-set-function': postcssImageSetPolyfill, - 'is-pseudo-class': postcssIsPseudoClass, - 'lab-function': postcssLabFunction, - 'logical-properties-and-values': postcssLogical, - 'media-query-ranges': postcssMediaMinmax, - 'nesting-rules': postcssNesting, - 'not-pseudo-class': postcssSelectorNot, - 'opacity-percentage': postcssOpacityPercentage, - 'overflow-property': postcssOverflowShorthand, - 'overflow-wrap-property': postcssReplaceOverflowWrap, - 'place-properties': postcssPlace, - 'prefers-color-scheme-query': postcssPrefersColorScheme, - 'rebeccapurple-color': postcssColorRebeccapurple, - 'system-ui-font-family': postcssFontFamilySystemUi, -}; +export const pluginsById = new Map( + [ + ['all-property', postcssInitial], + ['any-link-pseudo-class', postcssPseudoClassAnyLink], + ['blank-pseudo-class', postcssBlankPseudo], + ['break-properties', postcssPageBreak], + ['case-insensitive-attributes', postcssAttributeCaseInsensitive], + ['clamp', postcssClamp], + ['color-functional-notation', postcssColorFunctionalNotation], + ['custom-media-queries', postcssCustomMedia], + ['custom-properties', postcssCustomProperties], + ['custom-selectors', postcssCustomSelectors], + ['dir-pseudo-class', postcssDirPseudoClass], + ['display-two-values', postcssNormalizeDisplayValues], + ['double-position-gradients', postcssDoublePositionGradients], + ['environment-variables', postcssEnvFunction], + ['focus-visible-pseudo-class', postcssFocusVisible], + ['focus-within-pseudo-class', postcssFocusWithin], + ['font-format-keywords', postcssFontFormatKeywords], + ['font-variant-property', postcssFontVariant], + ['gap-properties', postcssGapProperties], + ['hwb-function', postcssHWBFunction], + ['has-pseudo-class', postcssHasPseudo], + ['hexadecimal-alpha-notation', postcssColorHexAlpha], + ['image-set-function', postcssImageSetPolyfill], + ['is-pseudo-class', postcssIsPseudoClass], + ['lab-function', postcssLabFunction], + ['logical-properties-and-values', postcssLogical], + ['media-query-ranges', postcssMediaMinmax], + ['nesting-rules', postcssNesting], + ['not-pseudo-class', postcssSelectorNot], + ['opacity-percentage', postcssOpacityPercentage], + ['overflow-property', postcssOverflowShorthand], + ['overflow-wrap-property', postcssReplaceOverflowWrap], + ['place-properties', postcssPlace], + ['prefers-color-scheme-query', postcssPrefersColorScheme], + ['rebeccapurple-color', postcssColorRebeccapurple], + ['system-ui-font-family', postcssFontFamilySystemUi], + ], +); diff --git a/plugin-packs/postcss-preset-env/src/lib/plugins-with-side-effects.js b/plugin-packs/postcss-preset-env/src/side-effects/plugins.mjs similarity index 96% rename from plugin-packs/postcss-preset-env/src/lib/plugins-with-side-effects.js rename to plugin-packs/postcss-preset-env/src/side-effects/plugins.mjs index 8ce135856..e788390dc 100644 --- a/plugin-packs/postcss-preset-env/src/lib/plugins-with-side-effects.js +++ b/plugin-packs/postcss-preset-env/src/side-effects/plugins.mjs @@ -1,6 +1,6 @@ // Some plugins have side effects that go beyond the scope of preset-env. // These plugins always need to run. -// IMPORTANT: this should be removed and cleanup in a next major version. +// IMPORTANT: this should be removed and cleaned up in a next major version. export function pluginHasSideEffects(feature) { if ('importFrom' in Object(feature.pluginOptions)) { switch (feature.id) { diff --git a/plugin-packs/postcss-preset-env/src/lib/write-to-exports.js b/plugin-packs/postcss-preset-env/src/side-effects/write-to-exports.mjs similarity index 100% rename from plugin-packs/postcss-preset-env/src/lib/write-to-exports.js rename to plugin-packs/postcss-preset-env/src/side-effects/write-to-exports.mjs diff --git a/plugin-packs/postcss-preset-env/src/test/lib/feature-is-inserted-or-has-plugin.mjs b/plugin-packs/postcss-preset-env/src/test/lib/feature-is-inserted-or-has-plugin.mjs new file mode 100644 index 000000000..1cae2566f --- /dev/null +++ b/plugin-packs/postcss-preset-env/src/test/lib/feature-is-inserted-or-has-plugin.mjs @@ -0,0 +1,32 @@ +import { featureIsInsertedOrHasAPlugin } from '../../lib/feature-is-inserted-or-has-plugin.mjs'; +import { strict as assert } from 'assert'; +import { insertAfterKey, insertBeforeKey } from '../../own-keys/keys.mjs'; + +// regular features +assert.strictEqual( + featureIsInsertedOrHasAPlugin({id: 'lab-function'}), + true, +); + +// unknown +assert.strictEqual( + featureIsInsertedOrHasAPlugin({id: 'unknown'}), + false, +); + +// inserted features +assert.strictEqual( + featureIsInsertedOrHasAPlugin({[insertBeforeKey]: true}), + true, +); + +assert.strictEqual( + featureIsInsertedOrHasAPlugin({[insertAfterKey]: true}), + true, +); + + +assert.strictEqual( + featureIsInsertedOrHasAPlugin({id: 'toString'}), + false, +); diff --git a/plugin-packs/postcss-preset-env/src/test/lib/feature-is-less.mjs b/plugin-packs/postcss-preset-env/src/test/lib/feature-is-less.mjs new file mode 100644 index 000000000..bc77f478d --- /dev/null +++ b/plugin-packs/postcss-preset-env/src/test/lib/feature-is-less.mjs @@ -0,0 +1,120 @@ +import { featureIsLess } from '../../lib/feature-is-less.mjs'; +import { strict as assert } from 'assert'; +import { insertAfterKey, insertBeforeKey, insertOrderKey } from '../../own-keys/keys.mjs'; + +// "featureIsLess" works as a sorting function. +const features = [ + { id: 'has-pseudo-class', [insertAfterKey]: true, [insertOrderKey]: 1 }, + { id: 'lab-function', [insertBeforeKey]: true, [insertOrderKey]: 2 }, + { id: 'has-pseudo-class', [insertAfterKey]: true, [insertOrderKey]: 0 }, + { id: 'lab-function' }, + { id: 'lab-function', [insertBeforeKey]: true, [insertOrderKey]: 0 }, + { id: 'has-pseudo-class' }, + { id: 'lab-function', [insertBeforeKey]: true, [insertOrderKey]: 1 }, +]; +features.sort((a, b) => { + return featureIsLess(a, b); +}); + +assert.deepStrictEqual( + features, + [ + { id: 'lab-function', [insertBeforeKey]: true, [insertOrderKey]: 0 }, + { id: 'lab-function', [insertBeforeKey]: true, [insertOrderKey]: 1 }, + { id: 'lab-function', [insertBeforeKey]: true, [insertOrderKey]: 2 }, + { id: 'lab-function' }, + { id: 'has-pseudo-class' }, + { id: 'has-pseudo-class', [insertAfterKey]: true, [insertOrderKey]: 0 }, + { id: 'has-pseudo-class', [insertAfterKey]: true, [insertOrderKey]: 1 }, + ], +); + +// regular features +assert.strictEqual( + featureIsLess( + { id: 'lab-function' }, + { id: 'has-pseudo-class' }, + ), + -1, +); + +assert.strictEqual( + featureIsLess( + { id: 'system-ui-font-family' }, + { id: 'has-pseudo-class' }, + ), + 1, +); + +assert.strictEqual( + featureIsLess( + { id: 'has-pseudo-class' }, + { id: 'has-pseudo-class' }, + ), + 0, +); + +// inserted features +assert.strictEqual( + featureIsLess( + { id: 'lab-function' }, + { id: 'lab-function', [insertBeforeKey]: true, [insertOrderKey]: 0 }, + ), + 1, +); + +assert.strictEqual( + featureIsLess( + { id: 'lab-function', [insertBeforeKey]: true, [insertOrderKey]: 0 }, + { id: 'lab-function' }, + ), + -1, +); + +assert.strictEqual( + featureIsLess( + { id: 'lab-function', [insertBeforeKey]: true, [insertOrderKey]: 0 }, + { id: 'lab-function', [insertBeforeKey]: true, [insertOrderKey]: 1 }, + ), + -1, +); + +assert.strictEqual( + featureIsLess( + { id: 'lab-function', [insertBeforeKey]: true, [insertOrderKey]: 1 }, + { id: 'lab-function', [insertBeforeKey]: true, [insertOrderKey]: 0 }, + ), + 1, +); + +assert.strictEqual( + featureIsLess( + { id: 'lab-function' }, + { id: 'lab-function', [insertAfterKey]: true, [insertOrderKey]: 0 }, + ), + -1, +); + +assert.strictEqual( + featureIsLess( + { id: 'lab-function', [insertAfterKey]: true, [insertOrderKey]: 0 }, + { id: 'lab-function' }, + ), + 1, +); + +assert.strictEqual( + featureIsLess( + { id: 'lab-function', [insertAfterKey]: true, [insertOrderKey]: 0 }, + { id: 'lab-function', [insertAfterKey]: true, [insertOrderKey]: 1 }, + ), + -1, +); + +assert.strictEqual( + featureIsLess( + { id: 'lab-function', [insertAfterKey]: true, [insertOrderKey]: 1 }, + { id: 'lab-function', [insertAfterKey]: true, [insertOrderKey]: 0 }, + ), + 1, +); diff --git a/plugin-packs/postcss-preset-env/src/test/lib/format-staged-feature.mjs b/plugin-packs/postcss-preset-env/src/test/lib/format-staged-feature.mjs new file mode 100644 index 000000000..b66ee22d5 --- /dev/null +++ b/plugin-packs/postcss-preset-env/src/test/lib/format-staged-feature.mjs @@ -0,0 +1,114 @@ +import { formatStagedFeature } from '../../lib/format-feature.mjs'; +import { strict as assert } from 'assert'; + +assert.deepStrictEqual( + formatStagedFeature( + [], + ['ie >= 1'], + {}, + { + id: 'any-link-pseudo-class', + plugin: true, + browsers: [ + 'ie >= 1', + ], + vendors_implementations: 1, + }, + undefined, + ), + { + browsers: [ + 'ie >= 1', + ], + vendors_implementations: 1, + plugin: true, + pluginOptions: { subFeatures: { areaHrefNeedsFixing: true } }, + id: 'any-link-pseudo-class', + }, +); + +assert.deepStrictEqual( + formatStagedFeature( + [], + ['ie >= 1'], + { + 'any-link-pseudo-class': true, + }, + { + id: 'any-link-pseudo-class', + plugin: true, + browsers: [ + 'ie >= 1', + ], + vendors_implementations: 1, + }, + undefined, + ), + { + browsers: [ + 'ie >= 1', + ], + vendors_implementations: 1, + plugin: true, + pluginOptions: { subFeatures: { areaHrefNeedsFixing: true } }, + id: 'any-link-pseudo-class', + }, +); + +assert.deepStrictEqual( + formatStagedFeature( + [], + ['ie >= 1'], + { + 'any-link-pseudo-class': true, + }, + { + id: 'any-link-pseudo-class', + plugin: true, + browsers: [ + 'ie >= 1', + ], + vendors_implementations: 1, + }, + { + shared: true, + }, + ), + { + browsers: [ + 'ie >= 1', + ], + vendors_implementations: 1, + plugin: true, + pluginOptions: { subFeatures: { areaHrefNeedsFixing: true }, shared: true }, + id: 'any-link-pseudo-class', + }, +); + +assert.deepStrictEqual( + formatStagedFeature( + [], + ['ie >= 1'], + {}, + { + id: 'any-link-pseudo-class', + plugin: true, + browsers: [ + 'ie >= 1', + ], + vendors_implementations: 1, + }, + { + shared: true, + }, + ), + { + browsers: [ + 'ie >= 1', + ], + vendors_implementations: 1, + plugin: true, + pluginOptions: { subFeatures: { areaHrefNeedsFixing: true }, shared: true }, + id: 'any-link-pseudo-class', + }, +); diff --git a/plugin-packs/postcss-preset-env/src/test/lib/get-unsupported-browsers-by-feature.mjs b/plugin-packs/postcss-preset-env/src/test/lib/get-unsupported-browsers-by-feature.mjs new file mode 100644 index 000000000..49a359cd7 --- /dev/null +++ b/plugin-packs/postcss-preset-env/src/test/lib/get-unsupported-browsers-by-feature.mjs @@ -0,0 +1,180 @@ + +import getUnsupportedBrowsersByFeature from '../../lib/get-unsupported-browsers-by-feature.mjs'; +import { strict as assert } from 'assert'; + +assert.deepStrictEqual( + getUnsupportedBrowsersByFeature(), + [], +); + +assert.deepStrictEqual( + getUnsupportedBrowsersByFeature({}), + ['> 0%'], +); + +assert.deepStrictEqual( + getUnsupportedBrowsersByFeature({ browser_support: { firefox: 30 }}), + [ + 'ie >= 1', + 'edge >= 1', + 'firefox >= 1', + 'chrome >= 1', + 'safari >= 1', + 'opera >= 1', + 'ios_saf >= 1', + 'android >= 1', + 'op_mob >= 1', + 'and_chr >= 1', + 'and_ff >= 1', + 'and_uc >= 1', + 'samsung >= 1', + 'and_qq >= 1', + 'baidu >= 1', + 'kaios >= 1', + ], +); + +assert.deepStrictEqual( + getUnsupportedBrowsersByFeature({ browser_support: { firefox: 'Beta' }}), + [ + 'ie >= 1', + 'edge >= 1', + 'firefox >= 1', + 'chrome >= 1', + 'safari >= 1', + 'opera >= 1', + 'ios_saf >= 1', + 'android >= 1', + 'op_mob >= 1', + 'and_chr >= 1', + 'and_ff >= 1', + 'and_uc >= 1', + 'samsung >= 1', + 'and_qq >= 1', + 'baidu >= 1', + 'kaios >= 1', + ], +); + +assert.deepStrictEqual( + getUnsupportedBrowsersByFeature({ browser_support: { firefox: 'Beta 5' }}), + [ + 'ie >= 1', + 'edge >= 1', + 'firefox >= 1', + 'chrome >= 1', + 'safari >= 1', + 'opera >= 1', + 'ios_saf >= 1', + 'android >= 1', + 'op_mob >= 1', + 'and_chr >= 1', + 'and_ff >= 1', + 'and_uc >= 1', + 'samsung >= 1', + 'and_qq >= 1', + 'baidu >= 1', + 'kaios >= 1', + ], +); + +assert.deepStrictEqual( + getUnsupportedBrowsersByFeature({ browser_support: { firefox: '5 Beta' }}), + [ + 'ie >= 1', + 'edge >= 1', + 'firefox >= 1', + 'chrome >= 1', + 'safari >= 1', + 'opera >= 1', + 'ios_saf >= 1', + 'android >= 1', + 'op_mob >= 1', + 'and_chr >= 1', + 'and_ff >= 1', + 'and_uc >= 1', + 'samsung >= 1', + 'and_qq >= 1', + 'baidu >= 1', + 'kaios >= 1', + ], +); + +assert.deepStrictEqual( + getUnsupportedBrowsersByFeature({ browser_support: { unknown: 30 }}), + [ + 'ie >= 1', + 'edge >= 1', + 'firefox >= 1', + 'chrome >= 1', + 'safari >= 1', + 'opera >= 1', + 'ios_saf >= 1', + 'android >= 1', + 'op_mob >= 1', + 'and_chr >= 1', + 'and_ff >= 1', + 'and_uc >= 1', + 'samsung >= 1', + 'and_qq >= 1', + 'baidu >= 1', + 'kaios >= 1', + ], +); + +assert.deepStrictEqual( + getUnsupportedBrowsersByFeature({ browser_support: { firefox: '30' }}), + [ + 'ie >= 1', + 'edge >= 1', + 'firefox < 30', + 'chrome >= 1', + 'safari >= 1', + 'opera >= 1', + 'ios_saf >= 1', + 'android >= 1', + 'op_mob >= 1', + 'and_chr >= 1', + 'and_ff >= 1', + 'and_uc >= 1', + 'samsung >= 1', + 'and_qq >= 1', + 'baidu >= 1', + 'kaios >= 1', + ], +); + +assert.deepStrictEqual( + getUnsupportedBrowsersByFeature({ + browser_support: { + 'edge': '89', + 'firefox': '66', + 'chrome': '89', + 'safari': '15', + 'opera': '76', + 'ios_saf': '15', + 'android': '97', + 'op_mob': '64', + 'and_chr': '97', + 'and_ff': '95', + 'samsung': '15', + }}), + [ + 'ie >= 1', + 'edge < 89', + 'firefox < 66', + 'chrome < 89', + 'safari < 15', + 'opera < 76', + 'ios_saf < 15', + 'android < 97', + 'op_mob < 64', + 'and_chr < 97', + 'and_ff < 95', + 'and_uc >= 1', + 'samsung < 15', + 'and_qq >= 1', + 'baidu >= 1', + 'kaios >= 1', + ], +); diff --git a/plugin-packs/postcss-preset-env/src/test/lib/list-features/client-side.mjs b/plugin-packs/postcss-preset-env/src/test/lib/list-features/client-side.mjs new file mode 100644 index 000000000..f5129f0f5 --- /dev/null +++ b/plugin-packs/postcss-preset-env/src/test/lib/list-features/client-side.mjs @@ -0,0 +1,95 @@ +import { testLogger } from '../../log/test-logger.mjs'; +import { strict as assert } from 'assert'; +import { dumpLogs, resetLogger } from '../../../log/helper.mjs'; +import { listFeatures } from '../../../lib/list-features.mjs'; +import { cssdb } from './cssdb-fixture.mjs'; + +const logger = testLogger(); + +resetLogger(); +assert.deepStrictEqual( + cleanResult(listFeatures(cssdb, {stage: 0, enableClientSidePolyfills: false})), + [ + { + browsers: [ + 'ie >= 1', 'edge < 79', + 'firefox < 1', 'chrome < 1', + 'safari < 3', 'opera < 15', + 'ios_saf < 1', 'android < 65', + 'op_mob < 14', 'and_chr < 18', + 'and_ff < 4', 'and_uc >= 1', + 'samsung < 1.0', 'and_qq >= 1', + 'baidu >= 1', 'kaios >= 1', + ], + pluginOptions: { subFeatures: { areaHrefNeedsFixing: true } }, + vendors_implementations: 3, + id: 'any-link-pseudo-class', + }, + ], +); + +dumpLogs(logger); +assert.deepStrictEqual( + logger.getLogs(), + [ + 'Using features from Stage 0', + ' blank-pseudo-class has been disabled by "enableClientSidePolyfills: false".', + 'Adding area[href] fallbacks for ":any-link" support in Edge and IE.', + ], +); + +resetLogger(); +assert.deepStrictEqual( + cleanResult(listFeatures(cssdb, {stage: 0, enableClientSidePolyfills: true})), + [ + { + browsers: [ + 'ie >= 1', 'edge < 79', + 'firefox < 1', 'chrome < 1', + 'safari < 3', 'opera < 15', + 'ios_saf < 1', 'android < 65', + 'op_mob < 14', 'and_chr < 18', + 'and_ff < 4', 'and_uc >= 1', + 'samsung < 1.0', 'and_qq >= 1', + 'baidu >= 1', 'kaios >= 1', + ], + pluginOptions: { subFeatures: { areaHrefNeedsFixing: true } }, + vendors_implementations: 3, + id: 'any-link-pseudo-class', + }, + { + browsers: [ + 'ie >= 1', 'edge >= 1', + 'firefox >= 1', 'chrome >= 1', + 'safari >= 1', 'opera >= 1', + 'ios_saf >= 1', 'android >= 1', + 'op_mob >= 1', 'and_chr >= 1', + 'and_ff >= 1', 'and_uc >= 1', + 'samsung >= 1', 'and_qq >= 1', + 'baidu >= 1', 'kaios >= 1', + ], + pluginOptions: {}, + vendors_implementations: 0, + id: 'blank-pseudo-class', + }, + ], +); + +dumpLogs(logger); +assert.deepStrictEqual( + logger.getLogs(), + [ + 'Using features from Stage 0', + 'Adding area[href] fallbacks for ":any-link" support in Edge and IE.', + ], +); + +function cleanResult(res) { + return res.map((x) => { + if (!x.plugin) { + throw new Error(`feature "${x.id}" must have a plugin`); + } + delete x.plugin; + return x; + }); +} diff --git a/plugin-packs/postcss-preset-env/src/test/lib/list-features/cssdb-fixture.mjs b/plugin-packs/postcss-preset-env/src/test/lib/list-features/cssdb-fixture.mjs new file mode 100644 index 000000000..c6251ca63 --- /dev/null +++ b/plugin-packs/postcss-preset-env/src/test/lib/list-features/cssdb-fixture.mjs @@ -0,0 +1,54 @@ +export const cssdb = [ + { + 'id': 'any-link-pseudo-class', + 'title': '`:any-link` Hyperlink Pseudo-Class', + 'description': 'A pseudo-class for matching anchor elements independent of whether they have been visited', + 'specification': 'https://www.w3.org/TR/selectors-4/#any-link-pseudo', + 'stage': 2, + 'browser_support': { + 'chrome': '1', + 'and_chr': '18', + 'edge': '79', + 'firefox': '1', + 'and_ff': '4', + 'opera': '15', + 'op_mob': '14', + 'safari': '3', + 'ios_saf': '1', + 'samsung': '1.0', + 'android': '65', + }, + 'docs': { + 'mdn': 'https://developer.mozilla.org/en-US/docs/Web/CSS/:any-link', + }, + 'polyfills': [ + { + 'type': 'PostCSS Plugin', + 'link': 'https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-pseudo-class-any-link', + }, + ], + 'vendors_implementations': 3, + }, + { + 'id': 'blank-pseudo-class', + 'title': '`:blank` Empty-Value Pseudo-Class', + 'description': 'A pseudo-class for matching form elements when they are empty', + 'specification': 'https://drafts.csswg.org/selectors-4/#blank', + 'stage': 1, + 'browser_support': {}, + 'docs': { + 'mdn': 'https://developer.mozilla.org/en-US/docs/Web/CSS/:blank', + }, + 'polyfills': [ + { + 'type': 'JavaScript Library', + 'link': 'https://github.com/csstools/postcss-plugins/tree/main/plugins/css-blank-pseudo', + }, + { + 'type': 'PostCSS Plugin', + 'link': 'https://github.com/csstools/postcss-plugins/tree/main/plugins/css-blank-pseudo', + }, + ], + 'vendors_implementations': 0, + }, +]; diff --git a/plugin-packs/postcss-preset-env/src/test/lib/list-features/list-features.mjs b/plugin-packs/postcss-preset-env/src/test/lib/list-features/list-features.mjs new file mode 100644 index 000000000..bcad2182f --- /dev/null +++ b/plugin-packs/postcss-preset-env/src/test/lib/list-features/list-features.mjs @@ -0,0 +1,5 @@ +import './no-options.mjs'; +import './client-side.mjs'; +import './stage-0.mjs'; +import './preserve.mjs'; +import './vendor-implementations.mjs'; diff --git a/plugin-packs/postcss-preset-env/src/test/lib/list-features/no-options.mjs b/plugin-packs/postcss-preset-env/src/test/lib/list-features/no-options.mjs new file mode 100644 index 000000000..9b2403fde --- /dev/null +++ b/plugin-packs/postcss-preset-env/src/test/lib/list-features/no-options.mjs @@ -0,0 +1,49 @@ +import { testLogger } from '../../log/test-logger.mjs'; +import { strict as assert } from 'assert'; +import { dumpLogs, resetLogger } from '../../../log/helper.mjs'; +import { listFeatures } from '../../../lib/list-features.mjs'; +import { cssdb } from './cssdb-fixture.mjs'; + +const logger = testLogger(); + +resetLogger(); +assert.deepStrictEqual( + cleanResult(listFeatures(cssdb, {})), + [ + { + browsers: [ + 'ie >= 1', 'edge < 79', + 'firefox < 1', 'chrome < 1', + 'safari < 3', 'opera < 15', + 'ios_saf < 1', 'android < 65', + 'op_mob < 14', 'and_chr < 18', + 'and_ff < 4', 'and_uc >= 1', + 'samsung < 1.0', 'and_qq >= 1', + 'baidu >= 1', 'kaios >= 1', + ], + vendors_implementations: 3, + pluginOptions: { subFeatures: { areaHrefNeedsFixing: true } }, + id: 'any-link-pseudo-class', + }, + ], +); + +dumpLogs(logger); +assert.deepStrictEqual( + logger.getLogs(), + [ + 'Using features from Stage 2 (default)', + ' blank-pseudo-class with stage 1 has been disabled', + 'Adding area[href] fallbacks for ":any-link" support in Edge and IE.', + ], +); + +function cleanResult(res) { + return res.map((x) => { + if (!x.plugin) { + throw new Error(`feature "${x.id}" must have a plugin`); + } + delete x.plugin; + return x; + }); +} diff --git a/plugin-packs/postcss-preset-env/src/test/lib/list-features/preserve.mjs b/plugin-packs/postcss-preset-env/src/test/lib/list-features/preserve.mjs new file mode 100644 index 000000000..56ba978d6 --- /dev/null +++ b/plugin-packs/postcss-preset-env/src/test/lib/list-features/preserve.mjs @@ -0,0 +1,110 @@ +import { testLogger } from '../../log/test-logger.mjs'; +import { strict as assert } from 'assert'; +import { dumpLogs, resetLogger } from '../../../log/helper.mjs'; +import { listFeatures } from '../../../lib/list-features.mjs'; +import { cssdb } from './cssdb-fixture.mjs'; + +const logger = testLogger(); + +resetLogger(); +assert.deepStrictEqual( + cleanResult(listFeatures(cssdb, { stage: 0 }, { preserve: true })), + [ + { + browsers: [ + 'ie >= 1', 'edge < 79', + 'firefox < 1', 'chrome < 1', + 'safari < 3', 'opera < 15', + 'ios_saf < 1', 'android < 65', + 'op_mob < 14', 'and_chr < 18', + 'and_ff < 4', 'and_uc >= 1', + 'samsung < 1.0', 'and_qq >= 1', + 'baidu >= 1', 'kaios >= 1', + ], + pluginOptions: { preserve: true, subFeatures: { areaHrefNeedsFixing: true } }, + vendors_implementations: 3, + id: 'any-link-pseudo-class', + }, + { + browsers: [ + 'ie >= 1', 'edge >= 1', + 'firefox >= 1', 'chrome >= 1', + 'safari >= 1', 'opera >= 1', + 'ios_saf >= 1', 'android >= 1', + 'op_mob >= 1', 'and_chr >= 1', + 'and_ff >= 1', 'and_uc >= 1', + 'samsung >= 1', 'and_qq >= 1', + 'baidu >= 1', 'kaios >= 1', + ], + pluginOptions: { preserve: true }, + vendors_implementations: 0, + id: 'blank-pseudo-class', + }, + ], +); + +dumpLogs(logger); +assert.deepStrictEqual( + logger.getLogs(), + [ + 'Using features from Stage 0', + 'Adding area[href] fallbacks for ":any-link" support in Edge and IE.', + ], +); + + +resetLogger(); +assert.deepStrictEqual( + cleanResult(listFeatures(cssdb, { stage: 0 }, { preserve: false })), + [ + { + browsers: [ + 'ie >= 1', 'edge < 79', + 'firefox < 1', 'chrome < 1', + 'safari < 3', 'opera < 15', + 'ios_saf < 1', 'android < 65', + 'op_mob < 14', 'and_chr < 18', + 'and_ff < 4', 'and_uc >= 1', + 'samsung < 1.0', 'and_qq >= 1', + 'baidu >= 1', 'kaios >= 1', + ], + pluginOptions: { preserve: false, subFeatures: { areaHrefNeedsFixing: true } }, + vendors_implementations: 3, + id: 'any-link-pseudo-class', + }, + { + browsers: [ + 'ie >= 1', 'edge >= 1', + 'firefox >= 1', 'chrome >= 1', + 'safari >= 1', 'opera >= 1', + 'ios_saf >= 1', 'android >= 1', + 'op_mob >= 1', 'and_chr >= 1', + 'and_ff >= 1', 'and_uc >= 1', + 'samsung >= 1', 'and_qq >= 1', + 'baidu >= 1', 'kaios >= 1', + ], + pluginOptions: { preserve: false }, + vendors_implementations: 0, + id: 'blank-pseudo-class', + }, + ], +); + +dumpLogs(logger); +assert.deepStrictEqual( + logger.getLogs(), + [ + 'Using features from Stage 0', + 'Adding area[href] fallbacks for ":any-link" support in Edge and IE.', + ], +); + +function cleanResult(res) { + return res.map((x) => { + if (!x.plugin) { + throw new Error(`feature "${x.id}" must have a plugin`); + } + delete x.plugin; + return x; + }); +} diff --git a/plugin-packs/postcss-preset-env/src/test/lib/list-features/stage-0.mjs b/plugin-packs/postcss-preset-env/src/test/lib/list-features/stage-0.mjs new file mode 100644 index 000000000..516a30bb5 --- /dev/null +++ b/plugin-packs/postcss-preset-env/src/test/lib/list-features/stage-0.mjs @@ -0,0 +1,63 @@ +import { testLogger } from '../../log/test-logger.mjs'; +import { strict as assert } from 'assert'; +import { dumpLogs, resetLogger } from '../../../log/helper.mjs'; +import { listFeatures } from '../../../lib/list-features.mjs'; +import { cssdb } from './cssdb-fixture.mjs'; + +const logger = testLogger(); + +resetLogger(); +assert.deepStrictEqual( + cleanResult(listFeatures(cssdb, {stage: 0})), + [ + { + browsers: [ + 'ie >= 1', 'edge < 79', + 'firefox < 1', 'chrome < 1', + 'safari < 3', 'opera < 15', + 'ios_saf < 1', 'android < 65', + 'op_mob < 14', 'and_chr < 18', + 'and_ff < 4', 'and_uc >= 1', + 'samsung < 1.0', 'and_qq >= 1', + 'baidu >= 1', 'kaios >= 1', + ], + pluginOptions: { subFeatures: { areaHrefNeedsFixing: true } }, + vendors_implementations: 3, + id: 'any-link-pseudo-class', + }, + { + browsers: [ + 'ie >= 1', 'edge >= 1', + 'firefox >= 1', 'chrome >= 1', + 'safari >= 1', 'opera >= 1', + 'ios_saf >= 1', 'android >= 1', + 'op_mob >= 1', 'and_chr >= 1', + 'and_ff >= 1', 'and_uc >= 1', + 'samsung >= 1', 'and_qq >= 1', + 'baidu >= 1', 'kaios >= 1', + ], + pluginOptions: {}, + vendors_implementations: 0, + id: 'blank-pseudo-class', + }, + ], +); + +dumpLogs(logger); +assert.deepStrictEqual( + logger.getLogs(), + [ + 'Using features from Stage 0', + 'Adding area[href] fallbacks for ":any-link" support in Edge and IE.', + ], +); + +function cleanResult(res) { + return res.map((x) => { + if (!x.plugin) { + throw new Error(`feature "${x.id}" must have a plugin`); + } + delete x.plugin; + return x; + }); +} diff --git a/plugin-packs/postcss-preset-env/src/test/lib/list-features/vendor-implementations.mjs b/plugin-packs/postcss-preset-env/src/test/lib/list-features/vendor-implementations.mjs new file mode 100644 index 000000000..415b276d5 --- /dev/null +++ b/plugin-packs/postcss-preset-env/src/test/lib/list-features/vendor-implementations.mjs @@ -0,0 +1,50 @@ +import { testLogger } from '../../log/test-logger.mjs'; +import { strict as assert } from 'assert'; +import { dumpLogs, resetLogger } from '../../../log/helper.mjs'; +import { listFeatures } from '../../../lib/list-features.mjs'; +import { cssdb } from './cssdb-fixture.mjs'; + +const logger = testLogger(); + +resetLogger(); +assert.deepStrictEqual( + cleanResult(listFeatures(cssdb, {minimumVendorImplementations: 2, stage: 0})), + [ + { + browsers: [ + 'ie >= 1', 'edge < 79', + 'firefox < 1', 'chrome < 1', + 'safari < 3', 'opera < 15', + 'ios_saf < 1', 'android < 65', + 'op_mob < 14', 'and_chr < 18', + 'and_ff < 4', 'and_uc >= 1', + 'samsung < 1.0', 'and_qq >= 1', + 'baidu >= 1', 'kaios >= 1', + ], + pluginOptions: { subFeatures: { areaHrefNeedsFixing: true } }, + vendors_implementations: 3, + id: 'any-link-pseudo-class', + }, + ], +); + +dumpLogs(logger); +assert.deepStrictEqual( + logger.getLogs(), + [ + 'Using features with 2 or more vendor implementations', + 'Using features from Stage 0', + ' blank-pseudo-class with 0 vendor implementations has been disabled', + 'Adding area[href] fallbacks for ":any-link" support in Edge and IE.', + ], +); + +function cleanResult(res) { + return res.map((x) => { + if (!x.plugin) { + throw new Error(`feature "${x.id}" must have a plugin`); + } + delete x.plugin; + return x; + }); +} diff --git a/plugin-packs/postcss-preset-env/src/test/lib/prepare-features-list.mjs b/plugin-packs/postcss-preset-env/src/test/lib/prepare-features-list.mjs new file mode 100644 index 000000000..223e1baa2 --- /dev/null +++ b/plugin-packs/postcss-preset-env/src/test/lib/prepare-features-list.mjs @@ -0,0 +1,138 @@ +import { prepareFeaturesList } from '../../lib/prepare-features-list.mjs'; +import { strict as assert } from 'assert'; +import { insertAfterKey, insertBeforeKey, insertOrderKey, pluginKey } from '../../own-keys/keys.mjs'; + +const fixedList = [ + { + id: 'unknown', + 'should-exist-in-result': true, + }, + { + id: 'overflow-wrap-property', + 'should-exist-in-result': true, + }, + { + id: 'place-properties', + 'should-exist-in-result': true, + }, +]; + +assert.deepStrictEqual( + prepareFeaturesList(fixedList), + [ + fixedList[1], + fixedList[2], + ], +); + +assert.deepStrictEqual( + prepareFeaturesList( + fixedList, + { + 'place-properties': true, + }, + ), + [ + fixedList[1], + { + id: 'place-properties', + 'should-exist-in-result': true, + [insertBeforeKey]: true, + [insertOrderKey]: 0, + [pluginKey]: true, + }, + fixedList[2], + ], +); + +assert.deepStrictEqual( + prepareFeaturesList( + fixedList, + undefined, + { + 'place-properties': true, + }, + ), + [ + fixedList[1], + fixedList[2], + { + id: 'place-properties', + 'should-exist-in-result': true, + [insertAfterKey]: true, + [insertOrderKey]: 0, + [pluginKey]: true, + }, + ], +); + +assert.deepStrictEqual( + prepareFeaturesList( + fixedList, + { + 'unknown': true, + }, + ), + [ + { + id: 'unknown', + 'should-exist-in-result': true, + [insertBeforeKey]: true, + [insertOrderKey]: 0, + [pluginKey]: true, + }, + fixedList[1], + fixedList[2], + ], +); + +assert.deepStrictEqual( + prepareFeaturesList( + fixedList, + undefined, + { + 'unknown': true, + }, + ), + [ + { + id: 'unknown', + 'should-exist-in-result': true, + [insertAfterKey]: true, + [insertOrderKey]: 0, + [pluginKey]: true, + }, + fixedList[1], + fixedList[2], + ], +); + +assert.deepStrictEqual( + prepareFeaturesList( + fixedList, + { + 'place-properties': 1, + }, + { + 'place-properties': 2, + }, + ), + [ + fixedList[1], + { + id: 'place-properties', + 'should-exist-in-result': true, + [insertBeforeKey]: true, + [insertOrderKey]: 0, + [pluginKey]: 1, + }, + fixedList[2], + { + id: 'place-properties', + 'should-exist-in-result': true, + [insertAfterKey]: true, + [insertOrderKey]: 0, + [pluginKey]: 2, + }, + ], +); diff --git a/plugin-packs/postcss-preset-env/src/test/lib/stage.mjs b/plugin-packs/postcss-preset-env/src/test/lib/stage.mjs new file mode 100644 index 000000000..2ea241bee --- /dev/null +++ b/plugin-packs/postcss-preset-env/src/test/lib/stage.mjs @@ -0,0 +1,90 @@ +import { testLogger } from '../log/test-logger.mjs'; +import { strict as assert } from 'assert'; +import { OUT_OF_RANGE_STAGE, stageFromOptions } from '../../lib/stage.mjs'; +import { dumpLogs, resetLogger } from '../../log/helper.mjs'; + +const logger = testLogger(); + +resetLogger(); +assert.deepStrictEqual( + stageFromOptions({stage: 4}), + 4, +); + +dumpLogs(logger); +assert.deepStrictEqual( + logger.getLogs(), + ['Using features from Stage 4'], +); + +resetLogger(); +assert.deepStrictEqual( + stageFromOptions({stage: '4'}), + 4, +); + +dumpLogs(logger); +assert.deepStrictEqual( + logger.getLogs(), + ['Using features from Stage 4'], +); + +resetLogger(); +assert.deepStrictEqual( + stageFromOptions({stage: -1}), + 0, +); + +dumpLogs(logger); +assert.deepStrictEqual( + logger.getLogs(), + ['Using features from Stage 0'], +); + +resetLogger(); +assert.deepStrictEqual( + stageFromOptions({stage: false}), + OUT_OF_RANGE_STAGE, +); + +dumpLogs(logger); +assert.deepStrictEqual( + logger.getLogs(), + ['Stage has been disabled, features will be handled via the "features" option.'], +); + +resetLogger(); +assert.deepStrictEqual( + stageFromOptions({stage: 'esfgdfg'}), + 0, +); + +dumpLogs(logger); +assert.deepStrictEqual( + logger.getLogs(), + ['Using features from Stage 0'], +); + +resetLogger(); +assert.deepStrictEqual( + stageFromOptions({}), + 2, +); + +dumpLogs(logger); +assert.deepStrictEqual( + logger.getLogs(), + ['Using features from Stage 2 (default)'], +); + +resetLogger(); +assert.deepStrictEqual( + stageFromOptions({stage: OUT_OF_RANGE_STAGE+2}), + OUT_OF_RANGE_STAGE, +); + +dumpLogs(logger); +assert.deepStrictEqual( + logger.getLogs(), + ['Stage has been disabled, features will be handled via the "features" option.'], +); diff --git a/plugin-packs/postcss-preset-env/src/test/lib/transformed-insertions.mjs b/plugin-packs/postcss-preset-env/src/test/lib/transformed-insertions.mjs new file mode 100644 index 000000000..9d91bf78c --- /dev/null +++ b/plugin-packs/postcss-preset-env/src/test/lib/transformed-insertions.mjs @@ -0,0 +1,256 @@ +import getTransformedInsertions from '../../lib/get-transformed-insertions.mjs'; +import assert from 'assert'; +import { insertAfterKey, insertBeforeKey, insertOrderKey, pluginKey } from '../../own-keys/keys.mjs'; + +const labFunctionFixture = { + 'id': 'lab-function', + 'should-exist-in-result': true, +}; + +const hasPseudoFixture = { + 'id': 'has-pseudo-class', + 'should-exist-in-result': true, +}; + +assert.deepEqual( + cleanResult(getTransformedInsertions( + [labFunctionFixture, hasPseudoFixture], + undefined, + 'insertAfter', + )), + [], +); + +assert.deepEqual( + cleanResult(getTransformedInsertions( + [labFunctionFixture, hasPseudoFixture], + null, + 'insertAfter', + )), + [], +); + +assert.deepEqual( + cleanResult(getTransformedInsertions( + [labFunctionFixture, hasPseudoFixture], + false, + 'insertAfter', + )), + [], +); + +assert.deepEqual( + cleanResult(getTransformedInsertions( + [labFunctionFixture, hasPseudoFixture], + '', + 'insertAfter', + )), + [], +); + +assert.deepEqual( + cleanResult(getTransformedInsertions( + [labFunctionFixture, hasPseudoFixture], + { + 'lab-function': () => { }, + }, + undefined, + )), + [], +); + +assert.deepEqual( + cleanResult(getTransformedInsertions( + [labFunctionFixture, hasPseudoFixture], + { + 'lab-function': () => { }, + }, + 'unknown', + )), + [], +); + +assert.deepEqual( + cleanResult(getTransformedInsertions( + [labFunctionFixture, hasPseudoFixture], + { + 'unknown': () => { }, + }, + 'insertAfter', + )), + [], +); + +assert.deepEqual( + cleanResult(getTransformedInsertions( + [labFunctionFixture, hasPseudoFixture], + undefined, + undefined, + )), + [], +); + +assert.deepEqual( + cleanResult(getTransformedInsertions( + [labFunctionFixture, hasPseudoFixture], + { + 'lab-function': () => { }, + }, + 'insertAfter', + )), + [ + { + id: 'lab-function', + 'should-exist-in-result': true, + [insertOrderKey]: 0, + [insertAfterKey]: true, + }, + ], +); + +assert.deepEqual( + cleanResult(getTransformedInsertions( + [labFunctionFixture, hasPseudoFixture], + { + 'lab-function': () => { }, + }, + 'insertBefore', + )), + [ + { + id: 'lab-function', + 'should-exist-in-result': true, + [insertOrderKey]: 0, + [insertBeforeKey]: true, + }, + ], +); + +assert.deepEqual( + cleanResult(getTransformedInsertions( + [labFunctionFixture, hasPseudoFixture], + { + 'lab-function': [ + () => { }, + ], + }, + 'insertAfter', + )), + [ + { + id: 'lab-function', + 'should-exist-in-result': true, + [insertOrderKey]: 0, + [insertAfterKey]: true, + }, + ], +); + +assert.deepEqual( + cleanResult(getTransformedInsertions( + [labFunctionFixture, hasPseudoFixture], + { + 'lab-function': [ + () => { }, + ], + }, + 'insertBefore', + )), + [ + { + id: 'lab-function', + 'should-exist-in-result': true, + [insertOrderKey]: 0, + [insertBeforeKey]: true, + }, + ], +); + +assert.deepEqual( + cleanResult(getTransformedInsertions( + [labFunctionFixture, hasPseudoFixture], + { + 'lab-function': [ + () => { }, + () => { }, + ], + }, + 'insertAfter', + )), + [ + { + id: 'lab-function', + 'should-exist-in-result': true, + [insertOrderKey]: 0, + [insertAfterKey]: true, + }, + { + id: 'lab-function', + 'should-exist-in-result': true, + [insertOrderKey]: 1, + [insertAfterKey]: true, + }, + ], +); + +assert.deepEqual( + cleanResult(getTransformedInsertions( + [labFunctionFixture, hasPseudoFixture], + { + 'lab-function': [ + () => { }, + () => { }, + ], + }, + 'insertBefore', + )), + [ + { + id: 'lab-function', + 'should-exist-in-result': true, + [insertOrderKey]: 0, + [insertBeforeKey]: true, + }, + { + id: 'lab-function', + 'should-exist-in-result': true, + [insertOrderKey]: 1, + [insertBeforeKey]: true, + }, + ], +); + +// Enumerables and inherited properties. +class Options {} +Object.defineProperty(Options.prototype, 'lab-function', { + enumerable: true, + get: function () { + return () => { }; + }, +}); + +const options = new Options(); +options['has-pseudo-class'] = () => { }; + +assert.deepEqual( + cleanResult(getTransformedInsertions( + [labFunctionFixture, hasPseudoFixture], + options, + 'insertAfter', + )), + [ + { + id: 'has-pseudo-class', + 'should-exist-in-result': true, + [insertAfterKey]: true, + [insertOrderKey]: 0, + }, + ], +); + +function cleanResult(res) { + return res.map((x) => { + delete x[pluginKey]; + return x; + }); +} diff --git a/plugin-packs/postcss-preset-env/src/test/log/test-logger.mjs b/plugin-packs/postcss-preset-env/src/test/log/test-logger.mjs new file mode 100644 index 000000000..130f0d1f9 --- /dev/null +++ b/plugin-packs/postcss-preset-env/src/test/log/test-logger.mjs @@ -0,0 +1,14 @@ +export function testLogger() { + let logs = []; + + return { + warn: (line) => { + logs.push(line); + }, + getLogs: () => { + const x = logs.slice(); + logs = []; + return x; + }, + }; +} diff --git a/plugin-packs/postcss-preset-env/src/test/test.mjs b/plugin-packs/postcss-preset-env/src/test/test.mjs new file mode 100644 index 000000000..ddd80083a --- /dev/null +++ b/plugin-packs/postcss-preset-env/src/test/test.mjs @@ -0,0 +1,9 @@ +// import './options-matrix.mjs'; +import './lib/feature-is-inserted-or-has-plugin.mjs'; +import './lib/feature-is-less.mjs'; +import './lib/format-staged-feature.mjs'; +import './lib/get-unsupported-browsers-by-feature.mjs'; +import './lib/list-features/list-features.mjs'; +import './lib/prepare-features-list.mjs'; +import './lib/stage.mjs'; +import './lib/transformed-insertions.mjs'; diff --git a/plugin-packs/postcss-preset-env/src/util/clamp.mjs b/plugin-packs/postcss-preset-env/src/util/clamp.mjs new file mode 100644 index 000000000..f5bfc5703 --- /dev/null +++ b/plugin-packs/postcss-preset-env/src/util/clamp.mjs @@ -0,0 +1,3 @@ +export function clamp(min, value, max) { + return Math.max(min, Math.min(value, max)); +} diff --git a/plugin-packs/postcss-preset-env/src/util/int-or-zero.mjs b/plugin-packs/postcss-preset-env/src/util/int-or-zero.mjs new file mode 100644 index 000000000..50e08b640 --- /dev/null +++ b/plugin-packs/postcss-preset-env/src/util/int-or-zero.mjs @@ -0,0 +1,8 @@ +export function intOrZero(x) { + const y = parseInt(x, 10); + if (Number.isNaN(y)) { + return 0; + } + + return y; +} diff --git a/plugin-packs/postcss-preset-env/stryker.conf.json b/plugin-packs/postcss-preset-env/stryker.conf.json new file mode 100644 index 000000000..69bbc9ca2 --- /dev/null +++ b/plugin-packs/postcss-preset-env/stryker.conf.json @@ -0,0 +1,19 @@ +{ + "$schema": "../../node_modules/@stryker-mutator/core/schema/stryker-schema.json", + "mutate": [ + "src/lib/*.mjs", + "src/lib/**/*.mjs" + ], + "testRunner": "command", + "coverageAnalysis": "perTest", + "tempDirName": "../../.stryker-tmp", + "commandRunner": { + "command": "node ./src/test/test.mjs" + }, + "thresholds": { + "high": 100, + "low": 100, + "break": 100 + }, + "inPlace": true +} diff --git a/plugin-packs/postcss-preset-env/test/basic.nesting.true.expect.css b/plugin-packs/postcss-preset-env/test/basic.nesting.true.expect.css index e0e952224..0a6563092 100644 --- a/plugin-packs/postcss-preset-env/test/basic.nesting.true.expect.css +++ b/plugin-packs/postcss-preset-env/test/basic.nesting.true.expect.css @@ -36,7 +36,7 @@ order: 7; } -:is(.test-nesting-rules,#test-is-pseudo) + p { +.test-nesting-rules + p, #test-is-pseudo + p { order: 8; } diff --git a/plugin-packs/postcss-preset-env/test/basic.vendors-2.expect.css b/plugin-packs/postcss-preset-env/test/basic.vendors-2.expect.css new file mode 100644 index 000000000..4ef33aba5 --- /dev/null +++ b/plugin-packs/postcss-preset-env/test/basic.vendors-2.expect.css @@ -0,0 +1,386 @@ +:root { + --order: 1; +} + +.test-custom-properties { + order: 1; + order: var(--order); +} + +.test-image-set-function { + background-image: -webkit-image-set(url(img/test.png) 1x, url(img/test-2x.png) 2x); + background-image: image-set(url(img/test.png) 1x, url(img/test-2x.png) 2x); + order: 2; +} + +.test-logical-properties-and-values:dir(ltr) { + margin: 1px 4px 3px 2px; +} + +.test-logical-properties-and-values:dir(rtl) { + margin: 1px 2px 3px 4px; +} + +.test-logical-properties-and-values { + order: 3; + padding-top: 5px; + padding-bottom: 5px; +} + +.test-nesting-rules { + order: 4; + + & p { + order: 5; + } + + order: 6; +} + +.test-nesting-rules, +#test-is-pseudo { + order: 7; + + & + p { + order: 8; + } + + order: 9; +} + +@custom-media --narrow-window (max-width: 30em); + +@media (--narrow-window) { + .test-custom-media-queries { + order: 10; + } +} + +@media (480px <= width < 768px) { + .test-media-query-ranges { + order: 11; + } +} + +@custom-media --dark-mode (prefers-color-scheme: dark); + +@media (--dark-mode) { + body { + background-color: black; + color: white; + } +} + +@custom-selector :--heading h1, h2, h3, h4, h5, h6; + +.test-custom-selectors:--heading { + order:12; +} + +.test-case-insensitive-attributes[frame=hsides],.test-case-insensitive-attributes[frame=Hsides],.test-case-insensitive-attributes[frame=hSides],.test-case-insensitive-attributes[frame=HSides],.test-case-insensitive-attributes[frame=hsIdes],.test-case-insensitive-attributes[frame=HsIdes],.test-case-insensitive-attributes[frame=hSIdes],.test-case-insensitive-attributes[frame=HSIdes],.test-case-insensitive-attributes[frame=hsiDes],.test-case-insensitive-attributes[frame=HsiDes],.test-case-insensitive-attributes[frame=hSiDes],.test-case-insensitive-attributes[frame=HSiDes],.test-case-insensitive-attributes[frame=hsIDes],.test-case-insensitive-attributes[frame=HsIDes],.test-case-insensitive-attributes[frame=hSIDes],.test-case-insensitive-attributes[frame=HSIDes],.test-case-insensitive-attributes[frame=hsidEs],.test-case-insensitive-attributes[frame=HsidEs],.test-case-insensitive-attributes[frame=hSidEs],.test-case-insensitive-attributes[frame=HSidEs],.test-case-insensitive-attributes[frame=hsIdEs],.test-case-insensitive-attributes[frame=HsIdEs],.test-case-insensitive-attributes[frame=hSIdEs],.test-case-insensitive-attributes[frame=HSIdEs],.test-case-insensitive-attributes[frame=hsiDEs],.test-case-insensitive-attributes[frame=HsiDEs],.test-case-insensitive-attributes[frame=hSiDEs],.test-case-insensitive-attributes[frame=HSiDEs],.test-case-insensitive-attributes[frame=hsIDEs],.test-case-insensitive-attributes[frame=HsIDEs],.test-case-insensitive-attributes[frame=hSIDEs],.test-case-insensitive-attributes[frame=HSIDEs],.test-case-insensitive-attributes[frame=hsideS],.test-case-insensitive-attributes[frame=HsideS],.test-case-insensitive-attributes[frame=hSideS],.test-case-insensitive-attributes[frame=HSideS],.test-case-insensitive-attributes[frame=hsIdeS],.test-case-insensitive-attributes[frame=HsIdeS],.test-case-insensitive-attributes[frame=hSIdeS],.test-case-insensitive-attributes[frame=HSIdeS],.test-case-insensitive-attributes[frame=hsiDeS],.test-case-insensitive-attributes[frame=HsiDeS],.test-case-insensitive-attributes[frame=hSiDeS],.test-case-insensitive-attributes[frame=HSiDeS],.test-case-insensitive-attributes[frame=hsIDeS],.test-case-insensitive-attributes[frame=HsIDeS],.test-case-insensitive-attributes[frame=hSIDeS],.test-case-insensitive-attributes[frame=HSIDeS],.test-case-insensitive-attributes[frame=hsidES],.test-case-insensitive-attributes[frame=HsidES],.test-case-insensitive-attributes[frame=hSidES],.test-case-insensitive-attributes[frame=HSidES],.test-case-insensitive-attributes[frame=hsIdES],.test-case-insensitive-attributes[frame=HsIdES],.test-case-insensitive-attributes[frame=hSIdES],.test-case-insensitive-attributes[frame=HSIdES],.test-case-insensitive-attributes[frame=hsiDES],.test-case-insensitive-attributes[frame=HsiDES],.test-case-insensitive-attributes[frame=hSiDES],.test-case-insensitive-attributes[frame=HSiDES],.test-case-insensitive-attributes[frame=hsIDES],.test-case-insensitive-attributes[frame=HsIDES],.test-case-insensitive-attributes[frame=hSIDES],.test-case-insensitive-attributes[frame=HSIDES] { + order: 13; +} + +.test-rebeccapurple-color { + color: #639; + order: 14; +} + +.test-hexadecimal-alpha-notation { + background-color: rgba(243,243,243,0.95294); + color: rgba(0,0,0,0.2); + order: 15; +} + +.test-color-functional-notation { + color: rgb(70% 13.5% 13.5% / 50%); + order: 16; +} + +.test-lab-function { + background-color: lab(40% 56.6 39); + color: lch(40% 68.8 34.5 / 50%); + order: 17; +} + +.test-system-ui-font-family { + font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, sans-serif; + order: 18; +} + +.test-font-variant-property { + font-variant-caps: small-caps; + order: 19; +} + +.test-all-property { + -webkit-animation: none 0s ease 0s 1 normal none running; + animation: none 0s ease 0s 1 normal none running; + -webkit-backface-visibility: visible; + backface-visibility: visible; + background: transparent none repeat 0 0 / auto auto padding-box border-box scroll; + border: medium none currentColor; + border-collapse: separate; + -o-border-image: none; + border-image: none; + border-radius: 0; + border-spacing: 0; + bottom: auto; + box-shadow: none; + box-sizing: content-box; + caption-side: top; + clear: none; + clip: auto; + color: #000; + -moz-columns: auto; + columns: auto; + -moz-column-count: auto; + column-count: auto; + -moz-column-fill: balance; + column-fill: balance; + -moz-column-gap: normal; + column-gap: normal; + -moz-column-rule: medium none currentColor; + column-rule: medium none currentColor; + -moz-column-span: 1; + column-span: 1; + -moz-column-width: auto; + column-width: auto; + content: normal; + counter-increment: none; + counter-reset: none; + cursor: auto; + direction: ltr; + display: inline; + empty-cells: show; + float: none; + font-family: serif; + font-size: medium; + font-style: normal; + font-variant: normal; + font-weight: normal; + font-stretch: normal; + line-height: normal; + height: auto; + -webkit-hyphens: none; + -ms-hyphens: none; + hyphens: none; + left: auto; + letter-spacing: normal; + list-style: disc outside none; + margin: 0; + max-height: none; + max-width: none; + min-height: 0; + min-width: 0; + opacity: 1; + orphans: 2; + outline: medium none invert; + overflow: visible; + overflow-x: visible; + overflow-y: visible; + padding: 0; + page-break-after: auto; + page-break-before: auto; + page-break-inside: auto; + perspective: none; + perspective-origin: 50% 50%; + position: static; + right: auto; + -moz-tab-size: 8; + -o-tab-size: 8; + tab-size: 8; + table-layout: auto; + text-align: left; + -moz-text-align-last: auto; + text-align-last: auto; + text-decoration: none; + text-indent: 0; + text-shadow: none; + text-transform: none; + top: auto; + transform: none; + transform-origin: 50% 50% 0; + transform-style: flat; + transition: none 0s ease 0s; + unicode-bidi: normal; + vertical-align: baseline; + visibility: visible; + white-space: normal; + widows: 2; + width: auto; + word-spacing: normal; + z-index: auto; + all: initial; + order: 20; +} + +.test-matches-pseudo-class:matches(:first-child, .special) { + order: 21; +} + +.test-not-pseudo-class:not(:first-child):not(.special) { + order: 22; +} + +.test-any-link-pseudo-class:link, .test-any-link-pseudo-class:visited, area.test-any-link-pseudo-class[href] { + order: 23; +} + +.test-any-link-pseudo-class:-webkit-any-link { + order: 23; +} + +.test-any-link-pseudo-class:-moz-any-link { + order: 23; +} + +.test-any-link-pseudo-class:any-link { + order: 23; +} + +.test-dir-pseudo-class:dir(rtl) { + order: 24; +} + +.test-overflow-wrap-property { + order: 25; + word-wrap: break-word; +} + +.test-focus-visible-pseudo-class:focus-visible { + order: 26; +} + +.test-double-position-gradients { + background-image: conic-gradient(yellowgreen 40%, gold 0deg,gold 75%, #f06 0deg); + background-image: conic-gradient(yellowgreen 40%, gold 0deg 75%, #f06 0deg); +} + +.test-blank-pseudo-class:blank { + background-color: yellow; +} + +.test-has-pseudo-class:has(.inner-class) { + background-color: yellow; +} + +.a:focus { + order: 27; +} + +.a:hover { + order: 27; +} + +.b:focus { + order: 27; +} + +.b:hover { + order: 27; +} + +.a.c > .b + .d { + order: 28; +} + +.test-hwb-function { + background-color: hwb(194 0% 0% / .5); +} + +.test-opacity-percent { + opacity: 42%; +} + +.clamp-same-unit { + width: clamp(10px, 64px, 80px); +} + +.complex-clamp { + width: clamp(calc(100% - 10px), min(10px, 100%), max(40px, 4em)); +} + +.clamp-different-units { + width: clamp(10%, 2px, 4rem); +} + +@font-face { + font-family: 'A'; + font-style: normal; + font-weight: 300; + font-display: swap; + src: url(a) format(woff2); +} + +.block-flow { + display: block flow; +} + +.block-flow-root { + display: block flow-root; +} + +.inline-flow { + display: inline flow; +} + +.inline-flow-root { + display: inline flow-root; +} + +.run-in-flow { + display: run-in flow; +} + +.list-item-block-flow { + display: list-item block flow; +} + +.inline-flow-list-item { + display: inline flow list-item; +} + +.block-flex { + display: block flex; +} + +.inline-flex { + display: inline flex; +} + +.block-grid { + display: block grid; +} + +.inline-grid { + display: inline grid; +} + +.inline-ruby { + display: inline ruby; +} + +.block-table { + display: block table; +} + +.inline-table { + display: inline table; +} + +.table-cell-flow { + display: table-cell flow; +} + +.table-caption-flow { + display: table-caption flow; +} + +.ruby-base-flow { + display: ruby-base flow; +} + +.ruby-text-flow { + display: ruby-text flow; +} From fcdb951e502b43ec1731bc755c8da3e093db348d Mon Sep 17 00:00:00 2001 From: romainmenke Date: Sat, 29 Jan 2022 16:06:23 +0100 Subject: [PATCH 2/7] tweak --- plugin-packs/postcss-preset-env/README.md | 9 +++++---- .../postcss-preset-env/src/lib/list-features.mjs | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/plugin-packs/postcss-preset-env/README.md b/plugin-packs/postcss-preset-env/README.md index 2e7ee0f1c..94b185305 100644 --- a/plugin-packs/postcss-preset-env/README.md +++ b/plugin-packs/postcss-preset-env/README.md @@ -142,8 +142,7 @@ The `stage` can be `0` (experimental) through `4` (stable), or `false`. Setting `stage` to `false` will disable every polyfill. Doing this would only be useful if you intended to exclusively use the [`features`](#features) option. -Without any configuration options, [PostCSS Preset Env] enables **Stage 2** -features. +Default: `2` ### minimumVendorImplementations @@ -154,9 +153,11 @@ This can be used to enable plugins that are available in browsers regardless of postcssPresetEnv({ minimumVendorImplementations: 2 }) ``` -`minimumVendorImplementations` can be `0` (no vendor has implemented it) through `3` (all major vendors). +`minimumVendorImplementations` can be `0` (no vendor has implemented it) through `3` (all major vendors).
A value of `2` is recommended when you want to use only those features that are stable. +Default: `0` + ### features The `features` option enables or disables specific polyfills by ID. Passing @@ -204,7 +205,7 @@ configuration is not available. postcssPresetEnv({ browsers: 'last 2 versions' }) ``` -If not valid browserslist configuration is specified, the +If no valid browserslist configuration is specified, the [default browserslist query](https://github.com/browserslist/browserslist#queries) will be used. diff --git a/plugin-packs/postcss-preset-env/src/lib/list-features.mjs b/plugin-packs/postcss-preset-env/src/lib/list-features.mjs index f015474c1..6138bcbb2 100644 --- a/plugin-packs/postcss-preset-env/src/lib/list-features.mjs +++ b/plugin-packs/postcss-preset-env/src/lib/list-features.mjs @@ -16,6 +16,7 @@ export function listFeatures(cssdbList, options, sharedOptions) { const insertAfter = Object(options.insertAfter); const browsers = options.browsers; + // defaults to 0 const minimumVendorImplementations = clamp( 0, // 0 equals not setting this options intOrZero(options.minimumVendorImplementations), From e83f3558d0c14c85d2f6f120157a5d84f01128b4 Mon Sep 17 00:00:00 2001 From: romainmenke Date: Sat, 29 Jan 2022 16:33:52 +0100 Subject: [PATCH 3/7] fix insertBefore/insertAfter --- .../src/lib/get-transformed-insertions.mjs | 4 ---- .../postcss-preset-env/src/lib/list-features.mjs | 5 +++++ .../src/test/lib/prepare-features-list.mjs | 12 +++--------- .../src/test/lib/transformed-insertions.mjs | 13 ++----------- rollup/configs/externals.js | 2 ++ 5 files changed, 12 insertions(+), 24 deletions(-) diff --git a/plugin-packs/postcss-preset-env/src/lib/get-transformed-insertions.mjs b/plugin-packs/postcss-preset-env/src/lib/get-transformed-insertions.mjs index 7008595c1..9e563423b 100644 --- a/plugin-packs/postcss-preset-env/src/lib/get-transformed-insertions.mjs +++ b/plugin-packs/postcss-preset-env/src/lib/get-transformed-insertions.mjs @@ -31,10 +31,6 @@ export default function getTransformedInsertions(cssdbList, insertions, placemen for (let i = 0; i < pluginsToInsert.length; i++) { out.push({ id: featureId, - // TODO : verify current behavior. - // At the moment it is unclear if inserted plugins have logic around browser lists. - // Adding this ensures they do, which might be a breaking change. - ...JSON.parse(JSON.stringify(cssdbFeature)), // deep copy [pluginKey]: pluginsToInsert[i], [insertOrderKey]: i, [insertPlacementKey]: true, diff --git a/plugin-packs/postcss-preset-env/src/lib/list-features.mjs b/plugin-packs/postcss-preset-env/src/lib/list-features.mjs index 6138bcbb2..f1dd0d8de 100644 --- a/plugin-packs/postcss-preset-env/src/lib/list-features.mjs +++ b/plugin-packs/postcss-preset-env/src/lib/list-features.mjs @@ -7,6 +7,7 @@ import { prepareFeaturesList } from './prepare-features-list.mjs'; import { formatPolyfillableFeature, formatStagedFeature } from './format-feature.mjs'; import { clamp } from '../util/clamp.mjs'; import { intOrZero } from '../util/int-or-zero.mjs'; +import { insertAfterKey, insertBeforeKey } from '../own-keys/keys.mjs'; export function listFeatures(cssdbList, options, sharedOptions) { // initialize options @@ -42,6 +43,10 @@ export function listFeatures(cssdbList, options, sharedOptions) { return true; } + if (feature[insertBeforeKey] || feature[insertAfterKey]) { + return true; + } + if ( minimumVendorImplementations < feature.vendors_implementations ) { return true; } diff --git a/plugin-packs/postcss-preset-env/src/test/lib/prepare-features-list.mjs b/plugin-packs/postcss-preset-env/src/test/lib/prepare-features-list.mjs index 223e1baa2..cd94776ae 100644 --- a/plugin-packs/postcss-preset-env/src/test/lib/prepare-features-list.mjs +++ b/plugin-packs/postcss-preset-env/src/test/lib/prepare-features-list.mjs @@ -5,15 +5,15 @@ import { insertAfterKey, insertBeforeKey, insertOrderKey, pluginKey } from '../. const fixedList = [ { id: 'unknown', - 'should-exist-in-result': true, + 'should-not-exist-in-result': true, }, { id: 'overflow-wrap-property', - 'should-exist-in-result': true, + 'should-not-exist-in-result': true, }, { id: 'place-properties', - 'should-exist-in-result': true, + 'should-not-exist-in-result': true, }, ]; @@ -36,7 +36,6 @@ assert.deepStrictEqual( fixedList[1], { id: 'place-properties', - 'should-exist-in-result': true, [insertBeforeKey]: true, [insertOrderKey]: 0, [pluginKey]: true, @@ -58,7 +57,6 @@ assert.deepStrictEqual( fixedList[2], { id: 'place-properties', - 'should-exist-in-result': true, [insertAfterKey]: true, [insertOrderKey]: 0, [pluginKey]: true, @@ -76,7 +74,6 @@ assert.deepStrictEqual( [ { id: 'unknown', - 'should-exist-in-result': true, [insertBeforeKey]: true, [insertOrderKey]: 0, [pluginKey]: true, @@ -97,7 +94,6 @@ assert.deepStrictEqual( [ { id: 'unknown', - 'should-exist-in-result': true, [insertAfterKey]: true, [insertOrderKey]: 0, [pluginKey]: true, @@ -121,7 +117,6 @@ assert.deepStrictEqual( fixedList[1], { id: 'place-properties', - 'should-exist-in-result': true, [insertBeforeKey]: true, [insertOrderKey]: 0, [pluginKey]: 1, @@ -129,7 +124,6 @@ assert.deepStrictEqual( fixedList[2], { id: 'place-properties', - 'should-exist-in-result': true, [insertAfterKey]: true, [insertOrderKey]: 0, [pluginKey]: 2, diff --git a/plugin-packs/postcss-preset-env/src/test/lib/transformed-insertions.mjs b/plugin-packs/postcss-preset-env/src/test/lib/transformed-insertions.mjs index 9d91bf78c..4501adb30 100644 --- a/plugin-packs/postcss-preset-env/src/test/lib/transformed-insertions.mjs +++ b/plugin-packs/postcss-preset-env/src/test/lib/transformed-insertions.mjs @@ -4,12 +4,12 @@ import { insertAfterKey, insertBeforeKey, insertOrderKey, pluginKey } from '../. const labFunctionFixture = { 'id': 'lab-function', - 'should-exist-in-result': true, + 'should-not-exist-in-result': true, }; const hasPseudoFixture = { 'id': 'has-pseudo-class', - 'should-exist-in-result': true, + 'should-not-exist-in-result': true, }; assert.deepEqual( @@ -101,7 +101,6 @@ assert.deepEqual( [ { id: 'lab-function', - 'should-exist-in-result': true, [insertOrderKey]: 0, [insertAfterKey]: true, }, @@ -119,7 +118,6 @@ assert.deepEqual( [ { id: 'lab-function', - 'should-exist-in-result': true, [insertOrderKey]: 0, [insertBeforeKey]: true, }, @@ -139,7 +137,6 @@ assert.deepEqual( [ { id: 'lab-function', - 'should-exist-in-result': true, [insertOrderKey]: 0, [insertAfterKey]: true, }, @@ -159,7 +156,6 @@ assert.deepEqual( [ { id: 'lab-function', - 'should-exist-in-result': true, [insertOrderKey]: 0, [insertBeforeKey]: true, }, @@ -180,13 +176,11 @@ assert.deepEqual( [ { id: 'lab-function', - 'should-exist-in-result': true, [insertOrderKey]: 0, [insertAfterKey]: true, }, { id: 'lab-function', - 'should-exist-in-result': true, [insertOrderKey]: 1, [insertAfterKey]: true, }, @@ -207,13 +201,11 @@ assert.deepEqual( [ { id: 'lab-function', - 'should-exist-in-result': true, [insertOrderKey]: 0, [insertBeforeKey]: true, }, { id: 'lab-function', - 'should-exist-in-result': true, [insertOrderKey]: 1, [insertBeforeKey]: true, }, @@ -241,7 +233,6 @@ assert.deepEqual( [ { id: 'has-pseudo-class', - 'should-exist-in-result': true, [insertAfterKey]: true, [insertOrderKey]: 0, }, diff --git a/rollup/configs/externals.js b/rollup/configs/externals.js index 5e1d812aa..b319cb7ad 100644 --- a/rollup/configs/externals.js +++ b/rollup/configs/externals.js @@ -55,8 +55,10 @@ export const externalsForPlugin = [ /^postcss-\d\.\d$/, /^postcss\/lib\/*/, + '@csstools/postcss-font-format-keywords', '@csstools/postcss-hwb-function', '@csstools/postcss-is-pseudo-class', + '@csstools/postcss-normalize-display-values', 'autoprefixer', 'browserslist', 'caniuse-lite', From 953a747d2c58336181e652d9f74cceadfba7e475 Mon Sep 17 00:00:00 2001 From: romainmenke Date: Sat, 29 Jan 2022 21:13:08 +0100 Subject: [PATCH 4/7] fix --- plugin-packs/postcss-preset-env/.tape.mjs | 3 +-- ...xpect.css => insert.after.match-result.no-array.expect.css} | 0 2 files changed, 1 insertion(+), 2 deletions(-) rename plugin-packs/postcss-preset-env/test/{insert.after.match-result.exec.expect.css => insert.after.match-result.no-array.expect.css} (100%) diff --git a/plugin-packs/postcss-preset-env/.tape.mjs b/plugin-packs/postcss-preset-env/.tape.mjs index f8d95bd90..dc06a1f49 100644 --- a/plugin-packs/postcss-preset-env/.tape.mjs +++ b/plugin-packs/postcss-preset-env/.tape.mjs @@ -286,7 +286,7 @@ postcssTape(plugin)({ } } }, - 'insert:after:match-result:exec': { + 'insert:after:match-result:no-array': { message: 'supports { insertAfter with a single plugin, not an array } usage when looking for a result', options: { stage: 0, @@ -299,7 +299,6 @@ postcssTape(plugin)({ }) } }, - expect: 'insert.after.match-result.expect.css' }, 'import': { message: 'supports { importFrom: { customMedia, customProperties, customSelectors, environmentVariables } } usage', diff --git a/plugin-packs/postcss-preset-env/test/insert.after.match-result.exec.expect.css b/plugin-packs/postcss-preset-env/test/insert.after.match-result.no-array.expect.css similarity index 100% rename from plugin-packs/postcss-preset-env/test/insert.after.match-result.exec.expect.css rename to plugin-packs/postcss-preset-env/test/insert.after.match-result.no-array.expect.css From 4f2604d44cf073220824d58803904419879335de Mon Sep 17 00:00:00 2001 From: romainmenke Date: Sat, 29 Jan 2022 23:48:11 +0100 Subject: [PATCH 5/7] few more tests and simplify postcss-tape expect/result option --- packages/postcss-tape/src/index.ts | 4 +- plugin-packs/postcss-preset-env/.tape.mjs | 17 + .../test/basic.vendors-1.expect.css | 387 ++++++++++++++++++ .../test/basic.vendors-2.expect.css | 8 +- .../test/basic.vendors-3.expect.css | 386 +++++++++++++++++ 5 files changed, 796 insertions(+), 6 deletions(-) create mode 100644 plugin-packs/postcss-preset-env/test/basic.vendors-1.expect.css create mode 100644 plugin-packs/postcss-preset-env/test/basic.vendors-3.expect.css diff --git a/packages/postcss-tape/src/index.ts b/packages/postcss-tape/src/index.ts index 73e3744cd..daa1e4b0c 100644 --- a/packages/postcss-tape/src/index.ts +++ b/packages/postcss-tape/src/index.ts @@ -149,10 +149,10 @@ export default function runner(currentPlugin: PluginCreator) { let resultFilePath = `${testFilePathWithoutExtension}.result.css`; if (testCaseOptions.expect) { - expectFilePath = expectFilePath.replace(testCaseLabel.replace(/:/g, '.') + '.expect.css', testCaseOptions.expect); + expectFilePath = path.join('.', 'test', testCaseOptions.expect); } if (testCaseOptions.result) { - resultFilePath = resultFilePath.replace(testCaseLabel.replace(/:/g, '.') + '.result.css', testCaseOptions.result); + resultFilePath = path.join('.', 'test', testCaseOptions.result); } const plugins = testCaseOptions.plugins ?? [currentPlugin(testCaseOptions.options)]; diff --git a/plugin-packs/postcss-preset-env/.tape.mjs b/plugin-packs/postcss-preset-env/.tape.mjs index dc06a1f49..05a5fb1d9 100644 --- a/plugin-packs/postcss-preset-env/.tape.mjs +++ b/plugin-packs/postcss-preset-env/.tape.mjs @@ -96,9 +96,26 @@ postcssTape(plugin)({ stage: 0 } }, + 'basic:vendors-1': { + message: 'supports { minimumVendorImplementations: 1, enableClientSidePolyfills: false } usage', + options: { + stage: 1, + minimumVendorImplementations: 1, + enableClientSidePolyfills: false + } + }, 'basic:vendors-2': { message: 'supports { minimumVendorImplementations: 2, enableClientSidePolyfills: false } usage', options: { + stage: 1, + minimumVendorImplementations: 2, + enableClientSidePolyfills: false + } + }, + 'basic:vendors-3': { + message: 'supports { minimumVendorImplementations: 3, enableClientSidePolyfills: false } usage', + options: { + stage: 1, minimumVendorImplementations: 2, enableClientSidePolyfills: false } diff --git a/plugin-packs/postcss-preset-env/test/basic.vendors-1.expect.css b/plugin-packs/postcss-preset-env/test/basic.vendors-1.expect.css new file mode 100644 index 000000000..04d62e1f3 --- /dev/null +++ b/plugin-packs/postcss-preset-env/test/basic.vendors-1.expect.css @@ -0,0 +1,387 @@ +:root { + --order: 1; +} + +.test-custom-properties { + order: 1; + order: var(--order); +} + +.test-image-set-function { + background-image: -webkit-image-set(url(img/test.png) 1x, url(img/test-2x.png) 2x); + background-image: image-set(url(img/test.png) 1x, url(img/test-2x.png) 2x); + order: 2; +} + +.test-logical-properties-and-values:dir(ltr) { + margin: 1px 4px 3px 2px; +} + +.test-logical-properties-and-values:dir(rtl) { + margin: 1px 2px 3px 4px; +} + +.test-logical-properties-and-values { + order: 3; + padding-top: 5px; + padding-bottom: 5px; +} + +.test-nesting-rules { + order: 4; + + & p { + order: 5; + } + + order: 6; +} + +.test-nesting-rules, +#test-is-pseudo { + order: 7; + + & + p { + order: 8; + } + + order: 9; +} + +@custom-media --narrow-window (max-width: 30em); + +@media (--narrow-window) { + .test-custom-media-queries { + order: 10; + } +} + +@media (480px <= width < 768px) { + .test-media-query-ranges { + order: 11; + } +} + +@custom-media --dark-mode (prefers-color-scheme: dark); + +@media (--dark-mode) { + body { + background-color: black; + color: white; + } +} + +@custom-selector :--heading h1, h2, h3, h4, h5, h6; + +.test-custom-selectors:--heading { + order:12; +} + +.test-case-insensitive-attributes[frame=hsides],.test-case-insensitive-attributes[frame=Hsides],.test-case-insensitive-attributes[frame=hSides],.test-case-insensitive-attributes[frame=HSides],.test-case-insensitive-attributes[frame=hsIdes],.test-case-insensitive-attributes[frame=HsIdes],.test-case-insensitive-attributes[frame=hSIdes],.test-case-insensitive-attributes[frame=HSIdes],.test-case-insensitive-attributes[frame=hsiDes],.test-case-insensitive-attributes[frame=HsiDes],.test-case-insensitive-attributes[frame=hSiDes],.test-case-insensitive-attributes[frame=HSiDes],.test-case-insensitive-attributes[frame=hsIDes],.test-case-insensitive-attributes[frame=HsIDes],.test-case-insensitive-attributes[frame=hSIDes],.test-case-insensitive-attributes[frame=HSIDes],.test-case-insensitive-attributes[frame=hsidEs],.test-case-insensitive-attributes[frame=HsidEs],.test-case-insensitive-attributes[frame=hSidEs],.test-case-insensitive-attributes[frame=HSidEs],.test-case-insensitive-attributes[frame=hsIdEs],.test-case-insensitive-attributes[frame=HsIdEs],.test-case-insensitive-attributes[frame=hSIdEs],.test-case-insensitive-attributes[frame=HSIdEs],.test-case-insensitive-attributes[frame=hsiDEs],.test-case-insensitive-attributes[frame=HsiDEs],.test-case-insensitive-attributes[frame=hSiDEs],.test-case-insensitive-attributes[frame=HSiDEs],.test-case-insensitive-attributes[frame=hsIDEs],.test-case-insensitive-attributes[frame=HsIDEs],.test-case-insensitive-attributes[frame=hSIDEs],.test-case-insensitive-attributes[frame=HSIDEs],.test-case-insensitive-attributes[frame=hsideS],.test-case-insensitive-attributes[frame=HsideS],.test-case-insensitive-attributes[frame=hSideS],.test-case-insensitive-attributes[frame=HSideS],.test-case-insensitive-attributes[frame=hsIdeS],.test-case-insensitive-attributes[frame=HsIdeS],.test-case-insensitive-attributes[frame=hSIdeS],.test-case-insensitive-attributes[frame=HSIdeS],.test-case-insensitive-attributes[frame=hsiDeS],.test-case-insensitive-attributes[frame=HsiDeS],.test-case-insensitive-attributes[frame=hSiDeS],.test-case-insensitive-attributes[frame=HSiDeS],.test-case-insensitive-attributes[frame=hsIDeS],.test-case-insensitive-attributes[frame=HsIDeS],.test-case-insensitive-attributes[frame=hSIDeS],.test-case-insensitive-attributes[frame=HSIDeS],.test-case-insensitive-attributes[frame=hsidES],.test-case-insensitive-attributes[frame=HsidES],.test-case-insensitive-attributes[frame=hSidES],.test-case-insensitive-attributes[frame=HSidES],.test-case-insensitive-attributes[frame=hsIdES],.test-case-insensitive-attributes[frame=HsIdES],.test-case-insensitive-attributes[frame=hSIdES],.test-case-insensitive-attributes[frame=HSIdES],.test-case-insensitive-attributes[frame=hsiDES],.test-case-insensitive-attributes[frame=HsiDES],.test-case-insensitive-attributes[frame=hSiDES],.test-case-insensitive-attributes[frame=HSiDES],.test-case-insensitive-attributes[frame=hsIDES],.test-case-insensitive-attributes[frame=HsIDES],.test-case-insensitive-attributes[frame=hSIDES],.test-case-insensitive-attributes[frame=HSIDES] { + order: 13; +} + +.test-rebeccapurple-color { + color: #639; + order: 14; +} + +.test-hexadecimal-alpha-notation { + background-color: rgba(243,243,243,0.95294); + color: rgba(0,0,0,0.2); + order: 15; +} + +.test-color-functional-notation { + color: rgba(178, 34, 34, 0.5); + order: 16; +} + +.test-lab-function { + background-color: lab(40% 56.6 39); + color: lch(40% 68.8 34.5 / 50%); + order: 17; +} + +.test-system-ui-font-family { + font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, sans-serif; + order: 18; +} + +.test-font-variant-property { + font-feature-settings: "smcp"; + font-variant-caps: small-caps; + order: 19; +} + +.test-all-property { + -webkit-animation: none 0s ease 0s 1 normal none running; + animation: none 0s ease 0s 1 normal none running; + -webkit-backface-visibility: visible; + backface-visibility: visible; + background: transparent none repeat 0 0 / auto auto padding-box border-box scroll; + border: medium none currentColor; + border-collapse: separate; + -o-border-image: none; + border-image: none; + border-radius: 0; + border-spacing: 0; + bottom: auto; + box-shadow: none; + box-sizing: content-box; + caption-side: top; + clear: none; + clip: auto; + color: #000; + -moz-columns: auto; + columns: auto; + -moz-column-count: auto; + column-count: auto; + -moz-column-fill: balance; + column-fill: balance; + -moz-column-gap: normal; + column-gap: normal; + -moz-column-rule: medium none currentColor; + column-rule: medium none currentColor; + -moz-column-span: 1; + column-span: 1; + -moz-column-width: auto; + column-width: auto; + content: normal; + counter-increment: none; + counter-reset: none; + cursor: auto; + direction: ltr; + display: inline; + empty-cells: show; + float: none; + font-family: serif; + font-size: medium; + font-style: normal; + font-variant: normal; + font-weight: normal; + font-stretch: normal; + line-height: normal; + height: auto; + -webkit-hyphens: none; + -ms-hyphens: none; + hyphens: none; + left: auto; + letter-spacing: normal; + list-style: disc outside none; + margin: 0; + max-height: none; + max-width: none; + min-height: 0; + min-width: 0; + opacity: 1; + orphans: 2; + outline: medium none invert; + overflow: visible; + overflow-x: visible; + overflow-y: visible; + padding: 0; + page-break-after: auto; + page-break-before: auto; + page-break-inside: auto; + perspective: none; + perspective-origin: 50% 50%; + position: static; + right: auto; + -moz-tab-size: 8; + -o-tab-size: 8; + tab-size: 8; + table-layout: auto; + text-align: left; + -moz-text-align-last: auto; + text-align-last: auto; + text-decoration: none; + text-indent: 0; + text-shadow: none; + text-transform: none; + top: auto; + transform: none; + transform-origin: 50% 50% 0; + transform-style: flat; + transition: none 0s ease 0s; + unicode-bidi: normal; + vertical-align: baseline; + visibility: visible; + white-space: normal; + widows: 2; + width: auto; + word-spacing: normal; + z-index: auto; + all: initial; + order: 20; +} + +.test-matches-pseudo-class:matches(:first-child, .special) { + order: 21; +} + +.test-not-pseudo-class:not(:first-child):not(.special) { + order: 22; +} + +.test-any-link-pseudo-class:link, .test-any-link-pseudo-class:visited, area.test-any-link-pseudo-class[href] { + order: 23; +} + +.test-any-link-pseudo-class:-webkit-any-link { + order: 23; +} + +.test-any-link-pseudo-class:-moz-any-link { + order: 23; +} + +.test-any-link-pseudo-class:any-link { + order: 23; +} + +.test-dir-pseudo-class:dir(rtl) { + order: 24; +} + +.test-overflow-wrap-property { + order: 25; + word-wrap: break-word; +} + +.test-focus-visible-pseudo-class:focus-visible { + order: 26; +} + +.test-double-position-gradients { + background-image: conic-gradient(yellowgreen 40%, gold 0deg,gold 75%, #f06 0deg); + background-image: conic-gradient(yellowgreen 40%, gold 0deg 75%, #f06 0deg); +} + +.test-blank-pseudo-class:blank { + background-color: yellow; +} + +.test-has-pseudo-class:has(.inner-class) { + background-color: yellow; +} + +.a:focus { + order: 27; +} + +.a:hover { + order: 27; +} + +.b:focus { + order: 27; +} + +.b:hover { + order: 27; +} + +.a.c > .b + .d { + order: 28; +} + +.test-hwb-function { + background-color: rgba(0, 195, 255, .5); +} + +.test-opacity-percent { + opacity: 0.42; +} + +.clamp-same-unit { + width: max(10px, min(64px, 80px)); +} + +.complex-clamp { + width: max(calc(100% - 10px), min(min(10px, 100%), max(40px, 4em))); +} + +.clamp-different-units { + width: max(10%, min(2px, 4rem)); +} + +@font-face { + font-family: 'A'; + font-style: normal; + font-weight: 300; + font-display: swap; + src: url(a) format(woff2); +} + +.block-flow { + display: block flow; +} + +.block-flow-root { + display: block flow-root; +} + +.inline-flow { + display: inline flow; +} + +.inline-flow-root { + display: inline flow-root; +} + +.run-in-flow { + display: run-in flow; +} + +.list-item-block-flow { + display: list-item block flow; +} + +.inline-flow-list-item { + display: inline flow list-item; +} + +.block-flex { + display: block flex; +} + +.inline-flex { + display: inline flex; +} + +.block-grid { + display: block grid; +} + +.inline-grid { + display: inline grid; +} + +.inline-ruby { + display: inline ruby; +} + +.block-table { + display: block table; +} + +.inline-table { + display: inline table; +} + +.table-cell-flow { + display: table-cell flow; +} + +.table-caption-flow { + display: table-caption flow; +} + +.ruby-base-flow { + display: ruby-base flow; +} + +.ruby-text-flow { + display: ruby-text flow; +} diff --git a/plugin-packs/postcss-preset-env/test/basic.vendors-2.expect.css b/plugin-packs/postcss-preset-env/test/basic.vendors-2.expect.css index 4ef33aba5..64f05ce29 100644 --- a/plugin-packs/postcss-preset-env/test/basic.vendors-2.expect.css +++ b/plugin-packs/postcss-preset-env/test/basic.vendors-2.expect.css @@ -93,7 +93,7 @@ } .test-color-functional-notation { - color: rgb(70% 13.5% 13.5% / 50%); + color: rgba(178, 34, 34, 0.5); order: 16; } @@ -294,15 +294,15 @@ } .clamp-same-unit { - width: clamp(10px, 64px, 80px); + width: max(10px, min(64px, 80px)); } .complex-clamp { - width: clamp(calc(100% - 10px), min(10px, 100%), max(40px, 4em)); + width: max(calc(100% - 10px), min(min(10px, 100%), max(40px, 4em))); } .clamp-different-units { - width: clamp(10%, 2px, 4rem); + width: max(10%, min(2px, 4rem)); } @font-face { diff --git a/plugin-packs/postcss-preset-env/test/basic.vendors-3.expect.css b/plugin-packs/postcss-preset-env/test/basic.vendors-3.expect.css new file mode 100644 index 000000000..64f05ce29 --- /dev/null +++ b/plugin-packs/postcss-preset-env/test/basic.vendors-3.expect.css @@ -0,0 +1,386 @@ +:root { + --order: 1; +} + +.test-custom-properties { + order: 1; + order: var(--order); +} + +.test-image-set-function { + background-image: -webkit-image-set(url(img/test.png) 1x, url(img/test-2x.png) 2x); + background-image: image-set(url(img/test.png) 1x, url(img/test-2x.png) 2x); + order: 2; +} + +.test-logical-properties-and-values:dir(ltr) { + margin: 1px 4px 3px 2px; +} + +.test-logical-properties-and-values:dir(rtl) { + margin: 1px 2px 3px 4px; +} + +.test-logical-properties-and-values { + order: 3; + padding-top: 5px; + padding-bottom: 5px; +} + +.test-nesting-rules { + order: 4; + + & p { + order: 5; + } + + order: 6; +} + +.test-nesting-rules, +#test-is-pseudo { + order: 7; + + & + p { + order: 8; + } + + order: 9; +} + +@custom-media --narrow-window (max-width: 30em); + +@media (--narrow-window) { + .test-custom-media-queries { + order: 10; + } +} + +@media (480px <= width < 768px) { + .test-media-query-ranges { + order: 11; + } +} + +@custom-media --dark-mode (prefers-color-scheme: dark); + +@media (--dark-mode) { + body { + background-color: black; + color: white; + } +} + +@custom-selector :--heading h1, h2, h3, h4, h5, h6; + +.test-custom-selectors:--heading { + order:12; +} + +.test-case-insensitive-attributes[frame=hsides],.test-case-insensitive-attributes[frame=Hsides],.test-case-insensitive-attributes[frame=hSides],.test-case-insensitive-attributes[frame=HSides],.test-case-insensitive-attributes[frame=hsIdes],.test-case-insensitive-attributes[frame=HsIdes],.test-case-insensitive-attributes[frame=hSIdes],.test-case-insensitive-attributes[frame=HSIdes],.test-case-insensitive-attributes[frame=hsiDes],.test-case-insensitive-attributes[frame=HsiDes],.test-case-insensitive-attributes[frame=hSiDes],.test-case-insensitive-attributes[frame=HSiDes],.test-case-insensitive-attributes[frame=hsIDes],.test-case-insensitive-attributes[frame=HsIDes],.test-case-insensitive-attributes[frame=hSIDes],.test-case-insensitive-attributes[frame=HSIDes],.test-case-insensitive-attributes[frame=hsidEs],.test-case-insensitive-attributes[frame=HsidEs],.test-case-insensitive-attributes[frame=hSidEs],.test-case-insensitive-attributes[frame=HSidEs],.test-case-insensitive-attributes[frame=hsIdEs],.test-case-insensitive-attributes[frame=HsIdEs],.test-case-insensitive-attributes[frame=hSIdEs],.test-case-insensitive-attributes[frame=HSIdEs],.test-case-insensitive-attributes[frame=hsiDEs],.test-case-insensitive-attributes[frame=HsiDEs],.test-case-insensitive-attributes[frame=hSiDEs],.test-case-insensitive-attributes[frame=HSiDEs],.test-case-insensitive-attributes[frame=hsIDEs],.test-case-insensitive-attributes[frame=HsIDEs],.test-case-insensitive-attributes[frame=hSIDEs],.test-case-insensitive-attributes[frame=HSIDEs],.test-case-insensitive-attributes[frame=hsideS],.test-case-insensitive-attributes[frame=HsideS],.test-case-insensitive-attributes[frame=hSideS],.test-case-insensitive-attributes[frame=HSideS],.test-case-insensitive-attributes[frame=hsIdeS],.test-case-insensitive-attributes[frame=HsIdeS],.test-case-insensitive-attributes[frame=hSIdeS],.test-case-insensitive-attributes[frame=HSIdeS],.test-case-insensitive-attributes[frame=hsiDeS],.test-case-insensitive-attributes[frame=HsiDeS],.test-case-insensitive-attributes[frame=hSiDeS],.test-case-insensitive-attributes[frame=HSiDeS],.test-case-insensitive-attributes[frame=hsIDeS],.test-case-insensitive-attributes[frame=HsIDeS],.test-case-insensitive-attributes[frame=hSIDeS],.test-case-insensitive-attributes[frame=HSIDeS],.test-case-insensitive-attributes[frame=hsidES],.test-case-insensitive-attributes[frame=HsidES],.test-case-insensitive-attributes[frame=hSidES],.test-case-insensitive-attributes[frame=HSidES],.test-case-insensitive-attributes[frame=hsIdES],.test-case-insensitive-attributes[frame=HsIdES],.test-case-insensitive-attributes[frame=hSIdES],.test-case-insensitive-attributes[frame=HSIdES],.test-case-insensitive-attributes[frame=hsiDES],.test-case-insensitive-attributes[frame=HsiDES],.test-case-insensitive-attributes[frame=hSiDES],.test-case-insensitive-attributes[frame=HSiDES],.test-case-insensitive-attributes[frame=hsIDES],.test-case-insensitive-attributes[frame=HsIDES],.test-case-insensitive-attributes[frame=hSIDES],.test-case-insensitive-attributes[frame=HSIDES] { + order: 13; +} + +.test-rebeccapurple-color { + color: #639; + order: 14; +} + +.test-hexadecimal-alpha-notation { + background-color: rgba(243,243,243,0.95294); + color: rgba(0,0,0,0.2); + order: 15; +} + +.test-color-functional-notation { + color: rgba(178, 34, 34, 0.5); + order: 16; +} + +.test-lab-function { + background-color: lab(40% 56.6 39); + color: lch(40% 68.8 34.5 / 50%); + order: 17; +} + +.test-system-ui-font-family { + font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, sans-serif; + order: 18; +} + +.test-font-variant-property { + font-variant-caps: small-caps; + order: 19; +} + +.test-all-property { + -webkit-animation: none 0s ease 0s 1 normal none running; + animation: none 0s ease 0s 1 normal none running; + -webkit-backface-visibility: visible; + backface-visibility: visible; + background: transparent none repeat 0 0 / auto auto padding-box border-box scroll; + border: medium none currentColor; + border-collapse: separate; + -o-border-image: none; + border-image: none; + border-radius: 0; + border-spacing: 0; + bottom: auto; + box-shadow: none; + box-sizing: content-box; + caption-side: top; + clear: none; + clip: auto; + color: #000; + -moz-columns: auto; + columns: auto; + -moz-column-count: auto; + column-count: auto; + -moz-column-fill: balance; + column-fill: balance; + -moz-column-gap: normal; + column-gap: normal; + -moz-column-rule: medium none currentColor; + column-rule: medium none currentColor; + -moz-column-span: 1; + column-span: 1; + -moz-column-width: auto; + column-width: auto; + content: normal; + counter-increment: none; + counter-reset: none; + cursor: auto; + direction: ltr; + display: inline; + empty-cells: show; + float: none; + font-family: serif; + font-size: medium; + font-style: normal; + font-variant: normal; + font-weight: normal; + font-stretch: normal; + line-height: normal; + height: auto; + -webkit-hyphens: none; + -ms-hyphens: none; + hyphens: none; + left: auto; + letter-spacing: normal; + list-style: disc outside none; + margin: 0; + max-height: none; + max-width: none; + min-height: 0; + min-width: 0; + opacity: 1; + orphans: 2; + outline: medium none invert; + overflow: visible; + overflow-x: visible; + overflow-y: visible; + padding: 0; + page-break-after: auto; + page-break-before: auto; + page-break-inside: auto; + perspective: none; + perspective-origin: 50% 50%; + position: static; + right: auto; + -moz-tab-size: 8; + -o-tab-size: 8; + tab-size: 8; + table-layout: auto; + text-align: left; + -moz-text-align-last: auto; + text-align-last: auto; + text-decoration: none; + text-indent: 0; + text-shadow: none; + text-transform: none; + top: auto; + transform: none; + transform-origin: 50% 50% 0; + transform-style: flat; + transition: none 0s ease 0s; + unicode-bidi: normal; + vertical-align: baseline; + visibility: visible; + white-space: normal; + widows: 2; + width: auto; + word-spacing: normal; + z-index: auto; + all: initial; + order: 20; +} + +.test-matches-pseudo-class:matches(:first-child, .special) { + order: 21; +} + +.test-not-pseudo-class:not(:first-child):not(.special) { + order: 22; +} + +.test-any-link-pseudo-class:link, .test-any-link-pseudo-class:visited, area.test-any-link-pseudo-class[href] { + order: 23; +} + +.test-any-link-pseudo-class:-webkit-any-link { + order: 23; +} + +.test-any-link-pseudo-class:-moz-any-link { + order: 23; +} + +.test-any-link-pseudo-class:any-link { + order: 23; +} + +.test-dir-pseudo-class:dir(rtl) { + order: 24; +} + +.test-overflow-wrap-property { + order: 25; + word-wrap: break-word; +} + +.test-focus-visible-pseudo-class:focus-visible { + order: 26; +} + +.test-double-position-gradients { + background-image: conic-gradient(yellowgreen 40%, gold 0deg,gold 75%, #f06 0deg); + background-image: conic-gradient(yellowgreen 40%, gold 0deg 75%, #f06 0deg); +} + +.test-blank-pseudo-class:blank { + background-color: yellow; +} + +.test-has-pseudo-class:has(.inner-class) { + background-color: yellow; +} + +.a:focus { + order: 27; +} + +.a:hover { + order: 27; +} + +.b:focus { + order: 27; +} + +.b:hover { + order: 27; +} + +.a.c > .b + .d { + order: 28; +} + +.test-hwb-function { + background-color: hwb(194 0% 0% / .5); +} + +.test-opacity-percent { + opacity: 42%; +} + +.clamp-same-unit { + width: max(10px, min(64px, 80px)); +} + +.complex-clamp { + width: max(calc(100% - 10px), min(min(10px, 100%), max(40px, 4em))); +} + +.clamp-different-units { + width: max(10%, min(2px, 4rem)); +} + +@font-face { + font-family: 'A'; + font-style: normal; + font-weight: 300; + font-display: swap; + src: url(a) format(woff2); +} + +.block-flow { + display: block flow; +} + +.block-flow-root { + display: block flow-root; +} + +.inline-flow { + display: inline flow; +} + +.inline-flow-root { + display: inline flow-root; +} + +.run-in-flow { + display: run-in flow; +} + +.list-item-block-flow { + display: list-item block flow; +} + +.inline-flow-list-item { + display: inline flow list-item; +} + +.block-flex { + display: block flex; +} + +.inline-flex { + display: inline flex; +} + +.block-grid { + display: block grid; +} + +.inline-grid { + display: inline grid; +} + +.inline-ruby { + display: inline ruby; +} + +.block-table { + display: block table; +} + +.inline-table { + display: inline table; +} + +.table-cell-flow { + display: table-cell flow; +} + +.table-caption-flow { + display: table-caption flow; +} + +.ruby-base-flow { + display: ruby-base flow; +} + +.ruby-text-flow { + display: ruby-text flow; +} From 65f612fdd70ddbafd29947451b54165bc28e22ab Mon Sep 17 00:00:00 2001 From: romainmenke Date: Sat, 29 Jan 2022 23:52:32 +0100 Subject: [PATCH 6/7] fix --- plugin-packs/postcss-preset-env/.tape.mjs | 2 +- .../src/lib/list-features.mjs | 2 +- .../test/basic.vendors-1.expect.css | 30 +++++++++++++++---- .../test/basic.vendors-2.expect.css | 5 ++-- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/plugin-packs/postcss-preset-env/.tape.mjs b/plugin-packs/postcss-preset-env/.tape.mjs index 05a5fb1d9..c5a630935 100644 --- a/plugin-packs/postcss-preset-env/.tape.mjs +++ b/plugin-packs/postcss-preset-env/.tape.mjs @@ -116,7 +116,7 @@ postcssTape(plugin)({ message: 'supports { minimumVendorImplementations: 3, enableClientSidePolyfills: false } usage', options: { stage: 1, - minimumVendorImplementations: 2, + minimumVendorImplementations: 3, enableClientSidePolyfills: false } }, diff --git a/plugin-packs/postcss-preset-env/src/lib/list-features.mjs b/plugin-packs/postcss-preset-env/src/lib/list-features.mjs index f1dd0d8de..ad2c0a0c0 100644 --- a/plugin-packs/postcss-preset-env/src/lib/list-features.mjs +++ b/plugin-packs/postcss-preset-env/src/lib/list-features.mjs @@ -47,7 +47,7 @@ export function listFeatures(cssdbList, options, sharedOptions) { return true; } - if ( minimumVendorImplementations < feature.vendors_implementations ) { + if ( minimumVendorImplementations <= feature.vendors_implementations ) { return true; } diff --git a/plugin-packs/postcss-preset-env/test/basic.vendors-1.expect.css b/plugin-packs/postcss-preset-env/test/basic.vendors-1.expect.css index 04d62e1f3..525450dc0 100644 --- a/plugin-packs/postcss-preset-env/test/basic.vendors-1.expect.css +++ b/plugin-packs/postcss-preset-env/test/basic.vendors-1.expect.css @@ -13,11 +13,11 @@ order: 2; } -.test-logical-properties-and-values:dir(ltr) { +[dir="ltr"] .test-logical-properties-and-values { margin: 1px 4px 3px 2px; } -.test-logical-properties-and-values:dir(rtl) { +[dir="rtl"] .test-logical-properties-and-values { margin: 1px 2px 3px 4px; } @@ -98,8 +98,8 @@ } .test-lab-function { - background-color: lab(40% 56.6 39); - color: lch(40% 68.8 34.5 / 50%); + background-color: rgb(179, 35, 35); + color: rgba(179, 34, 35, 0.5); order: 17; } @@ -240,7 +240,7 @@ order: 23; } -.test-dir-pseudo-class:dir(rtl) { +[dir="rtl"] .test-dir-pseudo-class { order: 24; } @@ -311,77 +311,95 @@ font-style: normal; font-weight: 300; font-display: swap; - src: url(a) format(woff2); + src: url(a) format("woff2"); } .block-flow { + display: block; display: block flow; } .block-flow-root { + display: flow-root; display: block flow-root; } .inline-flow { + display: inline; display: inline flow; } .inline-flow-root { + display: inline-block; display: inline flow-root; } .run-in-flow { + display: run-in; display: run-in flow; } .list-item-block-flow { + display: list-item; display: list-item block flow; } .inline-flow-list-item { + display: inline list-item; display: inline flow list-item; } .block-flex { + display: flex; display: block flex; } .inline-flex { + display: inline-flex; display: inline flex; } .block-grid { + display: grid; display: block grid; } .inline-grid { + display: inline-grid; display: inline grid; } .inline-ruby { + display: ruby; display: inline ruby; } .block-table { + display: table; display: block table; } .inline-table { + display: inline-table; display: inline table; } .table-cell-flow { + display: table-cell; display: table-cell flow; } .table-caption-flow { + display: table-caption; display: table-caption flow; } .ruby-base-flow { + display: ruby-base; display: ruby-base flow; } .ruby-text-flow { + display: ruby-text; display: ruby-text flow; } diff --git a/plugin-packs/postcss-preset-env/test/basic.vendors-2.expect.css b/plugin-packs/postcss-preset-env/test/basic.vendors-2.expect.css index 64f05ce29..04d62e1f3 100644 --- a/plugin-packs/postcss-preset-env/test/basic.vendors-2.expect.css +++ b/plugin-packs/postcss-preset-env/test/basic.vendors-2.expect.css @@ -109,6 +109,7 @@ } .test-font-variant-property { + font-feature-settings: "smcp"; font-variant-caps: small-caps; order: 19; } @@ -286,11 +287,11 @@ } .test-hwb-function { - background-color: hwb(194 0% 0% / .5); + background-color: rgba(0, 195, 255, .5); } .test-opacity-percent { - opacity: 42%; + opacity: 0.42; } .clamp-same-unit { From fcfb405204ba742f934efee35b7991cc3a296944 Mon Sep 17 00:00:00 2001 From: romainmenke Date: Sun, 30 Jan 2022 17:20:16 +0100 Subject: [PATCH 7/7] update docs --- plugin-packs/postcss-preset-env/README.md | 24 ++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/plugin-packs/postcss-preset-env/README.md b/plugin-packs/postcss-preset-env/README.md index 94b185305..fae3bb3f5 100644 --- a/plugin-packs/postcss-preset-env/README.md +++ b/plugin-packs/postcss-preset-env/README.md @@ -154,10 +154,19 @@ postcssPresetEnv({ minimumVendorImplementations: 2 }) ``` `minimumVendorImplementations` can be `0` (no vendor has implemented it) through `3` (all major vendors).
-A value of `2` is recommended when you want to use only those features that are stable. Default: `0` +**Note:** + +When a feature has not yet been implemented by any vendor it can be considered experimental.
+Even with a single implementation it might still change in the future.
+Sometimes issues with a feature/specification are only discovered after it becomes available. + +A value of `2` is recommended when you want to use only those features that should be [stable](#stability-and-portability). + +Having 2 independent implementations is [a critical step in proposals becoming standards](https://www.w3.org/2021/Process-20211102/#implementation-experience) and a good indicator of a feature's stability. + ### features The `features` option enables or disables specific polyfills by ID. Passing @@ -427,6 +436,19 @@ The `enableClientSidePolyfills` enables any feature that would need an extra bro Note that manually enabling/disabling features via the "feature" option overrides this flag. +## Stability and Portability + +[PostCSS Preset Env] will often include very modern CSS features that are not fully ready yet. +This gives users the chance to play around with these features and provide feedback. + +If the specification changes or is abandoned a new major version of the plugin will be released. +This will require you to update your source code so that everything works as expected. + +To have more stability between updates of [PostCSS Preset Env] you may set `stage: 3` and/or `minimumVendorImplementations: 2`. + +A side effect of staying close to the standard is that you can more easily migrate your project to other tooling all together. + + [cli-img]: https://github.com/csstools/postcss-plugins/workflows/test/badge.svg [cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test [discord]: https://discord.gg/bUadyRwkJS