CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-unified

Parse, inspect, transform, and serialize content through syntax trees

Pending
Overview
Eval results
Files

plugin-system.mddocs/

Plugin System

Comprehensive plugin architecture supporting parsers, transformers, compilers, and presets with flexible configuration management and reconfiguration capabilities.

Capabilities

Use Method

Configures the processor with plugins, presets, or plugin lists.

/**
 * Configure processor with plugins, presets, or plugin lists
 * @param value - Plugin, preset, or list of plugins
 * @param parameters - Plugin options and parameters
 * @returns Processor instance for chaining
 * @throws Error on frozen processor or invalid plugin types
 */
use(value: Pluggable, ...parameters: unknown[]): Processor;

// Overloaded signatures for specific types
use(plugin: Plugin, ...parameters: unknown[]): Processor;
use(preset: Preset): Processor;
use(list: PluggableList): Processor;
use(value: null | undefined): Processor; // No-op

Usage Examples:

import { unified } from "unified";

const processor = unified()
  // Single plugin
  .use(somePlugin)
  
  // Plugin with options
  .use(somePlugin, { option: "value" })
  
  // Plugin array
  .use([plugin1, plugin2])
  
  // Plugin tuples with options
  .use([
    [plugin1, { setting: true }],
    [plugin2, "parameter", { more: "options" }]
  ])
  
  // Preset
  .use({
    plugins: [plugin1, plugin2],
    settings: { key: "value" }
  });

Plugin Reconfiguration

Later calls to use() with the same plugin will merge or replace configuration.

const processor = unified()
  // Initial configuration
  .use(somePlugin, { x: true, y: true })
  
  // Reconfigure same plugin (merges objects)
  .use(somePlugin, { y: false, z: true });
  // Result: { x: true, y: false, z: true }

// Non-object parameters are replaced
processor
  .use(somePlugin, "initial")
  .use(somePlugin, "replaced"); // "initial" is replaced with "replaced"

Plugin Enabling/Disabling

Control plugin activation with boolean parameters.

const processor = unified()
  .use(somePlugin, false)  // Disable plugin
  .use(somePlugin, true)   // Enable with no options
  .use(somePlugin, {});    // Enable with empty options

Plugin Types

Parser Plugins

Plugins that configure the processor's parser function.

/**
 * Parser function that converts text to syntax tree
 * @param document - Input text to parse
 * @param file - VFile context with metadata
 * @returns Syntax tree (Node)
 */
type Parser<Tree extends Node = Node> = (
  document: string,
  file: VFile
) => Tree;

// Parser plugin example
function parserPlugin() {
  // 'this' refers to the processor
  this.parser = (document: string, file: VFile) => {
    // Parse document and return syntax tree
    return parsedTree;
  };
}

Usage:

import { unified } from "unified";

function myParser() {
  this.parser = (document, file) => {
    // Custom parsing logic
    return { type: "root", children: [] };
  };
}

const processor = unified().use(myParser);
const tree = processor.parse("content");

Compiler Plugins

Plugins that configure the processor's compiler function.

/**
 * Compiler function that converts syntax tree to output
 * @param tree - Syntax tree to compile
 * @param file - VFile context with metadata
 * @returns Compiled result (string, Uint8Array, or custom type)
 */
type Compiler<Tree extends Node = Node, Result = Value> = (
  tree: Tree,
  file: VFile
) => Result;

// Compiler plugin example  
function compilerPlugin() {
  this.compiler = (tree: Node, file: VFile) => {
    // Compile tree and return result
    return compiledOutput;
  };
}

Usage:

function myCompiler() {
  this.compiler = (tree, file) => {
    // Custom compilation logic
    return "compiled output";
  };
}

const processor = unified()
  .use(myParser)
  .use(myCompiler);

const result = processor.processSync("content");

Transformer Plugins

Plugins that return transformer functions to modify syntax trees.

/**
 * Transformer function that modifies syntax trees
 * @param tree - Input syntax tree
 * @param file - VFile context with metadata
 * @param next - Callback for async operations (optional)
 * @returns Modified tree, error, promise, or void
 */
type Transformer<Input extends Node = Node, Output extends Node = Input> = (
  tree: Input,
  file: VFile,
  next?: TransformCallback<Output>
) => Output | Error | Promise<Output> | void;

// Transformer plugin example
function transformerPlugin(options = {}) {
  return function transformer(tree, file) {
    // Transform tree based on options
    // Return modified tree, or void for no changes
    return modifiedTree;
  };
}

Usage Examples:

// Synchronous transformer
function syncTransformer() {
  return (tree, file) => {
    // Modify tree synchronously
    tree.children.forEach(child => {
      if (child.type === "heading") {
        child.data = { level: child.depth };
      }
    });
    return tree; // or return nothing for in-place modification
  };
}

// Asynchronous transformer with promises
function asyncTransformer() {
  return async (tree, file) => {
    // Async operations
    const result = await someAsyncOperation(tree);
    return result;
  };
}

// Callback-based transformer
function callbackTransformer() {
  return (tree, file, next) => {
    someAsyncOperation(tree, (error, result) => {
      next(error, result);
    });
  };
}

const processor = unified()
  .use(someParser)
  .use(syncTransformer)
  .use(asyncTransformer)
  .use(callbackTransformer)
  .use(someCompiler);

Plugin Configuration

Plugin Options

Plugins can accept configuration options as parameters.

function configurablePlugin(options = {}) {
  const settings = { 
    enabled: true,
    prefix: "default",
    ...options 
  };
  
  return function transformer(tree, file) {
    if (!settings.enabled) return;
    
    // Use settings in transformation
    // ...
  };
}

// Usage
processor.use(configurablePlugin, { 
  enabled: true, 
  prefix: "custom" 
});

Plugin Context

Plugins have access to the processor instance via this context.

function contextAwarePlugin() {
  // Access processor data
  const existingData = this.data();
  
  // Set processor data
  this.data("key", "value");
  
  // Access other processor properties
  console.log("Frozen:", this.frozen);
  
  return function transformer(tree, file) {
    // Transformer logic
  };
}

Presets

Collections of plugins and settings that can be shared and reused.

/**
 * Preset containing plugins and settings
 */
interface Preset {
  /** List of plugins to apply */
  plugins?: PluggableList;
  /** Settings to merge into processor data */
  settings?: Settings;
}

Usage Examples:

// Define a preset
const myPreset = {
  plugins: [
    plugin1,
    [plugin2, { option: "value" }],
    plugin3
  ],
  settings: {
    commonSetting: true,
    sharedValue: "preset-config"
  }
};

// Use preset
const processor = unified().use(myPreset);

// Preset with conditional plugins
const conditionalPreset = {
  plugins: [
    basePlugin,
    process.env.NODE_ENV === "development" ? debugPlugin : null,
    [optimizationPlugin, { level: 2 }]
  ].filter(Boolean),
  settings: {
    debug: process.env.NODE_ENV === "development"
  }
};

Advanced Plugin Patterns

Plugin Factories

Functions that return plugin configurations based on parameters.

function createPlugin(type, options = {}) {
  if (type === "parser") {
    return function parserPlugin() {
      this.parser = (doc, file) => customParse(doc, options);
    };
  } else if (type === "transformer") {
    return function transformerPlugin() {
      return (tree, file) => customTransform(tree, options);
    };
  }
}

// Usage
processor
  .use(createPlugin("parser", { strict: true }))
  .use(createPlugin("transformer", { optimize: true }));

Conditional Plugins

Plugins that adapt behavior based on context or configuration.

function conditionalPlugin(condition, plugin, options) {
  return function conditionalWrapper() {
    if (condition) {
      return plugin.call(this, options);
    }
    // Return nothing - no-op plugin
  };
}

// Usage
processor.use(conditionalPlugin(
  process.env.NODE_ENV === "production",
  optimizePlugin,
  { level: "aggressive" }
));

Error Handling

Plugin Errors

  • Invalid plugin types: "Expected usable value, not \{value}`"`
  • Empty presets: "Expected usable value but received an empty preset"
  • Frozen processor: "Cannot call \use` on a frozen processor"`

Transformer Errors

  • Returned errors are propagated through the pipeline
  • Promise rejections are handled appropriately
  • Callback errors are passed to completion handlers

Plugin Validation

  • Plugins must be functions, objects (presets), or arrays
  • Preset objects require plugins and/or settings properties
  • Plugin arrays must contain valid pluggable values

Install with Tessl CLI

npx tessl i tessl/npm-unified

docs

configuration-management.md

index.md

plugin-system.md

processing-pipeline.md

tile.json