Add: node postcss stuff for the container
parent
4d9fa27321
commit
c6a52ce7cc
|
@ -0,0 +1,108 @@
|
||||||
|
# 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
|
|
@ -0,0 +1,21 @@
|
||||||
|
# The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright © PostCSS
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,178 @@
|
||||||
|
# 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
|
|
@ -0,0 +1,557 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var fs = require('fs');
|
||||||
|
var path = require('path');
|
||||||
|
var postcss = require('postcss');
|
||||||
|
|
||||||
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
||||||
|
|
||||||
|
function _interopNamespace(e) {
|
||||||
|
if (e && e.__esModule) return e;
|
||||||
|
var n = Object.create(null);
|
||||||
|
if (e) {
|
||||||
|
Object.keys(e).forEach(function (k) {
|
||||||
|
if (k !== 'default') {
|
||||||
|
var d = Object.getOwnPropertyDescriptor(e, k);
|
||||||
|
Object.defineProperty(n, k, d.get ? d : {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return e[k];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
n['default'] = e;
|
||||||
|
return Object.freeze(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
|
||||||
|
var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
|
||||||
|
|
||||||
|
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');
|
||||||
|
var mediaASTFromString = (string => new MediaQueryList(string));
|
||||||
|
|
||||||
|
var getCustomMediaFromRoot = ((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);
|
||||||
|
|
||||||
|
/* Get Custom Media from CSS File
|
||||||
|
/* ========================================================================== */
|
||||||
|
|
||||||
|
async function getCustomMediaFromCSSFile(from) {
|
||||||
|
const css = await readFile(from);
|
||||||
|
const root = postcss.parse(css, {
|
||||||
|
from
|
||||||
|
});
|
||||||
|
return getCustomMediaFromRoot(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] = mediaASTFromString(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 Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require(from)); });
|
||||||
|
return getCustomMediaFromObject(object);
|
||||||
|
}
|
||||||
|
/* Get Custom Media from Sources
|
||||||
|
/* ========================================================================== */
|
||||||
|
|
||||||
|
|
||||||
|
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__default['default'].resolve(String(opts.from || '')); // type of file being read from
|
||||||
|
|
||||||
|
const type = (opts.type || path__default['default'].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__default['default'].readFile(from, 'utf8', (error, result) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const readJSON = async from => JSON.parse(await readFile(from));
|
||||||
|
|
||||||
|
// return transformed medias, replacing custom pseudo medias with custom medias
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
var transformAtrules = ((root, customMedia, opts) => {
|
||||||
|
root.walkAtRules(mediaAtRuleRegExp, atrule => {
|
||||||
|
if (customPseudoRegExp$1.test(atrule.params)) {
|
||||||
|
const mediaAST = mediaASTFromString(atrule.params);
|
||||||
|
const params = String(transformMediaList(mediaAST, customMedia));
|
||||||
|
|
||||||
|
if (opts.preserve) {
|
||||||
|
atrule.cloneBefore({
|
||||||
|
params
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
atrule.params = params;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const mediaAtRuleRegExp = /^media$/i;
|
||||||
|
const customPseudoRegExp$1 = /\(--[A-z][\w-]*\)/;
|
||||||
|
|
||||||
|
/* 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
|
||||||
|
/* ========================================================================== */
|
||||||
|
|
||||||
|
|
||||||
|
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__default['default'].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__default['default'].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');
|
||||||
|
|
||||||
|
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 customMediaPromise = getCustomMediaFromSources(importFrom);
|
||||||
|
return {
|
||||||
|
postcssPlugin: 'postcss-custom-media',
|
||||||
|
Once: async root => {
|
||||||
|
const customMedia = Object.assign(await customMediaPromise, getCustomMediaFromRoot(root, {
|
||||||
|
preserve
|
||||||
|
}));
|
||||||
|
await writeCustomMediaToExports(customMedia, exportTo);
|
||||||
|
transformAtrules(root, customMedia, {
|
||||||
|
preserve
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
creator.postcss = true;
|
||||||
|
|
||||||
|
module.exports = creator;
|
||||||
|
//# sourceMappingURL=index.cjs.js.map
|
|
@ -0,0 +1,530 @@
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { parse as parse$1 } from 'postcss';
|
||||||
|
|
||||||
|
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');
|
||||||
|
var mediaASTFromString = (string => new MediaQueryList(string));
|
||||||
|
|
||||||
|
var getCustomMediaFromRoot = ((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);
|
||||||
|
|
||||||
|
/* Get Custom Media from CSS File
|
||||||
|
/* ========================================================================== */
|
||||||
|
|
||||||
|
async function getCustomMediaFromCSSFile(from) {
|
||||||
|
const css = await readFile(from);
|
||||||
|
const root = parse$1(css, {
|
||||||
|
from
|
||||||
|
});
|
||||||
|
return getCustomMediaFromRoot(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] = mediaASTFromString(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
|
||||||
|
/* ========================================================================== */
|
||||||
|
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
// return transformed medias, replacing custom pseudo medias with custom medias
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
var transformAtrules = ((root, customMedia, opts) => {
|
||||||
|
root.walkAtRules(mediaAtRuleRegExp, atrule => {
|
||||||
|
if (customPseudoRegExp$1.test(atrule.params)) {
|
||||||
|
const mediaAST = mediaASTFromString(atrule.params);
|
||||||
|
const params = String(transformMediaList(mediaAST, customMedia));
|
||||||
|
|
||||||
|
if (opts.preserve) {
|
||||||
|
atrule.cloneBefore({
|
||||||
|
params
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
atrule.params = params;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const mediaAtRuleRegExp = /^media$/i;
|
||||||
|
const customPseudoRegExp$1 = /\(--[A-z][\w-]*\)/;
|
||||||
|
|
||||||
|
/* 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
|
||||||
|
/* ========================================================================== */
|
||||||
|
|
||||||
|
|
||||||
|
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');
|
||||||
|
|
||||||
|
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 customMediaPromise = getCustomMediaFromSources(importFrom);
|
||||||
|
return {
|
||||||
|
postcssPlugin: 'postcss-custom-media',
|
||||||
|
Once: async root => {
|
||||||
|
const customMedia = Object.assign(await customMediaPromise, getCustomMediaFromRoot(root, {
|
||||||
|
preserve
|
||||||
|
}));
|
||||||
|
await writeCustomMediaToExports(customMedia, exportTo);
|
||||||
|
transformAtrules(root, customMedia, {
|
||||||
|
preserve
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
creator.postcss = true;
|
||||||
|
|
||||||
|
export default creator;
|
||||||
|
//# sourceMappingURL=index.es.mjs.map
|
|
@ -0,0 +1,108 @@
|
||||||
|
{
|
||||||
|
"_from": "postcss-custom-media",
|
||||||
|
"_id": "postcss-custom-media@8.0.0",
|
||||||
|
"_inBundle": false,
|
||||||
|
"_integrity": "sha512-FvO2GzMUaTN0t1fBULDeIvxr5IvbDXcIatt6pnJghc736nqNgsGao5NT+5+WVLAQiTt6Cb3YUms0jiPaXhL//g==",
|
||||||
|
"_location": "/postcss-custom-media",
|
||||||
|
"_phantomChildren": {},
|
||||||
|
"_requested": {
|
||||||
|
"type": "tag",
|
||||||
|
"registry": true,
|
||||||
|
"raw": "postcss-custom-media",
|
||||||
|
"name": "postcss-custom-media",
|
||||||
|
"escapedName": "postcss-custom-media",
|
||||||
|
"rawSpec": "",
|
||||||
|
"saveSpec": null,
|
||||||
|
"fetchSpec": "latest"
|
||||||
|
},
|
||||||
|
"_requiredBy": [
|
||||||
|
"#USER",
|
||||||
|
"/"
|
||||||
|
],
|
||||||
|
"_resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-8.0.0.tgz",
|
||||||
|
"_shasum": "1be6aff8be7dc9bf1fe014bde3b71b92bb4552f1",
|
||||||
|
"_spec": "postcss-custom-media",
|
||||||
|
"_where": "/src",
|
||||||
|
"author": {
|
||||||
|
"name": "Jonathan Neal",
|
||||||
|
"email": "jonathantneal@hotmail.com"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/postcss/postcss-custom-media/issues"
|
||||||
|
},
|
||||||
|
"bundleDependencies": false,
|
||||||
|
"contributors": [
|
||||||
|
{
|
||||||
|
"name": "Maxime Thirouin"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"deprecated": false,
|
||||||
|
"description": "Use Custom Media Queries in CSS",
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"es6": true,
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": "eslint:recommended",
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 2018,
|
||||||
|
"impliedStrict": true,
|
||||||
|
"sourceType": "module"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"index.cjs.js",
|
||||||
|
"index.es.mjs"
|
||||||
|
],
|
||||||
|
"homepage": "https://github.com/postcss/postcss-custom-media#readme",
|
||||||
|
"keywords": [
|
||||||
|
"postcss",
|
||||||
|
"css",
|
||||||
|
"postcss-plugin",
|
||||||
|
"custom",
|
||||||
|
"media",
|
||||||
|
"query",
|
||||||
|
"queries",
|
||||||
|
"w3c",
|
||||||
|
"csswg",
|
||||||
|
"atrule",
|
||||||
|
"at-rule",
|
||||||
|
"specification"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"main": "index.cjs.js",
|
||||||
|
"module": "index.es.mjs",
|
||||||
|
"name": "postcss-custom-media",
|
||||||
|
"peerDependencies": {
|
||||||
|
"postcss": "^8.1.0"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/postcss/postcss-custom-media.git"
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"version": "8.0.0"
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"requires": true,
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"dependencies": {
|
||||||
|
"postcss-custom-media": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-FvO2GzMUaTN0t1fBULDeIvxr5IvbDXcIatt6pnJghc736nqNgsGao5NT+5+WVLAQiTt6Cb3YUms0jiPaXhL//g=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue