0
# Plugin Development
1
2
Plugin system for extending LiveReload to handle custom file types and processing workflows. Plugins can implement custom reload strategies, integrate with external tools, and provide specialized handling for different development environments.
3
4
## Capabilities
5
6
### Plugin Interface
7
8
All LiveReload plugins must implement a specific interface with static properties and instance methods.
9
10
```javascript { .api }
11
/**
12
* Base plugin interface that all LiveReload plugins must implement
13
*/
14
class LiveReloadPlugin {
15
/** Unique plugin identifier (required static property) */
16
static identifier = 'plugin-name';
17
18
/** Plugin version in x.y or x.y.z format (required static property) */
19
static version = '1.0.0';
20
21
/**
22
* Plugin constructor called when plugin is registered
23
* @param window - Browser window object
24
* @param host - Plugin host API object with LiveReload utilities
25
*/
26
constructor(window, host) {}
27
28
/**
29
* Attempt to reload the given file path (optional method)
30
* @param path - File path that changed
31
* @param options - Reload options object
32
* @returns True if plugin handled the reload, false otherwise
33
*/
34
reload(path, options) {}
35
36
/**
37
* Provide plugin-specific analysis data (optional method)
38
* @returns Object with plugin information for the server
39
*/
40
analyze() {}
41
}
42
```
43
44
**Usage Examples:**
45
46
```javascript
47
// Basic plugin implementation
48
class MyFileTypePlugin {
49
static identifier = 'my-file-type';
50
static version = '1.0.0';
51
52
constructor(window, host) {
53
this.window = window;
54
this.host = host;
55
this.console = host.console;
56
}
57
58
reload(path, options) {
59
if (path.endsWith('.myext')) {
60
this.console.log('Reloading custom file:', path);
61
// Implement custom reload logic
62
this.reloadCustomFile(path);
63
return true; // Indicate we handled this file
64
}
65
return false; // Let other plugins handle it
66
}
67
68
reloadCustomFile(path) {
69
// Custom implementation
70
this.window.location.reload();
71
}
72
}
73
74
// Register the plugin
75
LiveReload.addPlugin(MyFileTypePlugin);
76
```
77
78
### Plugin Host API
79
80
The host object provides access to LiveReload's internal APIs and utilities for plugin development.
81
82
```javascript { .api }
83
/**
84
* Plugin host API object passed to plugin constructors
85
*/
86
interface PluginHost {
87
/** Console logging that respects LiveReload's debug settings */
88
console: {
89
log(message: string): void;
90
error(message: string): void;
91
};
92
93
/** Timer utility class for scheduling operations */
94
Timer: typeof Timer;
95
96
/** Generate cache-busting URLs for resources */
97
generateCacheBustUrl(url: string): string;
98
99
/** Internal LiveReload instance (private API, subject to change) */
100
_livereload: LiveReload;
101
102
/** Internal Reloader instance (private API, subject to change) */
103
_reloader: Reloader;
104
105
/** Internal Connector instance (private API, subject to change) */
106
_connector: Connector;
107
}
108
```
109
110
**Usage Examples:**
111
112
```javascript
113
class AdvancedPlugin {
114
static identifier = 'advanced';
115
static version = '2.1.0';
116
117
constructor(window, host) {
118
this.window = window;
119
this.host = host;
120
121
// Use official APIs
122
this.console = host.console;
123
this.Timer = host.Timer;
124
125
// Set up timer for periodic operations
126
this.updateTimer = new this.Timer(() => {
127
this.checkForUpdates();
128
});
129
}
130
131
reload(path, options) {
132
if (path.match(/\.(scss|sass)$/)) {
133
this.console.log('Processing Sass file:', path);
134
135
// Generate cache-busting URL
136
const cacheBustedUrl = this.host.generateCacheBustUrl(path);
137
138
// Custom Sass processing
139
return this.processSassFile(cacheBustedUrl);
140
}
141
return false;
142
}
143
144
processSassFile(url) {
145
// Custom implementation
146
return true;
147
}
148
149
checkForUpdates() {
150
this.console.log('Checking for plugin updates');
151
}
152
}
153
```
154
155
### Built-in LESS Plugin
156
157
Example of a real plugin implementation that handles LESS stylesheet reloading.
158
159
```javascript { .api }
160
/**
161
* Built-in LESS plugin for handling LESS stylesheet compilation
162
*/
163
class LessPlugin {
164
static identifier = 'less';
165
static version = '1.0';
166
167
constructor(window, host) {
168
this.window = window;
169
this.host = host;
170
}
171
172
/**
173
* Handle LESS file reloading by triggering LESS.js recompilation
174
* @param path - File path that changed
175
* @param options - Reload options
176
* @returns True if LESS file was handled, false otherwise
177
*/
178
reload(path, options) {
179
if (this.window.less && this.window.less.refresh) {
180
if (path.match(/\.less$/i) || options.originalPath.match(/\.less$/i)) {
181
return this.reloadLess(path);
182
}
183
}
184
return false;
185
}
186
187
/**
188
* Reload LESS stylesheets by updating link hrefs and triggering recompilation
189
* @param path - LESS file path that changed
190
* @returns True if reloading was performed, false if no LESS links found
191
*/
192
reloadLess(path) {
193
const links = Array.from(document.getElementsByTagName('link')).filter(link =>
194
(link.href && link.rel.match(/^stylesheet\/less$/i)) ||
195
(link.rel.match(/stylesheet/i) && link.type.match(/^text\/(x-)?less$/i))
196
);
197
198
if (links.length === 0) return false;
199
200
for (const link of links) {
201
link.href = this.host.generateCacheBustUrl(link.href);
202
}
203
204
this.host.console.log('LiveReload is asking LESS to recompile all stylesheets');
205
this.window.less.refresh(true);
206
return true;
207
}
208
209
/**
210
* Provide analysis data about LESS.js availability
211
* @returns Object indicating if LESS compilation should be disabled server-side
212
*/
213
analyze() {
214
return {
215
disable: !!(this.window.less && this.window.less.refresh)
216
};
217
}
218
}
219
```
220
221
**Usage Examples:**
222
223
```javascript
224
// The LESS plugin is automatically registered by LiveReload
225
// But you can check if it's available:
226
if (LiveReload.hasPlugin('less')) {
227
console.log('LESS plugin is active');
228
229
// LESS.js will handle .less file recompilation automatically
230
// when files change on the server
231
}
232
```
233
234
### Custom Plugin Examples
235
236
Practical examples of custom plugins for different use cases.
237
238
**Usage Examples:**
239
240
```javascript
241
// React component hot reloading plugin
242
class ReactHotReloadPlugin {
243
static identifier = 'react-hot';
244
static version = '1.0.0';
245
246
constructor(window, host) {
247
this.window = window;
248
this.host = host;
249
}
250
251
reload(path, options) {
252
if (path.match(/\.jsx?$/) && this.window.React) {
253
this.host.console.log('Hot reloading React component:', path);
254
255
// Custom React hot reload logic
256
if (this.window.__REACT_HOT_LOADER__) {
257
this.window.__REACT_HOT_LOADER__.reload();
258
return true;
259
}
260
}
261
return false;
262
}
263
264
analyze() {
265
return {
266
reactVersion: this.window.React ? this.window.React.version : null,
267
hotReloadAvailable: !!this.window.__REACT_HOT_LOADER__
268
};
269
}
270
}
271
272
// TypeScript compilation plugin
273
class TypeScriptPlugin {
274
static identifier = 'typescript';
275
static version = '1.0.0';
276
277
constructor(window, host) {
278
this.window = window;
279
this.host = host;
280
this.pendingCompilation = false;
281
}
282
283
reload(path, options) {
284
if (path.match(/\.ts$/) && !this.pendingCompilation) {
285
this.host.console.log('TypeScript file changed:', path);
286
this.pendingCompilation = true;
287
288
// Trigger compilation and reload
289
this.compileTypeScript(path).then(() => {
290
this.pendingCompilation = false;
291
this.window.location.reload();
292
});
293
294
return true;
295
}
296
return false;
297
}
298
299
async compileTypeScript(path) {
300
// Custom TypeScript compilation logic
301
this.host.console.log('Compiling TypeScript...');
302
// Implementation would go here
303
}
304
}
305
306
// Image optimization plugin
307
class ImageOptimizationPlugin {
308
static identifier = 'image-optimizer';
309
static version = '1.0.0';
310
311
constructor(window, host) {
312
this.window = window;
313
this.host = host;
314
}
315
316
reload(path, options) {
317
const imageRegex = /\.(jpe?g|png|gif|svg|webp)$/i;
318
319
if (path.match(imageRegex)) {
320
this.host.console.log('Optimizing and reloading image:', path);
321
322
// Custom image handling with optimization
323
this.optimizeAndReload(path);
324
return true;
325
}
326
return false;
327
}
328
329
optimizeAndReload(path) {
330
// Generate optimized cache-busting URL
331
const optimizedUrl = this.host.generateCacheBustUrl(path) + '&optimize=true';
332
333
// Update all image references
334
const images = this.window.document.querySelectorAll('img');
335
images.forEach(img => {
336
if (img.src.includes(path)) {
337
img.src = optimizedUrl;
338
}
339
});
340
}
341
342
analyze() {
343
const images = this.window.document.querySelectorAll('img');
344
return {
345
imageCount: images.length,
346
optimizationEnabled: true
347
};
348
}
349
}
350
351
// Register custom plugins
352
LiveReload.addPlugin(ReactHotReloadPlugin);
353
LiveReload.addPlugin(TypeScriptPlugin);
354
LiveReload.addPlugin(ImageOptimizationPlugin);
355
```
356
357
### Plugin Registration
358
359
How plugins are automatically detected and registered with LiveReload.
360
361
```javascript { .api }
362
/**
363
* Automatic plugin registration for global plugins
364
* LiveReload scans window object for properties matching 'LiveReloadPlugin*'
365
*/
366
window.LiveReloadPluginMyCustom = MyCustomPlugin;
367
368
/**
369
* Manual plugin registration
370
* Register plugin classes directly with LiveReload instance
371
*/
372
LiveReload.addPlugin(MyCustomPlugin);
373
```
374
375
**Usage Examples:**
376
377
```javascript
378
// Automatic registration - plugin will be auto-detected
379
window.LiveReloadPluginSass = SassPlugin;
380
window.LiveReloadPluginVue = VuePlugin;
381
382
// Manual registration - explicit control
383
LiveReload.addPlugin(MyCustomPlugin);
384
LiveReload.addPlugin(AnotherPlugin);
385
386
// Check if plugins are registered
387
if (LiveReload.hasPlugin('sass')) {
388
console.log('Sass plugin detected and registered');
389
}
390
391
// Conditional plugin registration
392
if (window.Vue) {
393
LiveReload.addPlugin(VuePlugin);
394
}
395
```
396
397
### Plugin Development Best Practices
398
399
Guidelines for creating effective and reliable LiveReload plugins.
400
401
**Best Practices:**
402
403
1. **Unique Identifiers**: Use descriptive, unique plugin identifiers
404
2. **Proper Versioning**: Follow semantic versioning (x.y.z format)
405
3. **Graceful Degradation**: Handle missing dependencies gracefully
406
4. **Logging**: Use `host.console` for consistent logging behavior
407
5. **Performance**: Avoid blocking operations in reload methods
408
6. **Error Handling**: Catch and handle errors to prevent breaking other plugins
409
410
**Usage Examples:**
411
412
```javascript
413
class WellDesignedPlugin {
414
static identifier = 'company-toolname'; // Unique and descriptive
415
static version = '2.1.3'; // Semantic versioning
416
417
constructor(window, host) {
418
this.window = window;
419
this.host = host;
420
421
// Check for required dependencies
422
this.isEnabled = this.checkDependencies();
423
424
if (this.isEnabled) {
425
this.host.console.log('WellDesignedPlugin initialized successfully');
426
} else {
427
this.host.console.log('WellDesignedPlugin disabled - missing dependencies');
428
}
429
}
430
431
checkDependencies() {
432
// Graceful dependency checking
433
return !!(this.window.RequiredLibrary && this.window.RequiredLibrary.version);
434
}
435
436
reload(path, options) {
437
// Early return if disabled
438
if (!this.isEnabled) {
439
return false;
440
}
441
442
try {
443
if (this.shouldHandle(path, options)) {
444
this.host.console.log(`Processing ${path}`);
445
446
// Non-blocking async operation
447
this.handleReload(path, options).catch(error => {
448
this.host.console.error(`Error processing ${path}: ${error.message}`);
449
});
450
451
return true;
452
}
453
} catch (error) {
454
this.host.console.error(`Plugin error: ${error.message}`);
455
}
456
457
return false;
458
}
459
460
shouldHandle(path, options) {
461
// Clear logic for when to handle files
462
return path.match(/\.customext$/) && !options.skipCustom;
463
}
464
465
async handleReload(path, options) {
466
// Async processing without blocking
467
const result = await this.processFile(path);
468
469
if (result.success) {
470
this.applyChanges(result.data);
471
}
472
}
473
474
analyze() {
475
// Provide useful analysis data
476
return {
477
enabled: this.isEnabled,
478
version: this.constructor.version,
479
dependencies: {
480
RequiredLibrary: this.window.RequiredLibrary ?
481
this.window.RequiredLibrary.version : null
482
}
483
};
484
}
485
}
486
```