Writing plugins

Edit on GitHub

Writing visua plugins is very easy, just matter of creating a small TypeScript class.

Start from template

The easiest way of getting started writing plugins is by using the visua CLI to generate an empty plugin.

The plugin command starts an interactive shell that guides you through the creation of you plugin.

Once finished it should have created a folder called visua- followed by the slug you have chosen for your plugin with the following structure:

visua-plugin-name/
 |-- test/
      |-- identity.css
      |-- index.spec.ts
 |-- .gitignore
 |-- index.ts
 |-- package.json
 |-- package-lock.json
 |-- README.md
 |-- tsconfig.json

Plugin class

A visua plugin in its most basic form is a typescript class extending Plugin.

In index.ts you’ll find an empty implementation of Plugin:

import {StyleMap, Plugin, OptionsMap, Options} from 'visua';

export default class extends Plugin {

    static options: OptionsMap = {
        
    };

    run(styleMap: StyleMap, options: Options) {
        
    }

}

The plugin class must be the default export of this module.

Options

If your plugin accepts options then you should override the options property to return a non empty OptionsMap. OptionsMap is an Object whose keys are strings corresponding to your options names without the leading “--” and whose values are either type constructors such as String or Boolean or ValueInitializers: functions that take the option’s textual value and deserialize it.

Consider the following options:

  • --outFile, a string representing a filesystem path
  • --overwrite, a boolean flag
  • --tags, a comma separated list of tags.

The corresponding implementation of options would be the following:

static options: OptionsMap = {
    outFile: String,
    overwrite: Boolean,
    tags: (value: string) => value.split(','),
};

Choosing camel-case option names is preferable so they don’t have to be quoted in the object literal.

Run

The run method is the entry point of your plugin, called by the cli. The arguments are:

  • styleMap, the map generated by visua from the identity files of the current project,
  • options, a string to any map of the initialized options the user have provided in the cli.

Here you can get all the variables you need using the StyleMap API.

Note that options cannot be strictly required to the user and in many cases some of them won’t be available in the options object. Always check their existence with the in operator before trying to access them and try as much as you can to provide a fallback value when they’re not set.

If you think you really need an option to be set then check its existence in options and if it’s not there throw a PluginError specifying in the message that the user should set the option:

import {StyleMap, Plugin, OptionsMap, Options, PluginError} from 'visua';

export default class extends Plugin {

    static options: OptionsMap = {
        mandatoryOption: String,
    };

    run(styleMap: StyleMap, options: Options) {
        if (!('mandatoryOption' in options)) {
            throw new PluginError('Please provide `mandatoryOption`!');
        }
        // ...
    }

}

Variables

The same goes for CSS variables: the user is not required to define all of those that you plugin uses. Try to provide progressive results and when one or more missing variables are fundamental to the purpose of your plugin throw a PluginError requesting them to the user.

Middleware plugins

With the styleMap#set() method, plugins can modify/add variables to the stylemap. Changes persist during the execution (but not on the file system) and subsequent plugins will see the modified stylemap. Plugins are run serially in the order specified in the run command so the ones that provide a “middleware” functionality, modifying the stylemap, should come first.

Writing templates

If your plugin generates code files chances are you have to deal with templates. This can get pretty tricky because of possible undefined variables. The visua plugin module exports two utility template literals for this scenario: templatel and template.

They both correct template literals indentation and prevent null and undefined values from being displayed. In addition the former removes entire lines where there is at least one undefined value.

run(styleMap: StyleMap, options: Options) {
    // Suppose the first two variables are not defined
    const {varOne, varTwo, varThree} = styleMap.getAll(['var-one', 'var-two', 'var-three']);
    
    // Simple template literal
    const simpleLiteral = `
        $out-var-one: ${varOne};
        $out-var-two: ${varTwo};
        $out-var-three: ${varThree};
    `;
    //        $out-var-one: undefined;
    //        $out-var-two: undefined;
    //        $out-var-three: <var-three value>;
    
    // With templatel tag
    const tmpTag = templatel`
        $out-var-one: ${varOne};
        $out-var-two: ${varTwo};
        $out-var-three: ${varThree};
    `;
    // $out-var-three: <var-three value>;
}

Building and testing

The starter template is already set up with TypeScript. To build run:

$ npm run build

or, to run compilation in watch mode run:

$ npm run build:w

Testing

The starter template is already set up for testing with mocha, chai and ts-node. In test/index.spec.ts you can find a basic example of how to test your plugin with mock data.

To run tests launch the following command:

$ npm run test

Running plugins locally

To run a plugin locally without publishing it to npm you have two choices:

  • Install it locally by path

    From the package that depends on your plugin, install it with:

    $ npm install <path-to-plugin-folder>
    

    However this may cause problems with dependencies because it only creates a symlink to the plugin folder so the devDependencies are kept.

  • Pack and local install

    To install a production-like copy of your plugin you can use the pack npm command:

    $ # Move out from your plugin package folder to avoid creating the tarball here
    $ cd ..
    $ # Pack your plugin
    $ npm pack ./<plugin-folder>
    

    This creates a tarball in the current working directory named <plugin-name>-<version>.tgz. Now from the package where you’re installing your plugin, run:

    $ npm install [-D] <path-to-plugin-tarball>
    

Publishing

Review the following checklist before publishing your plugin to npm:

  • Naming and keywords
    Make sure the npm package of the plugin is named visua-<plugin-name> and has the visua and visua-plugin keywords (if you generated it using the CLI this should be fine).

  • Check visua versions
    Be specific about the version of visua with which your plugin is compatible: make sure the visua package is only listed in devDependencies and peerDependencies (with the same version) and not in dependencies.

  • Test it properly
    See testing.