first commit

This commit is contained in:
2026-09-08 20:28:54 +08:00
commit 2ada8d3d5a
37380 changed files with 4886169 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
Copyright (c) Ben Briggs <beneb.info@gmail.com> (http://beneb.info)
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.
+98
View File
@@ -0,0 +1,98 @@
# [postcss][postcss]-convert-values
> Convert values with PostCSS (e.g. ms -> s)
## Install
With [npm](https://npmjs.org/package/postcss-convert-values) do:
```
npm install postcss-convert-values --save
```
## Example
This plugin reduces CSS size by converting values to use different units
where possible; for example, `500ms` can be represented as `.5s`. You can
read more about these units in [this article][csstricks].
### Input
```css
h1 {
font-size: 16px;
width: 0em
}
```
### Output
```css
h1 {
font-size: 1pc;
width: 0
}
```
Note that this plugin only covers conversions for duration and absolute length
values. For color conversions, use [postcss-colormin][colormin].
## API
### convertValues([options])
#### options
##### length
Type: `boolean`
Default: `true`
Pass `false` to disable conversion from `px` to other absolute length units,
such as `pc` & `pt` & vice versa.
##### time
Type: `boolean`
Default: `true`
Pass `false` to disable conversion from `ms` to `s` & vice versa.
##### angle
Type: `boolean`
Default: `true`
Pass `false` to disable conversion from `deg` to `turn` & vice versa.
##### precision
Type: `boolean|number`
Default: `false`
Specify any numeric value here to round `px` values to that many decimal places;
for example, using `{precision: 2}` will round `6.66667px` to `6.67px`, and
`{precision: 0}` will round it to `7px`. Passing `false` (the default) will
leave these values as is.
It is recommended for most use cases to set this option to `2`.
## Usage
See the [PostCSS documentation](https://github.com/postcss/postcss#usage) for
examples for your environment.
## Contributors
See [CONTRIBUTORS.md](https://github.com/cssnano/cssnano/blob/master/CONTRIBUTORS.md).
## License
MIT © [Ben Briggs](http://beneb.info)
[postcss]: https://github.com/postcss/postcss
[csstricks]: https://css-tricks.com/the-lengths-of-css/
@@ -0,0 +1,22 @@
Copyright (c) Bogdan Chadkin <trysound@yandex.ru>
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,263 @@
# postcss-value-parser
[![Travis CI](https://travis-ci.org/TrySound/postcss-value-parser.svg)](https://travis-ci.org/TrySound/postcss-value-parser)
Transforms CSS declaration values and at-rule parameters into a tree of nodes, and provides a simple traversal API.
## Usage
```js
var valueParser = require('postcss-value-parser');
var cssBackgroundValue = 'url(foo.png) no-repeat 40px 73%';
var parsedValue = valueParser(cssBackgroundValue);
// parsedValue exposes an API described below,
// e.g. parsedValue.walk(..), parsedValue.toString(), etc.
```
For example, parsing the value `rgba(233, 45, 66, .5)` will return the following:
```js
{
nodes: [
{
type: 'function',
value: 'rgba',
before: '',
after: '',
nodes: [
{ type: 'word', value: '233' },
{ type: 'div', value: ',', before: '', after: ' ' },
{ type: 'word', value: '45' },
{ type: 'div', value: ',', before: '', after: ' ' },
{ type: 'word', value: '66' },
{ type: 'div', value: ',', before: ' ', after: '' },
{ type: 'word', value: '.5' }
]
}
]
}
```
If you wanted to convert each `rgba()` value in `sourceCSS` to a hex value, you could do so like this:
```js
var valueParser = require('postcss-value-parser');
var parsed = valueParser(sourceCSS);
// walk() will visit all the of the nodes in the tree,
// invoking the callback for each.
parsed.walk(function (node) {
// Since we only want to transform rgba() values,
// we can ignore anything else.
if (node.type !== 'function' && node.value !== 'rgba') return;
// We can make an array of the rgba() arguments to feed to a
// convertToHex() function
var color = node.nodes.filter(function (node) {
return node.type === 'word';
}).map(function (node) {
return Number(node.value);
}); // [233, 45, 66, .5]
// Now we will transform the existing rgba() function node
// into a word node with the hex value
node.type = 'word';
node.value = convertToHex(color);
})
parsed.toString(); // #E92D42
```
## Nodes
Each node is an object with these common properties:
- **type**: The type of node (`word`, `string`, `div`, `space`, `comment`, or `function`).
Each type is documented below.
- **value**: Each node has a `value` property; but what exactly `value` means
is specific to the node type. Details are documented for each type below.
- **sourceIndex**: The starting index of the node within the original source
string. For example, given the source string `10px 20px`, the `word` node
whose value is `20px` will have a `sourceIndex` of `5`.
### word
The catch-all node type that includes keywords (e.g. `no-repeat`),
quantities (e.g. `20px`, `75%`, `1.5`), and hex colors (e.g. `#e6e6e6`).
Node-specific properties:
- **value**: The "word" itself.
### string
A quoted string value, e.g. `"something"` in `content: "something";`.
Node-specific properties:
- **value**: The text content of the string.
- **quote**: The quotation mark surrounding the string, either `"` or `'`.
- **unclosed**: `true` if the string was not closed properly. e.g. `"unclosed string `.
### div
A divider, for example
- `,` in `animation-duration: 1s, 2s, 3s`
- `/` in `border-radius: 10px / 23px`
- `:` in `(min-width: 700px)`
Node-specific properties:
- **value**: The divider character. Either `,`, `/`, or `:` (see examples above).
- **before**: Whitespace before the divider.
- **after**: Whitespace after the divider.
### space
Whitespace used as a separator, e.g. ` ` occurring twice in `border: 1px solid black;`.
Node-specific properties:
- **value**: The whitespace itself.
### comment
A CSS comment starts with `/*` and ends with `*/`
Node-specific properties:
- **value**: The comment value without `/*` and `*/`
- **unclosed**: `true` if the comment was not closed properly. e.g. `/* comment without an end `.
### function
A CSS function, e.g. `rgb(0,0,0)` or `url(foo.bar)`.
Function nodes have nodes nested within them: the function arguments.
Additional properties:
- **value**: The name of the function, e.g. `rgb` in `rgb(0,0,0)`.
- **before**: Whitespace after the opening parenthesis and before the first argument,
e.g. ` ` in `rgb( 0,0,0)`.
- **after**: Whitespace before the closing parenthesis and after the last argument,
e.g. ` ` in `rgb(0,0,0 )`.
- **nodes**: More nodes representing the arguments to the function.
- **unclosed**: `true` if the parentheses was not closed properly. e.g. `( unclosed-function `.
Media features surrounded by parentheses are considered functions with an
empty value. For example, `(min-width: 700px)` parses to these nodes:
```js
[
{
type: 'function', value: '', before: '', after: '',
nodes: [
{ type: 'word', value: 'min-width' },
{ type: 'div', value: ':', before: '', after: ' ' },
{ type: 'word', value: '700px' }
]
}
]
```
`url()` functions can be parsed a little bit differently depending on
whether the first character in the argument is a quotation mark.
`url( /gfx/img/bg.jpg )` parses to:
```js
{ type: 'function', sourceIndex: 0, value: 'url', before: ' ', after: ' ', nodes: [
{ type: 'word', sourceIndex: 5, value: '/gfx/img/bg.jpg' }
] }
```
`url( "/gfx/img/bg.jpg" )`, on the other hand, parses to:
```js
{ type: 'function', sourceIndex: 0, value: 'url', before: ' ', after: ' ', nodes: [
type: 'string', sourceIndex: 5, quote: '"', value: '/gfx/img/bg.jpg' },
] }
```
### unicode-range
The unicode-range CSS descriptor sets the specific range of characters to be
used from a font defined by @font-face and made available
for use on the current page (`unicode-range: U+0025-00FF`).
Node-specific properties:
- **value**: The "unicode-range" itself.
## API
```
var valueParser = require('postcss-value-parser');
```
### valueParser.unit(quantity)
Parses `quantity`, distinguishing the number from the unit. Returns an object like the following:
```js
// Given 2rem
{
number: '2',
unit: 'rem'
}
```
If the `quantity` argument cannot be parsed as a number, returns `false`.
*This function does not parse complete values*: you cannot pass it `1px solid black` and expect `px` as
the unit. Instead, you should pass it single quantities only. Parse `1px solid black`, then pass it
the stringified `1px` node (a `word` node) to parse the number and unit.
### valueParser.stringify(nodes[, custom])
Stringifies a node or array of nodes.
The `custom` function is called for each `node`; return a string to override the default behaviour.
### valueParser.walk(nodes, callback[, bubble])
Walks each provided node, recursively walking all descendent nodes within functions.
Returning `false` in the `callback` will prevent traversal of descendent nodes (within functions).
You can use this feature to for shallow iteration, walking over only the *immediate* children.
*Note: This only applies if `bubble` is `false` (which is the default).*
By default, the tree is walked from the outermost node inwards.
To reverse the direction, pass `true` for the `bubble` argument.
The `callback` is invoked with three arguments: `callback(node, index, nodes)`.
- `node`: The current node.
- `index`: The index of the current node.
- `nodes`: The complete nodes array passed to `walk()`.
Returns the `valueParser` instance.
### var parsed = valueParser(value)
Returns the parsed node tree.
### parsed.nodes
The array of nodes.
### parsed.toString()
Stringifies the node tree.
### parsed.walk(callback[, bubble])
Walks each node inside `parsed.nodes`. See the documentation for `valueParser.walk()` above.
# License
MIT © [Bogdan Chadkin](mailto:trysound@yandex.ru)
@@ -0,0 +1,177 @@
declare namespace postcssValueParser {
interface BaseNode {
/**
* The offset, inclusive, inside the CSS value at which the node starts.
*/
sourceIndex: number;
/**
* The offset, exclusive, inside the CSS value at which the node ends.
*/
sourceEndIndex: number;
/**
* The node's characteristic value
*/
value: string;
}
interface ClosableNode {
/**
* Whether the parsed CSS value ended before the node was properly closed
*/
unclosed?: true;
}
interface AdjacentAwareNode {
/**
* The token at the start of the node
*/
before: string;
/**
* The token at the end of the node
*/
after: string;
}
interface CommentNode extends BaseNode, ClosableNode {
type: "comment";
}
interface DivNode extends BaseNode, AdjacentAwareNode {
type: "div";
}
interface FunctionNode extends BaseNode, ClosableNode, AdjacentAwareNode {
type: "function";
/**
* Nodes inside the function
*/
nodes: Node[];
}
interface SpaceNode extends BaseNode {
type: "space";
}
interface StringNode extends BaseNode, ClosableNode {
type: "string";
/**
* The quote type delimiting the string
*/
quote: '"' | "'";
}
interface UnicodeRangeNode extends BaseNode {
type: "unicode-range";
}
interface WordNode extends BaseNode {
type: "word";
}
/**
* Any node parsed from a CSS value
*/
type Node =
| CommentNode
| DivNode
| FunctionNode
| SpaceNode
| StringNode
| UnicodeRangeNode
| WordNode;
interface CustomStringifierCallback {
/**
* @param node The node to stringify
* @returns The serialized CSS representation of the node
*/
(nodes: Node): string | undefined;
}
interface WalkCallback {
/**
* @param node The currently visited node
* @param index The index of the node in the series of parsed nodes
* @param nodes The series of parsed nodes
* @returns Returning `false` will prevent traversal of descendant nodes (only applies if `bubble` was set to `true` in the `walk()` call)
*/
(node: Node, index: number, nodes: Node[]): void | boolean;
}
/**
* A CSS dimension, decomposed into its numeric and unit parts
*/
interface Dimension {
number: string;
unit: string;
}
/**
* A wrapper around a parsed CSS value that allows for inspecting and walking nodes
*/
interface ParsedValue {
/**
* The series of parsed nodes
*/
nodes: Node[];
/**
* Walk all parsed nodes, applying a callback
*
* @param callback A visitor callback that will be executed for each node
* @param bubble When set to `true`, walking will be done inside-out instead of outside-in
*/
walk(callback: WalkCallback, bubble?: boolean): this;
}
interface ValueParser {
/**
* Decompose a CSS dimension into its numeric and unit part
*
* @param value The dimension to decompose
* @returns An object representing `number` and `unit` part of the dimension or `false` if the decomposing fails
*/
unit(value: string): Dimension | false;
/**
* Serialize a series of nodes into a CSS value
*
* @param nodes The nodes to stringify
* @param custom A custom stringifier callback
* @returns The generated CSS value
*/
stringify(nodes: Node | Node[], custom?: CustomStringifierCallback): string;
/**
* Walk a series of nodes, applying a callback
*
* @param nodes The nodes to walk
* @param callback A visitor callback that will be executed for each node
* @param bubble When set to `true`, walking will be done inside-out instead of outside-in
*/
walk(nodes: Node[], callback: WalkCallback, bubble?: boolean): void;
/**
* Parse a CSS value into a series of nodes to operate on
*
* @param value The value to parse
*/
new (value: string): ParsedValue;
/**
* Parse a CSS value into a series of nodes to operate on
*
* @param value The value to parse
*/
(value: string): ParsedValue;
}
}
declare const postcssValueParser: postcssValueParser.ValueParser;
export = postcssValueParser;
@@ -0,0 +1,28 @@
var parse = require("./parse");
var walk = require("./walk");
var stringify = require("./stringify");
function ValueParser(value) {
if (this instanceof ValueParser) {
this.nodes = parse(value);
return this;
}
return new ValueParser(value);
}
ValueParser.prototype.toString = function() {
return Array.isArray(this.nodes) ? stringify(this.nodes) : "";
};
ValueParser.prototype.walk = function(cb, bubble) {
walk(this.nodes, cb, bubble);
return this;
};
ValueParser.unit = require("./unit");
ValueParser.walk = walk;
ValueParser.stringify = stringify;
module.exports = ValueParser;
@@ -0,0 +1,321 @@
var openParentheses = "(".charCodeAt(0);
var closeParentheses = ")".charCodeAt(0);
var singleQuote = "'".charCodeAt(0);
var doubleQuote = '"'.charCodeAt(0);
var backslash = "\\".charCodeAt(0);
var slash = "/".charCodeAt(0);
var comma = ",".charCodeAt(0);
var colon = ":".charCodeAt(0);
var star = "*".charCodeAt(0);
var uLower = "u".charCodeAt(0);
var uUpper = "U".charCodeAt(0);
var plus = "+".charCodeAt(0);
var isUnicodeRange = /^[a-f0-9?-]+$/i;
module.exports = function(input) {
var tokens = [];
var value = input;
var next,
quote,
prev,
token,
escape,
escapePos,
whitespacePos,
parenthesesOpenPos;
var pos = 0;
var code = value.charCodeAt(pos);
var max = value.length;
var stack = [{ nodes: tokens }];
var balanced = 0;
var parent;
var name = "";
var before = "";
var after = "";
while (pos < max) {
// Whitespaces
if (code <= 32) {
next = pos;
do {
next += 1;
code = value.charCodeAt(next);
} while (code <= 32);
token = value.slice(pos, next);
prev = tokens[tokens.length - 1];
if (code === closeParentheses && balanced) {
after = token;
} else if (prev && prev.type === "div") {
prev.after = token;
prev.sourceEndIndex += token.length;
} else if (
code === comma ||
code === colon ||
(code === slash &&
value.charCodeAt(next + 1) !== star &&
(!parent ||
(parent && parent.type === "function" && parent.value !== "calc")))
) {
before = token;
} else {
tokens.push({
type: "space",
sourceIndex: pos,
sourceEndIndex: next,
value: token
});
}
pos = next;
// Quotes
} else if (code === singleQuote || code === doubleQuote) {
next = pos;
quote = code === singleQuote ? "'" : '"';
token = {
type: "string",
sourceIndex: pos,
quote: quote
};
do {
escape = false;
next = value.indexOf(quote, next + 1);
if (~next) {
escapePos = next;
while (value.charCodeAt(escapePos - 1) === backslash) {
escapePos -= 1;
escape = !escape;
}
} else {
value += quote;
next = value.length - 1;
token.unclosed = true;
}
} while (escape);
token.value = value.slice(pos + 1, next);
token.sourceEndIndex = token.unclosed ? next : next + 1;
tokens.push(token);
pos = next + 1;
code = value.charCodeAt(pos);
// Comments
} else if (code === slash && value.charCodeAt(pos + 1) === star) {
next = value.indexOf("*/", pos);
token = {
type: "comment",
sourceIndex: pos,
sourceEndIndex: next + 2
};
if (next === -1) {
token.unclosed = true;
next = value.length;
token.sourceEndIndex = next;
}
token.value = value.slice(pos + 2, next);
tokens.push(token);
pos = next + 2;
code = value.charCodeAt(pos);
// Operation within calc
} else if (
(code === slash || code === star) &&
parent &&
parent.type === "function" &&
parent.value === "calc"
) {
token = value[pos];
tokens.push({
type: "word",
sourceIndex: pos - before.length,
sourceEndIndex: pos + token.length,
value: token
});
pos += 1;
code = value.charCodeAt(pos);
// Dividers
} else if (code === slash || code === comma || code === colon) {
token = value[pos];
tokens.push({
type: "div",
sourceIndex: pos - before.length,
sourceEndIndex: pos + token.length,
value: token,
before: before,
after: ""
});
before = "";
pos += 1;
code = value.charCodeAt(pos);
// Open parentheses
} else if (openParentheses === code) {
// Whitespaces after open parentheses
next = pos;
do {
next += 1;
code = value.charCodeAt(next);
} while (code <= 32);
parenthesesOpenPos = pos;
token = {
type: "function",
sourceIndex: pos - name.length,
value: name,
before: value.slice(parenthesesOpenPos + 1, next)
};
pos = next;
if (name === "url" && code !== singleQuote && code !== doubleQuote) {
next -= 1;
do {
escape = false;
next = value.indexOf(")", next + 1);
if (~next) {
escapePos = next;
while (value.charCodeAt(escapePos - 1) === backslash) {
escapePos -= 1;
escape = !escape;
}
} else {
value += ")";
next = value.length - 1;
token.unclosed = true;
}
} while (escape);
// Whitespaces before closed
whitespacePos = next;
do {
whitespacePos -= 1;
code = value.charCodeAt(whitespacePos);
} while (code <= 32);
if (parenthesesOpenPos < whitespacePos) {
if (pos !== whitespacePos + 1) {
token.nodes = [
{
type: "word",
sourceIndex: pos,
sourceEndIndex: whitespacePos + 1,
value: value.slice(pos, whitespacePos + 1)
}
];
} else {
token.nodes = [];
}
if (token.unclosed && whitespacePos + 1 !== next) {
token.after = "";
token.nodes.push({
type: "space",
sourceIndex: whitespacePos + 1,
sourceEndIndex: next,
value: value.slice(whitespacePos + 1, next)
});
} else {
token.after = value.slice(whitespacePos + 1, next);
token.sourceEndIndex = next;
}
} else {
token.after = "";
token.nodes = [];
}
pos = next + 1;
token.sourceEndIndex = token.unclosed ? next : pos;
code = value.charCodeAt(pos);
tokens.push(token);
} else {
balanced += 1;
token.after = "";
token.sourceEndIndex = pos + 1;
tokens.push(token);
stack.push(token);
tokens = token.nodes = [];
parent = token;
}
name = "";
// Close parentheses
} else if (closeParentheses === code && balanced) {
pos += 1;
code = value.charCodeAt(pos);
parent.after = after;
parent.sourceEndIndex += after.length;
after = "";
balanced -= 1;
stack[stack.length - 1].sourceEndIndex = pos;
stack.pop();
parent = stack[balanced];
tokens = parent.nodes;
// Words
} else {
next = pos;
do {
if (code === backslash) {
next += 1;
}
next += 1;
code = value.charCodeAt(next);
} while (
next < max &&
!(
code <= 32 ||
code === singleQuote ||
code === doubleQuote ||
code === comma ||
code === colon ||
code === slash ||
code === openParentheses ||
(code === star &&
parent &&
parent.type === "function" &&
parent.value === "calc") ||
(code === slash &&
parent.type === "function" &&
parent.value === "calc") ||
(code === closeParentheses && balanced)
)
);
token = value.slice(pos, next);
if (openParentheses === code) {
name = token;
} else if (
(uLower === token.charCodeAt(0) || uUpper === token.charCodeAt(0)) &&
plus === token.charCodeAt(1) &&
isUnicodeRange.test(token.slice(2))
) {
tokens.push({
type: "unicode-range",
sourceIndex: pos,
sourceEndIndex: next,
value: token
});
} else {
tokens.push({
type: "word",
sourceIndex: pos,
sourceEndIndex: next,
value: token
});
}
pos = next;
}
}
for (pos = stack.length - 1; pos; pos -= 1) {
stack[pos].unclosed = true;
stack[pos].sourceEndIndex = value.length;
}
return stack[0].nodes;
};
@@ -0,0 +1,48 @@
function stringifyNode(node, custom) {
var type = node.type;
var value = node.value;
var buf;
var customResult;
if (custom && (customResult = custom(node)) !== undefined) {
return customResult;
} else if (type === "word" || type === "space") {
return value;
} else if (type === "string") {
buf = node.quote || "";
return buf + value + (node.unclosed ? "" : buf);
} else if (type === "comment") {
return "/*" + value + (node.unclosed ? "" : "*/");
} else if (type === "div") {
return (node.before || "") + value + (node.after || "");
} else if (Array.isArray(node.nodes)) {
buf = stringify(node.nodes, custom);
if (type !== "function") {
return buf;
}
return (
value +
"(" +
(node.before || "") +
buf +
(node.after || "") +
(node.unclosed ? "" : ")")
);
}
return value;
}
function stringify(nodes, custom) {
var result, i;
if (Array.isArray(nodes)) {
result = "";
for (i = nodes.length - 1; ~i; i -= 1) {
result = stringifyNode(nodes[i], custom) + result;
}
return result;
}
return stringifyNode(nodes, custom);
}
module.exports = stringify;
@@ -0,0 +1,120 @@
var minus = "-".charCodeAt(0);
var plus = "+".charCodeAt(0);
var dot = ".".charCodeAt(0);
var exp = "e".charCodeAt(0);
var EXP = "E".charCodeAt(0);
// Check if three code points would start a number
// https://www.w3.org/TR/css-syntax-3/#starts-with-a-number
function likeNumber(value) {
var code = value.charCodeAt(0);
var nextCode;
if (code === plus || code === minus) {
nextCode = value.charCodeAt(1);
if (nextCode >= 48 && nextCode <= 57) {
return true;
}
var nextNextCode = value.charCodeAt(2);
if (nextCode === dot && nextNextCode >= 48 && nextNextCode <= 57) {
return true;
}
return false;
}
if (code === dot) {
nextCode = value.charCodeAt(1);
if (nextCode >= 48 && nextCode <= 57) {
return true;
}
return false;
}
if (code >= 48 && code <= 57) {
return true;
}
return false;
}
// Consume a number
// https://www.w3.org/TR/css-syntax-3/#consume-number
module.exports = function(value) {
var pos = 0;
var length = value.length;
var code;
var nextCode;
var nextNextCode;
if (length === 0 || !likeNumber(value)) {
return false;
}
code = value.charCodeAt(pos);
if (code === plus || code === minus) {
pos++;
}
while (pos < length) {
code = value.charCodeAt(pos);
if (code < 48 || code > 57) {
break;
}
pos += 1;
}
code = value.charCodeAt(pos);
nextCode = value.charCodeAt(pos + 1);
if (code === dot && nextCode >= 48 && nextCode <= 57) {
pos += 2;
while (pos < length) {
code = value.charCodeAt(pos);
if (code < 48 || code > 57) {
break;
}
pos += 1;
}
}
code = value.charCodeAt(pos);
nextCode = value.charCodeAt(pos + 1);
nextNextCode = value.charCodeAt(pos + 2);
if (
(code === exp || code === EXP) &&
((nextCode >= 48 && nextCode <= 57) ||
((nextCode === plus || nextCode === minus) &&
nextNextCode >= 48 &&
nextNextCode <= 57))
) {
pos += nextCode === plus || nextCode === minus ? 3 : 2;
while (pos < length) {
code = value.charCodeAt(pos);
if (code < 48 || code > 57) {
break;
}
pos += 1;
}
}
return {
number: value.slice(0, pos),
unit: value.slice(pos)
};
};
@@ -0,0 +1,22 @@
module.exports = function walk(nodes, cb, bubble) {
var i, max, node, result;
for (i = 0, max = nodes.length; i < max; i += 1) {
node = nodes[i];
if (!bubble) {
result = cb(node, i, nodes);
}
if (
result !== false &&
node.type === "function" &&
Array.isArray(node.nodes)
) {
walk(node.nodes, cb, bubble);
}
if (bubble) {
cb(node, i, nodes);
}
}
};
@@ -0,0 +1,58 @@
{
"name": "postcss-value-parser",
"version": "4.2.0",
"description": "Transforms css values and at-rule params into the tree",
"main": "lib/index.js",
"files": [
"lib"
],
"devDependencies": {
"eslint": "^5.16.0",
"husky": "^2.3.0",
"lint-staged": "^8.1.7",
"prettier": "^1.17.1",
"tap-spec": "^5.0.0",
"tape": "^4.10.2"
},
"scripts": {
"lint:prettier": "prettier \"**/*.js\" \"**/*.ts\" --list-different",
"lint:js": "eslint . --cache",
"lint": "yarn lint:js && yarn lint:prettier",
"pretest": "yarn lint",
"test": "tape test/*.js | tap-spec"
},
"eslintConfig": {
"env": {
"es6": true,
"node": true
},
"extends": "eslint:recommended"
},
"lint-staged": {
"*.js": [
"eslint",
"prettier --write",
"git add"
]
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"author": "Bogdan Chadkin <trysound@yandex.ru>",
"license": "MIT",
"homepage": "https://github.com/TrySound/postcss-value-parser",
"repository": {
"type": "git",
"url": "https://github.com/TrySound/postcss-value-parser.git"
},
"keywords": [
"postcss",
"value",
"parser"
],
"bugs": {
"url": "https://github.com/TrySound/postcss-value-parser/issues"
}
}
+42
View File
@@ -0,0 +1,42 @@
{
"name": "postcss-convert-values",
"version": "5.1.3",
"description": "Convert values with PostCSS (e.g. ms -> s)",
"main": "src/index.js",
"types": "types/index.d.ts",
"files": [
"LICENSE-MIT",
"src",
"types"
],
"keywords": [
"css",
"optimisation",
"postcss",
"postcss-plugin"
],
"license": "MIT",
"homepage": "https://github.com/cssnano/cssnano",
"author": {
"name": "Ben Briggs",
"email": "beneb.info@gmail.com",
"url": "http://beneb.info"
},
"repository": "cssnano/cssnano",
"dependencies": {
"browserslist": "^4.21.4",
"postcss-value-parser": "^4.2.0"
},
"bugs": {
"url": "https://github.com/cssnano/cssnano/issues"
},
"engines": {
"node": "^10 || ^12 || >=14.0"
},
"devDependencies": {
"postcss": "^8.2.15"
},
"peerDependencies": {
"postcss": "^8.2.15"
}
}
+207
View File
@@ -0,0 +1,207 @@
'use strict';
const valueParser = require('postcss-value-parser');
const browserslist = require('browserslist');
const convert = require('./lib/convert.js');
const LENGTH_UNITS = new Set([
'em',
'ex',
'ch',
'rem',
'vw',
'vh',
'vmin',
'vmax',
'cm',
'mm',
'q',
'in',
'pt',
'pc',
'px',
]);
// These properties only accept percentages, so no point in trying to transform
const notALength = new Set([
'descent-override',
'ascent-override',
'font-stretch',
'size-adjust',
'line-gap-override',
]);
// Can't change the unit on these properties when they're 0
const keepWhenZero = new Set([
'stroke-dashoffset',
'stroke-width',
'line-height',
]);
// Can't remove the % on these properties when they're 0 on IE 11
const keepZeroPercent = new Set(['max-height', 'height', 'min-width']);
/**
* Numbers without digits after the dot are technically invalid,
* but in that case css-value-parser returns the dot as part of the unit,
* so we use this to remove the dot.
*
* @param {string} item
* @return {string}
*/
function stripLeadingDot(item) {
if (item.charCodeAt(0) === '.'.charCodeAt(0)) {
return item.slice(1);
} else {
return item;
}
}
/**
* @param {valueParser.Node} node
* @param {Options} opts
* @param {boolean} keepZeroUnit
* @return {void}
*/
function parseWord(node, opts, keepZeroUnit) {
const pair = valueParser.unit(node.value);
if (pair) {
const num = Number(pair.number);
const u = stripLeadingDot(pair.unit);
if (num === 0) {
node.value =
0 +
(keepZeroUnit || (!LENGTH_UNITS.has(u.toLowerCase()) && u !== '%')
? u
: '');
} else {
node.value = convert(num, u, opts);
if (
typeof opts.precision === 'number' &&
u.toLowerCase() === 'px' &&
pair.number.includes('.')
) {
const precision = Math.pow(10, opts.precision);
node.value =
Math.round(parseFloat(node.value) * precision) / precision + u;
}
}
}
}
/**
* @param {valueParser.WordNode} node
* @return {void}
*/
function clampOpacity(node) {
const pair = valueParser.unit(node.value);
if (!pair) {
return;
}
let num = Number(pair.number);
if (num > 1) {
node.value = pair.unit === '%' ? num + pair.unit : 1 + pair.unit;
} else if (num < 0) {
node.value = 0 + pair.unit;
}
}
/**
* @param {import('postcss').Declaration} decl
* @param {string[]} browsers
* @return {boolean}
*/
function shouldKeepZeroUnit(decl, browsers) {
const { parent } = decl;
const lowerCasedProp = decl.prop.toLowerCase();
return (
(decl.value.includes('%') &&
keepZeroPercent.has(lowerCasedProp) &&
browsers.includes('ie 11')) ||
(parent &&
parent.parent &&
parent.parent.type === 'atrule' &&
/** @type {import('postcss').AtRule} */ (
parent.parent
).name.toLowerCase() === 'keyframes' &&
lowerCasedProp === 'stroke-dasharray') ||
keepWhenZero.has(lowerCasedProp)
);
}
/**
* @param {Options} opts
* @param {string[]} browsers
* @param {import('postcss').Declaration} decl
* @return {void}
*/
function transform(opts, browsers, decl) {
const lowerCasedProp = decl.prop.toLowerCase();
if (
lowerCasedProp.includes('flex') ||
lowerCasedProp.indexOf('--') === 0 ||
notALength.has(lowerCasedProp)
) {
return;
}
decl.value = valueParser(decl.value)
.walk((node) => {
const lowerCasedValue = node.value.toLowerCase();
if (node.type === 'word') {
parseWord(node, opts, shouldKeepZeroUnit(decl, browsers));
if (
lowerCasedProp === 'opacity' ||
lowerCasedProp === 'shape-image-threshold'
) {
clampOpacity(node);
}
} else if (node.type === 'function') {
if (
lowerCasedValue === 'calc' ||
lowerCasedValue === 'min' ||
lowerCasedValue === 'max' ||
lowerCasedValue === 'clamp' ||
lowerCasedValue === 'hsl' ||
lowerCasedValue === 'hsla'
) {
valueParser.walk(node.nodes, (n) => {
if (n.type === 'word') {
parseWord(n, opts, true);
}
});
return false;
}
if (lowerCasedValue === 'url') {
return false;
}
}
})
.toString();
}
const plugin = 'postcss-convert-values';
/**
* @typedef {{precision: boolean | number, angle?: boolean, time?: boolean, length?: boolean} & browserslist.Options} Options */
/**
* @type {import('postcss').PluginCreator<Options>}
* @param {Options} opts
* @return {import('postcss').Plugin}
*/
function pluginCreator(opts = { precision: false }) {
const browsers = browserslist(null, {
stats: opts.stats,
path: __dirname,
env: opts.env,
});
return {
postcssPlugin: plugin,
OnceExit(css) {
css.walkDecls((decl) => transform(opts, browsers, decl));
},
};
}
pluginCreator.postcss = true;
module.exports = pluginCreator;
+85
View File
@@ -0,0 +1,85 @@
'use strict';
const lengthConv = new Map([
['in', 96],
['px', 1],
['pt', 4 / 3],
['pc', 16],
]);
const timeConv = new Map([
['s', 1000],
['ms', 1],
]);
const angleConv = new Map([
['turn', 360],
['deg', 1],
]);
/**
* @param {number} number
* @return {string}
*/
function dropLeadingZero(number) {
const value = String(number);
if (number % 1) {
if (value[0] === '0') {
return value.slice(1);
}
if (value[0] === '-' && value[1] === '0') {
return '-' + value.slice(2);
}
}
return value;
}
/**
* @param {number} number
* @param {string} originalUnit
* @param {lengthConv | timeConv | angleConv} conversions
* @return {string}
*/
function transform(number, originalUnit, conversions) {
let conversionUnits = [...conversions.keys()].filter((u) => {
return originalUnit !== u;
});
const base = number * /** @type {number} */ (conversions.get(originalUnit));
return conversionUnits
.map(
(u) =>
dropLeadingZero(base / /** @type {number} */ (conversions.get(u))) + u
)
.reduce((a, b) => (a.length < b.length ? a : b));
}
/**
* @param {number} number
* @param {string} unit
* @param {{time?: boolean, length?: boolean, angle?: boolean}} options
* @return {string}
*/
module.exports = function (number, unit, { time, length, angle }) {
let value = dropLeadingZero(number) + (unit ? unit : '');
let converted;
const lowerCaseUnit = unit.toLowerCase();
if (length !== false && lengthConv.has(lowerCaseUnit)) {
converted = transform(number, lowerCaseUnit, lengthConv);
}
if (time !== false && timeConv.has(lowerCaseUnit)) {
converted = transform(number, lowerCaseUnit, timeConv);
}
if (angle !== false && angleConv.has(lowerCaseUnit)) {
converted = transform(number, lowerCaseUnit, angleConv);
}
if (converted && converted.length < value.length) {
value = converted;
}
return value;
};
+20
View File
@@ -0,0 +1,20 @@
export = pluginCreator;
/**
* @typedef {{precision: boolean | number, angle?: boolean, time?: boolean, length?: boolean} & browserslist.Options} Options */
/**
* @type {import('postcss').PluginCreator<Options>}
* @param {Options} opts
* @return {import('postcss').Plugin}
*/
declare function pluginCreator(opts?: Options): import('postcss').Plugin;
declare namespace pluginCreator {
export { postcss, Options };
}
type Options = {
precision: boolean | number;
angle?: boolean;
time?: boolean;
length?: boolean;
} & browserslist.Options;
declare var postcss: true;
import browserslist = require("browserslist");
+6
View File
@@ -0,0 +1,6 @@
declare function _exports(number: number, unit: string, { time, length, angle }: {
time?: boolean;
length?: boolean;
angle?: boolean;
}): string;
export = _exports;