CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-metalsmith

An extremely simple, pluggable static site generator for NodeJS

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-system.mddocs/

Plugin System

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.

Capabilities

Adding Plugins

Add plugins to the Metalsmith processing pipeline. Plugins are executed in the order they are added.

/**
 * Add a plugin function to the processing stack
 * @param plugin - Plugin function or array of plugin functions
 * @returns Metalsmith instance for chaining
 */
use(plugin: Plugin | Plugin[]): Metalsmith;

/**
 * Metalsmith plugin function signature
 * @param files - Object containing all files being processed
 * @param metalsmith - The Metalsmith instance
 * @param callback - Callback to signal completion (for async plugins)
 */
type Plugin = (
  files: Files,
  metalsmith: Metalsmith,
  callback: (error?: Error) => void
) => void | Promise<void>;

Usage Examples:

import Metalsmith from "metalsmith";
import markdown from "@metalsmith/markdown";
import layouts from "@metalsmith/layouts";

// Add plugins in sequence
metalsmith
  .use(markdown())
  .use(layouts({
    pattern: "**/*.html"
  }));

// Add multiple plugins at once
metalsmith.use([
  markdown(),
  layouts({ pattern: "**/*.html" })
]);

Plugin Access to Instance

Plugins have full access to the Metalsmith instance and can read configuration, metadata, and other settings.

/**
 * Plugin can access all Metalsmith methods and properties
 */
function examplePlugin(files, metalsmith, done) {
  // Access configuration
  const srcDir = metalsmith.source();
  const destDir = metalsmith.destination();
  const metadata = metalsmith.metadata();
  
  // Access environment variables
  const debugMode = metalsmith.env('DEBUG');
  
  // Manipulate files
  Object.keys(files).forEach(filepath => {
    const file = files[filepath];
    // Transform file...
  });
  
  // Signal completion
  done();
}

Plugin Types

Metalsmith supports both synchronous and asynchronous plugins.

Synchronous Plugin:

function syncPlugin(files, metalsmith) {
  // No callback needed for sync plugins
  Object.keys(files).forEach(filepath => {
    // Transform files synchronously
    files[filepath].processed = true;
  });
}

Asynchronous Plugin with Callback:

function asyncPlugin(files, metalsmith, done) {
  // Perform async operations
  setTimeout(() => {
    Object.keys(files).forEach(filepath => {
      files[filepath].delayed = true;
    });
    done(); // Must call done() when finished
  }, 100);
}

Promise-based Plugin:

async function promisePlugin(files, metalsmith) {
  // Return a promise or use async/await
  await new Promise(resolve => setTimeout(resolve, 100));
  
  Object.keys(files).forEach(filepath => {
    files[filepath].promised = true;
  });
}

Plugin Configuration Patterns

Common patterns for configurable plugins that accept options.

// Plugin factory pattern
function configurablePlugin(options = {}) {
  return function plugin(files, metalsmith, done) {
    const settings = {
      pattern: '**/*',
      ...options
    };
    
    // Use settings to configure behavior
    Object.keys(files)
      .filter(filepath => metalsmith.match(settings.pattern, [filepath]).length)
      .forEach(filepath => {
        // Process matching files
      });
      
    done();
  };
}

// Usage
metalsmith.use(configurablePlugin({
  pattern: '**/*.md',
  customOption: 'value'
}));

File Object Manipulation

Plugins work with file objects that contain contents, metadata, and filesystem information.

interface File {
  /** File contents as Buffer */
  contents: Buffer;
  /** Filesystem stats object */
  stats?: import('fs').Stats;
  /** File permission mode */
  mode?: string;
  /** Front-matter and custom properties */
  [key: string]: any;
}

interface Files {
  /** Mapping of file paths to File objects */
  [filepath: string]: File;
}

File Manipulation Examples:

function fileManipulationPlugin(files, metalsmith, done) {
  Object.keys(files).forEach(filepath => {
    const file = files[filepath];
    
    // Read file contents
    const content = file.contents.toString();
    
    // Access front-matter data
    const title = file.title;
    const date = file.date;
    
    // Modify contents
    file.contents = Buffer.from(content.toUpperCase());
    
    // Add metadata
    file.processed = true;
    file.processedAt = new Date();
    
    // Change file path (rename/move)
    if (filepath.endsWith('.md')) {
      const newPath = filepath.replace('.md', '.html');
      files[newPath] = file;
      delete files[filepath];
    }
  });
  
  done();
}

Error Handling in Plugins

Proper error handling patterns for plugins.

function errorHandlingPlugin(files, metalsmith, done) {
  try {
    Object.keys(files).forEach(filepath => {
      // Operations that might fail
      if (someCondition) {
        throw new Error(`Processing failed for ${filepath}`);
      }
    });
    done(); // Success
  } catch (error) {
    done(error); // Pass error to Metalsmith
  }
}

// Promise-based error handling
async function promiseErrorPlugin(files, metalsmith) {
  for (const filepath of Object.keys(files)) {
    try {
      await someAsyncOperation(files[filepath]);
    } catch (error) {
      throw new Error(`Failed to process ${filepath}: ${error.message}`);
    }
  }
}

Plugin Development Tips

Best practices for developing Metalsmith plugins:

function wellBehavedPlugin(options = {}) {
  // Validate options
  if (options.required && typeof options.required !== 'string') {
    throw new TypeError('required option must be a string');
  }
  
  return function plugin(files, metalsmith, done) {
    // Create debug logger
    const debug = metalsmith.debug('my-plugin');
    debug('Processing %d files', Object.keys(files).length);
    
    // Access metalsmith configuration
    const metadata = metalsmith.metadata();
    const srcDir = metalsmith.source();
    
    try {
      Object.keys(files).forEach(filepath => {
        debug('Processing file: %s', filepath);
        const file = files[filepath];
        
        // Plugin logic here
        
        debug('Finished processing: %s', filepath);
      });
      
      debug('Plugin completed successfully');
      done();
    } catch (error) {
      debug('Plugin failed: %s', error.message);
      done(error);
    }
  };
}

docs

build-processing.md

cli.md

core-configuration.md

debugging.md

file-operations.md

frontmatter.md

index.md

plugin-system.md

utilities.md

tile.json