or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

client-api.mdconfiguration.mddom-events.mdindex.mdplugin-development.md
tile.json

plugin-development.mddocs/

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

```