@tbela99/css-parser 0.0.1-rc1 → 0.0.1-rc2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -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)
2
2
 
3
3
  # css-parser
4
4
 
@@ -12,15 +12,15 @@ $ npm install @tbela99/css-parser
12
12
 
13
13
  ### Features
14
14
 
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] CSS nesting: automatically create nested 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
20
20
 
21
21
  ### Performance
22
22
 
23
- - [x] flatten @import
23
+ - flatten @import
24
24
 
25
25
  ## Transform
26
26
 
@@ -50,7 +50,7 @@ Include ParseOptions and RenderOptions
50
50
 
51
51
  - src: string, optional. css file location to be used with sourcemap.
52
52
  - minify: boolean, optional. default to _true_. optimize ast.
53
- - nestingRules: boolean, optional. automatically nest rules.
53
+ - nestingRules: boolean, optional. automatically generated nested rules.
54
54
  - removeEmpty: boolean, remove empty nodes from the ast.
55
55
  - location: boolean, optional. includes node location in the ast, required for sourcemap generation.
56
56
  - cwd: string, optional. the current working directory. when specified url() are resolved using this value
@@ -188,4 +188,4 @@ Single JavaScript file
188
188
  ### AtRuleStyleSheet
189
189
 
190
190
  - typ: string 'Stylesheet'
191
- - chi: array of children
191
+ - chi: array of children
@@ -93,10 +93,7 @@
93
93
  if (name.charAt(0) != '#') {
94
94
  return false;
95
95
  }
96
- if (isIdent(name.charAt(1))) {
97
- return true;
98
- }
99
- return true;
96
+ return isIdent(name.charAt(1));
100
97
  }
101
98
  function isNumber(name) {
102
99
  if (name.length == 0) {
@@ -1566,6 +1563,7 @@
1566
1563
  }
1567
1564
 
1568
1565
  function render(data, opt = {}) {
1566
+ const startTime = performance.now();
1569
1567
  const options = Object.assign(opt.minify ?? true ? {
1570
1568
  indent: '',
1571
1569
  newLine: '',
@@ -1584,7 +1582,9 @@
1584
1582
  }
1585
1583
  return acc + renderToken(curr, options);
1586
1584
  }
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
+ } };
1588
1588
  }
1589
1589
  // @ts-ignore
1590
1590
  function doRender(data, options, reducer, level = 0, indents = []) {
@@ -1613,7 +1613,7 @@
1613
1613
  case 'AtRule':
1614
1614
  case 'Rule':
1615
1615
  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};`;
1617
1617
  }
1618
1618
  // @ts-ignore
1619
1619
  let children = data.chi.reduce((css, node) => {
@@ -1625,7 +1625,7 @@
1625
1625
  str = `${node.nam}:${options.indent}${node.val.reduce(reducer, '').trimEnd()};`;
1626
1626
  }
1627
1627
  else if (node.typ == 'AtRule' && !('chi' in node)) {
1628
- str = `@${node.nam} ${node.val};`;
1628
+ str = `${data.val === '' ? '' : options.indent || ' '}${data.val};`;
1629
1629
  }
1630
1630
  else {
1631
1631
  str = doRender(node, options, reducer, level + 1, indents);
@@ -1642,7 +1642,7 @@
1642
1642
  children = children.slice(0, -1);
1643
1643
  }
1644
1644
  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 + `}`;
1646
1646
  }
1647
1647
  return data.sel + `${options.indent}{${options.newLine}` + (children === '' ? '' : indentSub + children + options.newLine) + indent + `}`;
1648
1648
  }
@@ -1792,7 +1792,9 @@
1792
1792
  case 'Delim':
1793
1793
  return /* options.minify && 'Pseudo-class' == token.typ && '::' == token.val.slice(0, 2) ? token.val.slice(1) : */ token.val;
1794
1794
  }
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 '';
1796
1798
  }
1797
1799
 
1798
1800
  function eq(a, b) {
@@ -2581,7 +2583,7 @@
2581
2583
  // @ts-ignore
2582
2584
  return null;
2583
2585
  }
2584
- return { result, node1: exchanged ? node2 : node1, node2: exchanged ? node2 : node2 };
2586
+ return { result, node1: exchanged ? node2 : node1, node2: exchanged ? node1 : node2 };
2585
2587
  }
2586
2588
  function matchSelectors(selector1, selector2, parentType) {
2587
2589
  let match = [[]];
@@ -2769,11 +2771,27 @@
2769
2771
  if (node.typ == 'AtRule' && node.nam == 'font-face') {
2770
2772
  continue;
2771
2773
  }
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});
2773
2782
  // @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
+ }
2777
2795
  }
2778
2796
  // @ts-ignore
2779
2797
  if (node.typ == 'Rule') {
@@ -3288,10 +3306,11 @@
3288
3306
  }
3289
3307
  buffer += quoteStr;
3290
3308
  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
+ // }
3295
3314
  if (value == '\\') {
3296
3315
  const sequence = peek(6);
3297
3316
  let escapeSequence = '';
@@ -3314,7 +3333,7 @@
3314
3333
  // not hex or new line
3315
3334
  // @ts-ignore
3316
3335
  if (i == 1 && !isNewLine(codepoint)) {
3317
- buffer += sequence[i];
3336
+ buffer += value + sequence[i];
3318
3337
  next(2);
3319
3338
  continue;
3320
3339
  }
@@ -3334,11 +3353,12 @@
3334
3353
  continue;
3335
3354
  }
3336
3355
  // 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
+ // }
3342
3362
  buffer += next(2);
3343
3363
  continue;
3344
3364
  }
@@ -3507,7 +3527,7 @@
3507
3527
  buffer = '';
3508
3528
  break;
3509
3529
  }
3510
- buffer += value;
3530
+ buffer += prev() + value;
3511
3531
  break;
3512
3532
  case '"':
3513
3533
  case "'":
@@ -3709,6 +3729,7 @@
3709
3729
  * @param opt
3710
3730
  */
3711
3731
  async function parse$1(iterator, opt = {}) {
3732
+ const startTime = performance.now();
3712
3733
  const errors = [];
3713
3734
  const options = {
3714
3735
  src: '',
@@ -3847,7 +3868,7 @@
3847
3868
  src: options.resolve(url, options.src).absolute
3848
3869
  }));
3849
3870
  });
3850
- bytesIn += root.bytesIn;
3871
+ bytesIn += root.stats.bytesIn;
3851
3872
  if (root.ast.chi.length > 0) {
3852
3873
  context.chi.push(...root.ast.chi);
3853
3874
  }
@@ -3893,13 +3914,6 @@
3893
3914
  // rule
3894
3915
  if (delim.typ == 'Block-start') {
3895
3916
  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
- // }
3903
3917
  const uniq = new Map;
3904
3918
  parseTokens(tokens, { minify: options.minify }).reduce((acc, curr, index, array) => {
3905
3919
  if (curr.typ == 'Whitespace') {
@@ -4075,12 +4089,21 @@
4075
4089
  if (tokens.length > 0) {
4076
4090
  await parseNode(tokens);
4077
4091
  }
4092
+ const endParseTime = performance.now();
4078
4093
  if (options.minify) {
4079
4094
  if (ast.chi.length > 0) {
4080
4095
  minify(ast, options, true);
4081
4096
  }
4082
4097
  }
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
+ };
4084
4107
  }
4085
4108
  function parseString(src, options = { location: false }) {
4086
4109
  return [...tokenize(src)].map(t => {
@@ -4431,19 +4454,17 @@
4431
4454
  async function transform$1(css, options = {}) {
4432
4455
  options = { minify: true, removeEmpty: true, ...options };
4433
4456
  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
+ });
4447
4468
  }
4448
4469
 
4449
4470
  const matchUrl = /^(https?:)?\/\//;
package/dist/index.cjs CHANGED
@@ -91,10 +91,7 @@ function isHash(name) {
91
91
  if (name.charAt(0) != '#') {
92
92
  return false;
93
93
  }
94
- if (isIdent(name.charAt(1))) {
95
- return true;
96
- }
97
- return true;
94
+ return isIdent(name.charAt(1));
98
95
  }
99
96
  function isNumber(name) {
100
97
  if (name.length == 0) {
@@ -1564,6 +1561,7 @@ function hsl2rgb(h, s, l, a = null) {
1564
1561
  }
1565
1562
 
1566
1563
  function render(data, opt = {}) {
1564
+ const startTime = performance.now();
1567
1565
  const options = Object.assign(opt.minify ?? true ? {
1568
1566
  indent: '',
1569
1567
  newLine: '',
@@ -1582,7 +1580,9 @@ function render(data, opt = {}) {
1582
1580
  }
1583
1581
  return acc + renderToken(curr, options);
1584
1582
  }
1585
- return { code: doRender(data, options, reducer, 0) };
1583
+ return { code: doRender(data, options, reducer, 0), stats: {
1584
+ total: `${(performance.now() - startTime).toFixed(2)}ms`
1585
+ } };
1586
1586
  }
1587
1587
  // @ts-ignore
1588
1588
  function doRender(data, options, reducer, level = 0, indents = []) {
@@ -1611,7 +1611,7 @@ function doRender(data, options, reducer, level = 0, indents = []) {
1611
1611
  case 'AtRule':
1612
1612
  case 'Rule':
1613
1613
  if (data.typ == 'AtRule' && !('chi' in data)) {
1614
- return `${indent}@${data.nam} ${data.val};`;
1614
+ return `${indent}@${data.nam}${data.val === '' ? '' : options.indent || ' '}${data.val};`;
1615
1615
  }
1616
1616
  // @ts-ignore
1617
1617
  let children = data.chi.reduce((css, node) => {
@@ -1623,7 +1623,7 @@ function doRender(data, options, reducer, level = 0, indents = []) {
1623
1623
  str = `${node.nam}:${options.indent}${node.val.reduce(reducer, '').trimEnd()};`;
1624
1624
  }
1625
1625
  else if (node.typ == 'AtRule' && !('chi' in node)) {
1626
- str = `@${node.nam} ${node.val};`;
1626
+ str = `${data.val === '' ? '' : options.indent || ' '}${data.val};`;
1627
1627
  }
1628
1628
  else {
1629
1629
  str = doRender(node, options, reducer, level + 1, indents);
@@ -1640,7 +1640,7 @@ function doRender(data, options, reducer, level = 0, indents = []) {
1640
1640
  children = children.slice(0, -1);
1641
1641
  }
1642
1642
  if (data.typ == 'AtRule') {
1643
- return `@${data.nam}${data.val ? ' ' + data.val + options.indent : ''}{${options.newLine}` + (children === '' ? '' : indentSub + children + options.newLine) + indent + `}`;
1643
+ return `@${data.nam}${data.val === '' ? '' : options.indent || ' '}${data.val}${options.indent}{${options.newLine}` + (children === '' ? '' : indentSub + children + options.newLine) + indent + `}`;
1644
1644
  }
1645
1645
  return data.sel + `${options.indent}{${options.newLine}` + (children === '' ? '' : indentSub + children + options.newLine) + indent + `}`;
1646
1646
  }
@@ -1790,7 +1790,9 @@ function renderToken(token, options = {}) {
1790
1790
  case 'Delim':
1791
1791
  return /* options.minify && 'Pseudo-class' == token.typ && '::' == token.val.slice(0, 2) ? token.val.slice(1) : */ token.val;
1792
1792
  }
1793
- throw new Error(`unexpected token ${JSON.stringify(token, null, 1)}`);
1793
+ console.error(`unexpected token ${JSON.stringify(token, null, 1)}`);
1794
+ // throw new Error(`unexpected token ${JSON.stringify(token, null, 1)}`);
1795
+ return '';
1794
1796
  }
1795
1797
 
1796
1798
  function eq(a, b) {
@@ -2579,7 +2581,7 @@ function minify(ast, options = {}, recursive = false) {
2579
2581
  // @ts-ignore
2580
2582
  return null;
2581
2583
  }
2582
- return { result, node1: exchanged ? node2 : node1, node2: exchanged ? node2 : node2 };
2584
+ return { result, node1: exchanged ? node2 : node1, node2: exchanged ? node1 : node2 };
2583
2585
  }
2584
2586
  function matchSelectors(selector1, selector2, parentType) {
2585
2587
  let match = [[]];
@@ -2767,11 +2769,27 @@ function minify(ast, options = {}, recursive = false) {
2767
2769
  if (node.typ == 'AtRule' && node.nam == 'font-face') {
2768
2770
  continue;
2769
2771
  }
2770
- if (node.typ == 'AtRule' && node.val == 'all') {
2772
+ if (node.typ == 'AtRule') {
2773
+ if (node.nam == 'media' && node.val == 'all') {
2774
+ // @ts-ignore
2775
+ ast.chi?.splice(i, 1, ...node.chi);
2776
+ i--;
2777
+ continue;
2778
+ }
2779
+ // console.debug({previous, node});
2771
2780
  // @ts-ignore
2772
- ast.chi?.splice(i, 1, ...node.chi);
2773
- i--;
2774
- continue;
2781
+ if (previous?.typ == 'AtRule' &&
2782
+ previous.nam == node.nam &&
2783
+ previous.val == node.val) {
2784
+ if ('chi' in node) {
2785
+ // @ts-ignore
2786
+ previous.chi.push(...node.chi);
2787
+ }
2788
+ // else {
2789
+ ast?.chi?.splice(i--, 1);
2790
+ continue;
2791
+ // }
2792
+ }
2775
2793
  }
2776
2794
  // @ts-ignore
2777
2795
  if (node.typ == 'Rule') {
@@ -3286,10 +3304,11 @@ function* tokenize(iterator) {
3286
3304
  }
3287
3305
  buffer += quoteStr;
3288
3306
  while (value = peek()) {
3289
- if (ind >= iterator.length) {
3290
- yield pushToken(buffer, hasNewLine ? 'Bad-string' : 'Unclosed-string');
3291
- break;
3292
- }
3307
+ // if (ind >= iterator.length) {
3308
+ //
3309
+ // yield pushToken(buffer, hasNewLine ? 'Bad-string' : 'Unclosed-string');
3310
+ // break;
3311
+ // }
3293
3312
  if (value == '\\') {
3294
3313
  const sequence = peek(6);
3295
3314
  let escapeSequence = '';
@@ -3312,7 +3331,7 @@ function* tokenize(iterator) {
3312
3331
  // not hex or new line
3313
3332
  // @ts-ignore
3314
3333
  if (i == 1 && !isNewLine(codepoint)) {
3315
- buffer += sequence[i];
3334
+ buffer += value + sequence[i];
3316
3335
  next(2);
3317
3336
  continue;
3318
3337
  }
@@ -3332,11 +3351,12 @@ function* tokenize(iterator) {
3332
3351
  continue;
3333
3352
  }
3334
3353
  // buffer += value;
3335
- if (ind >= iterator.length) {
3336
- // drop '\\' at the end
3337
- yield pushToken(buffer);
3338
- break;
3339
- }
3354
+ // if (ind >= iterator.length) {
3355
+ //
3356
+ // // drop '\\' at the end
3357
+ // yield pushToken(buffer);
3358
+ // break;
3359
+ // }
3340
3360
  buffer += next(2);
3341
3361
  continue;
3342
3362
  }
@@ -3505,7 +3525,7 @@ function* tokenize(iterator) {
3505
3525
  buffer = '';
3506
3526
  break;
3507
3527
  }
3508
- buffer += value;
3528
+ buffer += prev() + value;
3509
3529
  break;
3510
3530
  case '"':
3511
3531
  case "'":
@@ -3707,6 +3727,7 @@ const funcLike = ['Start-parens', 'Func', 'UrlFunc', 'Pseudo-class-func'];
3707
3727
  * @param opt
3708
3728
  */
3709
3729
  async function parse$1(iterator, opt = {}) {
3730
+ const startTime = performance.now();
3710
3731
  const errors = [];
3711
3732
  const options = {
3712
3733
  src: '',
@@ -3845,7 +3866,7 @@ async function parse$1(iterator, opt = {}) {
3845
3866
  src: options.resolve(url, options.src).absolute
3846
3867
  }));
3847
3868
  });
3848
- bytesIn += root.bytesIn;
3869
+ bytesIn += root.stats.bytesIn;
3849
3870
  if (root.ast.chi.length > 0) {
3850
3871
  context.chi.push(...root.ast.chi);
3851
3872
  }
@@ -3891,13 +3912,6 @@ async function parse$1(iterator, opt = {}) {
3891
3912
  // rule
3892
3913
  if (delim.typ == 'Block-start') {
3893
3914
  const position = map.get(tokens[0]);
3894
- // if (context.typ == 'Rule') {
3895
- //
3896
- // if (tokens[0]?.typ == 'Iden') {
3897
- // errors.push({action: 'drop', message: 'invalid nesting rule', location: {src, ...position}});
3898
- // return null;
3899
- // }
3900
- // }
3901
3915
  const uniq = new Map;
3902
3916
  parseTokens(tokens, { minify: options.minify }).reduce((acc, curr, index, array) => {
3903
3917
  if (curr.typ == 'Whitespace') {
@@ -4073,12 +4087,21 @@ async function parse$1(iterator, opt = {}) {
4073
4087
  if (tokens.length > 0) {
4074
4088
  await parseNode(tokens);
4075
4089
  }
4090
+ const endParseTime = performance.now();
4076
4091
  if (options.minify) {
4077
4092
  if (ast.chi.length > 0) {
4078
4093
  minify(ast, options, true);
4079
4094
  }
4080
4095
  }
4081
- return { ast, errors, bytesIn };
4096
+ const endTime = performance.now();
4097
+ return {
4098
+ ast, errors, stats: {
4099
+ bytesIn,
4100
+ parse: `${(endParseTime - startTime).toFixed(2)}ms`,
4101
+ minify: `${(endTime - endParseTime).toFixed(2)}ms`,
4102
+ total: `${(endTime - startTime).toFixed(2)}ms`
4103
+ }
4104
+ };
4082
4105
  }
4083
4106
  function parseString(src, options = { location: false }) {
4084
4107
  return [...tokenize(src)].map(t => {
@@ -4429,19 +4452,17 @@ function parseTokens(tokens, options = {}) {
4429
4452
  async function transform$1(css, options = {}) {
4430
4453
  options = { minify: true, removeEmpty: true, ...options };
4431
4454
  const startTime = performance.now();
4432
- const parseResult = await parse$1(css, options);
4433
- const renderTime = performance.now();
4434
- const rendered = render(parseResult.ast, options);
4435
- const endTime = performance.now();
4436
- return {
4437
- ...parseResult, ...rendered, stats: {
4438
- bytesIn: parseResult.bytesIn,
4439
- bytesOut: rendered.code.length,
4440
- parse: `${(renderTime - startTime).toFixed(2)}ms`,
4441
- render: `${(endTime - renderTime).toFixed(2)}ms`,
4442
- total: `${(endTime - startTime).toFixed(2)}ms`
4443
- }
4444
- };
4455
+ return parse$1(css, options).then((parseResult) => {
4456
+ const rendered = render(parseResult.ast, options);
4457
+ return {
4458
+ ...parseResult, ...rendered, stats: {
4459
+ bytesOut: rendered.code.length,
4460
+ ...parseResult.stats,
4461
+ render: rendered.stats.total,
4462
+ total: `${(performance.now() - startTime).toFixed(2)}ms`
4463
+ }
4464
+ };
4465
+ });
4445
4466
  }
4446
4467
 
4447
4468
  const matchUrl = /^(https?:)?\/\//;
package/dist/index.d.ts CHANGED
@@ -487,11 +487,19 @@ interface TransformOptions extends ParserOptions, RenderOptions {
487
487
  interface ParseResult {
488
488
  ast: AstRuleStyleSheet;
489
489
  errors: ErrorDescription[];
490
- bytesIn: number;
490
+ stats: {
491
+ bytesIn: number;
492
+ parse: string;
493
+ minify: string;
494
+ total: string;
495
+ }
491
496
  }
492
497
 
493
498
  interface RenderResult {
494
499
  code: string ;
500
+ stats: {
501
+ total: string;
502
+ }
495
503
  }
496
504
 
497
505
  interface TransformResult extends ParseResult, RenderResult {
@@ -500,6 +508,7 @@ interface TransformResult extends ParseResult, RenderResult {
500
508
  bytesIn: number;
501
509
  bytesOut: number;
502
510
  parse: string;
511
+ minify: string;
503
512
  render: string;
504
513
  total: string;
505
514
  }
@@ -135,7 +135,7 @@ function minify(ast, options = {}, recursive = false) {
135
135
  // @ts-ignore
136
136
  return null;
137
137
  }
138
- return { result, node1: exchanged ? node2 : node1, node2: exchanged ? node2 : node2 };
138
+ return { result, node1: exchanged ? node2 : node1, node2: exchanged ? node1 : node2 };
139
139
  }
140
140
  function matchSelectors(selector1, selector2, parentType) {
141
141
  let match = [[]];
@@ -323,11 +323,27 @@ function minify(ast, options = {}, recursive = false) {
323
323
  if (node.typ == 'AtRule' && node.nam == 'font-face') {
324
324
  continue;
325
325
  }
326
- if (node.typ == 'AtRule' && node.val == 'all') {
326
+ if (node.typ == 'AtRule') {
327
+ if (node.nam == 'media' && node.val == 'all') {
328
+ // @ts-ignore
329
+ ast.chi?.splice(i, 1, ...node.chi);
330
+ i--;
331
+ continue;
332
+ }
333
+ // console.debug({previous, node});
327
334
  // @ts-ignore
328
- ast.chi?.splice(i, 1, ...node.chi);
329
- i--;
330
- continue;
335
+ if (previous?.typ == 'AtRule' &&
336
+ previous.nam == node.nam &&
337
+ previous.val == node.val) {
338
+ if ('chi' in node) {
339
+ // @ts-ignore
340
+ previous.chi.push(...node.chi);
341
+ }
342
+ // else {
343
+ ast?.chi?.splice(i--, 1);
344
+ continue;
345
+ // }
346
+ }
331
347
  }
332
348
  // @ts-ignore
333
349
  if (node.typ == 'Rule') {
@@ -12,6 +12,7 @@ const funcLike = ['Start-parens', 'Func', 'UrlFunc', 'Pseudo-class-func'];
12
12
  * @param opt
13
13
  */
14
14
  async function parse(iterator, opt = {}) {
15
+ const startTime = performance.now();
15
16
  const errors = [];
16
17
  const options = {
17
18
  src: '',
@@ -150,7 +151,7 @@ async function parse(iterator, opt = {}) {
150
151
  src: options.resolve(url, options.src).absolute
151
152
  }));
152
153
  });
153
- bytesIn += root.bytesIn;
154
+ bytesIn += root.stats.bytesIn;
154
155
  if (root.ast.chi.length > 0) {
155
156
  context.chi.push(...root.ast.chi);
156
157
  }
@@ -196,13 +197,6 @@ async function parse(iterator, opt = {}) {
196
197
  // rule
197
198
  if (delim.typ == 'Block-start') {
198
199
  const position = map.get(tokens[0]);
199
- // if (context.typ == 'Rule') {
200
- //
201
- // if (tokens[0]?.typ == 'Iden') {
202
- // errors.push({action: 'drop', message: 'invalid nesting rule', location: {src, ...position}});
203
- // return null;
204
- // }
205
- // }
206
200
  const uniq = new Map;
207
201
  parseTokens(tokens, { minify: options.minify }).reduce((acc, curr, index, array) => {
208
202
  if (curr.typ == 'Whitespace') {
@@ -378,12 +372,21 @@ async function parse(iterator, opt = {}) {
378
372
  if (tokens.length > 0) {
379
373
  await parseNode(tokens);
380
374
  }
375
+ const endParseTime = performance.now();
381
376
  if (options.minify) {
382
377
  if (ast.chi.length > 0) {
383
378
  minify(ast, options, true);
384
379
  }
385
380
  }
386
- return { ast, errors, bytesIn };
381
+ const endTime = performance.now();
382
+ return {
383
+ ast, errors, stats: {
384
+ bytesIn,
385
+ parse: `${(endParseTime - startTime).toFixed(2)}ms`,
386
+ minify: `${(endTime - endParseTime).toFixed(2)}ms`,
387
+ total: `${(endTime - startTime).toFixed(2)}ms`
388
+ }
389
+ };
387
390
  }
388
391
  function parseString(src, options = { location: false }) {
389
392
  return [...tokenize(src)].map(t => {
@@ -36,10 +36,11 @@ function* tokenize(iterator) {
36
36
  }
37
37
  buffer += quoteStr;
38
38
  while (value = peek()) {
39
- if (ind >= iterator.length) {
40
- yield pushToken(buffer, hasNewLine ? 'Bad-string' : 'Unclosed-string');
41
- break;
42
- }
39
+ // if (ind >= iterator.length) {
40
+ //
41
+ // yield pushToken(buffer, hasNewLine ? 'Bad-string' : 'Unclosed-string');
42
+ // break;
43
+ // }
43
44
  if (value == '\\') {
44
45
  const sequence = peek(6);
45
46
  let escapeSequence = '';
@@ -62,7 +63,7 @@ function* tokenize(iterator) {
62
63
  // not hex or new line
63
64
  // @ts-ignore
64
65
  if (i == 1 && !isNewLine(codepoint)) {
65
- buffer += sequence[i];
66
+ buffer += value + sequence[i];
66
67
  next(2);
67
68
  continue;
68
69
  }
@@ -82,11 +83,12 @@ function* tokenize(iterator) {
82
83
  continue;
83
84
  }
84
85
  // buffer += value;
85
- if (ind >= iterator.length) {
86
- // drop '\\' at the end
87
- yield pushToken(buffer);
88
- break;
89
- }
86
+ // if (ind >= iterator.length) {
87
+ //
88
+ // // drop '\\' at the end
89
+ // yield pushToken(buffer);
90
+ // break;
91
+ // }
90
92
  buffer += next(2);
91
93
  continue;
92
94
  }
@@ -255,7 +257,7 @@ function* tokenize(iterator) {
255
257
  buffer = '';
256
258
  break;
257
259
  }
258
- buffer += value;
260
+ buffer += prev() + value;
259
261
  break;
260
262
  case '"':
261
263
  case "'":
@@ -87,10 +87,7 @@ function isHash(name) {
87
87
  if (name.charAt(0) != '#') {
88
88
  return false;
89
89
  }
90
- if (isIdent(name.charAt(1))) {
91
- return true;
92
- }
93
- return true;
90
+ return isIdent(name.charAt(1));
94
91
  }
95
92
  function isNumber(name) {
96
93
  if (name.length == 0) {
@@ -1,6 +1,7 @@
1
1
  import { COLORS_NAMES, rgb2Hex, hsl2Hex, hwb2hex, cmyk2hex, NAMES_COLORS } from './utils/color.js';
2
2
 
3
3
  function render(data, opt = {}) {
4
+ const startTime = performance.now();
4
5
  const options = Object.assign(opt.minify ?? true ? {
5
6
  indent: '',
6
7
  newLine: '',
@@ -19,7 +20,9 @@ function render(data, opt = {}) {
19
20
  }
20
21
  return acc + renderToken(curr, options);
21
22
  }
22
- return { code: doRender(data, options, reducer, 0) };
23
+ return { code: doRender(data, options, reducer, 0), stats: {
24
+ total: `${(performance.now() - startTime).toFixed(2)}ms`
25
+ } };
23
26
  }
24
27
  // @ts-ignore
25
28
  function doRender(data, options, reducer, level = 0, indents = []) {
@@ -48,7 +51,7 @@ function doRender(data, options, reducer, level = 0, indents = []) {
48
51
  case 'AtRule':
49
52
  case 'Rule':
50
53
  if (data.typ == 'AtRule' && !('chi' in data)) {
51
- return `${indent}@${data.nam} ${data.val};`;
54
+ return `${indent}@${data.nam}${data.val === '' ? '' : options.indent || ' '}${data.val};`;
52
55
  }
53
56
  // @ts-ignore
54
57
  let children = data.chi.reduce((css, node) => {
@@ -60,7 +63,7 @@ function doRender(data, options, reducer, level = 0, indents = []) {
60
63
  str = `${node.nam}:${options.indent}${node.val.reduce(reducer, '').trimEnd()};`;
61
64
  }
62
65
  else if (node.typ == 'AtRule' && !('chi' in node)) {
63
- str = `@${node.nam} ${node.val};`;
66
+ str = `${data.val === '' ? '' : options.indent || ' '}${data.val};`;
64
67
  }
65
68
  else {
66
69
  str = doRender(node, options, reducer, level + 1, indents);
@@ -77,7 +80,7 @@ function doRender(data, options, reducer, level = 0, indents = []) {
77
80
  children = children.slice(0, -1);
78
81
  }
79
82
  if (data.typ == 'AtRule') {
80
- return `@${data.nam}${data.val ? ' ' + data.val + options.indent : ''}{${options.newLine}` + (children === '' ? '' : indentSub + children + options.newLine) + indent + `}`;
83
+ return `@${data.nam}${data.val === '' ? '' : options.indent || ' '}${data.val}${options.indent}{${options.newLine}` + (children === '' ? '' : indentSub + children + options.newLine) + indent + `}`;
81
84
  }
82
85
  return data.sel + `${options.indent}{${options.newLine}` + (children === '' ? '' : indentSub + children + options.newLine) + indent + `}`;
83
86
  }
@@ -227,7 +230,9 @@ function renderToken(token, options = {}) {
227
230
  case 'Delim':
228
231
  return /* options.minify && 'Pseudo-class' == token.typ && '::' == token.val.slice(0, 2) ? token.val.slice(1) : */ token.val;
229
232
  }
230
- throw new Error(`unexpected token ${JSON.stringify(token, null, 1)}`);
233
+ console.error(`unexpected token ${JSON.stringify(token, null, 1)}`);
234
+ // throw new Error(`unexpected token ${JSON.stringify(token, null, 1)}`);
235
+ return '';
231
236
  }
232
237
 
233
238
  export { render, renderToken };
@@ -4,19 +4,17 @@ import { render } from './renderer/render.js';
4
4
  async function transform(css, options = {}) {
5
5
  options = { minify: true, removeEmpty: true, ...options };
6
6
  const startTime = performance.now();
7
- const parseResult = await parse(css, options);
8
- const renderTime = performance.now();
9
- const rendered = render(parseResult.ast, options);
10
- const endTime = performance.now();
11
- return {
12
- ...parseResult, ...rendered, stats: {
13
- bytesIn: parseResult.bytesIn,
14
- bytesOut: rendered.code.length,
15
- parse: `${(renderTime - startTime).toFixed(2)}ms`,
16
- render: `${(endTime - renderTime).toFixed(2)}ms`,
17
- total: `${(endTime - startTime).toFixed(2)}ms`
18
- }
19
- };
7
+ return parse(css, options).then((parseResult) => {
8
+ const rendered = render(parseResult.ast, options);
9
+ return {
10
+ ...parseResult, ...rendered, stats: {
11
+ bytesOut: rendered.code.length,
12
+ ...parseResult.stats,
13
+ render: rendered.stats.total,
14
+ total: `${(performance.now() - startTime).toFixed(2)}ms`
15
+ }
16
+ };
17
+ });
20
18
  }
21
19
 
22
20
  export { transform };
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@tbela99/css-parser",
3
3
  "description": "CSS parser for node and the browser",
4
- "version": "0.0.1-rc1",
4
+ "version": "0.0.1-rc2",
5
5
  "exports": {
6
6
  ".": "./dist/index.js",
7
+ "./umd": "./dist/index-umd-web.js",
7
8
  "./web": "./dist/web/index.js",
8
9
  "./cjs": "./dist/index.cjs"
9
10
  },