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
```