or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

bundling.mdindex.mdrust-api.mdstyle-attributes.mdtargets.mdtransformation.mdvisitors.md
tile.json

visitors.mddocs/

0

# AST Visitors

1

2

Extensible AST visitor pattern for custom CSS transformations and analysis with type-safe interfaces, enabling JavaScript-based CSS processing and manipulation.

3

4

## Capabilities

5

6

### Compose Visitors Function

7

8

Combine multiple visitor objects into a single visitor for modular transformation pipelines.

9

10

```typescript { .api }

11

/**

12

* Composes multiple visitor objects into a single one

13

* @param visitors - Array of visitor objects to combine

14

* @returns Single composed visitor with all transformations

15

*/

16

function composeVisitors<C extends CustomAtRules>(

17

visitors: Visitor<C>[]

18

): Visitor<C>;

19

```

20

21

**Usage Examples:**

22

23

```typescript

24

import { composeVisitors, transform } from "lightningcss";

25

26

// Create individual visitors

27

const colorVisitor = {

28

Color(color) {

29

// Convert all red colors to brand color

30

if (color.type === 'rgb' && color.r === 255 && color.g === 0 && color.b === 0) {

31

return { type: 'rgb', r: 42, g: 86, b: 153 }; // Brand blue

32

}

33

return color;

34

}

35

};

36

37

const urlVisitor = {

38

Url(url) {

39

// Rewrite relative URLs to absolute

40

if (!url.url.startsWith('http') && !url.url.startsWith('data:')) {

41

return { ...url, url: `https://cdn.example.com/${url.url}` };

42

}

43

return url;

44

}

45

};

46

47

// Compose visitors

48

const combinedVisitor = composeVisitors([colorVisitor, urlVisitor]);

49

50

const result = transform({

51

filename: "styles.css",

52

code: new TextEncoder().encode(`

53

.logo { background: url('./logo.png'); color: red; }

54

.button { background: blue; color: red; }

55

`),

56

visitor: combinedVisitor,

57

minify: true

58

});

59

```

60

61

### Visitor Interface

62

63

Comprehensive visitor interface for intercepting and transforming all CSS AST nodes.

64

65

```typescript { .api }

66

interface Visitor<C extends CustomAtRules> {

67

/** Visit stylesheet before processing rules */

68

StyleSheet?(stylesheet: StyleSheet): StyleSheet<ReturnedDeclaration, ReturnedMediaQuery> | void;

69

/** Visit stylesheet after processing rules */

70

StyleSheetExit?(stylesheet: StyleSheet): StyleSheet<ReturnedDeclaration, ReturnedMediaQuery> | void;

71

72

/** Visit CSS rules (can be function or object with rule-type-specific visitors) */

73

Rule?: RuleVisitor | RuleVisitors<C>;

74

/** Visit CSS rules after processing contents */

75

RuleExit?: RuleVisitor | RuleVisitors<C>;

76

77

/** Visit CSS declarations (can be function or object with property-specific visitors) */

78

Declaration?: DeclarationVisitor | DeclarationVisitors;

79

/** Visit CSS declarations after processing values */

80

DeclarationExit?: DeclarationVisitor | DeclarationVisitors;

81

82

/** Visit URL values */

83

Url?(url: Url): Url | void;

84

/** Visit color values */

85

Color?(color: CssColor): CssColor | void;

86

/** Visit image values */

87

Image?(image: Image): Image | void;

88

/** Visit image values after processing */

89

ImageExit?(image: Image): Image | void;

90

91

/** Visit length values */

92

Length?(length: LengthValue): LengthValue | void;

93

/** Visit angle values */

94

Angle?(angle: Angle): Angle | void;

95

/** Visit ratio values */

96

Ratio?(ratio: Ratio): Ratio | void;

97

/** Visit resolution values */

98

Resolution?(resolution: Resolution): Resolution | void;

99

/** Visit time values */

100

Time?(time: Time): Time | void;

101

102

/** Visit custom identifier values */

103

CustomIdent?(ident: string): string | void;

104

/** Visit dashed identifier values */

105

DashedIdent?(ident: string): string | void;

106

107

/** Visit media queries */

108

MediaQuery?(query: MediaQuery): ReturnedMediaQuery | ReturnedMediaQuery[] | void;

109

/** Visit media queries after processing */

110

MediaQueryExit?(query: MediaQuery): ReturnedMediaQuery | ReturnedMediaQuery[] | void;

111

112

/** Visit @supports conditions */

113

SupportsCondition?(condition: SupportsCondition): SupportsCondition;

114

/** Visit @supports conditions after processing */

115

SupportsConditionExit?(condition: SupportsCondition): SupportsCondition;

116

117

/** Visit selectors */

118

Selector?(selector: Selector): Selector | Selector[] | void;

119

120

/** Visit CSS tokens (can be function or object with token-type-specific visitors) */

121

Token?: TokenVisitor | TokenVisitors;

122

/** Visit CSS functions (can be function or object with function-name-specific visitors) */

123

Function?: FunctionVisitor | { [name: string]: FunctionVisitor };

124

/** Visit CSS functions after processing arguments */

125

FunctionExit?: FunctionVisitor | { [name: string]: FunctionVisitor };

126

127

/** Visit CSS variables */

128

Variable?(variable: Variable): TokenReturnValue;

129

/** Visit CSS variables after processing */

130

VariableExit?(variable: Variable): TokenReturnValue;

131

132

/** Visit environment variables (can be function or object with env-name-specific visitors) */

133

EnvironmentVariable?: EnvironmentVariableVisitor | EnvironmentVariableVisitors;

134

/** Visit environment variables after processing */

135

EnvironmentVariableExit?: EnvironmentVariableVisitor | EnvironmentVariableVisitors;

136

}

137

```

138

139

### Rule-Specific Visitors

140

141

Visit specific types of CSS rules with type-safe interfaces.

142

143

```typescript { .api }

144

type RuleVisitors<C extends CustomAtRules> = {

145

/** Visit @media rules */

146

media?: RuleVisitor<MediaRule>;

147

/** Visit @import rules */

148

import?: RuleVisitor<ImportRule>;

149

/** Visit style rules (selectors + declarations) */

150

style?: RuleVisitor<StyleRule>;

151

/** Visit @keyframes rules */

152

keyframes?: RuleVisitor<KeyframesRule>;

153

/** Visit @font-face rules */

154

'font-face'?: RuleVisitor<FontFaceRule>;

155

/** Visit @page rules */

156

page?: RuleVisitor<PageRule>;

157

/** Visit @supports rules */

158

supports?: RuleVisitor<SupportsRule>;

159

/** Visit @counter-style rules */

160

'counter-style'?: RuleVisitor<CounterStyleRule>;

161

/** Visit @namespace rules */

162

namespace?: RuleVisitor<NamespaceRule>;

163

/** Visit @layer rules */

164

layer?: RuleVisitor<LayerRule>;

165

/** Visit @container rules */

166

container?: RuleVisitor<ContainerRule>;

167

/** Visit unknown at-rules */

168

unknown?: UnknownVisitors<UnknownAtRule>;

169

/** Visit custom at-rules */

170

custom?: CustomVisitors<C>;

171

};

172

173

type RuleVisitor<R> = (rule: R) => ReturnedRule | ReturnedRule[] | void;

174

```

175

176

**Usage Examples:**

177

178

```typescript

179

// Rule-specific transformation

180

const ruleVisitor = {

181

Rule: {

182

// Transform media queries

183

media(rule) {

184

// Convert old max-width syntax to modern range syntax

185

if (rule.value.query.mediaType === 'screen') {

186

// Transform query...

187

return rule;

188

}

189

return rule;

190

},

191

192

// Transform style rules

193

style(rule) {

194

// Add vendor prefixes to flex properties

195

const hasFlexDisplay = rule.value.declarations.declarations.some(

196

decl => decl.property === 'display' && decl.value === 'flex'

197

);

198

199

if (hasFlexDisplay) {

200

// Add -webkit-box, -moz-box, etc.

201

return {

202

...rule,

203

value: {

204

...rule.value,

205

declarations: {

206

...rule.value.declarations,

207

declarations: [

208

{ property: 'display', value: '-webkit-box' },

209

{ property: 'display', value: '-moz-box' },

210

...rule.value.declarations.declarations

211

]

212

}

213

}

214

};

215

}

216

return rule;

217

},

218

219

// Remove @import rules

220

import(rule) {

221

console.log(`Removing import: ${rule.value.url}`);

222

return void 0; // Remove rule

223

}

224

}

225

};

226

```

227

228

### Declaration-Specific Visitors

229

230

Visit specific CSS properties with type-safe value access.

231

232

```typescript { .api }

233

type DeclarationVisitors = {

234

/** Visit background properties */

235

background?: DeclarationVisitor<BackgroundDeclaration>;

236

/** Visit color properties */

237

color?: DeclarationVisitor<ColorDeclaration>;

238

/** Visit display properties */

239

display?: DeclarationVisitor<DisplayDeclaration>;

240

/** Visit margin properties */

241

margin?: DeclarationVisitor<MarginDeclaration>;

242

/** Visit padding properties */

243

padding?: DeclarationVisitor<PaddingDeclaration>;

244

/** Visit transform properties */

245

transform?: DeclarationVisitor<TransformDeclaration>;

246

/** Visit custom properties */

247

custom?: CustomPropertyVisitors | DeclarationVisitor<CustomProperty>;

248

// ... many more property-specific visitors

249

};

250

251

type DeclarationVisitor<P> = (property: P) => ReturnedDeclaration | ReturnedDeclaration[] | void;

252

```

253

254

**Usage Examples:**

255

256

```typescript

257

// Property-specific transformations

258

const declarationVisitor = {

259

Declaration: {

260

// Transform background properties

261

background(decl) {

262

// Convert background shorthand to individual properties for IE support

263

if (decl.property === 'background' && typeof decl.value === 'object') {

264

const individual = [];

265

if (decl.value.color) {

266

individual.push({ property: 'background-color', value: decl.value.color });

267

}

268

if (decl.value.image) {

269

individual.push({ property: 'background-image', value: decl.value.image });

270

}

271

return individual;

272

}

273

return decl;

274

},

275

276

// Transform custom properties

277

custom: {

278

'--primary-color'(decl) {

279

// Replace CSS variable with computed value

280

return {

281

property: 'color',

282

value: { type: 'rgb', r: 42, g: 86, b: 153 }

283

};

284

}

285

},

286

287

// Transform display properties

288

display(decl) {

289

// Add fallbacks for CSS Grid

290

if (decl.value === 'grid') {

291

return [

292

{ property: 'display', value: 'block' }, // Fallback

293

decl // Original

294

];

295

}

296

return decl;

297

}

298

}

299

};

300

```

301

302

### Token and Value Visitors

303

304

Visit individual CSS tokens and values for fine-grained transformations.

305

306

```typescript { .api }

307

type TokenVisitors = {

308

/** Visit identifier tokens */

309

ident?: (token: IdentToken) => TokenReturnValue;

310

/** Visit at-keyword tokens */

311

'at-keyword'?: (token: AtKeywordToken) => TokenReturnValue;

312

/** Visit hash tokens */

313

hash?: (token: HashToken) => TokenReturnValue;

314

/** Visit string tokens */

315

string?: (token: StringToken) => TokenReturnValue;

316

/** Visit number tokens */

317

number?: (token: NumberToken) => TokenReturnValue;

318

/** Visit percentage tokens */

319

percentage?: (token: PercentageToken) => TokenReturnValue;

320

/** Visit dimension tokens */

321

dimension?: (token: DimensionToken) => TokenReturnValue;

322

};

323

324

type TokenReturnValue = TokenOrValue | TokenOrValue[] | RawValue | void;

325

326

interface RawValue {

327

/** A raw string value which will be parsed like CSS. */

328

raw: string;

329

}

330

```

331

332

**Usage Examples:**

333

334

```typescript

335

// Token-level transformations

336

const tokenVisitor = {

337

Token: {

338

// Transform dimension tokens

339

dimension(token) {

340

// Convert px to rem

341

if (token.unit === 'px' && typeof token.value === 'number') {

342

return {

343

type: 'dimension',

344

value: token.value / 16, // Assuming 16px = 1rem

345

unit: 'rem'

346

};

347

}

348

return token;

349

},

350

351

// Transform string tokens

352

string(token) {

353

// Replace font family names

354

if (token.value === 'Arial') {

355

return { type: 'string', value: 'system-ui' };

356

}

357

return token;

358

},

359

360

// Transform identifier tokens

361

ident(token) {

362

// Replace color names

363

const colorMap = {

364

'red': { raw: '#ff0000' },

365

'blue': { raw: '#0000ff' }

366

};

367

return colorMap[token.value] || token;

368

}

369

},

370

371

// Function-specific transformations

372

Function: {

373

// Transform calc() functions

374

calc(fn) {

375

// Simplify calc expressions

376

if (fn.arguments.length === 1) {

377

const arg = fn.arguments[0];

378

if (arg.type === 'token' && arg.value.type === 'dimension') {

379

return arg.value; // Remove unnecessary calc()

380

}

381

}

382

return fn;

383

},

384

385

// Transform url() functions

386

url(fn) {

387

// Rewrite URLs

388

if (fn.arguments[0]?.type === 'token' && fn.arguments[0].value.type === 'string') {

389

const url = fn.arguments[0].value.value;

390

if (url.startsWith('./')) {

391

return {

392

...fn,

393

arguments: [{

394

...fn.arguments[0],

395

value: {

396

...fn.arguments[0].value,

397

value: `https://cdn.example.com/${url.slice(2)}`

398

}

399

}]

400

};

401

}

402

}

403

return fn;

404

}

405

}

406

};

407

```

408

409

### Complete Visitor Example

410

411

Real-world example combining multiple visitor types for comprehensive CSS transformation.

412

413

```typescript

414

import { transform, composeVisitors } from "lightningcss";

415

416

// Asset optimization visitor

417

const assetVisitor = {

418

Url(url) {

419

// Convert relative URLs to CDN URLs

420

if (!url.url.startsWith('http') && !url.url.startsWith('data:')) {

421

return { ...url, url: `https://cdn.example.com/assets/${url.url}` };

422

}

423

return url;

424

}

425

};

426

427

// Color standardization visitor

428

const colorVisitor = {

429

Color(color) {

430

// Standardize brand colors

431

const brandColors = {

432

'#ff0000': { type: 'rgb', r: 42, g: 86, b: 153 }, // Brand blue

433

'#00ff00': { type: 'rgb', r: 40, g: 167, b: 69 } // Brand green

434

};

435

436

if (color.type === 'rgb') {

437

const hex = `#${color.r.toString(16).padStart(2, '0')}${color.g.toString(16).padStart(2, '0')}${color.b.toString(16).padStart(2, '0')}`;

438

return brandColors[hex] || color;

439

}

440

return color;

441

}

442

};

443

444

// Legacy support visitor

445

const legacyVisitor = {

446

Declaration: {

447

display(decl) {

448

// Add IE fallbacks for flexbox

449

if (decl.value === 'flex') {

450

return [

451

{ property: 'display', value: '-ms-flexbox' },

452

{ property: 'display', value: '-webkit-flex' },

453

decl

454

];

455

}

456

return decl;

457

}

458

},

459

460

Rule: {

461

style(rule) {

462

// Add -webkit- prefixes for flexbox properties

463

const needsPrefixing = rule.value.declarations.declarations.some(

464

decl => ['align-items', 'justify-content', 'flex-direction'].includes(decl.property)

465

);

466

467

if (needsPrefixing) {

468

const prefixedDeclarations = rule.value.declarations.declarations.flatMap(decl => {

469

if (['align-items', 'justify-content', 'flex-direction'].includes(decl.property)) {

470

return [

471

{ property: `-webkit-${decl.property}`, value: decl.value },

472

decl

473

];

474

}

475

return [decl];

476

});

477

478

return {

479

...rule,

480

value: {

481

...rule.value,

482

declarations: {

483

...rule.value.declarations,

484

declarations: prefixedDeclarations

485

}

486

}

487

};

488

}

489

return rule;

490

}

491

}

492

};

493

494

// Compose all visitors

495

const fullVisitor = composeVisitors([assetVisitor, colorVisitor, legacyVisitor]);

496

497

// Apply comprehensive transformations

498

const result = transform({

499

filename: "app.css",

500

code: new TextEncoder().encode(`

501

.hero {

502

display: flex;

503

align-items: center;

504

background: url('./hero-bg.jpg');

505

color: #ff0000;

506

}

507

508

.button {

509

background: #00ff00;

510

justify-content: center;

511

}

512

`),

513

visitor: fullVisitor,

514

targets: { ie: 11 << 16 },

515

minify: true

516

});

517

518

console.log(new TextDecoder().decode(result.code));

519

// Output includes CDN URLs, brand colors, and IE-compatible flexbox properties

520

```

521

522

### Environment Variable Visitors

523

524

Visit CSS environment variables like `env(safe-area-inset-top)` for custom processing and polyfills.

525

526

```typescript { .api }

527

type EnvironmentVariableVisitor = (env: EnvironmentVariable) => TokenReturnValue;

528

529

type EnvironmentVariableVisitors = {

530

[name: string]: EnvironmentVariableVisitor;

531

};

532

```

533

534

**Usage Examples:**

535

536

```typescript

537

// Environment variable transformations

538

const envVisitor = {

539

EnvironmentVariable: {

540

// Handle safe area insets for iOS

541

'safe-area-inset-top'(env) {

542

// Provide fallback value for browsers that don't support env()

543

return { raw: 'max(env(safe-area-inset-top), 20px)' };

544

},

545

546

'safe-area-inset-bottom'(env) {

547

return { raw: 'max(env(safe-area-inset-bottom), 20px)' };

548

},

549

550

// Handle custom environment variables

551

'keyboard-height'(env) {

552

// Polyfill custom env() variables

553

return { raw: 'var(--keyboard-height, 0px)' };

554

}

555

},

556

557

// Generic environment variable handler

558

EnvironmentVariable(env) {

559

console.log(`Processing env variable: ${env.name}`);

560

// Add debug information or logging

561

return env;

562

}

563

};

564

565

const result = transform({

566

filename: "mobile.css",

567

code: new TextEncoder().encode(`

568

.safe-area {

569

padding-top: env(safe-area-inset-top);

570

padding-bottom: env(safe-area-inset-bottom);

571

margin-bottom: env(keyboard-height);

572

}

573

`),

574

visitor: envVisitor,

575

minify: true

576

});

577

```

578

579

### Advanced Type Mapping Visitors

580

581

Lightning CSS provides detailed type mappings for rule and declaration visitors that enable precise targeting of specific CSS constructs.

582

583

```typescript { .api }

584

// Rule type mapping for maximum specificity

585

type MappedRuleVisitors = {

586

[Name in Exclude<Rule['type'], 'unknown' | 'custom'>]?: RuleVisitor<RequiredValue<FindByType<Rule, Name>>>;

587

}

588

589

// Declaration type mapping for property-specific handling

590

type MappedDeclarationVisitors = {

591

[Name in Exclude<Declaration['property'], 'unparsed' | 'custom'>]?: DeclarationVisitor<FindProperty<Declaration, Name> | FindProperty<Declaration, 'unparsed'>>;

592

}

593

594

// Unknown rule visitors for handling non-standard at-rules

595

type UnknownVisitors<T> = {

596

[name: string]: RuleVisitor<T>;

597

}

598

599

// Custom rule visitors for user-defined at-rules

600

type CustomVisitors<T extends CustomAtRules> = {

601

[Name in keyof T]?: RuleVisitor<CustomAtRule<Name, T[Name]>>;

602

};

603

```

604

605

**Usage Examples:**

606

607

```typescript

608

// Type-safe rule handling with comprehensive coverage

609

const advancedVisitor = {

610

Rule: {

611

// Type-safe media rule handling

612

media(rule) {

613

// rule is automatically typed as MediaRule

614

if (rule.value.query.mediaType === 'print') {

615

// Remove print-specific rules in web builds

616

return void 0;

617

}

618

return rule;

619

},

620

621

// Type-safe keyframes handling

622

keyframes(rule) {

623

// rule is automatically typed as KeyframesRule

624

const name = rule.value.name;

625

if (name.startsWith('legacy-')) {

626

// Rename legacy animations

627

return {

628

...rule,

629

value: {

630

...rule.value,

631

name: name.replace('legacy-', 'modern-')

632

}

633

};

634

}

635

return rule;

636

},

637

638

// Handle unknown at-rules

639

unknown: {

640

'custom-layout'(rule) {

641

// Convert custom at-rule to standard CSS

642

console.log('Converting custom layout rule');

643

return { raw: `/* Converted: ${rule.prelude} */` };

644

}

645

}

646

}

647

};

648

```