Skip to content

Commit 1c22625

Browse files
committed
add web export | add stats data to transform,, parse and render functions | fix escape sequence parsing bug #4
1 parent 6c27892 commit 1c22625

File tree

20 files changed

+393
-222
lines changed

20 files changed

+393
-222
lines changed

README.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[![cov](https://tbela99.github.io/css-parser/badges/coverage.svg)](https://github.com/tbela99/css-parser/actions)
1+
[![npm](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Ftbela99%2Fcss-parser%2Fmaster%2Fpackage.json&query=version&logo=npm&label=npm&link=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2F%40tbela99%2Fcss-parser)](https://www.npmjs.com/package/@tbela99/css-parser) [![cov](https://tbela99.github.io/css-parser/badges/coverage.svg)](https://github.com/tbela99/css-parser/actions)
22

33
# css-parser
44

@@ -12,15 +12,15 @@ $ npm install @tbela99/css-parser
1212

1313
### Features
1414

15-
- [x] fault tolerant parser, will try to fix invalid tokens according to the CSS syntax module 3 recommendations.
16-
- [x] efficient minification, see benchmark
17-
- [x] replace @import at-rules with actual css content of the imported rule
18-
- [x] automatically create nested css rules
19-
- [x] works the same way in node and web browser
15+
- fault tolerant parser, will try to fix invalid tokens according to the CSS syntax module 3 recommendations.
16+
- efficient minification, see [benchmark](https://tbela99.github.io/css-parser/benchmark/index.html)
17+
- replace @import at-rules with actual css content of the imported rule
18+
- automatically create nested css rules
19+
- works the same way in node and web browser
2020

2121
### Performance
2222

23-
- [x] flatten @import
23+
- flatten @import
2424

2525
## Transform
2626

@@ -50,7 +50,7 @@ Include ParseOptions and RenderOptions
5050

5151
- src: string, optional. css file location to be used with sourcemap.
5252
- minify: boolean, optional. default to _true_. optimize ast.
53-
- nestingRules: boolean, optional. automatically nest rules.
53+
- nestingRules: boolean, optional. automatically generated nested rules.
5454
- removeEmpty: boolean, remove empty nodes from the ast.
5555
- location: boolean, optional. includes node location in the ast, required for sourcemap generation.
5656
- cwd: string, optional. the current working directory. when specified url() are resolved using this value

dist/index-umd-web.js

Lines changed: 68 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,7 @@
9393
if (name.charAt(0) != '#') {
9494
return false;
9595
}
96-
if (isIdent(name.charAt(1))) {
97-
return true;
98-
}
99-
return true;
96+
return isIdent(name.charAt(1));
10097
}
10198
function isNumber(name) {
10299
if (name.length == 0) {
@@ -1566,6 +1563,7 @@
15661563
}
15671564

15681565
function render(data, opt = {}) {
1566+
const startTime = performance.now();
15691567
const options = Object.assign(opt.minify ?? true ? {
15701568
indent: '',
15711569
newLine: '',
@@ -1584,7 +1582,9 @@
15841582
}
15851583
return acc + renderToken(curr, options);
15861584
}
1587-
return { code: doRender(data, options, reducer, 0) };
1585+
return { code: doRender(data, options, reducer, 0), stats: {
1586+
total: `${(performance.now() - startTime).toFixed(2)}ms`
1587+
} };
15881588
}
15891589
// @ts-ignore
15901590
function doRender(data, options, reducer, level = 0, indents = []) {
@@ -1613,7 +1613,7 @@
16131613
case 'AtRule':
16141614
case 'Rule':
16151615
if (data.typ == 'AtRule' && !('chi' in data)) {
1616-
return `${indent}@${data.nam} ${data.val};`;
1616+
return `${indent}@${data.nam}${data.val === '' ? '' : options.indent || ' '}${data.val};`;
16171617
}
16181618
// @ts-ignore
16191619
let children = data.chi.reduce((css, node) => {
@@ -1625,7 +1625,7 @@
16251625
str = `${node.nam}:${options.indent}${node.val.reduce(reducer, '').trimEnd()};`;
16261626
}
16271627
else if (node.typ == 'AtRule' && !('chi' in node)) {
1628-
str = `@${node.nam} ${node.val};`;
1628+
str = `${data.val === '' ? '' : options.indent || ' '}${data.val};`;
16291629
}
16301630
else {
16311631
str = doRender(node, options, reducer, level + 1, indents);
@@ -1642,7 +1642,7 @@
16421642
children = children.slice(0, -1);
16431643
}
16441644
if (data.typ == 'AtRule') {
1645-
return `@${data.nam}${data.val ? ' ' + data.val + options.indent : ''}{${options.newLine}` + (children === '' ? '' : indentSub + children + options.newLine) + indent + `}`;
1645+
return `@${data.nam}${data.val === '' ? '' : options.indent || ' '}${data.val}${options.indent}{${options.newLine}` + (children === '' ? '' : indentSub + children + options.newLine) + indent + `}`;
16461646
}
16471647
return data.sel + `${options.indent}{${options.newLine}` + (children === '' ? '' : indentSub + children + options.newLine) + indent + `}`;
16481648
}
@@ -1792,7 +1792,9 @@
17921792
case 'Delim':
17931793
return /* options.minify && 'Pseudo-class' == token.typ && '::' == token.val.slice(0, 2) ? token.val.slice(1) : */ token.val;
17941794
}
1795-
throw new Error(`unexpected token ${JSON.stringify(token, null, 1)}`);
1795+
console.error(`unexpected token ${JSON.stringify(token, null, 1)}`);
1796+
// throw new Error(`unexpected token ${JSON.stringify(token, null, 1)}`);
1797+
return '';
17961798
}
17971799

17981800
function eq(a, b) {
@@ -2581,7 +2583,7 @@
25812583
// @ts-ignore
25822584
return null;
25832585
}
2584-
return { result, node1: exchanged ? node2 : node1, node2: exchanged ? node2 : node2 };
2586+
return { result, node1: exchanged ? node2 : node1, node2: exchanged ? node1 : node2 };
25852587
}
25862588
function matchSelectors(selector1, selector2, parentType) {
25872589
let match = [[]];
@@ -2769,11 +2771,27 @@
27692771
if (node.typ == 'AtRule' && node.nam == 'font-face') {
27702772
continue;
27712773
}
2772-
if (node.typ == 'AtRule' && node.val == 'all') {
2774+
if (node.typ == 'AtRule') {
2775+
if (node.nam == 'media' && node.val == 'all') {
2776+
// @ts-ignore
2777+
ast.chi?.splice(i, 1, ...node.chi);
2778+
i--;
2779+
continue;
2780+
}
2781+
// console.debug({previous, node});
27732782
// @ts-ignore
2774-
ast.chi?.splice(i, 1, ...node.chi);
2775-
i--;
2776-
continue;
2783+
if (previous?.typ == 'AtRule' &&
2784+
previous.nam == node.nam &&
2785+
previous.val == node.val) {
2786+
if ('chi' in node) {
2787+
// @ts-ignore
2788+
previous.chi.push(...node.chi);
2789+
}
2790+
// else {
2791+
ast?.chi?.splice(i--, 1);
2792+
continue;
2793+
// }
2794+
}
27772795
}
27782796
// @ts-ignore
27792797
if (node.typ == 'Rule') {
@@ -3288,10 +3306,11 @@
32883306
}
32893307
buffer += quoteStr;
32903308
while (value = peek()) {
3291-
if (ind >= iterator.length) {
3292-
yield pushToken(buffer, hasNewLine ? 'Bad-string' : 'Unclosed-string');
3293-
break;
3294-
}
3309+
// if (ind >= iterator.length) {
3310+
//
3311+
// yield pushToken(buffer, hasNewLine ? 'Bad-string' : 'Unclosed-string');
3312+
// break;
3313+
// }
32953314
if (value == '\\') {
32963315
const sequence = peek(6);
32973316
let escapeSequence = '';
@@ -3314,7 +3333,7 @@
33143333
// not hex or new line
33153334
// @ts-ignore
33163335
if (i == 1 && !isNewLine(codepoint)) {
3317-
buffer += sequence[i];
3336+
buffer += value + sequence[i];
33183337
next(2);
33193338
continue;
33203339
}
@@ -3334,11 +3353,12 @@
33343353
continue;
33353354
}
33363355
// buffer += value;
3337-
if (ind >= iterator.length) {
3338-
// drop '\\' at the end
3339-
yield pushToken(buffer);
3340-
break;
3341-
}
3356+
// if (ind >= iterator.length) {
3357+
//
3358+
// // drop '\\' at the end
3359+
// yield pushToken(buffer);
3360+
// break;
3361+
// }
33423362
buffer += next(2);
33433363
continue;
33443364
}
@@ -3507,7 +3527,7 @@
35073527
buffer = '';
35083528
break;
35093529
}
3510-
buffer += value;
3530+
buffer += prev() + value;
35113531
break;
35123532
case '"':
35133533
case "'":
@@ -3709,6 +3729,7 @@
37093729
* @param opt
37103730
*/
37113731
async function parse$1(iterator, opt = {}) {
3732+
const startTime = performance.now();
37123733
const errors = [];
37133734
const options = {
37143735
src: '',
@@ -3847,7 +3868,7 @@
38473868
src: options.resolve(url, options.src).absolute
38483869
}));
38493870
});
3850-
bytesIn += root.bytesIn;
3871+
bytesIn += root.stats.bytesIn;
38513872
if (root.ast.chi.length > 0) {
38523873
context.chi.push(...root.ast.chi);
38533874
}
@@ -3893,13 +3914,6 @@
38933914
// rule
38943915
if (delim.typ == 'Block-start') {
38953916
const position = map.get(tokens[0]);
3896-
// if (context.typ == 'Rule') {
3897-
//
3898-
// if (tokens[0]?.typ == 'Iden') {
3899-
// errors.push({action: 'drop', message: 'invalid nesting rule', location: {src, ...position}});
3900-
// return null;
3901-
// }
3902-
// }
39033917
const uniq = new Map;
39043918
parseTokens(tokens, { minify: options.minify }).reduce((acc, curr, index, array) => {
39053919
if (curr.typ == 'Whitespace') {
@@ -4075,12 +4089,21 @@
40754089
if (tokens.length > 0) {
40764090
await parseNode(tokens);
40774091
}
4092+
const endParseTime = performance.now();
40784093
if (options.minify) {
40794094
if (ast.chi.length > 0) {
40804095
minify(ast, options, true);
40814096
}
40824097
}
4083-
return { ast, errors, bytesIn };
4098+
const endTime = performance.now();
4099+
return {
4100+
ast, errors, stats: {
4101+
bytesIn,
4102+
parse: `${(endParseTime - startTime).toFixed(2)}ms`,
4103+
minify: `${(endTime - endParseTime).toFixed(2)}ms`,
4104+
total: `${(endTime - startTime).toFixed(2)}ms`
4105+
}
4106+
};
40844107
}
40854108
function parseString(src, options = { location: false }) {
40864109
return [...tokenize(src)].map(t => {
@@ -4431,19 +4454,17 @@
44314454
async function transform$1(css, options = {}) {
44324455
options = { minify: true, removeEmpty: true, ...options };
44334456
const startTime = performance.now();
4434-
const parseResult = await parse$1(css, options);
4435-
const renderTime = performance.now();
4436-
const rendered = render(parseResult.ast, options);
4437-
const endTime = performance.now();
4438-
return {
4439-
...parseResult, ...rendered, stats: {
4440-
bytesIn: parseResult.bytesIn,
4441-
bytesOut: rendered.code.length,
4442-
parse: `${(renderTime - startTime).toFixed(2)}ms`,
4443-
render: `${(endTime - renderTime).toFixed(2)}ms`,
4444-
total: `${(endTime - startTime).toFixed(2)}ms`
4445-
}
4446-
};
4457+
return parse$1(css, options).then((parseResult) => {
4458+
const rendered = render(parseResult.ast, options);
4459+
return {
4460+
...parseResult, ...rendered, stats: {
4461+
bytesOut: rendered.code.length,
4462+
...parseResult.stats,
4463+
render: rendered.stats.total,
4464+
total: `${(performance.now() - startTime).toFixed(2)}ms`
4465+
}
4466+
};
4467+
});
44474468
}
44484469

44494470
const matchUrl = /^(https?:)?\/\//;

0 commit comments

Comments
 (0)