CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-livereload-js

LiveReload JavaScript client that enables automatic browser refreshing and live CSS reloading during development

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

plugin-development.mddocs/

Plugin Development

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.

Capabilities

Plugin Interface

All LiveReload plugins must implement a specific interface with static properties and instance methods.

/**
 * Base plugin interface that all LiveReload plugins must implement
 */
class LiveReloadPlugin {
  /** Unique plugin identifier (required static property) */
  static identifier = 'plugin-name';
  
  /** Plugin version in x.y or x.y.z format (required static property) */
  static version = '1.0.0';
  
  /**
   * Plugin constructor called when plugin is registered
   * @param window - Browser window object
   * @param host - Plugin host API object with LiveReload utilities
   */
  constructor(window, host) {}
  
  /**
   * Attempt to reload the given file path (optional method)
   * @param path - File path that changed
   * @param options - Reload options object
   * @returns True if plugin handled the reload, false otherwise
   */
  reload(path, options) {}
  
  /**
   * Provide plugin-specific analysis data (optional method)  
   * @returns Object with plugin information for the server
   */
  analyze() {}
}

Usage Examples:

// Basic plugin implementation
class MyFileTypePlugin {
    static identifier = 'my-file-type';
    static version = '1.0.0';
    
    constructor(window, host) {
        this.window = window;
        this.host = host;
        this.console = host.console;
    }
    
    reload(path, options) {
        if (path.endsWith('.myext')) {
            this.console.log('Reloading custom file:', path);
            // Implement custom reload logic
            this.reloadCustomFile(path);
            return true; // Indicate we handled this file
        }
        return false; // Let other plugins handle it
    }
    
    reloadCustomFile(path) {
        // Custom implementation
        this.window.location.reload();
    }
}

// Register the plugin
LiveReload.addPlugin(MyFileTypePlugin);

Plugin Host API

The host object provides access to LiveReload's internal APIs and utilities for plugin development.

/**
 * Plugin host API object passed to plugin constructors
 */
interface PluginHost {
  /** Console logging that respects LiveReload's debug settings */
  console: {
    log(message: string): void;
    error(message: string): void;
  };
  
  /** Timer utility class for scheduling operations */
  Timer: typeof Timer;
  
  /** Generate cache-busting URLs for resources */
  generateCacheBustUrl(url: string): string;
  
  /** Internal LiveReload instance (private API, subject to change) */
  _livereload: LiveReload;
  
  /** Internal Reloader instance (private API, subject to change) */
  _reloader: Reloader;
  
  /** Internal Connector instance (private API, subject to change) */
  _connector: Connector;
}

Usage Examples:

class AdvancedPlugin {
    static identifier = 'advanced';
    static version = '2.1.0';
    
    constructor(window, host) {
        this.window = window;
        this.host = host;
        
        // Use official APIs
        this.console = host.console;
        this.Timer = host.Timer;
        
        // Set up timer for periodic operations
        this.updateTimer = new this.Timer(() => {
            this.checkForUpdates();
        });
    }
    
    reload(path, options) {
        if (path.match(/\.(scss|sass)$/)) {
            this.console.log('Processing Sass file:', path);
            
            // Generate cache-busting URL 
            const cacheBustedUrl = this.host.generateCacheBustUrl(path);
            
            // Custom Sass processing
            return this.processSassFile(cacheBustedUrl);
        }
        return false;
    }
    
    processSassFile(url) {
        // Custom implementation
        return true;
    }
    
    checkForUpdates() {
        this.console.log('Checking for plugin updates');
    }
}

Built-in LESS Plugin

Example of a real plugin implementation that handles LESS stylesheet reloading.

/**
 * Built-in LESS plugin for handling LESS stylesheet compilation
 */
class LessPlugin {
  static identifier = 'less';
  static version = '1.0';
  
  constructor(window, host) {
    this.window = window;
    this.host = host;
  }
  
  /**
   * Handle LESS file reloading by triggering LESS.js recompilation
   * @param path - File path that changed
   * @param options - Reload options
   * @returns True if LESS file was handled, false otherwise
   */
  reload(path, options) {
    if (this.window.less && this.window.less.refresh) {
      if (path.match(/\.less$/i) || options.originalPath.match(/\.less$/i)) {
        return this.reloadLess(path);
      }
    }
    return false;
  }
  
  /**
   * Reload LESS stylesheets by updating link hrefs and triggering recompilation
   * @param path - LESS file path that changed
   * @returns True if reloading was performed, false if no LESS links found
   */
  reloadLess(path) {
    const links = Array.from(document.getElementsByTagName('link')).filter(link => 
      (link.href && link.rel.match(/^stylesheet\/less$/i)) || 
      (link.rel.match(/stylesheet/i) && link.type.match(/^text\/(x-)?less$/i))
    );
    
    if (links.length === 0) return false;
    
    for (const link of links) {
      link.href = this.host.generateCacheBustUrl(link.href);
    }
    
    this.host.console.log('LiveReload is asking LESS to recompile all stylesheets');
    this.window.less.refresh(true);
    return true;
  }
  
  /**
   * Provide analysis data about LESS.js availability
   * @returns Object indicating if LESS compilation should be disabled server-side
   */
  analyze() {
    return {
      disable: !!(this.window.less && this.window.less.refresh)
    };
  }
}

Usage Examples:

// The LESS plugin is automatically registered by LiveReload
// But you can check if it's available:
if (LiveReload.hasPlugin('less')) {
    console.log('LESS plugin is active');
    
    // LESS.js will handle .less file recompilation automatically
    // when files change on the server
}

Custom Plugin Examples

Practical examples of custom plugins for different use cases.

Usage Examples:

// React component hot reloading plugin
class ReactHotReloadPlugin {
    static identifier = 'react-hot';
    static version = '1.0.0';
    
    constructor(window, host) {
        this.window = window;
        this.host = host;
    }
    
    reload(path, options) {
        if (path.match(/\.jsx?$/) && this.window.React) {
            this.host.console.log('Hot reloading React component:', path);
            
            // Custom React hot reload logic
            if (this.window.__REACT_HOT_LOADER__) {
                this.window.__REACT_HOT_LOADER__.reload();
                return true;
            }
        }
        return false;
    }
    
    analyze() {
        return {
            reactVersion: this.window.React ? this.window.React.version : null,
            hotReloadAvailable: !!this.window.__REACT_HOT_LOADER__
        };
    }
}

// TypeScript compilation plugin
class TypeScriptPlugin {
    static identifier = 'typescript';
    static version = '1.0.0';
    
    constructor(window, host) {
        this.window = window;
        this.host = host;
        this.pendingCompilation = false;
    }
    
    reload(path, options) {
        if (path.match(/\.ts$/) && !this.pendingCompilation) {
            this.host.console.log('TypeScript file changed:', path);
            this.pendingCompilation = true;
            
            // Trigger compilation and reload
            this.compileTypeScript(path).then(() => {
                this.pendingCompilation = false;
                this.window.location.reload();
            });
            
            return true;
        }
        return false;
    }
    
    async compileTypeScript(path) {
        // Custom TypeScript compilation logic
        this.host.console.log('Compiling TypeScript...');
        // Implementation would go here
    }
}

// Image optimization plugin
class ImageOptimizationPlugin {
    static identifier = 'image-optimizer';
    static version = '1.0.0';
    
    constructor(window, host) {
        this.window = window;
        this.host = host;
    }
    
    reload(path, options) {
        const imageRegex = /\.(jpe?g|png|gif|svg|webp)$/i;
        
        if (path.match(imageRegex)) {
            this.host.console.log('Optimizing and reloading image:', path);
            
            // Custom image handling with optimization
            this.optimizeAndReload(path);
            return true;
        }
        return false;
    }
    
    optimizeAndReload(path) {
        // Generate optimized cache-busting URL
        const optimizedUrl = this.host.generateCacheBustUrl(path) + '&optimize=true';
        
        // Update all image references
        const images = this.window.document.querySelectorAll('img');
        images.forEach(img => {
            if (img.src.includes(path)) {
                img.src = optimizedUrl;
            }
        });
    }
    
    analyze() {
        const images = this.window.document.querySelectorAll('img');
        return {
            imageCount: images.length,
            optimizationEnabled: true
        };
    }
}

// Register custom plugins
LiveReload.addPlugin(ReactHotReloadPlugin);
LiveReload.addPlugin(TypeScriptPlugin);  
LiveReload.addPlugin(ImageOptimizationPlugin);

Plugin Registration

How plugins are automatically detected and registered with LiveReload.

/**
 * Automatic plugin registration for global plugins
 * LiveReload scans window object for properties matching 'LiveReloadPlugin*'
 */
window.LiveReloadPluginMyCustom = MyCustomPlugin;

/**
 * Manual plugin registration
 * Register plugin classes directly with LiveReload instance
 */
LiveReload.addPlugin(MyCustomPlugin);

Usage Examples:

// Automatic registration - plugin will be auto-detected
window.LiveReloadPluginSass = SassPlugin;
window.LiveReloadPluginVue = VuePlugin;

// Manual registration - explicit control
LiveReload.addPlugin(MyCustomPlugin);
LiveReload.addPlugin(AnotherPlugin);

// Check if plugins are registered
if (LiveReload.hasPlugin('sass')) {
    console.log('Sass plugin detected and registered');
}

// Conditional plugin registration
if (window.Vue) {
    LiveReload.addPlugin(VuePlugin);
}

Plugin Development Best Practices

Guidelines for creating effective and reliable LiveReload plugins.

Best Practices:

  1. Unique Identifiers: Use descriptive, unique plugin identifiers
  2. Proper Versioning: Follow semantic versioning (x.y.z format)
  3. Graceful Degradation: Handle missing dependencies gracefully
  4. Logging: Use host.console for consistent logging behavior
  5. Performance: Avoid blocking operations in reload methods
  6. Error Handling: Catch and handle errors to prevent breaking other plugins

Usage Examples:

class WellDesignedPlugin {
    static identifier = 'company-toolname'; // Unique and descriptive
    static version = '2.1.3'; // Semantic versioning
    
    constructor(window, host) {
        this.window = window;
        this.host = host;
        
        // Check for required dependencies
        this.isEnabled = this.checkDependencies();
        
        if (this.isEnabled) {
            this.host.console.log('WellDesignedPlugin initialized successfully');
        } else {
            this.host.console.log('WellDesignedPlugin disabled - missing dependencies');
        }
    }
    
    checkDependencies() {
        // Graceful dependency checking
        return !!(this.window.RequiredLibrary && this.window.RequiredLibrary.version);
    }
    
    reload(path, options) {
        // Early return if disabled
        if (!this.isEnabled) {
            return false;
        }
        
        try {
            if (this.shouldHandle(path, options)) {
                this.host.console.log(`Processing ${path}`);
                
                // Non-blocking async operation
                this.handleReload(path, options).catch(error => {
                    this.host.console.error(`Error processing ${path}: ${error.message}`);
                });
                
                return true;
            }
        } catch (error) {
            this.host.console.error(`Plugin error: ${error.message}`);
        }
        
        return false;
    }
    
    shouldHandle(path, options) {
        // Clear logic for when to handle files
        return path.match(/\.customext$/) && !options.skipCustom;
    }
    
    async handleReload(path, options) {
        // Async processing without blocking
        const result = await this.processFile(path);
        
        if (result.success) {
            this.applyChanges(result.data);
        }
    }
    
    analyze() {
        // Provide useful analysis data
        return {
            enabled: this.isEnabled,
            version: this.constructor.version,
            dependencies: {
                RequiredLibrary: this.window.RequiredLibrary ? 
                    this.window.RequiredLibrary.version : null
            }
        };
    }
}

docs

client-api.md

configuration.md

dom-events.md

index.md

plugin-development.md

tile.json