0
# Plugin System
1
2
Plugin management and execution system for extending Metalsmith functionality with custom transformations. All Metalsmith logic is handled through plugins that manipulate files in a middleware-style pattern.
3
4
## Capabilities
5
6
### Adding Plugins
7
8
Add plugins to the Metalsmith processing pipeline. Plugins are executed in the order they are added.
9
10
```javascript { .api }
11
/**
12
* Add a plugin function to the processing stack
13
* @param plugin - Plugin function or array of plugin functions
14
* @returns Metalsmith instance for chaining
15
*/
16
use(plugin: Plugin | Plugin[]): Metalsmith;
17
18
/**
19
* Metalsmith plugin function signature
20
* @param files - Object containing all files being processed
21
* @param metalsmith - The Metalsmith instance
22
* @param callback - Callback to signal completion (for async plugins)
23
*/
24
type Plugin = (
25
files: Files,
26
metalsmith: Metalsmith,
27
callback: (error?: Error) => void
28
) => void | Promise<void>;
29
```
30
31
**Usage Examples:**
32
33
```javascript
34
import Metalsmith from "metalsmith";
35
import markdown from "@metalsmith/markdown";
36
import layouts from "@metalsmith/layouts";
37
38
// Add plugins in sequence
39
metalsmith
40
.use(markdown())
41
.use(layouts({
42
pattern: "**/*.html"
43
}));
44
45
// Add multiple plugins at once
46
metalsmith.use([
47
markdown(),
48
layouts({ pattern: "**/*.html" })
49
]);
50
```
51
52
### Plugin Access to Instance
53
54
Plugins have full access to the Metalsmith instance and can read configuration, metadata, and other settings.
55
56
```javascript { .api }
57
/**
58
* Plugin can access all Metalsmith methods and properties
59
*/
60
function examplePlugin(files, metalsmith, done) {
61
// Access configuration
62
const srcDir = metalsmith.source();
63
const destDir = metalsmith.destination();
64
const metadata = metalsmith.metadata();
65
66
// Access environment variables
67
const debugMode = metalsmith.env('DEBUG');
68
69
// Manipulate files
70
Object.keys(files).forEach(filepath => {
71
const file = files[filepath];
72
// Transform file...
73
});
74
75
// Signal completion
76
done();
77
}
78
```
79
80
### Plugin Types
81
82
Metalsmith supports both synchronous and asynchronous plugins.
83
84
**Synchronous Plugin:**
85
86
```javascript
87
function syncPlugin(files, metalsmith) {
88
// No callback needed for sync plugins
89
Object.keys(files).forEach(filepath => {
90
// Transform files synchronously
91
files[filepath].processed = true;
92
});
93
}
94
```
95
96
**Asynchronous Plugin with Callback:**
97
98
```javascript
99
function asyncPlugin(files, metalsmith, done) {
100
// Perform async operations
101
setTimeout(() => {
102
Object.keys(files).forEach(filepath => {
103
files[filepath].delayed = true;
104
});
105
done(); // Must call done() when finished
106
}, 100);
107
}
108
```
109
110
**Promise-based Plugin:**
111
112
```javascript
113
async function promisePlugin(files, metalsmith) {
114
// Return a promise or use async/await
115
await new Promise(resolve => setTimeout(resolve, 100));
116
117
Object.keys(files).forEach(filepath => {
118
files[filepath].promised = true;
119
});
120
}
121
```
122
123
### Plugin Configuration Patterns
124
125
Common patterns for configurable plugins that accept options.
126
127
```javascript
128
// Plugin factory pattern
129
function configurablePlugin(options = {}) {
130
return function plugin(files, metalsmith, done) {
131
const settings = {
132
pattern: '**/*',
133
...options
134
};
135
136
// Use settings to configure behavior
137
Object.keys(files)
138
.filter(filepath => metalsmith.match(settings.pattern, [filepath]).length)
139
.forEach(filepath => {
140
// Process matching files
141
});
142
143
done();
144
};
145
}
146
147
// Usage
148
metalsmith.use(configurablePlugin({
149
pattern: '**/*.md',
150
customOption: 'value'
151
}));
152
```
153
154
### File Object Manipulation
155
156
Plugins work with file objects that contain contents, metadata, and filesystem information.
157
158
```javascript { .api }
159
interface File {
160
/** File contents as Buffer */
161
contents: Buffer;
162
/** Filesystem stats object */
163
stats?: import('fs').Stats;
164
/** File permission mode */
165
mode?: string;
166
/** Front-matter and custom properties */
167
[key: string]: any;
168
}
169
170
interface Files {
171
/** Mapping of file paths to File objects */
172
[filepath: string]: File;
173
}
174
```
175
176
**File Manipulation Examples:**
177
178
```javascript
179
function fileManipulationPlugin(files, metalsmith, done) {
180
Object.keys(files).forEach(filepath => {
181
const file = files[filepath];
182
183
// Read file contents
184
const content = file.contents.toString();
185
186
// Access front-matter data
187
const title = file.title;
188
const date = file.date;
189
190
// Modify contents
191
file.contents = Buffer.from(content.toUpperCase());
192
193
// Add metadata
194
file.processed = true;
195
file.processedAt = new Date();
196
197
// Change file path (rename/move)
198
if (filepath.endsWith('.md')) {
199
const newPath = filepath.replace('.md', '.html');
200
files[newPath] = file;
201
delete files[filepath];
202
}
203
});
204
205
done();
206
}
207
```
208
209
### Error Handling in Plugins
210
211
Proper error handling patterns for plugins.
212
213
```javascript
214
function errorHandlingPlugin(files, metalsmith, done) {
215
try {
216
Object.keys(files).forEach(filepath => {
217
// Operations that might fail
218
if (someCondition) {
219
throw new Error(`Processing failed for ${filepath}`);
220
}
221
});
222
done(); // Success
223
} catch (error) {
224
done(error); // Pass error to Metalsmith
225
}
226
}
227
228
// Promise-based error handling
229
async function promiseErrorPlugin(files, metalsmith) {
230
for (const filepath of Object.keys(files)) {
231
try {
232
await someAsyncOperation(files[filepath]);
233
} catch (error) {
234
throw new Error(`Failed to process ${filepath}: ${error.message}`);
235
}
236
}
237
}
238
```
239
240
### Plugin Development Tips
241
242
Best practices for developing Metalsmith plugins:
243
244
```javascript
245
function wellBehavedPlugin(options = {}) {
246
// Validate options
247
if (options.required && typeof options.required !== 'string') {
248
throw new TypeError('required option must be a string');
249
}
250
251
return function plugin(files, metalsmith, done) {
252
// Create debug logger
253
const debug = metalsmith.debug('my-plugin');
254
debug('Processing %d files', Object.keys(files).length);
255
256
// Access metalsmith configuration
257
const metadata = metalsmith.metadata();
258
const srcDir = metalsmith.source();
259
260
try {
261
Object.keys(files).forEach(filepath => {
262
debug('Processing file: %s', filepath);
263
const file = files[filepath];
264
265
// Plugin logic here
266
267
debug('Finished processing: %s', filepath);
268
});
269
270
debug('Plugin completed successfully');
271
done();
272
} catch (error) {
273
debug('Plugin failed: %s', error.message);
274
done(error);
275
}
276
};
277
}
278
```