Lesson 10-Custom Directives and Plugin Development in Svelte

Creating Custom Svelte Directives

Svelte directives are a special syntax used to control DOM operations within components. In addition to built-in directives (e.g., bind, on, if, each), Svelte allows you to create custom directives to extend the framework’s functionality. Custom directives enable you to encapsulate complex DOM operations, making them reusable across multiple components while keeping the code clear and modular.

Basic Structure of Custom Directives

A custom directive is typically a function that accepts the following parameters:

  • node: The DOM node to which the directive is applied.
  • binding: An object containing the directive’s value and possible modifiers.
  • attributes: An object containing attributes and events related to the directive.
  • context: The current component’s context, which can be used to access the component instance.

Creating a Custom Directive

Let’s create a simple custom directive to highlight text nodes.

// highlight.directive.js
export default function highlight({ value }) {
  return {
    // Called when the directive is applied to the node
    create: (node) => {
      node.style.backgroundColor = value || 'yellow';
    },
    // Called when the directive’s value changes
    update: (node, newValue) => {
      node.style.backgroundColor = newValue || 'yellow';
    },
    // Called when the directive is removed from the node
    destroy: (node) => {
      node.style.backgroundColor = '';
    }
  };
}

Using a Custom Directive in a Component

Next, we’ll use this custom directive in a Svelte component.

<!-- HighlightText.svelte -->
<script>
  import highlight from './highlight.directive.js';
  let text = 'Hello, Svelte!';
</script>

<p use:highlight={text}>This text will be highlighted.</p>

Advanced Usage of Custom Directives

Custom directives can also accept modifiers, making them more flexible.

// toggleVisibility.directive.js
export default function toggleVisibility({ modifiers }) {
  return {
    create: (node) => {
      node.style.display = 'block';
    },
    update: (node) => {
      if (modifiers.includes('hidden')) {
        node.style.display = 'none';
      } else {
        node.style.display = 'block';
      }
    },
    destroy: (node) => {
      node.style.display = '';
    }
  };
}

Using the Directive in a Component:

<!-- ToggleVisibility.svelte -->
<script>
  let isVisible = true;
</script>

<p use:toggleVisibility={{hidden: !isVisible}}>This text can be hidden.</p>

Lifecycle Methods of Custom Directives

Custom directives have three primary lifecycle methods:

  • create: Called when the directive is first applied to a node.
  • update: Called when the directive’s value or modifiers change.
  • destroy: Called when the directive is removed from the node.

These methods allow you to perform operations at different stages of the directive’s lifecycle.

Debugging and Testing Custom Directives

To debug custom directives, you can use Svelte’s debug directive to inspect their execution. In development mode, the debug directive logs information about the directive’s invocation to the console.

<!-- Debugging.svelte -->
<script>
  import highlight from './highlight.directive.js';
</script>

<p use:highlight={true} use:debug>This text will be highlighted and debugged.</p>

Developing Svelte Plugins to Extend Functionality

Svelte plugins are a way to extend the functionality of the Svelte compiler, enabling the addition of custom syntax, preprocessors, postprocessors, or other features needed during the compilation process. By developing Svelte plugins, you can tailor Svelte’s behavior to meet specific project requirements or workflows.

Basic Structure of Svelte Plugins

A Svelte plugin is a function that returns an object containing methods such as:

  • transform(code, id): Modifies the source code of Svelte components. code is the component’s source code string, and id is the file path of the component.
  • load(id): Loads content for non-Svelte files, such as those handled by preprocessors.
  • resolveId(importee, importer): Resolves module import paths.
  • buildStart(): Called when the build process starts.
  • buildEnd(): Called when the build process ends.

Creating Your First Svelte Plugin

Let’s create a simple plugin that replaces all instances of “Hello” with “Hi” in component code.

// hello-to-hi.plugin.js
export default function helloToHi() {
  return {
    transform: (code, id) => {
      if (id.endsWith('.svelte')) {
        return {
          code: code.replace(/Hello/g, 'Hi'),
          map: null // Source map
        };
      }
    }
  };
}

Using a Svelte Plugin in a Project

To use a plugin in a Svelte project, add it to the build configuration. If you’re using Rollup, you can include the plugin in the rollup.config.js file.

// rollup.config.js
import svelte from 'rollup-plugin-svelte';
import helloToHi from './hello-to-hi.plugin.js';

export default {
  input: 'src/main.js',
  output: {
    file: 'public/build/bundle.js',
    format: 'iife',
    sourcemap: true
  },
  plugins: [
    svelte(),
    helloToHi()
  ]
};

Advanced Usage of Svelte Plugins

Plugins can perform more complex operations, such as adding custom preprocessors, postprocessors, or even modifying Svelte’s syntax.

Custom Preprocessor

// markdown-preprocessor.plugin.js
import fs from 'fs';

export default function markdownPreprocessor() {
  return {
    load: (id) => {
      if (id.endsWith('.md')) {
        return import('markdown-it').then(({ default: markdownIt }) => {
          const md = new markdownIt();
          return md.render(fs.readFileSync(id, 'utf-8'));
        });
      }
    }
  };
}

Custom Postprocessor

// css-postprocessor.plugin.js
import postcss from 'postcss';
import autoprefixer from 'autoprefixer';

export default function cssPostprocessor() {
  return {
    transform: async (code, id) => {
      if (id.endsWith('.css')) {
        const result = await postcss([autoprefixer]).process(code, { from: undefined });
        return {
          code: result.css,
          map: null // Source map
        };
      }
    }
  };
}

Plugin Lifecycle Methods

Plugins can define buildStart() and buildEnd() methods, which are executed at the start and end of the build process, respectively.

// lifecycle.plugin.js
export default function lifecyclePlugin() {
  return {
    buildStart() {
      console.log('Build started');
    },
    buildEnd() {
      console.log('Build ended');
    }
  };
}

Share your love