diff --git a/.github/ISSUE_TEMPLATE/css-issue.yml b/.github/ISSUE_TEMPLATE/css-issue.yml
index 537325a3c..de9e120c7 100644
--- a/.github/ISSUE_TEMPLATE/css-issue.yml
+++ b/.github/ISSUE_TEMPLATE/css-issue.yml
@@ -81,6 +81,7 @@ body:
- PostCSS Focus Within
- PostCSS Font Variant
- PostCSS Gap Properties
+ - PostCSS Global Data
- PostCSS Gradients Interpolation Method
- PostCSS HWB Function
- PostCSS Image Set Function
diff --git a/.github/ISSUE_TEMPLATE/plugin-issue.yml b/.github/ISSUE_TEMPLATE/plugin-issue.yml
index 24223e904..750673a98 100644
--- a/.github/ISSUE_TEMPLATE/plugin-issue.yml
+++ b/.github/ISSUE_TEMPLATE/plugin-issue.yml
@@ -83,6 +83,7 @@ body:
- PostCSS Focus Within
- PostCSS Font Variant
- PostCSS Gap Properties
+ - PostCSS Global Data
- PostCSS Gradients Interpolation Method
- PostCSS HWB Function
- PostCSS Image Set Function
diff --git a/.github/bin/generate-docs/readme.mjs b/.github/bin/generate-docs/readme.mjs
index 9445b5d57..36685845d 100644
--- a/.github/bin/generate-docs/readme.mjs
+++ b/.github/bin/generate-docs/readme.mjs
@@ -6,7 +6,13 @@ const template = await fsp.readFile(path.join('docs', './README.md'), 'utf8');
const corsTemplate = await fsp.readFile(path.join(path.dirname(fileURLToPath(import.meta.url)), './cors-template.md'), 'utf8');
const packageJSONInfo = JSON.parse(await fsp.readFile('./package.json', 'utf8'));
-const exampleFilePaths = await fsp.readdir(path.join('test', 'examples'));
+let exampleFilePaths = [];
+
+try {
+ exampleFilePaths = await fsp.readdir(path.join('test', 'examples'));
+} catch(error) {
+ // No examples
+}
let readmeDoc = template.toString();
diff --git a/.github/labeler.yml b/.github/labeler.yml
index f0c8c0a46..4ec78093c 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -119,6 +119,10 @@
- plugins/postcss-gap-properties/**
- experimental/postcss-gap-properties/**
+"plugins/postcss-global-data":
+ - plugins/postcss-global-data/**
+ - experimental/postcss-global-data/**
+
"plugins/postcss-gradients-interpolation-method":
- plugins/postcss-gradients-interpolation-method/**
- experimental/postcss-gradients-interpolation-method/**
diff --git a/package-lock.json b/package-lock.json
index a9fcbeb5a..749b32f41 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1879,6 +1879,10 @@
"resolved": "plugins/postcss-font-format-keywords",
"link": true
},
+ "node_modules/@csstools/postcss-global-data": {
+ "resolved": "plugins/postcss-global-data",
+ "link": true
+ },
"node_modules/@csstools/postcss-gradients-interpolation-method": {
"resolved": "plugins/postcss-gradients-interpolation-method",
"link": true
@@ -7655,6 +7659,24 @@
"postcss": "^8.4"
}
},
+ "plugins/postcss-global-data": {
+ "name": "@csstools/postcss-global-data",
+ "version": "0.0.0",
+ "license": "CC0-1.0",
+ "devDependencies": {
+ "postcss-custom-media": "^9.0.0"
+ },
+ "engines": {
+ "node": "^14 || ^16 || >=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4"
+ }
+ },
"plugins/postcss-gradients-interpolation-method": {
"name": "@csstools/postcss-gradients-interpolation-method",
"version": "2.0.1",
diff --git a/plugins/postcss-global-data/.gitignore b/plugins/postcss-global-data/.gitignore
new file mode 100644
index 000000000..e5b28db4a
--- /dev/null
+++ b/plugins/postcss-global-data/.gitignore
@@ -0,0 +1,6 @@
+node_modules
+package-lock.json
+yarn.lock
+*.result.css
+*.result.css.map
+*.result.html
diff --git a/plugins/postcss-global-data/.nvmrc b/plugins/postcss-global-data/.nvmrc
new file mode 100644
index 000000000..39e593ebe
--- /dev/null
+++ b/plugins/postcss-global-data/.nvmrc
@@ -0,0 +1 @@
+v18.8.0
diff --git a/plugins/postcss-global-data/.tape.mjs b/plugins/postcss-global-data/.tape.mjs
new file mode 100644
index 000000000..71eaaf013
--- /dev/null
+++ b/plugins/postcss-global-data/.tape.mjs
@@ -0,0 +1,17 @@
+import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs';
+import plugin from '@csstools/postcss-global-data';
+import postcssCustomMedia from 'postcss-custom-media';
+
+postcssTape(plugin)({
+ basic: {
+ message: "supports basic usage",
+ plugins: [
+ plugin({
+ files: [
+ './test/fixtures/fixture.css',
+ ]
+ }),
+ postcssCustomMedia(),
+ ],
+ },
+});
diff --git a/plugins/postcss-global-data/CHANGELOG.md b/plugins/postcss-global-data/CHANGELOG.md
new file mode 100644
index 000000000..26539bb4b
--- /dev/null
+++ b/plugins/postcss-global-data/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Changes to PostCSS global-data
+
+### Unreleased (major)
+
+- Initial version
diff --git a/plugins/postcss-global-data/INSTALL.md b/plugins/postcss-global-data/INSTALL.md
new file mode 100644
index 000000000..2026d5eb3
--- /dev/null
+++ b/plugins/postcss-global-data/INSTALL.md
@@ -0,0 +1,235 @@
+# Installing PostCSS Global Data
+
+[PostCSS Global Data] runs in all Node environments, with special instructions for:
+
+- [Node](#node)
+- [PostCSS CLI](#postcss-cli)
+- [PostCSS Load Config](#postcss-load-config)
+- [Webpack](#webpack)
+- [Next.js](#nextjs)
+- [Gulp](#gulp)
+- [Grunt](#grunt)
+
+
+
+## Node
+
+Add [PostCSS Global Data] to your project:
+
+```bash
+npm install postcss @csstools/postcss-global-data --save-dev
+```
+
+Use it as a [PostCSS] plugin:
+
+```js
+// commonjs
+const postcss = require('postcss');
+const postcssGlobalData = require('@csstools/postcss-global-data');
+
+postcss([
+ postcssGlobalData(/* pluginOptions */)
+]).process(YOUR_CSS /*, processOptions */);
+```
+
+```js
+// esm
+import postcss from 'postcss';
+import postcssGlobalData from '@csstools/postcss-global-data';
+
+postcss([
+ postcssGlobalData(/* pluginOptions */)
+]).process(YOUR_CSS /*, processOptions */);
+```
+
+## PostCSS CLI
+
+Add [PostCSS CLI] to your project:
+
+```bash
+npm install postcss-cli @csstools/postcss-global-data --save-dev
+```
+
+Use [PostCSS Global Data] in your `postcss.config.js` configuration file:
+
+```js
+const postcssGlobalData = require('@csstools/postcss-global-data');
+
+module.exports = {
+ plugins: [
+ postcssGlobalData(/* pluginOptions */)
+ ]
+}
+```
+
+## PostCSS Load Config
+
+If your framework/CLI supports [`postcss-load-config`](https://github.com/postcss/postcss-load-config).
+
+```bash
+npm install @csstools/postcss-global-data --save-dev
+```
+
+`package.json`:
+
+```json
+{
+ "postcss": {
+ "plugins": {
+ "@csstools/postcss-global-data": {}
+ }
+ }
+}
+```
+
+`.postcssrc.json`:
+
+```json
+{
+ "plugins": {
+ "@csstools/postcss-global-data": {}
+ }
+}
+```
+
+_See the [README of `postcss-load-config`](https://github.com/postcss/postcss-load-config#usage) for more usage options._
+
+## Webpack
+
+_Webpack version 5_
+
+Add [PostCSS Loader] to your project:
+
+```bash
+npm install postcss-loader @csstools/postcss-global-data --save-dev
+```
+
+Use [PostCSS Global Data] in your Webpack configuration:
+
+```js
+module.exports = {
+ module: {
+ rules: [
+ {
+ test: /\.css$/i,
+ use: [
+ "style-loader",
+ {
+ loader: "css-loader",
+ options: { importLoaders: 1 },
+ },
+ {
+ loader: "postcss-loader",
+ options: {
+ postcssOptions: {
+ plugins: [
+ // Other plugins,
+ [
+ "@csstools/postcss-global-data",
+ {
+ // Options
+ },
+ ],
+ ],
+ },
+ },
+ },
+ ],
+ },
+ ],
+ },
+};
+```
+
+## Next.js
+
+Read the instructions on how to [customize the PostCSS configuration in Next.js](https://nextjs.org/docs/advanced-features/customizing-postcss-config)
+
+```bash
+npm install @csstools/postcss-global-data --save-dev
+```
+
+Use [PostCSS Global Data] in your `postcss.config.json` file:
+
+```json
+{
+ "plugins": [
+ "@csstools/postcss-global-data"
+ ]
+}
+```
+
+```json5
+{
+ "plugins": [
+ [
+ "@csstools/postcss-global-data",
+ {
+ // Optionally add plugin options
+ }
+ ]
+ ]
+}
+```
+
+## Gulp
+
+Add [Gulp PostCSS] to your project:
+
+```bash
+npm install gulp-postcss @csstools/postcss-global-data --save-dev
+```
+
+Use [PostCSS Global Data] in your Gulpfile:
+
+```js
+const postcss = require('gulp-postcss');
+const postcssGlobalData = require('@csstools/postcss-global-data');
+
+gulp.task('css', function () {
+ var plugins = [
+ postcssGlobalData(/* pluginOptions */)
+ ];
+
+ return gulp.src('./src/*.css')
+ .pipe(postcss(plugins))
+ .pipe(gulp.dest('.'));
+});
+```
+
+## Grunt
+
+Add [Grunt PostCSS] to your project:
+
+```bash
+npm install grunt-postcss @csstools/postcss-global-data --save-dev
+```
+
+Use [PostCSS Global Data] in your Gruntfile:
+
+```js
+const postcssGlobalData = require('@csstools/postcss-global-data');
+
+grunt.loadNpmTasks('grunt-postcss');
+
+grunt.initConfig({
+ postcss: {
+ options: {
+ processors: [
+ postcssGlobalData(/* pluginOptions */)
+ ]
+ },
+ dist: {
+ src: '*.css'
+ }
+ }
+});
+```
+
+[Gulp PostCSS]: https://github.com/postcss/gulp-postcss
+[Grunt PostCSS]: https://github.com/nDmitry/grunt-postcss
+[PostCSS]: https://github.com/postcss/postcss
+[PostCSS CLI]: https://github.com/postcss/postcss-cli
+[PostCSS Loader]: https://github.com/postcss/postcss-loader
+[PostCSS Global Data]: https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-global-data
+[Next.js]: https://nextjs.org
diff --git a/plugins/postcss-global-data/LICENSE.md b/plugins/postcss-global-data/LICENSE.md
new file mode 100644
index 000000000..0bc1fa706
--- /dev/null
+++ b/plugins/postcss-global-data/LICENSE.md
@@ -0,0 +1,108 @@
+# CC0 1.0 Universal
+
+## Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator and
+subsequent owner(s) (each and all, an “owner”) of an original work of
+authorship and/or a database (each, a “Work”).
+
+Certain owners wish to permanently relinquish those rights to a Work for the
+purpose of contributing to a commons of creative, cultural and scientific works
+(“Commons”) that the public can reliably and without fear of later claims of
+infringement build upon, modify, incorporate in other works, reuse and
+redistribute as freely as possible in any form whatsoever and for any purposes,
+including without limitation commercial purposes. These owners may contribute
+to the Commons to promote the ideal of a free culture and the further
+production of creative, cultural and scientific works, or to gain reputation or
+greater distribution for their Work in part through the use and efforts of
+others.
+
+For these and/or other purposes and motivations, and without any expectation of
+additional consideration or compensation, the person associating CC0 with a
+Work (the “Affirmer”), to the extent that he or she is an owner of Copyright
+and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and
+publicly distribute the Work under its terms, with knowledge of his or her
+Copyright and Related Rights in the Work and the meaning and intended legal
+effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+ protected by copyright and related or neighboring rights (“Copyright and
+ Related Rights”). Copyright and Related Rights include, but are not limited
+ to, the following:
+ 1. the right to reproduce, adapt, distribute, perform, display, communicate,
+ and translate a Work;
+ 2. moral rights retained by the original author(s) and/or performer(s);
+ 3. publicity and privacy rights pertaining to a person’s image or likeness
+ depicted in a Work;
+ 4. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(i), below;
+ 5. rights protecting the extraction, dissemination, use and reuse of data in
+ a Work;
+ 6. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation thereof,
+ including any amended or successor version of such directive); and
+ 7. other similar, equivalent or corresponding rights throughout the world
+ based on applicable law or treaty, and any national implementations
+ thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention of,
+ applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
+ unconditionally waives, abandons, and surrenders all of Affirmer’s Copyright
+ and Related Rights and associated claims and causes of action, whether now
+ known or unknown (including existing as well as future claims and causes of
+ action), in the Work (i) in all territories worldwide, (ii) for the maximum
+ duration provided by applicable law or treaty (including future time
+ extensions), (iii) in any current or future medium and for any number of
+ copies, and (iv) for any purpose whatsoever, including without limitation
+ commercial, advertising or promotional purposes (the “Waiver”). Affirmer
+ makes the Waiver for the benefit of each member of the public at large and
+ to the detriment of Affirmer’s heirs and successors, fully intending that
+ such Waiver shall not be subject to revocation, rescission, cancellation,
+ termination, or any other legal or equitable action to disrupt the quiet
+ enjoyment of the Work by the public as contemplated by Affirmer’s express
+ Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason be
+ judged legally invalid or ineffective under applicable law, then the Waiver
+ shall be preserved to the maximum extent permitted taking into account
+ Affirmer’s express Statement of Purpose. In addition, to the extent the
+ Waiver is so judged Affirmer hereby grants to each affected person a
+ royalty-free, non transferable, non sublicensable, non exclusive,
+ irrevocable and unconditional license to exercise Affirmer’s Copyright and
+ Related Rights in the Work (i) in all territories worldwide, (ii) for the
+ maximum duration provided by applicable law or treaty (including future time
+ extensions), (iii) in any current or future medium and for any number of
+ copies, and (iv) for any purpose whatsoever, including without limitation
+ commercial, advertising or promotional purposes (the “License”). The License
+ shall be deemed effective as of the date CC0 was applied by Affirmer to the
+ Work. Should any part of the License for any reason be judged legally
+ invalid or ineffective under applicable law, such partial invalidity or
+ ineffectiveness shall not invalidate the remainder of the License, and in
+ such case Affirmer hereby affirms that he or she will not (i) exercise any
+ of his or her remaining Copyright and Related Rights in the Work or (ii)
+ assert any associated claims and causes of action with respect to the Work,
+ in either case contrary to Affirmer’s express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+ 1. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+ 2. Affirmer offers the Work as-is and makes no representations or warranties
+ of any kind concerning the Work, express, implied, statutory or
+ otherwise, including without limitation warranties of title,
+ merchantability, fitness for a particular purpose, non infringement, or
+ the absence of latent or other defects, accuracy, or the present or
+ absence of errors, whether or not discoverable, all to the greatest
+ extent permissible under applicable law.
+ 3. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without
+ limitation any person’s Copyright and Related Rights in the Work.
+ Further, Affirmer disclaims responsibility for obtaining any necessary
+ consents, permissions or other rights required for any use of the Work.
+ 4. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to this
+ CC0 or use of the Work.
+
+For more information, please see
+http://creativecommons.org/publicdomain/zero/1.0/.
diff --git a/plugins/postcss-global-data/README.md b/plugins/postcss-global-data/README.md
new file mode 100644
index 000000000..9a7e2dc65
--- /dev/null
+++ b/plugins/postcss-global-data/README.md
@@ -0,0 +1,66 @@
+# PostCSS Global Data [][PostCSS]
+
+[
][npm-url] [
][cli-url] [
][discord]
+
+[PostCSS Global Data] lets you inject CSS that is removed again before the final output. This is useful for plugins that use global CSS as data.
+
+For example, in the case of CSS Modules with [PostCSS Custom Media](https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-custom-media), rules are usually not imported by every single file, so PostCSS Custom Media cannot generate fallbacks.
+By providing a list of files, this plugin will inject the global CSS as data so that PostCSS Custom Media can generate fallbacks.
+
+It is important that [PostCSS Global Data] is used before the plugin that actually needs the data.
+
+Please note that [PostCSS Global Data] does not add anything to the output of your CSS. It only injects data into PostCSS so that other plugins
+can actually use it.
+
+## Usage
+
+Add [PostCSS Global Data] to your project:
+
+```bash
+npm install postcss @csstools/postcss-global-data --save-dev
+```
+
+Use it as a [PostCSS] plugin:
+
+```js
+const postcss = require('postcss');
+const postcssGlobalData = require('@csstools/postcss-global-data');
+
+postcss([
+ postcssGlobalData(/* pluginOptions */)
+]).process(YOUR_CSS /*, processOptions */);
+```
+
+[PostCSS Global Data] runs in all Node environments, with special
+instructions for:
+
+- [Node](INSTALL.md#node)
+- [PostCSS CLI](INSTALL.md#postcss-cli)
+- [PostCSS Load Config](INSTALL.md#postcss-load-config)
+- [Webpack](INSTALL.md#webpack)
+- [Next.js](INSTALL.md#nextjs)
+- [Gulp](INSTALL.md#gulp)
+- [Grunt](INSTALL.md#grunt)
+
+## Options
+
+### files
+
+The `files` option determines which files to inject into PostCSS.
+
+```js
+postcssGlobalData({
+ files: [
+ './src/css/variables.css',
+ './src/css/media-queries.css',
+ ],
+});
+```
+
+[cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test
+
+[discord]: https://discord.gg/bUadyRwkJS
+[npm-url]: https://www.npmjs.com/package/@csstools/postcss-global-data
+
+[PostCSS]: https://github.com/postcss/postcss
+[PostCSS Global Data]: https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-global-data
diff --git a/plugins/postcss-global-data/dist/index.cjs b/plugins/postcss-global-data/dist/index.cjs
new file mode 100644
index 000000000..50e8f3ace
--- /dev/null
+++ b/plugins/postcss-global-data/dist/index.cjs
@@ -0,0 +1 @@
+"use strict";var e=require("path"),r=require("fs"),s=require("postcss");function parseImport(t,a){let o="";if(t.startsWith("node_modules://"))try{o=require.resolve(t.slice(15),{paths:[e.dirname(t)]})}catch(e){throw new Error(`Failed to read ${t} with error ${e.message}`)}else o=e.resolve(t);if(a.has(o))return!1;a.add(o);const n=r.readFileSync(o,"utf8");return s.parse(n)}const creator=e=>{const r=Object.assign({files:[]},e);return{postcssPlugin:"postcss-global-data",prepare(){let e=new Set,s=new Set;return{Once:t=>{r.files.forEach((r=>{if(e.has(r))return;const a=parseImport(r,e);a&&a.each((e=>{t.append(e),s.add(e)}))}))},OnceExit:()=>{s.forEach((e=>{e.remove()})),s=new Set,e=new Set}}}}};creator.postcss=!0,module.exports=creator;
diff --git a/plugins/postcss-global-data/dist/index.d.ts b/plugins/postcss-global-data/dist/index.d.ts
new file mode 100644
index 000000000..5fb2057a1
--- /dev/null
+++ b/plugins/postcss-global-data/dist/index.d.ts
@@ -0,0 +1,8 @@
+import type { PluginCreator } from 'postcss';
+/** postcss-global-data plugin options */
+export type pluginOptions = {
+ /** List of files to be used as context */
+ files?: [];
+};
+declare const creator: PluginCreator