Piecewise

Build chat bots with blocks

Plugins

Part of Piecewise is a robust plugin system that allows you to extend the editor and add custom blocks.

Note: the plugins system is implemented as part of Piecewise 22.03.0 or later. To add custom blocks in previous versions, see the legacy “adding custom blocks” documentation.

Creating a barebones plugin

Start out by creating a JavaScript file (.js) in the addons folder next to Piecewise.app, Piecewise.exe, or Piecewise. It can be named whatever you like, as long as it doesn’t conflict with an existing plugin. Starting in Piecewise 22.05.0, plugins will have domain-namespaced names, such as im.piecewise.rss.js - and it’s recommended that you follow this scheme for all new plugins.

Plugins must export a class that extends LazuritePlugin. This is done by declaring a bare function that will in turn return the class. Therefore, at the bare minimum, your plugin should look like:

(() => {
    return class MyPlugin extends LazuritePlugin {
        constructor(){
            super();

            // Your code here...
        }
    };
});

Enabling developer mode

“Developer mode” will grant you access to the Chrome Dev Tools, the ability to see how blocks in Piecewise are generated into code, and the ability to reload the page (and by extension, your plugin).

To enable developer mode, focus on the Piecewise, window, then press the UP arrow key 7 times. You’ll be prompted for a “cheat code”; just type youaremyangel and you’ll see the Debug menu at the top appear. Within the Debug menu…

  • Build (Ctrl-B or Cmd-B) will generate all of the code for all of the blocks that are in the workspace, beautify it, and then show it within a text box.
  • Toggle Developer Tools (Alt-Shift-I or Opt-Shift-I) will open or close the Chrome DevTools.
  • Reload (Ctrl-R or Cmd-R) will reload the page.

Adding blocks

Piecewise accepts block schemas that match Blockly’s JSON. To form schemas, you can use the Block Factory to quickly develop blocks using Blockly itself, or the “View source…” context menu option on any custom block within Piecewise to view examples.

You can access Blockly via LazuritePlugin.getBlockly, and once you’ve finished your schema and generator, you can use LazuritePlugin.addBlock to add it to Piecewise. Do not add it directly using Blockly functions as Piecewise does some processing on blocks that are added.

(() => {
    return class RSSPlugin extends LazuritePlugin {
        constructor(){
            super();

            let Blockly = this.getBlockly();

            this.addBlock({
                block: {
                    "type": "lazurite_rss_feed_items",
                    "message0": "%1 items of RSS feed %2",
                    "args0": [{
                            "type": "field_image",
                            "src": "https://piecewise.im/favicon.png",
                            "width": 15,
                            "height": 15,
                            "alt": "RSS",
                            "flipRtl": false
                        },
                        {
                            "type": "input_value",
                            "name": "feed",
                            "required": true,
                            "check": "lazurite_rss_feed",
                            "doc_description": "The RSS feed."
                        }
                    ],
                    "output": "Array",
                    "doc_output": "The items in the RSS feed.",
                    "colour": "#8c8c8c",
                    "tooltip": "Get the items in an RSS feed.",
                    "category": [
                        "RSS"
                    ]
                },
                generator: block => {
                    return [`(${Blockly.JavaScript.valueToCode(block, "feed", Blockly.JavaScript.ORDER_ATOMIC)}).items`, Blockly.JavaScript.ORDER_NONE];
                }
            });
        }
    };
});

Adding dependencies

Piecewise can internally use npm’s arborist library to obtain and install third-party packages from npm, GitHub repositories, source tarballs, etc. To add a dependency, use LazuritePlugin.addBotDependency. When the bot runs for the first time, the user will have the option to install requested dependencies. It is not recommended to add or overwrite dependencies for packages that ship with Piecewise, as this could create “interesting” side effects.

(() => {
    return class MyPlugin extends LazuritePlugin {
        constructor(){
            super();

            this.addBotDependency("express"); // This will install express@latest
            this.addBotDependency("express@^4"); // This will install the latest v4 semantic version of express
            this.addBotDependency("expressjs/express"); // This will install the latest master version of express directly from GitHub
            this.addBotDependency("https://registry.npmjs.org/express/-/express-4.17.3.tgz"); // This will install whatever's in the tarball
        }
    };
});

Note: dependencies will be installed once per open Piecewise process. If you change the dependencies you install, simply reloading Piecewise will not work - you’ll need to restart the whole program.

Full API documentation for LazuritePlugin

I don’t currently have a way to export this as a dedicated page, so for now, I will just lay the class definition for LazuritePlugin here. Please note that anything outside of LazuritePlugin is not API-stable and subject to change at any time.

class LazuritePlugin {
    /**
     * Import a block into Blockly. If the block has the same `type` as another block, it will be overwritten.
     * 
     * **Never** import blocks through Blockly directly, always use this method.
     * 
     * @param {object} blockDef The definition of the block, as an object.
     */
    addBlock(blockDef);

    /**
     * Get the main Blockly instance.
     * 
     * @returns {Blockly} The main Blockly instance.
     */
    getBlockly();

    /**
     * Get all of the main Blockly workspaces in this project.
     * 
     * @returns {Array<Blockly.WorkspaceSvg>} All of the main Blockly workspaces.
     */
    getWorkspaces();

    /**
     * Add a new dependency to bots. New dependencies will be installed when the bot is run. Packages should be available on `npm`, unless a URL is provided.
     * 
     * @param {string} identifier A package spec, i.e. discord.js@13 or https://packages.matrix.org/npm/olm/olm-3.2.1.tgz.
     */
    addBotDependency(identifier);

    /**
     * Get all of the declared depdenencies for this plugin.
     * 
     * @returns {Array<string>} All of the dependencies for this plugin.
     */
    getBotDependencies();

    /**
     * Get whether we are exporting this bot.
     * 
     * @returns {boolean} Whether we are currently exporting this bot.
     */
    isExporting();

    /**
     * Show a modal.
     * 
     * @param {string} html The HTML of the modal.
     * @param {boolean?} showClose Whether to show a close button with this modal.
     * @param {number?} width The width of this modal.
     */
    showModal(html, showClose = true, width = "50vw");
}

Using Webpack

Starting with Piecewise d69e51, plugins can be built and distributed using Webpack. In general, your entrypoint must have a single default export - a class that extends LazuritePlugin - and bind as a library to plugin. Here’s an example of a plugin that does this properly:

webpack.config.js

module.exports = {
    output: {
        globalObject: 'plugin',
        library: {
            name: 'plugin',
            type: 'umd',
        }
    }
};

src/index.js

export default class ExamplePlugin extends LazuritePlugin {
    constructor(){
        super();

        alert("Plugin loaded!");
    }
}

More detailed instructions, and a sample plugin, are available in the templates directory within your Piecewise installation directory.