Repository: postcss/postcss-custom-media
Branch: master
Commit: a9a0ce03d759
Files: 34
Total size: 46.7 KB
Directory structure:
gitextract_e30ojqmi/
├── .editorconfig
├── .gitignore
├── .rollup.js
├── .tape.js
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── INSTALL.md
├── LICENSE.md
├── README.md
├── index.js
├── lib/
│ ├── custom-media-from-root.js
│ ├── get-custom-media-from-imports.js
│ ├── media-ast-from-string.js
│ ├── transform-atrules.js
│ ├── transform-media-list.js
│ └── write-custom-media-to-exports.js
├── package.json
└── test/
├── basic.css
├── basic.expect.css
├── basic.import.expect.css
├── basic.preserve.expect.css
├── export-media.css
├── export-media.js
├── export-media.json
├── export-media.mjs
├── import-css.css
├── import-media.css
├── import-media.js
├── import-media.json
├── import.css
├── import.empty.expect.css
├── import.expect.css
└── import.plugin.expect.css
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = tab
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{json,md,yml}]
indent_size = 2
indent_style = space
================================================
FILE: .gitignore
================================================
node_modules
index.*.*
package-lock.json
*.log*
*.result.css
.*
!.editorconfig
!.gitignore
!.rollup.js
!.tape.js
!.travis.yml
================================================
FILE: .rollup.js
================================================
import babel from '@rollup/plugin-babel';
export default {
input: 'index.js',
output: [
{ file: 'index.cjs.js', format: 'cjs', sourcemap: true, exports: 'default' },
{ file: 'index.es.mjs', format: 'es', sourcemap: true, exports: 'default' }
],
plugins: [
babel({
babelHelpers: 'bundled',
plugins: [
'@babel/plugin-syntax-dynamic-import'
],
presets: [
['@babel/env', { modules: false, targets: { node: 10 } }]
]
})
]
};
================================================
FILE: .tape.js
================================================
module.exports = {
'basic': {
message: 'supports basic usage'
},
'basic:preserve': {
message: 'supports { preserve: true } usage',
options: {
preserve: true
}
},
'import': {
message: 'supports { importFrom: { customMedia: { ... } } } usage',
options: {
importFrom: {
customMedia: {
'--mq-a': '(max-width: 30em), (max-height: 30em)',
'--not-mq-a': 'not all and (--mq-a)'
}
}
}
},
'import:import-fn': {
message: 'supports { importFrom() } usage',
options: {
importFrom() {
return {
customMedia: {
'--mq-a': '(max-width: 30em), (max-height: 30em)',
'--not-mq-a': 'not all and (--mq-a)'
}
};
}
},
expect: 'import.expect.css',
result: 'import.result.css'
},
'import:import-fn-promise': {
message: 'supports { async importFrom() } usage',
options: {
importFrom() {
return new Promise(resolve => {
resolve({
customMedia: {
'--mq-a': '(max-width: 30em), (max-height: 30em)',
'--not-mq-a': 'not all and (--mq-a)'
}
})
});
}
},
expect: 'import.expect.css',
result: 'import.result.css'
},
'import:json': {
message: 'supports { importFrom: "test/import-media.json" } usage',
options: {
importFrom: 'test/import-media.json'
},
expect: 'import.expect.css',
result: 'import.result.css'
},
'import:js': {
message: 'supports { importFrom: "test/import-media.js" } usage',
options: {
importFrom: 'test/import-media.js'
},
expect: 'import.expect.css',
result: 'import.result.css'
},
'import:css': {
message: 'supports { importFrom: "test/import-media.css" } usage',
options: {
importFrom: 'test/import-media.css'
},
expect: 'import.expect.css',
result: 'import.result.css'
},
'import:css-from': {
message: 'supports { importFrom: { from: "test/import-media.css" } } usage',
options: {
importFrom: { from: 'test/import-media.css' }
},
expect: 'import.expect.css',
result: 'import.result.css'
},
'import:css-from-type': {
message: 'supports { importFrom: [ { from: "test/import-media.css", type: "css" } ] } usage',
options: {
importFrom: [ { from: 'test/import-media.css', type: 'css' } ]
},
expect: 'import.expect.css',
result: 'import.result.css'
},
'import:empty': {
message: 'supports { importFrom: {} } usage',
options: {
importFrom: {}
}
},
'basic:export': {
message: 'supports { exportTo: { customMedia: { ... } } } usage',
options: {
exportTo: (global.__exportMediaObject = global.__exportMediaObject || {
customMedia: null
})
},
expect: 'basic.expect.css',
result: 'basic.result.css',
after() {
if (__exportMediaObject.customMedia['--mq-a'] !== '(max-width: 30em), (max-height: 30em)') {
throw new Error('The exportTo function failed');
}
}
},
'basic:export-fn': {
message: 'supports { exportTo() } usage',
options: {
exportTo(customMedia) {
if (customMedia['--mq-a'] !== '(max-width: 30em), (max-height: 30em)') {
throw new Error('The exportTo function failed');
}
}
},
expect: 'basic.expect.css',
result: 'basic.result.css'
},
'basic:export-fn-promise': {
message: 'supports { async exportTo() } usage',
options: {
exportTo(customMedia) {
return new Promise((resolve, reject) => {
if (customMedia['--mq-a'] !== '(max-width: 30em), (max-height: 30em)') {
reject('The exportTo function failed');
} else {
resolve();
}
});
}
},
expect: 'basic.expect.css',
result: 'basic.result.css'
},
'basic:export-json': {
message: 'supports { exportTo: "test/export-media.json" } usage',
options: {
exportTo: 'test/export-media.json'
},
expect: 'basic.expect.css',
result: 'basic.result.css',
before() {
global.__exportMediaString = require('fs').readFileSync('test/export-media.json', 'utf8');
},
after() {
if (global.__exportMediaString !== require('fs').readFileSync('test/export-media.json', 'utf8')) {
throw new Error('The original file did not match the freshly exported copy');
}
}
},
'basic:export-js': {
message: 'supports { exportTo: "test/export-media.js" } usage',
options: {
exportTo: 'test/export-media.js'
},
expect: 'basic.expect.css',
result: 'basic.result.css',
before() {
global.__exportMediaString = require('fs').readFileSync('test/export-media.js', 'utf8');
},
after() {
if (global.__exportMediaString !== require('fs').readFileSync('test/export-media.js', 'utf8')) {
throw new Error('The original file did not match the freshly exported copy');
}
}
},
'basic:export-mjs': {
message: 'supports { exportTo: "test/export-media.mjs" } usage',
options: {
exportTo: 'test/export-media.mjs'
},
expect: 'basic.expect.css',
result: 'basic.result.css',
before() {
global.__exportMediaString = require('fs').readFileSync('test/export-media.mjs', 'utf8');
},
after() {
if (global.__exportMediaString !== require('fs').readFileSync('test/export-media.mjs', 'utf8')) {
throw new Error('The original file did not match the freshly exported copy');
}
}
},
'basic:export-css': {
message: 'supports { exportTo: "test/export-media.css" } usage',
options: {
exportTo: 'test/export-media.css'
},
expect: 'basic.expect.css',
result: 'basic.result.css',
before() {
global.__exportMediaString = require('fs').readFileSync('test/export-media.css', 'utf8');
},
after() {
if (global.__exportMediaString !== require('fs').readFileSync('test/export-media.css', 'utf8')) {
throw new Error('The original file did not match the freshly exported copy');
}
}
},
'basic:export-css-to': {
message: 'supports { exportTo: { to: "test/export-media.css" } } usage',
options: {
exportTo: { to: 'test/export-media.css' }
},
expect: 'basic.expect.css',
result: 'basic.result.css',
before() {
global.__exportMediaString = require('fs').readFileSync('test/export-media.css', 'utf8');
},
after() {
if (global.__exportMediaString !== require('fs').readFileSync('test/export-media.css', 'utf8')) {
throw new Error('The original file did not match the freshly exported copy');
}
}
},
'basic:export-css-to-type': {
message: 'supports { exportTo: { to: "test/export-media.css", type: "css" } } usage',
options: {
exportTo: { to: 'test/export-media.css', type: 'css' }
},
expect: 'basic.expect.css',
result: 'basic.result.css',
before() {
global.__exportMediaString = require('fs').readFileSync('test/export-media.css', 'utf8');
},
after() {
if (global.__exportMediaString !== require('fs').readFileSync('test/export-media.css', 'utf8')) {
throw new Error('The original file did not match the freshly exported copy');
}
}
}
};
================================================
FILE: .travis.yml
================================================
# https://docs.travis-ci.com/user/travis-lint
language: node_js
node_js:
- 14
- 12
- 10
install:
- npm install --ignore-scripts
================================================
FILE: CHANGELOG.md
================================================
# Changes to PostCSS Custom Media
### 8.0.0 (January 12, 2021)
- Added: Support for PostCSS v8
### 7.0.8 (March 30, 2019)
- Fixed: Issue importing from `.pcss` files
- Updated: `postcss` to 7.0.14 (patch)
### 7.0.7 (October 19, 2018)
- Fixed: Issue combining custom media media queries with `and`
### 7.0.6 (October 12, 2018)
- Fixed: Issue combining multiple custom media
### 7.0.5 (October 5, 2018)
- Fixed: Possible issues resolving paths to imports and exports
- Added: Imports from `customMedia` and `custom-media` simultaneously
- Updated: `postcss` to 7.0.5
### 7.0.4 (September 23, 2018)
- Added: `importFromPlugins` option to process imports
### 7.0.3 (September 20, 2018)
- Fixed: Do not break on an empty `importFrom` object
### 7.0.2 (September 15, 2018)
- Fixed: An issue with re-assigning params as a non-string
### 7.0.1 (September 14, 2018)
- Fixed: An issue with how opposing queries are resolved.
### 7.0.0 (September 14, 2018)
- Added: New `preserve` option to preserve custom media and atrules using them
- Added: New `exportTo` function to specify where to export custom media
- Added: New `importFrom` option to specify where to import custom media
- Added: Support for PostCSS v7
- Added: Support for Node v6+
# 6.0.0 (May 12, 2017)
- Added: compatibility with postcss v6.x
# 5.0.1 (February 3, 2016)
- Fixed: circular dependencies are properly detected
([#17](https://github.com/postcss/postcss-custom-media/pull/17))
# 5.0.0 (August 25, 2015)
- Removed: compatibility with postcss v4.x
- Added: compatibility with postcss v5.x
# 4.1.0 (06 30, 2015)
- Added: Allow custom media to reference each other
([#10](https://github.com/postcss/postcss-custom-media/pull/10))
# 4.0.0 (May 17, 2015)
- Changed: warning messages are now sent via postcss messages api (^4.1.0)
- Added: automatic custom media `--` prefixing
([#11](https://github.com/postcss/postcss-custom-media/issues/11))
- Added: `preserve` allows you to preserve custom media query defintions
- Added: `appendExtensions` allows you (when `preserve` is truthy) to append your extensions as media queries
# 3.0.0 (January 29, 2015)
- Added: compatibility with postcss v4.x
- Removed: compatibility with postcss v3.x
# 2.0.0 [Yanked]
_You never saw this version (this is a bad release that points to 1.0.0)._
# 1.3.0 (November 25, 2014)
- Changed: better gnu message
# 1.2.1 (October 9, 2014)
- Fixed: npm description
# 1.2.0 (October 1, 2014)
- Added: support for multiples media in query list (ref [#rework-custom-media/5](https://github.com/reworkcss/rework-custom-media/pull/5))
# 1.1.0 (September 30, 2014)
- Added: support for js-defined media queries (fix [#3](https://github.com/postcss/postcss-custom-media/issues/3))
# 1.0.1 (September 16, 2014)
- Added: Allow whitespace around custom media name (fix [#2](https://github.com/postcss/postcss-custom-media/issues/2))
# 1.0.0 (August 12, 2014)
✨ First release based on [rework-custom-media](https://github.com/reworkcss/rework-custom-media) v0.1.1
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to PostCSS Custom Media
You want to help? You rock! Now, take a moment to be sure your contributions
make sense to everyone else.
## Reporting Issues
Found a problem? Want a new feature?
- See if your issue or idea has [already been reported].
- Provide a [reduced test case] or a [live example].
Remember, a bug is a _demonstrable problem_ caused by _our_ code.
## Submitting Pull Requests
Pull requests are the greatest contributions, so be sure they are focused in
scope and avoid unrelated commits.
1. To begin; [fork this project], clone your fork, and add our upstream.
```bash
# Clone your fork of the repo into the current directory
git clone git@github.com:YOUR_USER/postcss-custom-media.git
# Navigate to the newly cloned directory
cd postcss-custom-media
# Assign the original repo to a remote called "upstream"
git remote add upstream git@github.com:postcss/postcss-custom-media.git
# Install the tools necessary for testing
npm install
```
2. Create a branch for your feature or fix:
```bash
# Move into a new branch for your feature
git checkout -b feature/thing
```
```bash
# Move into a new branch for your fix
git checkout -b fix/something
```
3. If your code follows our practices, then push your feature branch:
```bash
# Test current code
npm test
```
```bash
# Push the branch for your new feature
git push origin feature/thing
```
```bash
# Or, push the branch for your update
git push origin update/something
```
That’s it! Now [open a pull request] with a clear title and description.
[already been reported]: issues
[fork this project]: fork
[live example]: https://codepen.io/pen
[open a pull request]: https://help.github.com/articles/using-pull-requests/
[reduced test case]: https://css-tricks.com/reduced-test-cases/
================================================
FILE: INSTALL.md
================================================
# Installing PostCSS Custom Media
[PostCSS Custom Media] runs in all Node environments, with special instructions for:
| [Node](#node) | [PostCSS CLI](#postcss-cli) | [Webpack](#webpack) | [Create React App](#create-react-app) | [Gulp](#gulp) | [Grunt](#grunt) |
| --- | --- | --- | --- | --- | --- |
## Node
Add [PostCSS Custom Media] to your project:
```bash
npm install postcss-custom-media --save-dev
```
Use [PostCSS Custom Media] to process your CSS:
```js
const postcssCustomMedia = require('postcss-custom-media');
postcssCustomMedia.process(YOUR_CSS /*, processOptions, pluginOptions */);
```
Or use it as a [PostCSS] plugin:
```js
const postcss = require('postcss');
const postcssCustomMedia = require('postcss-custom-media');
postcss([
postcssCustomMedia(/* pluginOptions */)
]).process(YOUR_CSS /*, processOptions */);
```
## PostCSS CLI
Add [PostCSS CLI] to your project:
```bash
npm install postcss-cli --save-dev
```
Use [PostCSS Custom Media] in your `postcss.config.js` configuration file:
```js
const postcssCustomMedia = require('postcss-custom-media');
module.exports = {
plugins: [
postcssCustomMedia(/* pluginOptions */)
]
}
```
## Webpack
Add [PostCSS Loader] to your project:
```bash
npm install postcss-loader --save-dev
```
Use [PostCSS Custom Media] in your Webpack configuration:
```js
const postcssCustomMedia = require('postcss-custom-media');
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
{ loader: 'css-loader', options: { importLoaders: 1 } },
{ loader: 'postcss-loader', options: {
ident: 'postcss',
plugins: () => [
postcssCustomMedia(/* pluginOptions */)
]
} }
]
}
]
}
}
```
## Create React App
Add [React App Rewired] and [React App Rewire PostCSS] to your project:
```bash
npm install react-app-rewired react-app-rewire-postcss --save-dev
```
Use [React App Rewire PostCSS] and [PostCSS Custom Media] in your
`config-overrides.js` file:
```js
const reactAppRewirePostcss = require('react-app-rewire-postcss');
const postcssCustomMedia = require('postcss-custom-media');
module.exports = config => reactAppRewirePostcss(config, {
plugins: () => [
postcssCustomMedia(/* pluginOptions */)
]
});
```
## Gulp
Add [Gulp PostCSS] to your project:
```bash
npm install gulp-postcss --save-dev
```
Use [PostCSS Custom Media] in your Gulpfile:
```js
const postcss = require('gulp-postcss');
const postcssCustomMedia = require('postcss-custom-media');
gulp.task('css', () => gulp.src('./src/*.css').pipe(
postcss([
postcssCustomMedia(/* pluginOptions */)
])
).pipe(
gulp.dest('.')
));
```
## Grunt
Add [Grunt PostCSS] to your project:
```bash
npm install grunt-postcss --save-dev
```
Use [PostCSS Custom Media] in your Gruntfile:
```js
const postcssCustomMedia = require('postcss-custom-media');
grunt.loadNpmTasks('grunt-postcss');
grunt.initConfig({
postcss: {
options: {
use: [
postcssCustomMedia(/* pluginOptions */)
]
},
dist: {
src: '*.css'
}
}
});
```
[Gulp PostCSS]: https://github.com/postcss/gulp-postcss
[Grunt PostCSS]: https://github.com/nDmitry/grunt-postcss
[PostCSS]: https://github.com/postcss/postcss
[PostCSS CLI]: https://github.com/postcss/postcss-cli
[PostCSS Loader]: https://github.com/postcss/postcss-loader
[PostCSS Custom Media]: https://github.com/postcss/postcss-custom-media
[React App Rewire PostCSS]: https://github.com/csstools/react-app-rewire-postcss
[React App Rewired]: https://github.com/timarney/react-app-rewired
================================================
FILE: LICENSE.md
================================================
# The MIT License (MIT)
Copyright © PostCSS
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
<div align="center">⚠️ PostCSS Custom Media was moved to <a href="https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-custom-media">@csstools/postcss-plugins</a>. ⚠️ <br>
<a href="https://github.com/csstools/postcss-plugins/discussions/75">Read the announcement</a></div>
# PostCSS Custom Media [<img src="https://postcss.github.io/postcss/logo.svg" alt="PostCSS" width="90" height="90" align="right">][postcss]
[![NPM Version][npm-img]][npm-url]
[![CSS Standard Status][css-img]][css-url]
[![Build Status][cli-img]][cli-url]
[![Support Chat][git-img]][git-url]
[PostCSS Custom Media] lets you use Custom Media Queries in CSS, following the
[CSS Media Queries] specification.
```pcss
@custom-media --small-viewport (max-width: 30em);
@media (--small-viewport) {
/* styles for small viewport */
}
/* becomes */
@media (max-width: 30em) {
/* styles for small viewport */
}
```
## Usage
Add [PostCSS Custom Media] to your project:
```bash
npm install postcss-custom-media --save-dev
```
Use [PostCSS Custom Media] to process your CSS:
```js
const postcssCustomMedia = require('postcss-custom-media');
postcssCustomMedia.process(YOUR_CSS /*, processOptions, pluginOptions */);
```
Or use it as a [PostCSS] plugin:
```js
const postcss = require('postcss');
const postcssCustomMedia = require('postcss-custom-media');
postcss([
postcssCustomMedia(/* pluginOptions */)
]).process(YOUR_CSS /*, processOptions */);
```
[PostCSS Custom Media] runs in all Node environments, with special instructions for:
| [Node](INSTALL.md#node) | [PostCSS CLI](INSTALL.md#postcss-cli) | [Webpack](INSTALL.md#webpack) | [Create React App](INSTALL.md#create-react-app) | [Gulp](INSTALL.md#gulp) | [Grunt](INSTALL.md#grunt) |
| --- | --- | --- | --- | --- | --- |
## Options
### preserve
The `preserve` option determines whether custom media and atrules using custom
media should be preserved in their original form.
```pcss
@custom-media --small-viewport (max-width: 30em);
@media (--small-viewport) {
/* styles for small viewport */
}
/* becomes */
@custom-media --small-viewport (max-width: 30em);
@media (max-width: 30em) {
/* styles for small viewport */
}
@media (--small-viewport) {
/* styles for small viewport */
}
```
### importFrom
The `importFrom` option specifies sources where custom media can be imported
from, which might be CSS, JS, and JSON files, functions, and directly passed
objects.
```js
postcssCustomMedia({
importFrom: 'path/to/file.css' // => @custom-selector --small-viewport (max-width: 30em);
});
```
```pcss
@media (max-width: 30em) {
/* styles for small viewport */
}
@media (--small-viewport) {
/* styles for small viewport */
}
```
Multiple sources can be passed into this option, and they will be parsed in the
order they are received. JavaScript files, JSON files, functions, and objects
will need to namespace custom media using the `customMedia` or
`custom-media` key.
```js
postcssCustomMedia({
importFrom: [
'path/to/file.css',
'and/then/this.js',
'and/then/that.json',
{
customMedia: { '--small-viewport': '(max-width: 30em)' }
},
() => {
const customMedia = { '--small-viewport': '(max-width: 30em)' };
return { customMedia };
}
]
});
```
### exportTo
The `exportTo` option specifies destinations where custom media can be exported
to, which might be CSS, JS, and JSON files, functions, and directly passed
objects.
```js
postcssCustomMedia({
exportTo: 'path/to/file.css' // @custom-media --small-viewport (max-width: 30em);
});
```
Multiple destinations can be passed into this option, and they will be parsed
in the order they are received. JavaScript files, JSON files, and objects will
need to namespace custom media using the `customMedia` or
`custom-media` key.
```js
const cachedObject = { customMedia: {} };
postcssCustomMedia({
exportTo: [
'path/to/file.css', // @custom-media --small-viewport (max-width: 30em);
'and/then/this.js', // module.exports = { customMedia: { '--small-viewport': '(max-width: 30em)' } }
'and/then/this.mjs', // export const customMedia = { '--small-viewport': '(max-width: 30em)' } }
'and/then/that.json', // { "custom-media": { "--small-viewport": "(max-width: 30em)" } }
cachedObject,
customMedia => {
customMedia // { '--small-viewport': '(max-width: 30em)' }
}
]
});
```
See example exports written to [CSS](test/export-media.css),
[JS](test/export-media.js), [MJS](test/export-media.mjs), and
[JSON](test/export-media.json).
[cli-img]: https://img.shields.io/travis/postcss/postcss-custom-media/master.svg
[cli-url]: https://travis-ci.org/postcss/postcss-custom-media
[css-img]: https://cssdb.org/badge/custom-media-queries.svg
[css-url]: https://cssdb.org/#custom-media-queries
[git-img]: https://img.shields.io/badge/support-chat-blue.svg
[git-url]: https://gitter.im/postcss/postcss
[npm-img]: https://img.shields.io/npm/v/postcss-custom-media.svg
[npm-url]: https://www.npmjs.com/package/postcss-custom-media
[CSS Media Queries]: https://drafts.csswg.org/mediaqueries-5/#custom-mq
[PostCSS]: https://github.com/postcss/postcss
[PostCSS Custom Media]: https://github.com/postcss/postcss-custom-media
================================================
FILE: index.js
================================================
import getCustomMediaFromRoot from './lib/custom-media-from-root';
import getCustomMediaFromImports from './lib/get-custom-media-from-imports';
import transformAtrules from './lib/transform-atrules';
import writeCustomMediaToExports from './lib/write-custom-media-to-exports';
const creator = opts => {
// whether to preserve custom media and at-rules using them
const preserve = 'preserve' in Object(opts) ? Boolean(opts.preserve) : false;
// sources to import custom media from
const importFrom = [].concat(Object(opts).importFrom || []);
// destinations to export custom media to
const exportTo = [].concat(Object(opts).exportTo || []);
// promise any custom media are imported
const customMediaImportsPromise = getCustomMediaFromImports(importFrom);
return {
postcssPlugin: 'postcss-custom-media',
Once: async (root, helpers) => {
// combine rules from root and from imports
helpers.customMedia = Object.assign(
await customMediaImportsPromise,
getCustomMediaFromRoot(root, { preserve })
);
await writeCustomMediaToExports(helpers.customMedia, exportTo);
},
AtRule: {
media: (atrule, helpers) => {
transformAtrules(atrule, {preserve}, helpers)
}
}
}
}
creator.postcss = true
export default creator
================================================
FILE: lib/custom-media-from-root.js
================================================
import mediaASTFromString from './media-ast-from-string';
// return custom selectors from the css root, conditionally removing them
export default (root, opts) => {
// initialize custom selectors
const customMedias = {};
// for each custom selector atrule that is a child of the css root
root.nodes.slice().forEach(node => {
if (isCustomMedia(node)) {
// extract the name and selectors from the params of the custom selector
const [, name, selectors] = node.params.match(customMediaParamsRegExp);
// write the parsed selectors to the custom selector
customMedias[name] = mediaASTFromString(selectors);
// conditionally remove the custom selector atrule
if (!Object(opts).preserve) {
node.remove();
}
}
});
return customMedias;
};
// match the custom selector name
const customMediaNameRegExp = /^custom-media$/i;
// match the custom selector params
const customMediaParamsRegExp = /^(--[A-z][\w-]*)\s+([\W\w]+)\s*$/;
// whether the atrule is a custom selector
const isCustomMedia = node => node.type === 'atrule' && customMediaNameRegExp.test(node.name) && customMediaParamsRegExp.test(node.params);
================================================
FILE: lib/get-custom-media-from-imports.js
================================================
import fs from 'fs';
import path from 'path';
import { parse } from 'postcss';
import getMediaAstFromMediaString from './media-ast-from-string';
import getCustomMedia from './custom-media-from-root';
/* Get Custom Media from CSS File
/* ========================================================================== */
async function getCustomMediaFromCSSFile(from) {
const css = await readFile(from);
const root = parse(css, { from });
return getCustomMedia(root, { preserve: true });
}
/* Get Custom Media from Object
/* ========================================================================== */
function getCustomMediaFromObject(object) {
const customMedia = Object.assign(
{},
Object(object).customMedia,
Object(object)['custom-media']
);
for (const key in customMedia) {
customMedia[key] = getMediaAstFromMediaString(customMedia[key]);
}
return customMedia;
}
/* Get Custom Media from JSON file
/* ========================================================================== */
async function getCustomMediaFromJSONFile(from) {
const object = await readJSON(from);
return getCustomMediaFromObject(object);
}
/* Get Custom Media from JS file
/* ========================================================================== */
async function getCustomMediaFromJSFile(from) {
const object = await import(from);
return getCustomMediaFromObject(object);
}
/* Get Custom Media from Sources
/* ========================================================================== */
export default function getCustomMediaFromSources(sources) {
return sources.map(source => {
if (source instanceof Promise) {
return source;
} else if (source instanceof Function) {
return source();
}
// read the source as an object
const opts = source === Object(source) ? source : { from: String(source) };
// skip objects with custom media
if (Object(opts).customMedia || Object(opts)['custom-media']) {
return opts
}
// source pathname
const from = path.resolve(String(opts.from || ''));
// type of file being read from
const type = (opts.type || path.extname(from).slice(1)).toLowerCase();
return { type, from };
}).reduce(async (customMedia, source) => {
const { type, from } = await source;
if (type === 'css' || type === 'pcss') {
return Object.assign(await customMedia, await getCustomMediaFromCSSFile(from));
}
if (type === 'js') {
return Object.assign(await customMedia, await getCustomMediaFromJSFile(from));
}
if (type === 'json') {
return Object.assign(await customMedia, await getCustomMediaFromJSONFile(from));
}
return Object.assign(await customMedia, getCustomMediaFromObject(await source));
}, {});
}
/* Helper utilities
/* ========================================================================== */
const readFile = from => new Promise((resolve, reject) => {
fs.readFile(from, 'utf8', (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
const readJSON = async from => JSON.parse(await readFile(from));
================================================
FILE: lib/media-ast-from-string.js
================================================
function parse(string, splitByAnd) {
const array = [];
let buffer = '';
let split = false;
let func = 0;
let i = -1;
while (++i < string.length) {
const char = string[i];
if (char === '(') {
func += 1;
} else if (char === ')') {
if (func > 0) {
func -= 1;
}
} else if (func === 0) {
if (splitByAnd && andRegExp.test(buffer + char)) {
split = true;
} else if (!splitByAnd && char === ',') {
split = true;
}
}
if (split) {
array.push(splitByAnd ? new MediaExpression(buffer + char) : new MediaQuery(buffer));
buffer = '';
split = false;
} else {
buffer += char
}
}
if (buffer !== '') {
array.push(splitByAnd ? new MediaExpression(buffer) : new MediaQuery(buffer));
}
return array;
}
class MediaQueryList {
constructor(string) {
this.nodes = parse(string);
}
invert() {
this.nodes.forEach(node => {
node.invert();
})
return this;
}
clone() {
return new MediaQueryList(String(this));
}
toString() {
return this.nodes.join(',');
}
}
class MediaQuery {
constructor(string) {
const [, before, media, after ] = string.match(spaceWrapRegExp);
const [, modifier = '', afterModifier = ' ', type = '', beforeAnd = '', and = '', beforeExpression = '', expression1 = '', expression2 = ''] = media.match(mediaRegExp) || [];
const raws = { before, after, afterModifier, originalModifier: modifier || '', beforeAnd, and, beforeExpression };
const nodes = parse(expression1 || expression2, true);
Object.assign(this, {
modifier,
type,
raws,
nodes
});
}
clone(overrides) {
const instance = new MediaQuery(String(this));
Object.assign(instance, overrides);
return instance;
}
invert() {
this.modifier = this.modifier ? '' : this.raws.originalModifier;
return this;
}
toString() {
const { raws } = this;
return `${raws.before}${this.modifier}${this.modifier ? `${raws.afterModifier}` : ''}${this.type}${raws.beforeAnd}${raws.and}${raws.beforeExpression}${this.nodes.join('')}${this.raws.after}`;
}
}
class MediaExpression {
constructor(string) {
const [, value, after = '', and = '', afterAnd = '' ] = string.match(andRegExp) || [null, string];
const raws = { after, and, afterAnd };
Object.assign(this, { value, raws });
}
clone(overrides) {
const instance = new MediaExpression(String(this));
Object.assign(instance, overrides);
return instance;
}
toString() {
const { raws } = this;
return `${this.value}${raws.after}${raws.and}${raws.afterAnd}`;
}
}
const modifierRE = '(not|only)';
const typeRE = '(all|print|screen|speech)';
const noExpressionRE = '([\\W\\w]*)';
const expressionRE = '([\\W\\w]+)';
const noSpaceRE = '(\\s*)';
const spaceRE = '(\\s+)';
const andRE = '(?:(\\s+)(and))';
const andRegExp = new RegExp(`^${expressionRE}(?:${andRE}${spaceRE})$`, 'i');
const spaceWrapRegExp = new RegExp(`^${noSpaceRE}${noExpressionRE}${noSpaceRE}$`);
const mediaRegExp = new RegExp(`^(?:${modifierRE}${spaceRE})?(?:${typeRE}(?:${andRE}${spaceRE}${expressionRE})?|${expressionRE})$`, 'i');
export default string => new MediaQueryList(string);
================================================
FILE: lib/transform-atrules.js
================================================
import transformMediaList from "./transform-media-list";
import mediaASTFromString from "./media-ast-from-string";
// transform custom pseudo selectors with custom selectors
export default (atrule, { preserve }, { customMedia }) => {
if (customPseudoRegExp.test(atrule.params)) {
// prevent infinite loops when using 'preserve' option
if (!atrule[visitedFlag]) {
atrule[visitedFlag] = true;
const mediaAST = mediaASTFromString(atrule.params);
const params = String(transformMediaList(mediaAST, customMedia));
if (preserve) {
// keep an original copy
const node = atrule.cloneAfter();
node[visitedFlag] = true;
}
// replace the variable with the params from @custom-media rule
// skip if the variable is undefined
if (params != null) {
atrule.params = params;
}
}
}
};
const visitedFlag = Symbol("customMediaVisited");
const customPseudoRegExp = /\(--[A-z][\w-]*\)/;
================================================
FILE: lib/transform-media-list.js
================================================
// return transformed medias, replacing custom pseudo medias with custom medias
export default function transformMediaList(mediaList, customMedias) {
let index = mediaList.nodes.length - 1;
while (index >= 0) {
const transformedMedias = transformMedia(mediaList.nodes[index], customMedias);
if (transformedMedias.length) {
mediaList.nodes.splice(index, 1, ...transformedMedias);
}
--index;
}
return mediaList;
}
// return custom pseudo medias replaced with custom medias
function transformMedia(media, customMedias) {
const transpiledMedias = [];
for (const index in media.nodes) {
const { value, nodes } = media.nodes[index];
const key = value.replace(customPseudoRegExp, '$1');
if (key in customMedias) {
for (const replacementMedia of customMedias[key].nodes) {
// use the first available modifier unless they cancel each other out
const modifier = media.modifier !== replacementMedia.modifier
? media.modifier || replacementMedia.modifier
: '';
const mediaClone = media.clone({
modifier,
// conditionally use the raws from the first available modifier
raws: !modifier || media.modifier
? { ...media.raws }
: { ...replacementMedia.raws },
type: media.type || replacementMedia.type,
});
// conditionally include more replacement raws when the type is present
if (mediaClone.type === replacementMedia.type) {
Object.assign(mediaClone.raws, {
and: replacementMedia.raws.and,
beforeAnd: replacementMedia.raws.beforeAnd,
beforeExpression: replacementMedia.raws.beforeExpression
});
}
mediaClone.nodes.splice(index, 1, ...replacementMedia.clone().nodes.map(node => {
// use raws and spacing from the current usage
if (media.nodes[index].raws.and) {
node.raws = { ...media.nodes[index].raws };
}
node.spaces = { ...media.nodes[index].spaces };
return node;
}));
// remove the currently transformed key to prevent recursion
const nextCustomMedia = getCustomMediasWithoutKey(customMedias, key);
const retranspiledMedias = transformMedia(mediaClone, nextCustomMedia);
if (retranspiledMedias.length) {
transpiledMedias.push(...retranspiledMedias);
} else {
transpiledMedias.push(mediaClone);
}
}
return transpiledMedias;
} else if (nodes && nodes.length) {
transformMediaList(media.nodes[index], customMedias);
}
}
return transpiledMedias;
}
const customPseudoRegExp = /\((--[A-z][\w-]*)\)/;
const getCustomMediasWithoutKey = (customMedias, key) => {
const nextCustomMedias = Object.assign({}, customMedias);
delete nextCustomMedias[key];
return nextCustomMedias;
};
================================================
FILE: lib/write-custom-media-to-exports.js
================================================
import fs from 'fs';
import path from 'path';
/* Write Custom Media from CSS File
/* ========================================================================== */
async function writeCustomMediaToCssFile(to, customMedia) {
const cssContent = Object.keys(customMedia).reduce((cssLines, name) => {
cssLines.push(`@custom-media ${name} ${customMedia[name]};`);
return cssLines;
}, []).join('\n');
const css = `${cssContent}\n`;
await writeFile(to, css);
}
/* Write Custom Media from JSON file
/* ========================================================================== */
async function writeCustomMediaToJsonFile(to, customMedia) {
const jsonContent = JSON.stringify({
'custom-media': customMedia
}, null, ' ');
const json = `${jsonContent}\n`;
await writeFile(to, json);
}
/* Write Custom Media from Common JS file
/* ========================================================================== */
async function writeCustomMediaToCjsFile(to, customMedia) {
const jsContents = Object.keys(customMedia).reduce((jsLines, name) => {
jsLines.push(`\t\t'${escapeForJS(name)}': '${escapeForJS(customMedia[name])}'`);
return jsLines;
}, []).join(',\n');
const js = `module.exports = {\n\tcustomMedia: {\n${jsContents}\n\t}\n};\n`;
await writeFile(to, js);
}
/* Write Custom Media from Module JS file
/* ========================================================================== */
async function writeCustomMediaToMjsFile(to, customMedia) {
const mjsContents = Object.keys(customMedia).reduce((mjsLines, name) => {
mjsLines.push(`\t'${escapeForJS(name)}': '${escapeForJS(customMedia[name])}'`);
return mjsLines;
}, []).join(',\n');
const mjs = `export const customMedia = {\n${mjsContents}\n};\n`;
await writeFile(to, mjs);
}
/* Write Custom Media to Exports
/* ========================================================================== */
export default function writeCustomMediaToExports(customMedia, destinations) {
return Promise.all(destinations.map(async destination => {
if (destination instanceof Function) {
await destination(defaultCustomMediaToJSON(customMedia));
} else {
// read the destination as an object
const opts = destination === Object(destination) ? destination : { to: String(destination) };
// transformer for custom media into a JSON-compatible object
const toJSON = opts.toJSON || defaultCustomMediaToJSON;
if ('customMedia' in opts) {
// write directly to an object as customMedia
opts.customMedia = toJSON(customMedia);
} else if ('custom-media' in opts) {
// write directly to an object as custom-media
opts['custom-media'] = toJSON(customMedia);
} else {
// destination pathname
const to = String(opts.to || '');
// type of file being written to
const type = (opts.type || path.extname(to).slice(1)).toLowerCase();
// transformed custom media
const customMediaJSON = toJSON(customMedia);
if (type === 'css') {
await writeCustomMediaToCssFile(to, customMediaJSON);
}
if (type === 'js') {
await writeCustomMediaToCjsFile(to, customMediaJSON);
}
if (type === 'json') {
await writeCustomMediaToJsonFile(to, customMediaJSON);
}
if (type === 'mjs') {
await writeCustomMediaToMjsFile(to, customMediaJSON);
}
}
}
}));
}
/* Helper utilities
/* ========================================================================== */
const defaultCustomMediaToJSON = customMedia => {
return Object.keys(customMedia).reduce((customMediaJSON, key) => {
customMediaJSON[key] = String(customMedia[key]);
return customMediaJSON;
}, {});
};
const writeFile = (to, text) => new Promise((resolve, reject) => {
fs.writeFile(to, text, error => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
const escapeForJS = string => string.replace(/\\([\s\S])|(')/g, '\\$1$2').replace(/\n/g, '\\n').replace(/\r/g, '\\r');
================================================
FILE: package.json
================================================
{
"name": "postcss-custom-media",
"version": "8.0.0",
"description": "Use Custom Media Queries in CSS",
"author": "Jonathan Neal <jonathantneal@hotmail.com>",
"contributors": [
"Maxime Thirouin"
],
"license": "MIT",
"repository": "postcss/postcss-custom-media",
"homepage": "https://github.com/postcss/postcss-custom-media#readme",
"bugs": "https://github.com/postcss/postcss-custom-media/issues",
"main": "index.cjs.js",
"module": "index.es.mjs",
"files": [
"index.cjs.js",
"index.es.mjs"
],
"scripts": {
"prepublishOnly": "npm test",
"pretest": "rollup -c .rollup.js --silent",
"test": "echo 'Running tests...'; npm run test:js && npm run test:tape",
"test:js": "eslint *.js lib/*.js --cache --ignore-path .gitignore --quiet",
"test:tape": "postcss-tape"
},
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"postcss": "^8.1.0"
},
"devDependencies": {
"@babel/core": "^7.11.6",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/preset-env": "^7.11.5",
"@rollup/plugin-babel": "^5.2.1",
"babel-eslint": "^10.1.0",
"eslint": "^7.10.0",
"postcss": "^8.1.0",
"postcss-tape": "^6.0.0",
"pre-commit": "^1.2.2",
"rollup": "^2.28.2"
},
"eslintConfig": {
"env": {
"browser": true,
"es6": true,
"node": true
},
"extends": "eslint:recommended",
"parser": "babel-eslint",
"parserOptions": {
"ecmaVersion": 2018,
"impliedStrict": true,
"sourceType": "module"
}
},
"keywords": [
"postcss",
"css",
"postcss-plugin",
"custom",
"media",
"query",
"queries",
"w3c",
"csswg",
"atrule",
"at-rule",
"specification"
]
}
================================================
FILE: test/basic.css
================================================
@custom-media --mq-a (max-width: 30em), (max-height: 30em);
@custom-media --mq-b screen and (max-width: 30em);
@custom-media --not-mq-a not all and (--mq-a);
@media (--mq-a) {
body {
order: 1;
}
}
@media (--mq-b) {
body {
order: 1;
}
}
@media (--mq-a), (--mq-a) {
body {
order: 1;
}
}
@media not all and (--mq-a) {
body {
order: 2;
}
}
@media (--not-mq-a) {
body {
order: 1;
}
}
@media not all and (--not-mq-a) {
body {
order: 2;
}
}
@custom-media --circular-mq-a (--circular-mq-b);
@custom-media --circular-mq-b (--circular-mq-a);
@media (--circular-mq-a) {
body {
order: 3;
}
}
@media (--circular-mq-b) {
body {
order: 4;
}
}
@media (--unresolved-mq) {
body {
order: 5;
}
}
@custom-media --min (min-width: 320px);
@custom-media --max (max-width: 640px);
@media (--min) and (--max) {
body {
order: 6;
}
}
@custom-media --concat (min-width: 320px) and (max-width: 640px);
@media (--concat) {
body {
order: 7;
}
}
@media (--concat) and (min-aspect-ratio: 16/9) {
body {
order: 8;
}
}
================================================
FILE: test/basic.expect.css
================================================
@media (max-width: 30em),(max-height: 30em) {
body {
order: 1;
}
}
@media screen and (max-width: 30em) {
body {
order: 1;
}
}
@media (max-width: 30em),(max-height: 30em), (max-width: 30em), (max-height: 30em) {
body {
order: 1;
}
}
@media not all and (max-width: 30em),not all and (max-height: 30em) {
body {
order: 2;
}
}
@media not all and (max-width: 30em),not all and (max-height: 30em) {
body {
order: 1;
}
}
@media all and (max-width: 30em),all and (max-height: 30em) {
body {
order: 2;
}
}
@media (--circular-mq-a) {
body {
order: 3;
}
}
@media (--circular-mq-b) {
body {
order: 4;
}
}
@media (--unresolved-mq) {
body {
order: 5;
}
}
@media (min-width: 320px) and (max-width: 640px) {
body {
order: 6;
}
}
@media (min-width: 320px) and (max-width: 640px) {
body {
order: 7;
}
}
@media (min-width: 320px) and (max-width: 640px) and (min-aspect-ratio: 16/9) {
body {
order: 8;
}
}
================================================
FILE: test/basic.import.expect.css
================================================
================================================
FILE: test/basic.preserve.expect.css
================================================
@custom-media --mq-a (max-width: 30em), (max-height: 30em);
@custom-media --mq-b screen and (max-width: 30em);
@custom-media --not-mq-a not all and (--mq-a);
@media (max-width: 30em),(max-height: 30em) {
body {
order: 1;
}
}
@media (--mq-a) {
body {
order: 1;
}
}
@media screen and (max-width: 30em) {
body {
order: 1;
}
}
@media (--mq-b) {
body {
order: 1;
}
}
@media (max-width: 30em),(max-height: 30em), (max-width: 30em), (max-height: 30em) {
body {
order: 1;
}
}
@media (--mq-a), (--mq-a) {
body {
order: 1;
}
}
@media not all and (max-width: 30em),not all and (max-height: 30em) {
body {
order: 2;
}
}
@media not all and (--mq-a) {
body {
order: 2;
}
}
@media not all and (max-width: 30em),not all and (max-height: 30em) {
body {
order: 1;
}
}
@media (--not-mq-a) {
body {
order: 1;
}
}
@media all and (max-width: 30em),all and (max-height: 30em) {
body {
order: 2;
}
}
@media not all and (--not-mq-a) {
body {
order: 2;
}
}
@custom-media --circular-mq-a (--circular-mq-b);
@custom-media --circular-mq-b (--circular-mq-a);
@media (--circular-mq-a) {
body {
order: 3;
}
}
@media (--circular-mq-a) {
body {
order: 3;
}
}
@media (--circular-mq-b) {
body {
order: 4;
}
}
@media (--circular-mq-b) {
body {
order: 4;
}
}
@media (--unresolved-mq) {
body {
order: 5;
}
}
@media (--unresolved-mq) {
body {
order: 5;
}
}
@custom-media --min (min-width: 320px);
@custom-media --max (max-width: 640px);
@media (min-width: 320px) and (max-width: 640px) {
body {
order: 6;
}
}
@media (--min) and (--max) {
body {
order: 6;
}
}
@custom-media --concat (min-width: 320px) and (max-width: 640px);
@media (min-width: 320px) and (max-width: 640px) {
body {
order: 7;
}
}
@media (--concat) {
body {
order: 7;
}
}
@media (min-width: 320px) and (max-width: 640px) and (min-aspect-ratio: 16/9) {
body {
order: 8;
}
}
@media (--concat) and (min-aspect-ratio: 16/9) {
body {
order: 8;
}
}
================================================
FILE: test/export-media.css
================================================
@custom-media --mq-a (max-width: 30em), (max-height: 30em);
@custom-media --mq-b screen and (max-width: 30em);
@custom-media --not-mq-a not all and (--mq-a);
@custom-media --circular-mq-a (--circular-mq-b);
@custom-media --circular-mq-b (--circular-mq-a);
@custom-media --min (min-width: 320px);
@custom-media --max (max-width: 640px);
@custom-media --concat (min-width: 320px) and (max-width: 640px);
================================================
FILE: test/export-media.js
================================================
module.exports = {
customMedia: {
'--mq-a': '(max-width: 30em), (max-height: 30em)',
'--mq-b': 'screen and (max-width: 30em)',
'--not-mq-a': 'not all and (--mq-a)',
'--circular-mq-a': '(--circular-mq-b)',
'--circular-mq-b': '(--circular-mq-a)',
'--min': '(min-width: 320px)',
'--max': '(max-width: 640px)',
'--concat': '(min-width: 320px) and (max-width: 640px)'
}
};
================================================
FILE: test/export-media.json
================================================
{
"custom-media": {
"--mq-a": "(max-width: 30em), (max-height: 30em)",
"--mq-b": "screen and (max-width: 30em)",
"--not-mq-a": "not all and (--mq-a)",
"--circular-mq-a": "(--circular-mq-b)",
"--circular-mq-b": "(--circular-mq-a)",
"--min": "(min-width: 320px)",
"--max": "(max-width: 640px)",
"--concat": "(min-width: 320px) and (max-width: 640px)"
}
}
================================================
FILE: test/export-media.mjs
================================================
export const customMedia = {
'--mq-a': '(max-width: 30em), (max-height: 30em)',
'--mq-b': 'screen and (max-width: 30em)',
'--not-mq-a': 'not all and (--mq-a)',
'--circular-mq-a': '(--circular-mq-b)',
'--circular-mq-b': '(--circular-mq-a)',
'--min': '(min-width: 320px)',
'--max': '(max-width: 640px)',
'--concat': '(min-width: 320px) and (max-width: 640px)'
};
================================================
FILE: test/import-css.css
================================================
================================================
FILE: test/import-media.css
================================================
@custom-media --mq-a (max-width: 30em), (max-height: 30em);
@custom-media --not-mq-a not all and (--mq-a);
================================================
FILE: test/import-media.js
================================================
module.exports = {
customMedia: {
'--mq-a': '(max-width: 30em), (max-height: 30em)',
'--not-mq-a': 'not all and (--mq-a)'
}
}
================================================
FILE: test/import-media.json
================================================
{
"customMedia": {
"--mq-a": "(max-width: 30em), (max-height: 30em)",
"--not-mq-a": "not all and (--mq-a)"
}
}
================================================
FILE: test/import.css
================================================
@media (--mq-a) {
body {
order: 1;
}
}
@media (--mq-a), (--mq-a) {
body {
order: 1;
}
}
@media not all and (--mq-a) {
body {
order: 2;
}
}
@media (--not-mq-a) {
body {
order: 1;
}
}
@media not all and (--not-mq-a) {
body {
order: 2;
}
}
================================================
FILE: test/import.empty.expect.css
================================================
@media (--mq-a) {
body {
order: 1;
}
}
@media (--mq-a), (--mq-a) {
body {
order: 1;
}
}
@media not all and (--mq-a) {
body {
order: 2;
}
}
@media (--not-mq-a) {
body {
order: 1;
}
}
@media not all and (--not-mq-a) {
body {
order: 2;
}
}
================================================
FILE: test/import.expect.css
================================================
@media (max-width: 30em),(max-height: 30em) {
body {
order: 1;
}
}
@media (max-width: 30em),(max-height: 30em), (max-width: 30em), (max-height: 30em) {
body {
order: 1;
}
}
@media not all and (max-width: 30em),not all and (max-height: 30em) {
body {
order: 2;
}
}
@media not all and (max-width: 30em),not all and (max-height: 30em) {
body {
order: 1;
}
}
@media all and (max-width: 30em),all and (max-height: 30em) {
body {
order: 2;
}
}
================================================
FILE: test/import.plugin.expect.css
================================================
@media (max-width: 30em),(max-height: 30em) {
body {
order: 1;
}
}
@media (max-width: 30em),(max-height: 30em), (max-width: 30em), (max-height: 30em) {
body {
order: 1;
}
}
@media not all and (max-width: 30em),not all and (max-height: 30em) {
body {
order: 2;
}
}
@media not all and (max-width: 30em),not all and (max-height: 30em) {
body {
order: 1;
}
}
@media all and (max-width: 30em),all and (max-height: 30em) {
body {
order: 2;
}
}
gitextract_e30ojqmi/
├── .editorconfig
├── .gitignore
├── .rollup.js
├── .tape.js
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── INSTALL.md
├── LICENSE.md
├── README.md
├── index.js
├── lib/
│ ├── custom-media-from-root.js
│ ├── get-custom-media-from-imports.js
│ ├── media-ast-from-string.js
│ ├── transform-atrules.js
│ ├── transform-media-list.js
│ └── write-custom-media-to-exports.js
├── package.json
└── test/
├── basic.css
├── basic.expect.css
├── basic.import.expect.css
├── basic.preserve.expect.css
├── export-media.css
├── export-media.js
├── export-media.json
├── export-media.mjs
├── import-css.css
├── import-media.css
├── import-media.js
├── import-media.json
├── import.css
├── import.empty.expect.css
├── import.expect.css
└── import.plugin.expect.css
SYMBOL INDEX (44 symbols across 5 files)
FILE: .tape.js
method importFrom (line 25) | importFrom() {
method importFrom (line 40) | importFrom() {
method after (line 109) | after() {
method exportTo (line 118) | exportTo(customMedia) {
method exportTo (line 130) | exportTo(customMedia) {
method before (line 150) | before() {
method after (line 153) | after() {
method before (line 166) | before() {
method after (line 169) | after() {
method before (line 182) | before() {
method after (line 185) | after() {
method before (line 198) | before() {
method after (line 201) | after() {
method before (line 214) | before() {
method after (line 217) | after() {
method before (line 230) | before() {
method after (line 233) | after() {
FILE: lib/get-custom-media-from-imports.js
function getCustomMediaFromCSSFile (line 10) | async function getCustomMediaFromCSSFile(from) {
function getCustomMediaFromObject (line 20) | function getCustomMediaFromObject(object) {
function getCustomMediaFromJSONFile (line 37) | async function getCustomMediaFromJSONFile(from) {
function getCustomMediaFromJSFile (line 46) | async function getCustomMediaFromJSFile(from) {
function getCustomMediaFromSources (line 55) | function getCustomMediaFromSources(sources) {
FILE: lib/media-ast-from-string.js
function parse (line 1) | function parse(string, splitByAnd) {
class MediaQueryList (line 42) | class MediaQueryList {
method constructor (line 43) | constructor(string) {
method invert (line 47) | invert() {
method clone (line 55) | clone() {
method toString (line 59) | toString() {
class MediaQuery (line 64) | class MediaQuery {
method constructor (line 65) | constructor(string) {
method clone (line 79) | clone(overrides) {
method invert (line 87) | invert() {
method toString (line 93) | toString() {
class MediaExpression (line 100) | class MediaExpression {
method constructor (line 101) | constructor(string) {
method clone (line 108) | clone(overrides) {
method toString (line 116) | toString() {
FILE: lib/transform-media-list.js
function transformMediaList (line 2) | function transformMediaList(mediaList, customMedias) {
function transformMedia (line 19) | function transformMedia(media, customMedias) {
FILE: lib/write-custom-media-to-exports.js
function writeCustomMediaToCssFile (line 7) | async function writeCustomMediaToCssFile(to, customMedia) {
function writeCustomMediaToJsonFile (line 21) | async function writeCustomMediaToJsonFile(to, customMedia) {
function writeCustomMediaToCjsFile (line 33) | async function writeCustomMediaToCjsFile(to, customMedia) {
function writeCustomMediaToMjsFile (line 47) | async function writeCustomMediaToMjsFile(to, customMedia) {
function writeCustomMediaToExports (line 61) | function writeCustomMediaToExports(customMedia, destinations) {
Condensed preview — 34 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (53K chars).
[
{
"path": ".editorconfig",
"chars": 226,
"preview": "root = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_style = tab\ninsert_final_newline = true\ntrim_trailing_whitespac"
},
{
"path": ".gitignore",
"chars": 126,
"preview": "node_modules\nindex.*.*\npackage-lock.json\n*.log*\n*.result.css\n.*\n!.editorconfig\n!.gitignore\n!.rollup.js\n!.tape.js\n!.travi"
},
{
"path": ".rollup.js",
"chars": 457,
"preview": "import babel from '@rollup/plugin-babel';\n\nexport default {\n\tinput: 'index.js',\n\toutput: [\n\t\t{ file: 'index.cjs.js', for"
},
{
"path": ".tape.js",
"chars": 6752,
"preview": "module.exports = {\n\t'basic': {\n\t\tmessage: 'supports basic usage'\n\t},\n\t'basic:preserve': {\n\t\tmessage: 'supports { preserv"
},
{
"path": ".travis.yml",
"chars": 139,
"preview": "# https://docs.travis-ci.com/user/travis-lint\n\nlanguage: node_js\n\nnode_js:\n - 14\n - 12\n - 10\n\ninstall:\n - npm instal"
},
{
"path": "CHANGELOG.md",
"chars": 3032,
"preview": "# Changes to PostCSS Custom Media\n\n### 8.0.0 (January 12, 2021)\n\n- Added: Support for PostCSS v8\n\n### 7.0.8 (March 30, 2"
},
{
"path": "CONTRIBUTING.md",
"chars": 1886,
"preview": "# Contributing to PostCSS Custom Media\n\nYou want to help? You rock! Now, take a moment to be sure your contributions\nmak"
},
{
"path": "INSTALL.md",
"chars": 3666,
"preview": "# Installing PostCSS Custom Media\n\n[PostCSS Custom Media] runs in all Node environments, with special instructions for:\n"
},
{
"path": "LICENSE.md",
"chars": 1069,
"preview": "# The MIT License (MIT)\n\nCopyright © PostCSS\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
},
{
"path": "README.md",
"chars": 5237,
"preview": "<div align=\"center\">⚠️ PostCSS Custom Media was moved to <a href=\"https://github.com/csstools/postcss-plugins/tree/main/"
},
{
"path": "index.js",
"chars": 1261,
"preview": "import getCustomMediaFromRoot from './lib/custom-media-from-root';\nimport getCustomMediaFromImports from './lib/get-cust"
},
{
"path": "lib/custom-media-from-root.js",
"chars": 1143,
"preview": "import mediaASTFromString from './media-ast-from-string';\n\n// return custom selectors from the css root, conditionally r"
},
{
"path": "lib/get-custom-media-from-imports.js",
"chars": 3044,
"preview": "import fs from 'fs';\nimport path from 'path';\nimport { parse } from 'postcss';\nimport getMediaAstFromMediaString from '."
},
{
"path": "lib/media-ast-from-string.js",
"chars": 3113,
"preview": "function parse(string, splitByAnd) {\n\tconst array = [];\n\tlet buffer = '';\n\tlet split = false;\n\tlet func = 0;\n\tlet i = -1"
},
{
"path": "lib/transform-atrules.js",
"chars": 921,
"preview": "import transformMediaList from \"./transform-media-list\";\nimport mediaASTFromString from \"./media-ast-from-string\";\n\n// t"
},
{
"path": "lib/transform-media-list.js",
"chars": 2691,
"preview": "// return transformed medias, replacing custom pseudo medias with custom medias\nexport default function transformMediaLi"
},
{
"path": "lib/write-custom-media-to-exports.js",
"chars": 3922,
"preview": "import fs from 'fs';\nimport path from 'path';\n\n/* Write Custom Media from CSS File\n/* =================================="
},
{
"path": "package.json",
"chars": 1753,
"preview": "{\n \"name\": \"postcss-custom-media\",\n \"version\": \"8.0.0\",\n \"description\": \"Use Custom Media Queries in CSS\",\n \"author\""
},
{
"path": "test/basic.css",
"chars": 1046,
"preview": "@custom-media --mq-a (max-width: 30em), (max-height: 30em);\n@custom-media --mq-b screen and (max-width: 30em);\n@custom-m"
},
{
"path": "test/basic.expect.css",
"chars": 945,
"preview": "@media (max-width: 30em),(max-height: 30em) {\n\tbody {\n\t\torder: 1;\n\t}\n}\n\n@media screen and (max-width: 30em) {\n\tbody {\n\t\t"
},
{
"path": "test/basic.import.expect.css",
"chars": 0,
"preview": ""
},
{
"path": "test/basic.preserve.expect.css",
"chars": 1992,
"preview": "@custom-media --mq-a (max-width: 30em), (max-height: 30em);\n@custom-media --mq-b screen and (max-width: 30em);\n@custom-m"
},
{
"path": "test/export-media.css",
"chars": 402,
"preview": "@custom-media --mq-a (max-width: 30em), (max-height: 30em);\n@custom-media --mq-b screen and (max-width: 30em);\n@custom-m"
},
{
"path": "test/export-media.js",
"chars": 386,
"preview": "module.exports = {\n\tcustomMedia: {\n\t\t'--mq-a': '(max-width: 30em), (max-height: 30em)',\n\t\t'--mq-b': 'screen and (max-wid"
},
{
"path": "test/export-media.json",
"chars": 389,
"preview": "{\n \"custom-media\": {\n \"--mq-a\": \"(max-width: 30em), (max-height: 30em)\",\n \"--mq-b\": \"screen and (max-width: 30em)"
},
{
"path": "test/export-media.mjs",
"chars": 369,
"preview": "export const customMedia = {\n\t'--mq-a': '(max-width: 30em), (max-height: 30em)',\n\t'--mq-b': 'screen and (max-width: 30em"
},
{
"path": "test/import-css.css",
"chars": 0,
"preview": ""
},
{
"path": "test/import-media.css",
"chars": 107,
"preview": "@custom-media --mq-a (max-width: 30em), (max-height: 30em);\n@custom-media --not-mq-a not all and (--mq-a);\n"
},
{
"path": "test/import-media.js",
"chars": 132,
"preview": "module.exports = {\n\tcustomMedia: {\n\t\t'--mq-a': '(max-width: 30em), (max-height: 30em)',\n\t\t'--not-mq-a': 'not all and (--"
},
{
"path": "test/import-media.json",
"chars": 123,
"preview": "{\n \"customMedia\": {\n \"--mq-a\": \"(max-width: 30em), (max-height: 30em)\",\n \"--not-mq-a\": \"not all and (--mq-a)\"\n }"
},
{
"path": "test/import.css",
"chars": 261,
"preview": "@media (--mq-a) {\n\tbody {\n\t\torder: 1;\n\t}\n}\n\n@media (--mq-a), (--mq-a) {\n\tbody {\n\t\torder: 1;\n\t}\n}\n\n@media not all and (--"
},
{
"path": "test/import.empty.expect.css",
"chars": 261,
"preview": "@media (--mq-a) {\n\tbody {\n\t\torder: 1;\n\t}\n}\n\n@media (--mq-a), (--mq-a) {\n\tbody {\n\t\torder: 1;\n\t}\n}\n\n@media not all and (--"
},
{
"path": "test/import.expect.css",
"chars": 462,
"preview": "@media (max-width: 30em),(max-height: 30em) {\n\tbody {\n\t\torder: 1;\n\t}\n}\n\n@media (max-width: 30em),(max-height: 30em), (ma"
},
{
"path": "test/import.plugin.expect.css",
"chars": 462,
"preview": "@media (max-width: 30em),(max-height: 30em) {\n\tbody {\n\t\torder: 1;\n\t}\n}\n\n@media (max-width: 30em),(max-height: 30em), (ma"
}
]
About this extraction
This page contains the full source code of the postcss/postcss-custom-media GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 34 files (46.7 KB), approximately 14.4k tokens, and a symbol index with 44 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.