0
# Plugin System
1
2
Extensible plugin architecture for handling custom data types with both modern and legacy interfaces. Plugins allow pretty-format to serialize application-specific data structures with custom formatting logic.
3
4
## Capabilities
5
6
### Plugin Interface
7
8
Two plugin interfaces are supported: the modern `NewPlugin` interface (recommended) and the legacy `OldPlugin` interface for backwards compatibility.
9
10
```typescript { .api }
11
type Plugin = NewPlugin | OldPlugin;
12
13
interface NewPlugin {
14
/** Test function to determine if this plugin should handle the value */
15
test: (val: any) => boolean;
16
/** Serialize function using the improved interface (version 21+) */
17
serialize: (
18
val: any,
19
config: Config,
20
indentation: string,
21
depth: number,
22
refs: Refs,
23
printer: Printer
24
) => string;
25
}
26
27
interface OldPlugin {
28
/** Test function to determine if this plugin should handle the value */
29
test: (val: any) => boolean;
30
/** Print function using the original interface */
31
print: (
32
val: unknown,
33
print: (val: unknown) => string,
34
indent: (str: string) => string,
35
options: PluginOptions,
36
colors: Colors
37
) => string;
38
}
39
40
interface PluginOptions {
41
edgeSpacing: string;
42
min: boolean;
43
spacing: string;
44
}
45
```
46
47
### Test Function
48
49
The test function determines whether a plugin should handle a specific value.
50
51
**Guidelines for writing test functions:**
52
53
```typescript
54
// Example test functions
55
const reactElementTest = (val: any) =>
56
val && val.$$typeof === Symbol.for('react.element');
57
58
const domElementTest = (val: any) =>
59
val && typeof val.nodeType === 'number' && val.nodeType === 1;
60
61
// Safe property access patterns
62
const safeTest = (val: any) =>
63
val != null && typeof val.customProp === 'string';
64
65
// Efficient type checking
66
const arrayLikeTest = (val: any) =>
67
Array.isArray(val) && val.constructor.name === 'CustomArray';
68
```
69
70
**Usage Examples:**
71
72
```typescript
73
const customPlugin = {
74
test(val) {
75
// Safe null checking to prevent TypeErrors
76
return val != null &&
77
typeof val === 'object' &&
78
val.constructor.name === 'MyCustomClass';
79
},
80
serialize(val, config, indentation, depth, refs, printer) {
81
return `MyCustomClass { ${val.toString()} }`;
82
}
83
};
84
```
85
86
### Modern Plugin Interface (NewPlugin)
87
88
The recommended interface available in version 21 and later with full access to formatting context.
89
90
```typescript { .api }
91
interface NewPlugin {
92
test: (val: any) => boolean;
93
serialize: (
94
val: any,
95
config: Config,
96
indentation: string,
97
depth: number,
98
refs: Refs,
99
printer: Printer
100
) => string;
101
}
102
```
103
104
**Serialize Function Parameters:**
105
106
- `val`: The value that "passed the test"
107
- `config`: Unchanging config object derived from options
108
- `indentation`: Current indentation string (concatenate to config.indent)
109
- `depth`: Current depth number (compare to config.maxDepth)
110
- `refs`: Current refs array for finding circular references
111
- `printer`: Callback function to serialize children
112
113
**Usage Examples:**
114
115
```typescript
116
const advancedPlugin = {
117
test(val) {
118
return val && val.type === 'CustomCollection';
119
},
120
serialize(val, config, indentation, depth, refs, printer) {
121
// Check for circular references
122
if (refs.includes(val)) {
123
return '[Circular]';
124
}
125
126
// Check depth limit
127
if (++depth > config.maxDepth) {
128
return '[CustomCollection]';
129
}
130
131
// Add to refs for circular detection
132
const newRefs = [...refs, val];
133
134
// Format children using printer callback
135
const items = val.items
136
.slice(0, config.maxWidth)
137
.map(item =>
138
indentation + config.indent +
139
printer(item, config, indentation + config.indent, depth, newRefs)
140
)
141
.join(config.spacingInner);
142
143
return `CustomCollection {${config.spacingOuter}${items}${config.spacingOuter}${indentation}}`;
144
}
145
};
146
```
147
148
### Legacy Plugin Interface (OldPlugin)
149
150
The original interface for backwards compatibility with limited access to formatting context.
151
152
```typescript { .api }
153
interface OldPlugin {
154
test: (val: any) => boolean;
155
print: (
156
val: unknown,
157
print: (val: unknown) => string,
158
indent: (str: string) => string,
159
options: PluginOptions,
160
colors: Colors
161
) => string;
162
}
163
```
164
165
**Print Function Parameters:**
166
167
- `val`: The value that "passed the test"
168
- `print`: Current printer callback function to serialize children
169
- `indent`: Current indenter callback function to indent lines at next level
170
- `options`: Config object with `min`, `spacing`, and `edgeSpacing` properties
171
- `colors`: Colors object derived from options
172
173
**Usage Examples:**
174
175
```typescript
176
const legacyPlugin = {
177
test(val) {
178
return typeof val === 'function';
179
},
180
print(val, printer, indenter, options, colors) {
181
const name = val.name || 'anonymous';
182
const paramCount = val.length;
183
184
return `[Function ${name} ${paramCount}]`;
185
}
186
};
187
```
188
189
### Plugin Registration
190
191
Plugins are registered through the `plugins` option in the format function.
192
193
```typescript { .api }
194
interface OptionsReceived {
195
plugins?: Plugin[];
196
}
197
```
198
199
**Usage Examples:**
200
201
```typescript
202
import { format } from "pretty-format";
203
204
// Single plugin
205
const result = format(value, {
206
plugins: [myCustomPlugin]
207
});
208
209
// Multiple plugins (order matters - first matching plugin wins)
210
const formatted = format(value, {
211
plugins: [
212
specificPlugin, // More specific plugins first
213
generalPlugin, // General plugins last
214
fallbackPlugin
215
]
216
});
217
218
// With built-in plugins
219
import { plugins } from "pretty-format";
220
const { ReactElement, DOMElement } = plugins;
221
222
const formatted = format(reactComponent, {
223
plugins: [ReactElement, myCustomPlugin]
224
});
225
```
226
227
### Plugin Execution Flow
228
229
1. **Test Phase**: Each plugin's `test` function is called in order
230
2. **First Match**: The first plugin returning `true` is selected
231
3. **Serialization**: The selected plugin's `serialize` or `print` method is called
232
4. **Fallback**: If no plugin matches, built-in formatting is used
233
234
### Error Handling
235
236
Plugins can throw errors which are wrapped in `PrettyFormatPluginError`.
237
238
```typescript { .api }
239
class PrettyFormatPluginError extends Error {
240
constructor(message: string, stack: string);
241
}
242
```
243
244
**Error Scenarios:**
245
246
- Plugin test function throws an exception
247
- Plugin serialize/print function throws an exception
248
- Plugin returns non-string value from serialize/print
249
250
**Usage Examples:**
251
252
```typescript
253
const robustPlugin = {
254
test(val) {
255
try {
256
return val && val.customType === 'special';
257
} catch (error) {
258
// Test failures are caught and re-thrown as PrettyFormatPluginError
259
return false;
260
}
261
},
262
serialize(val, config, indentation, depth, refs, printer) {
263
try {
264
return `Special: ${val.value}`;
265
} catch (error) {
266
// Serialize failures become PrettyFormatPluginError
267
throw new Error(`Failed to serialize special value: ${error.message}`);
268
}
269
}
270
};
271
```
272
273
### Performance Considerations
274
275
- **Test Efficiency**: Keep test functions fast since they're called frequently
276
- **Early Returns**: Return `false` quickly for non-matching values
277
- **Minimal Computation**: Avoid heavy computation in test functions
278
- **Property Access**: Use safe property access patterns to prevent errors
279
280
**Usage Examples:**
281
282
```typescript
283
// Efficient test function
284
const efficientPlugin = {
285
test(val) {
286
// Quick type check first
287
if (typeof val !== 'object' || val === null) {
288
return false;
289
}
290
291
// Then check specific properties
292
return val.constructor.name === 'MyClass';
293
},
294
serialize(val, config, indentation, depth, refs, printer) {
295
// Heavy computation only happens for matching values
296
return formatMyClass(val, config);
297
}
298
};
299
```