or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

index.mdsource-map-consumption.mdsource-map-generation.mdsource-node-building.md

source-node-building.mddocs/

0

# Source Node Building

1

2

The SourceNode class provides a high-level API for building source maps through code concatenation and manipulation. It offers fluent method chaining and automatic source map generation, making it ideal for template engines, code generators, and build tools that construct JavaScript programmatically.

3

4

## Capabilities

5

6

### SourceNode Class

7

8

Build source maps through hierarchical code construction with automatic position tracking.

9

10

```typescript { .api }

11

/**

12

* SourceNodes provide a way to abstract over interpolating/concatenating

13

* snippets of generated JavaScript source code while maintaining the line and

14

* column information associated with the original source code

15

* @param line - The original line number (1-based, nullable)

16

* @param column - The original column number (0-based, nullable)

17

* @param source - The original source's filename (nullable)

18

* @param chunks - Optional array of strings or SourceNodes to add as children

19

* @param name - The original identifier name (nullable)

20

*/

21

class SourceNode {

22

constructor(

23

line?: number | null,

24

column?: number | null,

25

source?: string | null,

26

chunks?: Array<string | SourceNode> | SourceNode | string,

27

name?: string

28

);

29

30

/** Child source nodes and strings */

31

children: SourceNode[];

32

/** Source content storage for associated files */

33

sourceContents: { [sourceFile: string]: string };

34

/** Original line number (1-based, can be null) */

35

line: number | null;

36

/** Original column number (0-based, can be null) */

37

column: number | null;

38

/** Original source file name (can be null) */

39

source: string | null;

40

/** Original identifier name (can be null) */

41

name: string | null;

42

}

43

```

44

45

**Usage Examples:**

46

47

```typescript

48

import { SourceNode } from "source-map";

49

50

// Create a simple source node

51

const node = new SourceNode(1, 0, "math.js", "function add(a, b) {");

52

53

// Create with multiple chunks

54

const complexNode = new SourceNode(10, 4, "utils.js", [

55

"if (",

56

new SourceNode(10, 8, "utils.js", "condition"),

57

") {\n return true;\n}"

58

]);

59

60

// Create without position info (for generated code)

61

const generatedNode = new SourceNode(null, null, null, "/* Generated code */");

62

63

// Access properties directly

64

console.log(complexNode.children.length); // Number of child nodes

65

console.log(complexNode.sourceContents); // Object containing source file contents

66

console.log(complexNode.line, complexNode.column); // Original position info

67

```

68

69

### Creating from Existing Source Maps

70

71

Build SourceNodes from code with an existing source map.

72

73

```typescript { .api }

74

/**

75

* Creates a SourceNode from generated code and a SourceMapConsumer

76

* @param code - The generated code string

77

* @param sourceMapConsumer - The source map for the generated code

78

* @param relativePath - Optional relative path for the generated code file

79

* @returns SourceNode representing the code with source map information

80

*/

81

static fromStringWithSourceMap(

82

code: string,

83

sourceMapConsumer: SourceMapConsumer,

84

relativePath?: string

85

): SourceNode;

86

```

87

88

**Usage:**

89

90

```typescript

91

import { SourceNode, SourceMapConsumer } from "source-map";

92

93

// Load existing source map

94

const consumer = await new SourceMapConsumer(existingSourceMap);

95

96

// Create SourceNode from code + source map

97

const node = SourceNode.fromStringWithSourceMap(

98

generatedCode,

99

consumer,

100

"output.js"

101

);

102

103

// Now you can manipulate the node further

104

node.add("\n// Additional code");

105

106

// Don't forget to clean up

107

consumer.destroy();

108

```

109

110

### Building and Manipulating Nodes

111

112

Add, prepend, and modify source node content with fluent chaining.

113

114

```typescript { .api }

115

/**

116

* Add a chunk to the end of this source node

117

* @param chunk - String, SourceNode, or array of strings/SourceNodes to add

118

* @returns This SourceNode for chaining

119

*/

120

add(chunk: Array<string | SourceNode> | SourceNode | string): SourceNode;

121

122

/**

123

* Add a chunk to the beginning of this source node

124

* @param chunk - String, SourceNode, or array of strings/SourceNodes to prepend

125

* @returns This SourceNode for chaining

126

*/

127

prepend(chunk: Array<string | SourceNode> | SourceNode | string): SourceNode;

128

129

/**

130

* Set the source content for a source file

131

* @param sourceFile - The filename of the original source

132

* @param sourceContent - The content of the original source file

133

*/

134

setSourceContent(sourceFile: string, sourceContent: string): void;

135

```

136

137

**Usage Examples:**

138

139

```typescript

140

// Build complex structure with chaining

141

const functionNode = new SourceNode(1, 0, "math.js")

142

.add("function multiply(")

143

.add(new SourceNode(1, 17, "math.js", "a", "paramA"))

144

.add(", ")

145

.add(new SourceNode(1, 20, "math.js", "b", "paramB"))

146

.add(") {\n")

147

.add(new SourceNode(2, 2, "math.js", "return a * b;"))

148

.add("\n}");

149

150

// Prepend header comment

151

functionNode.prepend("/* Math utilities */\n");

152

153

// Add multiple chunks at once

154

functionNode.add([

155

"\n\n",

156

new SourceNode(5, 0, "math.js", "function divide(a, b) {"),

157

new SourceNode(6, 2, "math.js", "return a / b;"),

158

"\n}"

159

]);

160

161

// Set source content for debugging

162

functionNode.setSourceContent("math.js", `

163

function multiply(a, b) {

164

return a * b;

165

}

166

167

function divide(a, b) {

168

return a / b;

169

}

170

`);

171

```

172

173

### String Manipulation

174

175

Modify source node content with string operations while preserving source map information.

176

177

```typescript { .api }

178

/**

179

* Join all children with a separator string

180

* @param sep - Separator string to insert between children

181

* @returns New SourceNode with joined children

182

*/

183

join(sep: string): SourceNode;

184

185

/**

186

* Call String.prototype.replace on the rightmost child (if it's a string)

187

* @param pattern - Pattern to search for (string or RegExp)

188

* @param replacement - Replacement string

189

* @returns This SourceNode for chaining

190

*/

191

replaceRight(pattern: string, replacement: string): SourceNode;

192

```

193

194

**Usage:**

195

196

```typescript

197

// Create array of elements

198

const elements = [

199

new SourceNode(1, 0, "data.js", "first"),

200

new SourceNode(2, 0, "data.js", "second"),

201

new SourceNode(3, 0, "data.js", "third")

202

];

203

204

// Join with commas

205

const arrayNode = new SourceNode(null, null, null, elements).join(", ");

206

console.log(arrayNode.toString()); // "first, second, third"

207

208

// Replace trailing content

209

const codeNode = new SourceNode(10, 0, "test.js", "console.log('debug');");

210

codeNode.replaceRight("'debug'", "'production'");

211

console.log(codeNode.toString()); // "console.log('production');"

212

```

213

214

### Traversal and Inspection

215

216

Walk through source node hierarchies for analysis and transformation.

217

218

```typescript { .api }

219

/**

220

* Walk over the tree of JS snippets in this node and its children

221

* @param fn - Function called for each chunk with its associated mapping

222

*/

223

walk(fn: (chunk: string, mapping: MappedPosition) => void): void;

224

225

/**

226

* Walk over the tree of SourceNodes and gather their source content

227

* @param fn - Function called for each source file and its content

228

*/

229

walkSourceContents(fn: (file: string, content: string) => void): void;

230

231

interface MappedPosition {

232

source: string;

233

line: number;

234

column: number;

235

name?: string;

236

}

237

```

238

239

**Usage Examples:**

240

241

```typescript

242

// Analyze all code chunks

243

node.walk((chunk, mapping) => {

244

console.log(`Chunk: "${chunk}"`);

245

if (mapping.source) {

246

console.log(` From: ${mapping.source}:${mapping.line}:${mapping.column}`);

247

if (mapping.name) {

248

console.log(` Name: ${mapping.name}`);

249

}

250

}

251

});

252

253

// Gather all source contents

254

const sourceFiles = new Map();

255

node.walkSourceContents((file, content) => {

256

sourceFiles.set(file, content);

257

console.log(`Source file: ${file} (${content.length} characters)`);

258

});

259

260

// Find all references to a specific identifier

261

const references = [];

262

node.walk((chunk, mapping) => {

263

if (mapping.name === "myFunction") {

264

references.push({

265

chunk,

266

source: mapping.source,

267

line: mapping.line,

268

column: mapping.column

269

});

270

}

271

});

272

```

273

274

### Code Generation

275

276

Generate final code and source maps from the source node hierarchy.

277

278

```typescript { .api }

279

/**

280

* Return the string representation of this source node

281

* @returns Concatenated string of all children

282

*/

283

toString(): string;

284

285

/**

286

* Returns the string representation of this source node along with a SourceMapGenerator

287

* @param startOfSourceMap - Optional configuration for the generated source map

288

* @returns Object containing the generated code and source map

289

*/

290

toStringWithSourceMap(startOfSourceMap?: StartOfSourceMap): CodeWithSourceMap;

291

292

interface StartOfSourceMap {

293

file?: string;

294

sourceRoot?: string;

295

skipValidation?: boolean;

296

}

297

298

interface CodeWithSourceMap {

299

code: string;

300

map: SourceMapGenerator;

301

}

302

```

303

304

**Usage Examples:**

305

306

```typescript

307

// Get generated code only

308

const generatedCode = node.toString();

309

console.log(generatedCode);

310

311

// Get code with source map

312

const result = node.toStringWithSourceMap({

313

file: "bundle.js",

314

sourceRoot: "/project/src/"

315

});

316

317

console.log("Generated code:", result.code);

318

console.log("Source map:", result.map.toString());

319

320

// Write to files

321

import fs from 'fs';

322

fs.writeFileSync('bundle.js', result.code);

323

fs.writeFileSync('bundle.js.map', result.map.toString());

324

325

// Add source map reference to code

326

const codeWithReference = result.code + '\n//# sourceMappingURL=bundle.js.map';

327

fs.writeFileSync('bundle-with-map.js', codeWithReference);

328

```

329

330

## Common Patterns

331

332

### Template Engine Integration

333

334

```typescript

335

// Template compilation with source mapping

336

function compileTemplate(template: string, filename: string): CodeWithSourceMap {

337

const root = new SourceNode(null, null, null);

338

339

// Parse template and build nodes

340

const lines = template.split('\n');

341

lines.forEach((line, index) => {

342

if (line.startsWith('<%= ')) {

343

// Expression: map to original template line

344

const expr = line.slice(4, -3);

345

root.add(new SourceNode(index + 1, 4, filename, `output += ${expr};\n`));

346

} else {

347

// Literal: add as generated code

348

root.add(`output += ${JSON.stringify(line + '\n')};\n`);

349

}

350

});

351

352

// Set original template content

353

root.setSourceContent(filename, template);

354

355

return root.toStringWithSourceMap({

356

file: filename.replace('.template', '.js')

357

});

358

}

359

```

360

361

### Code Concatenation

362

363

```typescript

364

// Bundle multiple files with source maps

365

function bundleFiles(files: Array<{name: string, content: string}>): CodeWithSourceMap {

366

const bundle = new SourceNode(null, null, null);

367

368

files.forEach(file => {

369

// Add file separator comment

370

bundle.add(`\n/* === ${file.name} === */\n`);

371

372

// Add each line with position mapping

373

const lines = file.content.split('\n');

374

lines.forEach((line, index) => {

375

bundle.add(new SourceNode(index + 1, 0, file.name, line + '\n'));

376

});

377

378

// Embed source content

379

bundle.setSourceContent(file.name, file.content);

380

});

381

382

return bundle.toStringWithSourceMap({

383

file: 'bundle.js',

384

sourceRoot: '/src/'

385

});

386

}

387

```

388

389

### Transformation Pipeline

390

391

```typescript

392

// Multi-stage transformation with source map preservation

393

async function transformCode(code: string, filename: string): Promise<CodeWithSourceMap> {

394

// Stage 1: Parse and create initial source node

395

let node = new SourceNode(1, 0, filename, code);

396

node.setSourceContent(filename, code);

397

398

// Stage 2: Apply transformations

399

node = node

400

.add('\n// Transformed code')

401

.prepend('(function() {\n')

402

.add('\n})();');

403

404

// Stage 3: Generate final result

405

return node.toStringWithSourceMap({

406

file: filename.replace('.js', '.transformed.js')

407

});

408

}

409

```

410

411

## Integration with Other APIs

412

413

### With SourceMapGenerator

414

415

```typescript

416

// Combine SourceNode with manual SourceMapGenerator

417

const generator = new SourceMapGenerator({ file: 'output.js' });

418

const node = new SourceNode(null, null, null);

419

420

// Add manual mappings to generator

421

generator.addMapping({

422

generated: { line: 1, column: 0 },

423

original: { line: 1, column: 0 },

424

source: 'input.js'

425

});

426

427

// Convert generator to SourceNode for further manipulation

428

const consumer = SourceMapConsumer.fromSourceMap(generator);

429

const nodeFromGenerator = SourceNode.fromStringWithSourceMap(

430

'var x = 1;',

431

await consumer

432

);

433

434

consumer.destroy();

435

```

436

437

### With SourceMapConsumer

438

439

```typescript

440

// Load existing source map and extend it

441

const consumer = await new SourceMapConsumer(existingSourceMap);

442

const existingNode = SourceNode.fromStringWithSourceMap(existingCode, consumer);

443

444

// Extend with additional code

445

const extendedNode = new SourceNode(null, null, null, [

446

existingNode,

447

'\n// Additional functionality\n',

448

new SourceNode(100, 0, 'extensions.js', 'function extend() {}')

449

]);

450

451

consumer.destroy();

452

453

const final = extendedNode.toStringWithSourceMap({

454

file: 'extended.js'

455

});

456

```