@tbela99/css-parser 0.0.1-alpha3 → 0.0.1-alpha4

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.
@@ -1,7 +1,7 @@
1
1
  import { isWhiteSpace, isPseudo, isAtKeyword, isFunction, isNumber, isDimension, parseDimension, isPercentage, isIdent, isHash, isNewLine, isIdentStart, isHexColor } from './utils/syntax.js';
2
2
  import { renderToken } from '../renderer/render.js';
3
3
  import { COLORS_NAMES } from '../renderer/utils/color.js';
4
- import { hasDeclaration, deduplicateRule, deduplicate } from './deduplicate.js';
4
+ import { deduplicate } from './deduplicate.js';
5
5
 
6
6
  const urlTokenMatcher = /^(["']?)[a-zA-Z0-9_/.-][a-zA-Z0-9_/:.#?-]+(\1)$/;
7
7
  const funcLike = ['Start-parens', 'Func', 'UrlFunc', 'Pseudo-class-func'];
@@ -11,6 +11,7 @@ async function parse(iterator, opt = {}) {
11
11
  src: '',
12
12
  sourcemap: false,
13
13
  compress: false,
14
+ nestingRules: false,
14
15
  resolveImport: false,
15
16
  resolveUrls: false,
16
17
  removeEmpty: true,
@@ -281,11 +282,16 @@ async function parse(iterator, opt = {}) {
281
282
  try {
282
283
  // @ts-ignore
283
284
  const root = await options.load(url, options.src).then((src) => {
284
- bytesIn += src.length;
285
- // @ts-ignore
286
- return parse(src, Object.assign({}, options, { src: options.resolve(url, options.src).absolute }));
285
+ return parse(src, Object.assign({}, options, {
286
+ compress: false,
287
+ // @ts-ignore
288
+ src: options.resolve(url, options.src).absolute
289
+ }));
287
290
  });
288
- context.chi.push(...root.ast.chi);
291
+ bytesIn += root.bytesIn;
292
+ if (root.ast.chi.length > 0) {
293
+ context.chi.push(...root.ast.chi);
294
+ }
289
295
  if (root.errors.length > 0) {
290
296
  errors.push(...root.errors);
291
297
  }
@@ -300,20 +306,16 @@ async function parse(iterator, opt = {}) {
300
306
  // https://www.w3.org/TR/css-nesting-1/#conditionals
301
307
  // allowed nesting at-rules
302
308
  // there must be a top level rule in the stack
309
+ const raw = tokens.reduce((acc, curr, index, array) => {
310
+ acc.push(renderToken(curr, { removeComments: true }));
311
+ return acc;
312
+ }, []);
303
313
  const node = {
304
314
  typ: 'AtRule',
305
315
  nam: renderToken(atRule, { removeComments: true }),
306
- val: tokens.reduce((acc, curr, index, array) => {
307
- if (curr.typ == 'Whitespace') {
308
- if (array[index + 1]?.typ == 'Start-parens' ||
309
- array[index - 1]?.typ == 'End-parens' ||
310
- array[index - 1]?.typ == 'Func') {
311
- return acc;
312
- }
313
- }
314
- return acc + renderToken(curr, { removeComments: true });
315
- }, '')
316
+ val: raw.join('')
316
317
  };
318
+ Object.defineProperty(node, 'raw', { enumerable: false, writable: false, value: raw });
317
319
  if (delim.typ == 'Block-start') {
318
320
  node.chi = [];
319
321
  }
@@ -338,23 +340,28 @@ async function parse(iterator, opt = {}) {
338
340
  return null;
339
341
  }
340
342
  }
341
- const sel = parseTokens(tokens, { compress: options.compress }).map(curr => renderToken(curr, { compress: true }));
342
- const raw = [...new Set(sel.reduce((acc, curr) => {
343
- if (curr == ',') {
344
- acc.push('');
345
- }
346
- else {
347
- acc[acc.length - 1] += curr;
348
- }
349
- return acc;
350
- }, ['']))];
343
+ const uniq = new Map;
344
+ parseTokens(tokens, { compress: options.compress }).reduce((acc, curr) => {
345
+ let t = renderToken(curr, { compress: true });
346
+ if (t == ',') {
347
+ acc.push([]);
348
+ }
349
+ else {
350
+ acc[acc.length - 1].push(t);
351
+ }
352
+ return acc;
353
+ }, [[]]).reduce((acc, curr) => {
354
+ acc.set(curr.join(''), curr);
355
+ return acc;
356
+ }, uniq);
351
357
  const node = {
352
358
  typ: 'Rule',
353
359
  // @ts-ignore
354
- sel: raw.join(','),
360
+ sel: [...uniq.keys()].join(','),
355
361
  chi: []
356
362
  };
357
- Object.defineProperty(node, 'raw', { enumerable: false, get: () => raw });
363
+ let raw = [...uniq.values()];
364
+ Object.defineProperty(node, 'raw', { enumerable: false, writable: true, value: raw });
358
365
  loc = {
359
366
  sta: position,
360
367
  src
@@ -378,7 +385,13 @@ async function parse(iterator, opt = {}) {
378
385
  }
379
386
  if (tokens[i].typ == 'Colon') {
380
387
  name = tokens.slice(0, i);
381
- value = parseTokens(tokens.slice(i + 1), { parseColor: true, src: options.src, resolveUrls: options.resolveUrls, resolve: options.resolve, cwd: options.cwd });
388
+ value = parseTokens(tokens.slice(i + 1), {
389
+ parseColor: true,
390
+ src: options.src,
391
+ resolveUrls: options.resolveUrls,
392
+ resolve: options.resolve,
393
+ cwd: options.cwd
394
+ });
382
395
  }
383
396
  }
384
397
  if (name == null) {
@@ -397,11 +410,6 @@ async function parse(iterator, opt = {}) {
397
410
  }
398
411
  }
399
412
  }
400
- // if (name.length == 0) {
401
- //
402
- // errors.push({action: 'drop', message: 'invalid declaration', location: {src, ...position}});
403
- // return null;
404
- // }
405
413
  if (value == null) {
406
414
  errors.push({ action: 'drop', message: 'invalid declaration', location: { src, ...position } });
407
415
  return null;
@@ -424,16 +432,6 @@ async function parse(iterator, opt = {}) {
424
432
  errors.push({ action: 'drop', message: 'invalid declaration', location: { src, ...position } });
425
433
  return null;
426
434
  }
427
- // // location not needed for declaration
428
- // loc = <Location>{
429
- // sta: position,
430
- // src
431
- // };
432
- //
433
- // if (options.sourcemap) {
434
- //
435
- // node.loc = loc
436
- // }
437
435
  // @ts-ignore
438
436
  context.chi.push(node);
439
437
  return null;
@@ -658,11 +656,6 @@ async function parse(iterator, opt = {}) {
658
656
  }
659
657
  }
660
658
  }
661
- // else {
662
- //
663
- // pushToken(getType(buffer));
664
- // buffer = '';
665
- // }
666
659
  break;
667
660
  case '<':
668
661
  if (buffer.length > 0) {
@@ -740,6 +733,10 @@ async function parse(iterator, opt = {}) {
740
733
  if (tokens[tokens.length - 1]?.typ == 'Whitespace') {
741
734
  tokens.pop();
742
735
  }
736
+ if (buffer !== '') {
737
+ pushToken(getType(buffer));
738
+ buffer = '';
739
+ }
743
740
  pushToken({ typ: 'Gt' });
744
741
  consumeWhiteSpace();
745
742
  break;
@@ -754,10 +751,6 @@ async function parse(iterator, opt = {}) {
754
751
  buffer += value + next();
755
752
  break;
756
753
  }
757
- // if (value == ',' && tokens[tokens.length - 1]?.typ == 'Whitespace') {
758
- //
759
- // tokens.pop();
760
- // }
761
754
  pushToken(getType(value));
762
755
  buffer = '';
763
756
  while (isWhiteSpace(peek().charCodeAt(0))) {
@@ -780,7 +773,7 @@ async function parse(iterator, opt = {}) {
780
773
  pushToken(getType(buffer));
781
774
  buffer = '';
782
775
  const token = tokens[tokens.length - 1];
783
- if (token.typ == 'UrlFunc' /* && token.chi == 'url' */) {
776
+ if (token.typ == 'UrlFunc') {
784
777
  // consume either string or url token
785
778
  let whitespace = '';
786
779
  value = peek();
@@ -897,15 +890,6 @@ async function parse(iterator, opt = {}) {
897
890
  if (options.removeEmpty && previousNode != null && previousNode.chi.length == 0 && context.chi[context.chi.length - 1] == previousNode) {
898
891
  context.chi.pop();
899
892
  }
900
- else if (previousNode != null && previousNode != ast && options.compress) {
901
- // @ts-ignore
902
- if (hasDeclaration(previousNode)) {
903
- deduplicateRule(previousNode);
904
- }
905
- else {
906
- deduplicate(previousNode, options);
907
- }
908
- }
909
893
  tokens.length = 0;
910
894
  map.clear();
911
895
  buffer = '';
@@ -940,17 +924,8 @@ async function parse(iterator, opt = {}) {
940
924
  await parseNode(tokens);
941
925
  }
942
926
  if (options.compress) {
943
- while (stack.length > 0) {
944
- const node = stack.pop();
945
- if (hasDeclaration(node)) {
946
- deduplicateRule(node, options);
947
- }
948
- else {
949
- deduplicate(node, options);
950
- }
951
- }
952
927
  if (ast.chi.length > 0) {
953
- deduplicate(ast, options);
928
+ deduplicate(ast, options, true);
954
929
  }
955
930
  }
956
931
  return { ast, errors, bytesIn };
@@ -960,8 +935,10 @@ function parseTokens(tokens, options = {}) {
960
935
  const t = tokens[i];
961
936
  if (t.typ == 'Whitespace' && ((i == 0 ||
962
937
  i + 1 == tokens.length ||
963
- ['Comma', 'Start-parens'].includes(tokens[i + 1].typ) ||
964
- (i > 0 && funcLike.includes(tokens[i - 1].typ))))) {
938
+ ['Comma'].includes(tokens[i + 1].typ) ||
939
+ (i > 0 &&
940
+ funcLike.includes(tokens[i - 1].typ) &&
941
+ !['var', 'calc'].includes(tokens[i - 1].val))))) {
965
942
  tokens.splice(i--, 1);
966
943
  continue;
967
944
  }
@@ -1059,9 +1036,9 @@ function parseTokens(tokens, options = {}) {
1059
1036
  // @ts-ignore
1060
1037
  t.chi.pop();
1061
1038
  }
1039
+ let isColor = true;
1062
1040
  // @ts-ignore
1063
1041
  if (options.parseColor && ['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk'].includes(t.val)) {
1064
- let isColor = true;
1065
1042
  // @ts-ignore
1066
1043
  for (const v of t.chi) {
1067
1044
  if (v.typ == 'Func' && v.val == 'var') {
@@ -1069,37 +1046,38 @@ function parseTokens(tokens, options = {}) {
1069
1046
  break;
1070
1047
  }
1071
1048
  }
1072
- if (!isColor) {
1073
- continue;
1074
- }
1075
- // @ts-ignore
1076
- t.typ = 'Color';
1077
- // @ts-ignore
1078
- t.kin = t.val;
1079
- // @ts-ignore
1080
- let m = t.chi.length;
1081
- while (m-- > 0) {
1049
+ if (isColor) {
1050
+ // @ts-ignore
1051
+ t.typ = 'Color';
1082
1052
  // @ts-ignore
1083
- if (t.chi[m].typ == 'Literal') {
1053
+ t.kin = t.val;
1054
+ // @ts-ignore
1055
+ let m = t.chi.length;
1056
+ while (m-- > 0) {
1084
1057
  // @ts-ignore
1085
- if (t.chi[m + 1]?.typ == 'Whitespace') {
1058
+ if (t.chi[m].typ == 'Literal') {
1086
1059
  // @ts-ignore
1087
- t.chi.splice(m + 1, 1);
1088
- }
1089
- // @ts-ignore
1090
- if (t.chi[m - 1]?.typ == 'Whitespace') {
1060
+ if (t.chi[m + 1]?.typ == 'Whitespace') {
1061
+ // @ts-ignore
1062
+ t.chi.splice(m + 1, 1);
1063
+ }
1091
1064
  // @ts-ignore
1092
- t.chi.splice(m - 1, 1);
1093
- m--;
1065
+ if (t.chi[m - 1]?.typ == 'Whitespace') {
1066
+ // @ts-ignore
1067
+ t.chi.splice(m - 1, 1);
1068
+ m--;
1069
+ }
1094
1070
  }
1095
1071
  }
1072
+ continue;
1096
1073
  }
1097
1074
  }
1098
- else if (t.typ == 'UrlFunc') {
1075
+ if (t.typ == 'UrlFunc') {
1099
1076
  // @ts-ignore
1100
1077
  if (t.chi[0]?.typ == 'String') {
1101
1078
  // @ts-ignore
1102
1079
  const value = t.chi[0].val.slice(1, -1);
1080
+ // @ts-ignore
1103
1081
  if (t.chi[0].val.slice(1, 5) != 'data:' && urlTokenMatcher.test(value)) {
1104
1082
  // @ts-ignore
1105
1083
  t.chi[0].typ = 'Url-token';
@@ -49,7 +49,7 @@ function isIdent(name) {
49
49
  // -
50
50
  if (codepoint == 0x2d) {
51
51
  const nextCodepoint = name.charCodeAt(1);
52
- if (nextCodepoint == null) {
52
+ if (Number.isNaN(nextCodepoint)) {
53
53
  return false;
54
54
  }
55
55
  // -
@@ -98,6 +98,9 @@ function isNumber(name) {
98
98
  let codepoint = name.charCodeAt(0);
99
99
  let i = 0;
100
100
  const j = name.length;
101
+ if (j == 1 && !isDigit(codepoint)) {
102
+ return false;
103
+ }
101
104
  // '+' '-'
102
105
  if ([0x2b, 0x2d].includes(codepoint)) {
103
106
  i++;
@@ -241,7 +244,9 @@ function isNewLine(codepoint) {
241
244
  return codepoint == 0xa || codepoint == 0xc || codepoint == 0xd;
242
245
  }
243
246
  function isWhiteSpace(codepoint) {
244
- return codepoint == 0x9 || codepoint == 0x20 || isNewLine(codepoint);
247
+ return codepoint == 0x9 || codepoint == 0x20 ||
248
+ // isNewLine
249
+ codepoint == 0xa || codepoint == 0xc || codepoint == 0xd;
245
250
  }
246
251
 
247
252
  export { isAngle, isAtKeyword, isDigit, isDimension, isFrequency, isFunction, isHash, isHexColor, isIdent, isIdentCodepoint, isIdentStart, isLength, isNewLine, isNumber, isPercentage, isPseudo, isResolution, isTime, isWhiteSpace, parseDimension };
@@ -18,18 +18,21 @@ function render(data, opt = {}) {
18
18
  return acc;
19
19
  }
20
20
  }
21
- if (options.compress && curr.typ == 'Whitespace') {
22
- if (original[index + 1]?.typ == 'Start-parens' ||
23
- (index > 0 && (original[index - 1].typ == 'Pseudo-class-func' ||
24
- original[index - 1].typ == 'End-parens' ||
25
- original[index - 1].typ == 'UrlFunc' ||
26
- original[index - 1].typ == 'Func' ||
27
- (original[index - 1].typ == 'Color' &&
28
- original[index - 1].kin != 'hex' &&
29
- original[index - 1].kin != 'lit')))) {
30
- return acc;
31
- }
32
- }
21
+ // if (options.compress && curr.typ == 'Whitespace') {
22
+ //
23
+ // if (original[index + 1]?.typ == 'Start-parens' ||
24
+ // (index > 0 && (original[index - 1].typ == 'Pseudo-class-func' ||
25
+ // original[index - 1].typ == 'End-parens' ||
26
+ // original[index - 1].typ == 'UrlFunc' ||
27
+ // original[index - 1].typ == 'Func' ||
28
+ // (
29
+ // original[index - 1].typ == 'Color' &&
30
+ // (<ColorToken>original[index - 1]).kin != 'hex' &&
31
+ // (<ColorToken>original[index - 1]).kin != 'lit')))) {
32
+ //
33
+ // return acc;
34
+ // }
35
+ // }
33
36
  return acc + renderToken(curr, options);
34
37
  }
35
38
  return { code: doRender(data, options, reducer) };
@@ -137,11 +140,15 @@ function renderToken(token, options = {}) {
137
140
  if (token.kin == 'hex' || token.kin == 'lit') {
138
141
  return token.val;
139
142
  }
143
+ case 'Start-parens':
144
+ if (!('chi' in token)) {
145
+ return '(';
146
+ }
140
147
  case 'Func':
141
148
  case 'UrlFunc':
142
149
  case 'Pseudo-class-func':
143
150
  // @ts-ignore
144
- return (options.compress && 'Pseudo-class-func' == token.typ && token.val.slice(0, 2) == '::' ? token.val.slice(1) : token.val) + '(' + token.chi.reduce((acc, curr) => {
151
+ return ( /* options.compress && 'Pseudo-class-func' == token.typ && token.val.slice(0, 2) == '::' ? token.val.slice(1) :*/token.val ?? '') + '(' + token.chi.reduce((acc, curr) => {
145
152
  if (options.removeComments && curr.typ == 'Comment') {
146
153
  if (!options.preserveLicense || !curr.val.startsWith('/*!')) {
147
154
  return acc;
@@ -157,8 +164,6 @@ function renderToken(token, options = {}) {
157
164
  return '<';
158
165
  case 'Gt':
159
166
  return '>';
160
- case 'Start-parens':
161
- return '(';
162
167
  case 'End-parens':
163
168
  return ')';
164
169
  case 'Attr-start':
@@ -234,7 +239,7 @@ function renderToken(token, options = {}) {
234
239
  case 'String':
235
240
  case 'Iden':
236
241
  case 'Delim':
237
- return options.compress && 'Pseudo-class' == token.typ && '::' == token.val.slice(0, 2) ? token.val.slice(1) : token.val;
242
+ return /* options.compress && 'Pseudo-class' == token.typ && '::' == token.val.slice(0, 2) ? token.val.slice(1) : */ token.val;
238
243
  }
239
244
  throw new Error(`unexpected token ${JSON.stringify(token, null, 1)}`);
240
245
  }
@@ -6,12 +6,10 @@ import { resolve } from '../lib/fs/resolve.js';
6
6
  export { dirname, matchUrl } from '../lib/fs/resolve.js';
7
7
 
8
8
  function parse(iterator, opt = {}) {
9
- Object.assign(opt, { load, resolve, cwd: opt.cwd ?? process.cwd() });
10
- return parse$1(iterator, opt);
9
+ return parse$1(iterator, Object.assign(opt, { load, resolve, cwd: opt.cwd ?? process.cwd() }));
11
10
  }
12
11
  function transform(css, options = {}) {
13
- Object.assign(options, { load, resolve, cwd: options.cwd ?? process.cwd() });
14
- return transform$1(css, options);
12
+ return transform$1(css, Object.assign(options, { load, resolve, cwd: options.cwd ?? process.cwd() }));
15
13
  }
16
14
 
17
15
  export { load, parse, resolve, transform };
package/dist/web/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { parse as parse$1 } from '../lib/parser/parse.js';
2
- export { deduplicate, deduplicateRule, hasDeclaration } from '../lib/parser/deduplicate.js';
2
+ export { deduplicate, deduplicateRule, hasDeclaration, reduceSelector } from '../lib/parser/deduplicate.js';
3
3
  export { render, renderToken } from '../lib/renderer/render.js';
4
4
  export { walk } from '../lib/walker/walk.js';
5
5
  import { transform as transform$1 } from '../lib/transform.js';
@@ -8,20 +8,18 @@ import { resolve, dirname } from '../lib/fs/resolve.js';
8
8
  export { matchUrl } from '../lib/fs/resolve.js';
9
9
 
10
10
  function parse(iterator, opt = {}) {
11
- Object.assign(opt, {
11
+ return parse$1(iterator, Object.assign(opt, {
12
12
  load,
13
13
  resolve,
14
14
  cwd: opt.cwd ?? self.location.pathname.endsWith('/') ? self.location.pathname : dirname(self.location.pathname)
15
- });
16
- return parse$1(iterator, opt);
15
+ }));
17
16
  }
18
17
  function transform(css, options = {}) {
19
- Object.assign(options, {
18
+ return transform$1(css, Object.assign(options, {
20
19
  load,
21
20
  resolve,
22
21
  cwd: options.cwd ?? self.location.pathname.endsWith('/') ? self.location.pathname : dirname(self.location.pathname)
23
- });
24
- return transform$1(css, options);
22
+ }));
25
23
  }
26
24
 
27
25
  export { dirname, load, parse, resolve, transform };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tbela99/css-parser",
3
3
  "description": "CSS parser for node and the browser",
4
- "version": "0.0.1-alpha3",
4
+ "version": "0.0.1-alpha4",
5
5
  "exports": {
6
6
  ".": "./dist/index.js",
7
7
  "./web": "./dist/web/index.js",