0
# SugarSS
1
2
SugarSS is an indent-based CSS syntax parser for PostCSS that provides an alternative to traditional CSS syntax with braces and semicolons. It allows developers to write CSS using indentation similar to Sass or Stylus while maintaining full compatibility with PostCSS plugins and source maps.
3
4
## Package Information
5
6
- **Package Name**: sugarss
7
- **Package Type**: npm
8
- **Language**: JavaScript
9
- **Installation**: `npm install sugarss postcss`
10
11
## Core Imports
12
13
### ES Modules
14
15
```javascript
16
// Default import (contains parse and stringify)
17
import sugarss from "sugarss";
18
19
// Named imports
20
import { parse, stringify } from "sugarss";
21
22
// Direct module imports (as per package.json exports)
23
import parse from "sugarss/parse";
24
import stringify from "sugarss/stringify";
25
import tokenize from "sugarss/tokenize";
26
```
27
28
### CommonJS
29
30
```javascript
31
// Default require (object with parse and stringify)
32
const sugarss = require("sugarss");
33
const { parse, stringify } = sugarss;
34
35
// Destructured require
36
const { parse, stringify } = require("sugarss");
37
38
// Direct module requires (as per package.json exports)
39
const parse = require("sugarss/parse");
40
const stringify = require("sugarss/stringify");
41
const tokenize = require("sugarss/tokenize");
42
```
43
44
## Basic Usage
45
46
### As PostCSS Parser
47
48
```javascript
49
import postcss from "postcss";
50
import sugarss from "sugarss";
51
52
const sugarssInput = `
53
a
54
color: blue
55
font-size: 16px
56
57
.nested
58
.child
59
padding: 10px
60
`;
61
62
// Parse SugarSS to CSS
63
const result = await postcss()
64
.process(sugarssInput, { parser: sugarss });
65
66
console.log(result.css);
67
```
68
69
### As Complete PostCSS Syntax
70
71
```javascript
72
import postcss from "postcss";
73
import sugarss from "sugarss";
74
75
// Use as both parser and stringifier
76
const result = await postcss()
77
.process(sugarssInput, { syntax: sugarss });
78
```
79
80
### Convert CSS to SugarSS
81
82
```javascript
83
import postcss from "postcss";
84
import sugarss from "sugarss";
85
86
const cssInput = `
87
a {
88
color: blue;
89
font-size: 16px;
90
}
91
`;
92
93
// Convert CSS to SugarSS format
94
const result = await postcss()
95
.process(cssInput, { stringifier: sugarss });
96
97
console.log(result.css);
98
// Output:
99
// a
100
// color: blue
101
// font-size: 16px
102
```
103
104
## Architecture
105
106
SugarSS is built around several key components:
107
108
- **Parser Pipeline**: tokenize → liner → preprocess → parse workflow
109
- **PostCSS Integration**: Full compatibility with PostCSS's Input, AST nodes, and plugin system
110
- **Syntax Features**: Indentation-based nesting, multiline selectors/values, dual comment types
111
- **Error Handling**: Detailed parse errors with precise location information
112
- **Type Safety**: Works seamlessly with PostCSS's type system
113
114
## Capabilities
115
116
### Core Parser Function
117
118
Parses SugarSS syntax into PostCSS AST nodes for further processing.
119
120
```javascript { .api }
121
/**
122
* Parse SugarSS syntax into PostCSS AST
123
* @param source - SugarSS source code string
124
* @param opts - PostCSS Input options (from, map, etc.)
125
* @returns PostCSS Root node
126
*/
127
function parse(source: string, opts?: InputOptions): Root;
128
129
interface InputOptions {
130
from?: string;
131
map?: SourceMapOptions | boolean;
132
to?: string;
133
origin?: string;
134
}
135
136
interface SourceMapOptions {
137
inline?: boolean;
138
annotation?: boolean | string;
139
sourcesContent?: boolean;
140
from?: string;
141
to?: string;
142
}
143
144
interface Root {
145
type: 'root';
146
nodes: ChildNode[];
147
source?: NodeSource;
148
raws?: RootRaws;
149
walkRules(callback: (rule: Rule) => void): void;
150
walkDecls(callback: (decl: Declaration) => void): void;
151
walkAtRules(callback: (atrule: AtRule) => void): void;
152
walkComments(callback: (comment: Comment) => void): void;
153
}
154
155
interface NodeSource {
156
input: Input;
157
start?: Position;
158
end?: Position;
159
}
160
161
interface Position {
162
line: number;
163
column: number;
164
offset: number;
165
}
166
167
interface RootRaws {
168
indent?: string;
169
after?: string;
170
semicolon?: boolean;
171
}
172
173
type ChildNode = Rule | AtRule | Declaration | Comment;
174
```
175
176
**Usage Examples:**
177
178
```javascript
179
import { parse } from "sugarss";
180
import { Input } from "postcss";
181
182
// Basic parsing
183
const root = parse(`
184
a
185
color: red
186
font-size: 14px
187
`, { from: 'input.sss' });
188
189
// With source maps
190
const rootWithMap = parse(sugarssCode, {
191
from: 'styles.sss',
192
map: { inline: false }
193
});
194
195
// Access parsed nodes
196
root.walkRules(rule => {
197
console.log(rule.selector); // "a"
198
});
199
200
root.walkDecls(decl => {
201
console.log(decl.prop, decl.value); // "color", "red"
202
});
203
```
204
205
### Core Stringifier Function
206
207
Converts PostCSS AST nodes back to SugarSS syntax format.
208
209
```javascript { .api }
210
/**
211
* Stringify PostCSS AST to SugarSS format
212
* @param node - PostCSS AST node to stringify
213
* @param builder - PostCSS builder function for output
214
*/
215
function stringify(node: AnyNode, builder: Builder): void;
216
217
type Builder = (str: string, node?: AnyNode, type?: 'start' | 'end') => void;
218
219
interface Rule {
220
type: 'rule';
221
selector: string;
222
nodes: Declaration[];
223
source?: NodeSource;
224
raws?: RuleRaws;
225
}
226
227
interface AtRule {
228
type: 'atrule';
229
name: string;
230
params: string;
231
nodes?: ChildNode[];
232
source?: NodeSource;
233
raws?: AtRuleRaws;
234
}
235
236
interface Declaration {
237
type: 'decl';
238
prop: string;
239
value: string;
240
important?: boolean;
241
source?: NodeSource;
242
raws?: DeclRaws;
243
}
244
245
interface Comment {
246
type: 'comment';
247
text: string;
248
source?: NodeSource;
249
raws?: CommentRaws;
250
}
251
252
interface RuleRaws {
253
before?: string;
254
after?: string;
255
selector?: RawSelector;
256
}
257
258
interface AtRuleRaws {
259
before?: string;
260
after?: string;
261
afterName?: string;
262
params?: RawParams;
263
sssBetween?: string;
264
}
265
266
interface DeclRaws {
267
before?: string;
268
after?: string;
269
between?: string;
270
value?: RawValue;
271
}
272
273
interface CommentRaws {
274
before?: string;
275
after?: string;
276
left?: string;
277
right?: string;
278
}
279
280
interface RawSelector {
281
value: string;
282
raw: string;
283
}
284
285
interface RawParams {
286
value: string;
287
raw: string;
288
}
289
290
interface RawValue {
291
value: string;
292
raw: string;
293
}
294
295
type AnyNode = Root | AtRule | Rule | Declaration | Comment;
296
```
297
298
**Usage Examples:**
299
300
```javascript
301
import { stringify } from "sugarss";
302
import postcss from "postcss";
303
304
// Use as PostCSS stringifier
305
const result = await postcss()
306
.process(cssInput, {
307
parser: postcss.parse,
308
stringifier: stringify
309
});
310
311
// Direct usage with PostCSS process
312
const convertToCss = await postcss()
313
.process(sugarssInput, {
314
parser: parse,
315
stringifier: postcss.stringify
316
});
317
```
318
319
### Tokenizer Function
320
321
Low-level tokenization function that converts SugarSS source into structured tokens.
322
323
```javascript { .api }
324
/**
325
* Tokenize SugarSS source into structured tokens
326
* @param input - PostCSS Input object containing source
327
* @returns Array of tokens with position information
328
*/
329
function tokenize(input: Input): Token[];
330
331
interface Input {
332
css: string;
333
from?: string;
334
origin?: string;
335
error(message: string, offset: number): CssSyntaxError;
336
error(message: string, line: number, column: number): CssSyntaxError;
337
}
338
339
type Token = [
340
type: TokenType,
341
value: string,
342
startOffset: number,
343
endOffset: number,
344
...additional: any[]
345
];
346
347
type TokenType =
348
| 'newline' // \n, \r\n, \f, \r
349
| 'space' // spaces and tabs
350
| '{' // opening curly brace
351
| '}' // closing curly brace
352
| ':' // colon separator
353
| ';' // semicolon
354
| ',' // comma
355
| '(' // opening parenthesis
356
| ')' // closing parenthesis
357
| 'brackets' // matched parentheses content
358
| 'string' // quoted strings
359
| 'at-word' // @-rules like @media, @import
360
| 'word' // identifiers, values, selectors
361
| 'comment'; // /* */ and // comments
362
363
interface CssSyntaxError extends Error {
364
name: 'CssSyntaxError';
365
message: string;
366
file?: string;
367
line: number;
368
column: number;
369
source: string;
370
pos: number;
371
}
372
```
373
374
**Usage Examples:**
375
376
```javascript
377
import { tokenize } from "sugarss/tokenize";
378
import { Input } from "postcss";
379
380
const input = new Input(`
381
a
382
color: blue
383
`, { from: 'test.sss' });
384
385
const tokens = tokenize(input);
386
387
tokens.forEach(token => {
388
const [type, value, start, end] = token;
389
console.log(`${type}: "${value}" at ${start}-${end}`);
390
});
391
392
// Example output:
393
// newline: "\n" at 0-1
394
// word: "a" at 1-2
395
// newline: "\n" at 2-3
396
// space: " " at 3-5
397
// word: "color" at 5-10
398
// :: ":" at 10-11
399
// space: " " at 11-12
400
// word: "blue" at 12-16
401
```
402
403
### Syntax Features
404
405
SugarSS supports comprehensive CSS syntax with indentation-based structure:
406
407
#### Indentation Rules
408
409
```javascript
410
// ✅ Valid: Consistent 2-space indentation
411
const validIndent = `
412
.parent
413
color: blue
414
.child
415
padding: 10px
416
`;
417
418
// ❌ Invalid: Mixed tabs and spaces
419
const invalidMixed = `
420
.parent
421
color: blue // 2 spaces
422
\t.child // tab - will throw error
423
`;
424
425
// ❌ Invalid: First line cannot have indent
426
const invalidFirst = `
427
.parent // Error: First line should not have indent
428
color: blue
429
`;
430
```
431
432
#### Multiline Selectors and Values
433
434
```javascript
435
// Multiline selectors with consistent indentation
436
const multilineSelector = `
437
.parent >
438
.child,
439
.sibling
440
color: black
441
`;
442
443
// Multiline values with increased indentation
444
const multilineValue = `
445
.element
446
background:
447
linear-gradient(rgba(0, 0, 0, 0), black)
448
linear-gradient(red, rgba(255, 0, 0, 0))
449
box-shadow: 1px 0 9px rgba(0, 0, 0, .4),
450
1px 0 3px rgba(0, 0, 0, .6)
451
`;
452
453
// Continuation rules
454
const continuationRules = `
455
// 1. Brackets allow line breaks
456
@supports ( (display: flex) and
457
(display: grid) )
458
.flex-grid
459
display: flex
460
461
// 2. Comma at line end continues
462
@media (max-width: 400px),
463
(max-height: 800px)
464
.responsive
465
padding: 10px
466
467
// 3. Backslash before newline continues
468
@media screen and \\
469
(min-width: 600px)
470
.desktop
471
width: 100%
472
`;
473
```
474
475
#### Comment Types
476
477
```javascript
478
const comments = `
479
/*
480
Multiline comments
481
preserved in output
482
*/
483
484
.element
485
color: blue // Inline comments also preserved
486
487
// Standalone inline comment
488
.another
489
font-size: 16px
490
`;
491
```
492
493
### Integration with PostCSS Ecosystem
494
495
SugarSS works seamlessly with PostCSS plugins and tools:
496
497
```javascript
498
import postcss from "postcss";
499
import sugarss from "sugarss";
500
import autoprefixer from "autoprefixer";
501
import cssnano from "cssnano";
502
503
// Complete preprocessing pipeline
504
const result = await postcss([
505
autoprefixer(),
506
cssnano()
507
])
508
.process(sugarssInput, {
509
parser: sugarss,
510
from: 'src/styles.sss',
511
to: 'dist/styles.css'
512
});
513
514
// Configuration file usage (.postcssrc)
515
const config = {
516
"parser": "sugarss",
517
"plugins": {
518
"postcss-simple-vars": {},
519
"postcss-nested": {},
520
"autoprefixer": {}
521
}
522
};
523
```
524
525
### Error Handling
526
527
SugarSS provides detailed error messages with precise location information:
528
529
530
SugarSS uses PostCSS's standard error system, throwing `CssSyntaxError` instances:
531
532
```javascript
533
import { parse } from "sugarss";
534
535
// Indentation errors
536
try {
537
parse(`
538
.parent
539
color: blue
540
\t.child // Mixed tabs/spaces
541
padding: 10px
542
`);
543
} catch (error) {
544
console.log(error.name); // "CssSyntaxError"
545
console.log(error.message); // "Mixed tabs and spaces are not allowed"
546
console.log(`Line ${error.line}, Column ${error.column}`);
547
console.log(error.pos); // Character offset position
548
}
549
550
// Property syntax errors
551
try {
552
parse(`
553
.element
554
color:blue // Missing space after colon
555
`);
556
} catch (error) {
557
console.log(error.message);
558
// "Unexpected separator in property"
559
console.log(error.source); // Original source code
560
}
561
562
// Unclosed constructs
563
try {
564
parse(`
565
.element
566
content: "unclosed string
567
`);
568
} catch (error) {
569
console.log(error.message);
570
// "Unclosed quote"
571
console.log(error.file); // Input file path (if provided)
572
}
573
```
574
575
### Configuration and Options
576
577
SugarSS automatically detects and adapts to different indentation styles:
578
579
```javascript
580
// Auto-detection of indentation
581
const spacesInput = `
582
.element
583
color: blue // Detects 2-space indent
584
font-size: 16px // Nested with 4 spaces
585
`;
586
587
const tabsInput = `
588
.element
589
\tcolor: blue // Detects tab indent
590
\t\tfont-size: 16px // Nested with 2 tabs
591
`;
592
593
// Both parse correctly with auto-detection
594
const spacesRoot = parse(spacesInput);
595
const tabsRoot = parse(tabsInput);
596
597
console.log(spacesRoot.raws.indent); // " " (2 spaces)
598
console.log(tabsRoot.raws.indent); // "\t" (tab)
599
```
600
601
### Source Map Support
602
603
Full source map support for debugging and development tools:
604
605
```javascript
606
import { parse } from "sugarss";
607
608
const result = parse(sugarssCode, {
609
from: 'styles.sss',
610
map: {
611
inline: false,
612
annotation: true,
613
sourcesContent: true
614
}
615
});
616
617
// Source positions are preserved
618
result.walkDecls(decl => {
619
console.log(decl.source);
620
// {
621
// input: Input { css: '...', from: 'styles.sss' },
622
// start: { line: 3, column: 3, offset: 25 },
623
// end: { line: 3, column: 15, offset: 37 }
624
// }
625
});
626
```