Documentation

Get started

Mechanic* is a powerful open source framework that helps forward-looking designers move away from a manual design workflow by converting their design rules into tools.

To start working with Mechanic, you need to create a project. Run the following command in the terminal:

npm init mechanic@latest my-project

Make sure you have Node.js (version 12.20 or greater) and npm installed.

This will install the Mechanic command-line interface and prompt you with instructions on how to customize your new Mechanic project, including your very first design function. You can choose between creating a new design function based on a template or a more sophisticated example.

As a result, you will end up with a folder with your chosen project name and the following files in it:

  • README.md contains some pointers on how to run your Mechanic project
  • package.json contains all the dependencies needed to use Mechanic
  • mechanic.config.js contains the Mechanic configuration
  • functions/ is a folder that will contain all your design functions

Design functions

Design functions are JavaScript functions that generate a design asset. They are the main building block of Mechanic, and a project can have one or more design functions. Based on the content in the design functions, Mechanic creates a design tool you will be able to use in your web browser.

The generated asset can be either static or animated, simple or complex. A design function creates assets using a specific web technology and a corresponding Mechanic engine that enables it. Currently, the available options for engines are SVG, Canvas, React.js and p5.js.

Each design function definition should live in a separate folder in the functions/ folder. Each function folder will contain an index.js file with the following elements:

Main handler

This is where you write the code that produces your design, and this code will be specific to the chosen engine. A handler function always receives a single argument, and your code can produce variable designs based on the values in this object. Learn more about handler arguments.

export const handler = ({ inputs, mechanic }) => {

  //  Implementation of the design goes here

};

inputs

inputs is an object defining the variables that will be passed to your design function. Each input will create an element in the design tool UI, and users can change these inputs and run the function with updated values. Learn more about inputs.

// Specify as many inputs as you want
export const inputs = {
  input1: {
    type: "number",
    ...
  },
  input2: {
    type: "color",
    ...
  },
  ...
};

Presets

Presets give you the opportunity to set predefined sets of values for the inputs that users can select with a single click in the design tool UI. Learn more about presets.

// Specify as many presets as you want
export const presets = {
  preset: {
    input1: 2,
    input2: "#454545",
    ...
  }
};

Settings

Settings allow you to set general configuration for your function. This is where you specify the engine that your design function will be using and whether the design function produces a static or animated asset. Learn more about settings.

// General configuration for your function
export const settings = {
  engine: require("@mechanic-design/engine_name"),
  animated: false,
  optimize: true,
};

Templates

Templates are simple design functions created to show you how to use Mechanic with specific web technologies for either animated or static assets. You can use one to get to know Mechanic, or use one as a base to start your design function.

Use the npm init mechanic@latest command to create a new Mechanic project with a single design function based on a template. Once you have created your Mechanic project, you can use the npm run new command to add more design functions.

The currently available templates you can find are:

  • Canvas image: Produce a static asset using the Canvas engine.
  • Canvas video: Produce an animated asset using the Canvas engine.
  • D3 image: Produce a static asset using D3.js through the SVG engine.
  • p5.js image: Produce a static asset using the p5.js engine.
  • p5.js video: Produce an animated asset using the p5.js engine.
  • React image: Produce a static asset using the React engine.
  • React video: Produce an animated asset using the React engine.
  • SVG image: Produce a static asset using the SVG engine.
  • SVG video: Produce an animated asset using the SVG engine.
  • SVG.js image: Produce a static asset using SVG.js through the SVG engine.

Let's take a closer look at the p5.js video template, which produces an animated asset.

p5.js video

When using the p5.js engine, the handler function argument will have three properties: inputs are the values of the inputs defined below, mechanic holds API functions to record the frames of the animation, and sketch is the p5.js sketch that you can use to invoke p5.js functions. This last argument is specific to this engine.

export const handler = ({inputs, mechanic, sketch}) => {
  // Extract values of the inputs
  const { width, height, radiusPercentage } = inputs;
  const radius = ((height / 2) * radiusPercentage) / 100;
  let angle = 0;

  // The p5.js draw function is called 60 fps
  sketch.draw = () => {
    sketch.rotate(angle);
    sketch.arc(0, 0, 2 * radius, 2 * radius, -sketch.PI, 0);
    sketch.arc(0, 0, 2 * radius, 2 * radius, 0, sketch.PI);
    sketch.rotate(-angle);

The mechanic.frame() function is used to record each individual frame of the animation, while the mechanic.done() function tells Mechanic that the asset has finished rendering. You can read more about these API functions in handler arguments.

    if (angle < turns * 2 * Math.PI) {
      mechanic.frame();
      angle += (2 * Math.PI) / 100;
    } else {
      mechanic.done();
    }
  };
};

export const inputs = {
  width: {
    type: "number",
    default: 400,
  },
  height: {
    type: "number",
    default: 300,
  },
  radiusPercentage: {
    type: "number",
    default: 40,
    min: 0,
    max: 100,
    slider: true,
  },
  // ...
};

export const presets = {
  medium: {
    width: 800,
    height: 600,
  },
  // ...
};

The settings define that this design function uses the p5.js engine via the @mechanic-design/engine-p5 package. Also, since this asset is animated and uses the frame() method, this is specified in the settings too.

export const settings = {
  engine: require("@mechanic-design/engine-p5"),
  animated: true,
};

Examples

Examples are more complex design functions created to show you how to use Mechanic to tackle some more advanced use cases. Currently, Mechanic has three examples, and more may be added along the way.

Use the npm init mechanic@latest command to create a new Mechanic project with a single design function based on an example. Once you have created your Mechanic project, you can use the npm run new command to add more design functions.

  • Business card generator

    This is an example of how Mechanic can be used to create a custom business card generator. Using the react engine, this design function creates personalized business cards with a random decoration. It enables the user to customize the personal information to show and certain visual properties like scale and color.

  • Instagram story generator

    This is an example of how Mechanic can be used to create a Instagram story generator. Using the react engine, this design function creates randomized animations that can be later exported to post on social media. It enables the user to customize parameters related to the randomized aspect of the animations, as well as colors and it's duration.

  • Poster generator

    This is an example of how Mechanic can be used to create a custom poster generator. Using the p5.js engine, this design function creates random event posters and enables the user to customize the text and image content. It also shows how to load custom webfonts to generate these posters. You will find this example embedded on the front page of this website.

Add a function

You can always add more design functions to a Mechanic project. To do so, run the following command in the terminal in your mechanic project folder. This will create a new folder in your Mechanic project with an index.js file based on a template or an example.

npm run new

Alternatively, you can create the corresponding folders and files manually to add a new design function. Make sure you follow a similar folder structure as the one mentioned and that you add the necessary dependencies to you project to make it work.

Add an input

inputs is an object defining the variables that will be passed to your design function. Each input will create an element in the design tool UI, and users can change their values using these inputs and run the function with updated values.

There are many types of inputs and each of them have different settings that change their behavior. Once defined, the input will be accessible in the handler function via the name provided in the inputs object.

Definition
const inputs = {
  name: {
    type: "text",
    default: "my name",
  },
  year: {
    type: "number",
    default: 2022,
  }
};
Use in handler
export const handler = ({ inputs, mechanic }) => {
  const { name, year } = inputs;
  // Use "name" and "year" in your code...
};

The type property is the minimum required property you need to define an input. It's a string value that determines the kind of input that you need, the type of object that the handler will receive as a value and how it looks in the UI. Mechanic provides out of the box a bunch input types ("number", "text", "color", "boolean", and "image"), but you can also create your own.

By default all inputs are debounced with a delay of 100 milliseconds. This behavior can be adjusted using debounceDelay and debounceInputs in the design function’s settings.

Certain input types have more properties to customize them, and some of them may be required as part of their definition. The following are the most common input properties available:

  • default: value that acts as a fallback for input value if not yet set by the user. This property is available for most types of inputs, except for "image".
  • validation: a function that receives the current value assigned to the input and outputs an error (as a string) when the value entered doesn't pass a certain condition. When the given function returns an error string, the error is shown in the UI. If the value passes conditions (no error) a null value is expected.
  • editable: either a boolean value or a function that receives all current values assigned to the defined inputs and determines if the input can be edited or not. If a function is set, a boolean value is expected as an output for that function.
  • label: string value to use as label for input in UI input. If not set, by default the input name is used as the label for the UI.
  • Text input

    The text input lets you define a variable for text values. This will be translated in a UI input that accepts text and the function handler will receive a string value.

    Text input accepts the following properties:

    • default: string fallback value for input. This property is required for the text input definition.
    • validation: previously described validation function. This property isn't required.
    • editable: previously described editable value or function. This property isn't required.
    • label: previously described label. This property isn't required.
    • options: array of string values or object with string values. This will turn the input into a select input where the value can be selected from the set of given options

    Here are some examples:

    myText: { 
     type: "text", 
     default: "hello", 
    }
    myText: { 
     type: "text", 
     default: "goodbye", 
     label: "Label", 
     editable: false  
    }
    myText: { 
     type: "text", 
     default: "hello", 
     options: ["hello", "goodbye"] 
    }
    myText: { 
     type: "text", 
     default: "hello", 
     options: {
       hey: "hello",
       bye: "goodbye" 
     } 
    }
  • Number input

    The number input lets you define a variable for numeric values. This will be translated into a UI input that accepts numbers and the function handler will receive a number value.

    Number input accepts the following properties:

    • default: number fallback value for input. This property is required for the number input definition.
    • validation: previously described validation function. This property isn't required.
    • editable: previously described editable value or function. This property isn't required.
    • label: previously described label. This property isn't required.
    • options: array of number values or object with number values. This will turn the input into a select input where the value can be selected from the set of given options
    • min: minimum value that number input value can take.
    • max: maximum value that number input value can take.
    • step: granularity to which values will change through arrow keys.
    • slider: boolean value that if true, will turn the input into a slider. Should be used with the min, max and step.

    Here are some examples:

    myNumber: { 
     type: "number", 
     default: 10, 
    }
    myNumber: { 
     type: "number", 
     default: 20, 
     label: "Label", 
     editable: false  
    }
    myNumber: { 
     type: "number", 
     default: 10, 
     options: [10, 20] 
    }
    myNumber: { 
     type: "number", 
     default: 10, 
     options: {
       ten: 10,
       twenty: 20 
     } 
    }
    myNumber: { 
     type: "number", 
     default: 10, 
     min: 5, 
     max: 15, 
     step: 1 
    }
    myNumber: { 
     type: "number", 
     default: 10, 
     min: 5, 
     max: 15, 
     step: 1,
     slider: true 
    }
  • Color input

    The color input lets you define a variable for color values. This will be translated into a color selector UI input and the handler will receive a string value that represents a color. By default, the color model expected is rbga but it can be changed with a property to hex.

    Color input accepts the following properties:

    • default: color string fallback value for input. This property is required for the color input definition.
    • validation: previously described validation function. This property isn't required.
    • editable: previously described editable value or function. This property isn't required.
    • label: previously described label. This property isn't required.
    • options: array of color string values or object with color string values. This will turn the input into a select input where the value can be selected from the set of given options. This property isn't required.
    • model: string value that determines the color model to use. Can either be "true" or "rgba". This property isn't required, but it will take value "rgba" by default.

    Here are some examples:

    myColor: { 
     type: "color", 
     default: "#ff00ff",
     model: "hex"
    }
    myColor: { 
     type: "color", 
     default: "#0000ff", 
     label: "Label", 
     editable: false  
    }
    myColor: { 
     type: "color", 
     default: "#ff00ff", 
     options: ["#ff00ff", "#0000ff"] 
    }
    myColor: { 
     type: "color", 
     default: "#ff00ff", 
     options: {
       magenta: "#ff00ff",
       blue: "#0000ff" 
     } 
    }
    myColor: { 
     type: "color", 
     default: "rgba(255,0,255,1)", 
     model: "rgba" 
    }
  • Boolean input

    The boolean input lets you define a variable for a boolean value (either true or false). This input will be translated into a toggle button element that changes the value, and the handler will get the corresponding boolean value.

    Boolean input accepts the following properties:

    • default: boolean fallback value for input. This property is required for the boolean input definition.
    • validation: previously described validation function. This property isn't required.
    • editable: previously described editable value or function. This property isn't required.
    • label: previously described label. This property isn't required.

    Here are some examples:

    myBoolean: { 
     type: "boolean", 
     default: true, 
    }
    myBoolean: { 
     type: "boolean", 
     default: false, 
     label: "Label", 
     editable: false  
    }
  • Image input

    The image input lets you upload an image. The UI will show an input that can be clicked to upload an image from your computer. The multiple property allows for upload of multiple images in the same input. The handler function will receive a File object or a FileList object depending on the number of images.

    Image input accepts the following properties:

    • validation: previously described validation function. This property isn't required.
    • editable: previously described editable value or function. This property isn't required.
    • label: previously described label. This property isn't required.
    • multiple: boolean value that if true allows for multiple multiple files being loaded. This property isn't required, but it defaults to false.

    Here are some examples:

    myImage: { 
     type: "image" 
    }
    myImage: { 
     type: "image", 
     label: "Label", 
     editable: false  
    }
    myImage: { 
     type: "image", 
     multiple: true  
    }

Add a preset

Presets give you the opportunity to set predefined sets of values for the inputs that users can select with a single click in the design tool UI. You can have multiple presets per function, which allow the user to quickly select a number of values in a single action.

export const presets = {
  presetName1: {
    input1: value,
    input2: value,
    // ...
  },
  presetName2: {
    input1: value,
    input2: value,
    // ...
  },
};

Handler arguments

The handler function of a design function always receives a single argument with at least two properties that are key to the function's rendering: inputs and mechanic. Depending on the engine, the handler function may receive more properties in the object argument. Read more about the individual engines.

  • inputsThe inputs property will contain all values selected through the UI interface for each defined input. Your design function can use these values to change what is being rendered.
  • mechanicThe mechanic property serves as a proxy to Mechanic’s core functionality, wrapping two important methods: mechanic.done() and mechanic.frame(). The done() method tells Mechanic that the code to generate an asset has finished. This function must be called at some point in the handler's code for both static and animated assets. For animated assets, the frame() method adds separate frames of the asset and is expected to be called before the call to done().

Select an engine

Engines are JavaScript functions that enable certain web technologies with Mechanic’s environment. Currently there are four official engines published. It is unlikely you will need to write an engine from scratch, but the offical engines should be a good guide in case you want to enable a non-supported JavaScript library to work with Mechanic.

Canvas

This engine enables the use of the Canvas API in the Mechanic framework. While using this engine, both the frame() and the done() method expect to be called with a single argument: the canvas object that the design function is using to render the asset.

Definition
const settings = {
  engine: require("@mechanic-design/engine-canvas"),
};
Use in handler
export const handler = ({ inputs, mechanic }) => {
  const { width, height, color } = inputs;
  const canvas = document.createElement("canvas");
  canvas.width = width;
  canvas.height = height;
  const ctx = canvas.getContext("2d");
  ctx.fillStyle = color;
  // ...
  mechanic.done(canvas)
};

SVG

This engine enables the use of SVG strings in the Mechanic framework. While using this engine, both methods of the mechanic handler parameter are expected to be called with a single argument: the string value of the SVG image.

Definition
const settings = {
  engine: require("@mechanic-design/engine-svg"),
};
Use in handler
export const handler = ({ inputs, mechanic }) => {
  const { width, height, color } = inputs;
  const svg = `<svg width="${width}" height="${height}">
      <rect fill="${color}" width="${width}" height="${height}" />
    </svg>`

  mechanic.done(svg);
};

p5.js

This engine enables the use of p5.js in the Mechanic framework. Handlers that use this engine also receive a sketch parameter which allows the use of p5.js in instance mode to invoke all p5.js functions.

Definition
const settings = {
  engine: require("@mechanic-design/engine-p5"),
};
Use in handler
export const handler = ({ inputs, mechanic, sketch }) => {
  const { width, height, color } = inputs;
  sketch.setup = () => {
    sketch.createCanvas(width, height);
  };

  sketch.draw = () => {
    sketch.background(color);
    mechanic.done();
  };
};

React

This engine enables the use of the React SVG components in the Mechanic framework.

Definition
const settings = {
  engine: require("@mechanic-design/engine-react"),
};
Use in handler
export const handler = ({ inputs, mechanic }) => {
  const { width, height, color } = inputs;
  useEffect(() => {
    mechanic.done();
  }, []);

  return (
    <svg width={width} height={height}>
      <rect fill={color} width={width} height={height} />
    </svg>
  );
};

Settings

Settings allow you to set general configuration for your design function. The following are the possible properties that are available to be set.

  • engine: sets the engine for the design function. This property always has to be set.
  • name: Set the display name for the design function in the UI.
  • animated: determines whether the design function is an animated sequence or a static image. Defaults to false.
  • persistRandomOnExport: if true, enables a seeded random that forces the export to generate the same output as the last preview. Defaults to true.
  • optimize: optimizes SVG outputs using SVGO. If an object is received, it is merged with the default SVGO options and passed to the optimize function. Defaults to true.
  • hideFeedback: When true, Mechanic's feedback button is hidden. Defaults to false.
  • hideNavigation: When true, the navigation input that lets users select a design function is hidden. Defaults to false.
  • hidePresets: When true, preset selection input is hidden. Defaults to false.
  • hideScaleToFit: When true, Scale to Fit toggle is hidden. Defaults to false.
  • initialScaleToFit: When false, Scale to Fit will be off initially. Defaults to true.
  • hideAutoRefresh: When true, Auto Refresh toggle is hidden. Defaults to false.
  • initialAutoRefresh: When false, Auto Refresh will be off initially. Defaults to true.
  • hideGenerate: When true, Generate button is hidden. Defaults to false.
  • showMultipleExports: When false, single export button is shown. When true, two separate export buttons are shown: one for PNG export and another for SVG export. Defaults to false.
  • ignoreStyles: When false, CSS in iframe is injected into design function's SVG output. Defaults to false.
  • debounceInputs: Debouncing is a technique that’s optimizing render performance by limiting the amount of design function re-renders. A design function will only re-render when no input has changed for the specified delay. When false, debouncing on the inputs is disabled. This means that any update to an input’s value will re-render the function immediately. For complex design function this can negatively impact render performance. Defaults to true.
  • debounceDelay: When using debounce on the inputs this value can be used to set the debounce delay in milliseconds. A design function will only re-render if there hasn't been any new input for the specified delay. A higher value will reduce the amount of re-renders, but will also make the UI feel less responsive. Keeping the value as low as possible and only increasing it for computationally complex design functions is recommended. Defaults to 100.

Advanced use

We've been slowly developing ways for you to customize the Mechanic interface. Apart from the settings listed above, the following are other ways that let's you define your own inputs and modify how the Mechanic UI looks.

Static folder

A static/ assets folder can be created in the root directory of a Mechanic project that will be copied over to be served along with the Mechanic app, allowing you to reference assets from that folder directly.

function-1/index.js

export const handler = ({ inputs, mechanic }) => {
  const { width, height } = inputs;

  useEffect(() => {
    mechanic.done();
  }, []);

  return (
    <svg width={width} height={height}>

For instance, if an image gets stored in the static folder, static/image.png, then this becomes an available asset to be used in a design function.


      <image href="static/image.png" width={width} height={height} />
    </svg>
  );
};

export const inputs = {
  width: {
    type: "number",
    default: 400,
  },
  height: {
    type: "number",
    default: 300,
  }
};

export const settings = {
  engine: require("@mechanic-design/engine-react"),
};

Add a custom input

Mechanic allows you to define custom inputs that are not part of the predefined set of ready to use inputs. Custom inputs are defined in a special folder inputs/ at the root of a Mechanic project. Each custom input needs to have its own folder which contains an index.js file that exports all expected input definitions.

Creating a custom input is a bit more complex, as you need to define required properties that someone can set when using the input, and determine what the rendered component looks like. Consider the following example:

function/index.js

export const handler = ({ inputs, mechanic }) => {
  const { width, height, center } = inputs;

  useEffect(() => {
    mechanic.done();
  }, []);

  return (
    <svg width={width} height={height}>

Let's say we wish to have a single input type able to hold both coordinates of a point.

      <circle cx={center.x} cy={center.y} width={10} height={10} />
    </svg>
  );
};
export const inputs = {
  width: {
    type: "number",
    default: 400,
  },
  height: {
    type: "number",
    default: 300,
  },
As all inputs, we declare it as part of the function's inputs. The type of it could be called "coordinate", and have an object default value.

  center: {
    type: "coordinate",
    default: {x: 100, y: 100}
  }
};

export const settings = {
  engine: require("@mechanic-design/engine-react"),
};

To create that new type of input, you'll need to define it in the inputs/ folder of the project:

inputs/customInput/index.js

import React, { useState } from "react";

The typeName export specifies the name of your custom property. If not set, then the name will default to the name of the folder. In this case "customInput".

export const typeName = "coordinate";

The properties export is where you can define possible properties that a definition for the custom input can adjust, like the min and max properties for the "number" input. Here you may define a validation function per property that makes sure the used values for the property are appropriate. In this case, the "default" property should be an object with a x and y keys. The function returns a string error if the value isn't valid, and null if it does.

 export const properties = {
  default: {
    validation: (value) => {
      if (typeof value !== "object")
        return "Property "default" should be object, not undefined";
      if (!Object.values(value).every((v) => typeof v === "number"))
        return "Property "default" object should only contain numbers. Found value that's not number.";
      if (!value.hasOwnProperty("x") || !value.hasOwnProperty("y"))
        return "Property "default" object should contain "x" and "y" keys. One is missing.";
      return null;
    },
  },
};

Through the requiredProperties export you can label some properties as required which means that the custom input will need to have that property assigned in it's definition. If not, an error will be thrown.

 export const requiredProperties = ["default"];

The initValue export is a function that returns the which value the custom input will take when first shown. The function receives input which is the function definition of the corresponding input made in the design function.

export const initValue = (input) => ({
  x: input.default.x,
  y: input.default.y
});



The prepareValue export is another function that gets called each time before the design function gets called. It receives the current value that the UI has recorded for the input, and the return value is the value that the design function actually gets. It could be used to change the type of value being stored. The function also receives the input definition which may be useful to create fallback values.

export const prepareValue = (value, input) => {
  return !value ? { x: input.default.x, y: input.default.y } : value;
};

The Input export is where you specify how you want your custom input to look and behave. It's expected to be a React component that will get rendered as the other inputs in the sidebar.

It receives three props. name contains the name given to the input by the user ("center" in the example). That prop allows the component to get the current value of the input from the values prop, which contains all current values of the design function's inputs. Finally onChange is a function that should be called whenever the value of an input should be updated, and will trigger an update of the Mechanic app and design function.

export const Input = ({ name, values, onChange }) => {
  const value = values[name];
  return (
    <div>
      {name}: {value.x}, {value.y}
      <input
        type="number"
        value={value.x}
        onChange={(e) =>
          onChange(e, name, { ...value, x: parseFloat(e.target.value) })
        }
      />
      <input
        type="number"
        value={value.y}
        onChange={(e) =>
          onChange(e, name, { ...value, y: parseFloat(e.target.value) })
        }
      />
    </div>
  );
};

Finally the eventHandlers export allows us to subscribe handlers to JS DOM events in the canvas of the design function. Allowing yet another way of interacting and changing an inputs value.

Each property key added should be an HTML event name that can be subscribed to. And the property value is the event handler that will get subscribed to the corresponding event type. The returned value will update the value of the corresponding input and therefore in the design function. This interface is still a bit limiting, but we are working on improving it!

export const eventHandlers = {
  mousedown: (event) => {
    return { x: event.clientX, y: event.clientY };
  },
};

Custom sidebar

Apart from all the hiding ui elements settings, you can further customize the sidebar UI. Currently, you can override the wrapper element that contains all elements that eventually appear in the sidebar, and add extra elements at the bottom of the sidebar where the export and other buttons usually go.

To override the wrapper element that contains all elements rendered in the sidebar, you can create an app/ folder in the root of your Mechanic project and export from an index.js file a SideBar React component. This will affect the side bars of all design functions in the project.

app/index.js

import React from "react";
export const SideBar = ({ children }) => {
  return (
In this example, the wrapper element will get switch by one that includes a title at the top.
    <aside>
      <h1>This is a custom sidebar!</h1>
Remember to use the children prop so that the content of the sidebar (inputs and buttons) actually get rendered!
      {children}

    </aside>
  );
};

To add extra elements at the bottom of the sidebar, export a ExtraUI React component from a design function's index.js file. Whatever gets rendered by this component, will be shown first in the bottom section where the Scale to fit and Generate buttons are. Doing this will only affect the side bar of the corresponding design functions.

functions/my-function/index.js

import React from "react";
The component will receive two props: values and onChange. The first one is an object with all current values of inputs that also get passed to the design function as inputs. The second one is a function handler that allows changing the value of a specific input.
export const ExtraUI = (props) => {
  const { values, onChange } = props;

  const clickHandler = (e) => {
    onChange(e, 'columns', Math.floor(Math.random() * 8));
  };

  return (
In this example, a button gets rendered that when clicked changes the value of an input called "columns" to a random value between 0 and 7. This will trigger an update in the Mechanic app and design function.
    <button onClick={clickHandler}>
      Randomize columns!
    </button>
  );
};

Preloading

Design functions can often rely on external resources such as images or webfonts. As these can be quite heavy in file size they can slow down the rendering of a design function. To avoid this, Mechanic provides a set of helper functions you can use to preload external assets. Loading images and fonts through these functions makes sure every resource is only loaded once per design function and then cached for quick access on subsequent function runs.

Mechanic provides two preloading functions preload and preloadFont as well as a more generic memo function. All functions can be imported directly from the @mechanic-design/core module.

preload

import { preload } from "@mechanic-design/core";

The preload function allows you to preload any resource. It's using the native fetch API to asynchronously load and return a base64 string of the loaded resource, which in most cases should be ready for use.

You should use the preload function whenever you cannot know the exact resource to load at build time. So for example if the resource needs to be fetched via the network or if it depends on user input.

const myImage = await preload('https://example.com/image.png');

// SVG Example
mechanic.done(`
  <svg>
    <img href={myImage} />
  </svg>
`);

preloadFont

import { preloadFont } from "@mechanic-design/core";

preloadFont is a specialized font preloader. It fetches a font file over the network and then installs it into the design function's document as a webfont. It returns the name of the webfont to be used wherever you can specify a font-family property.

const font = await preloadFont('https://example.com/font.woff2');

// Canvas Example
ctx.font = `24px ${font}`;

You should use preloadFontwhenever you need to work with a webfont in a CSS-like way.

memo

import { memo } from "@mechanic-design/core";

Lastly there is the memo util. It works similar to the useMemo hook in React. With this you can create your own preloader that allow you to cache anything. For example the result of an expensive calculation or an already parsed API response.

const apiRespone = await memo(async () => {
  // This is a potentially slow call to an external API.
  // The way this is set up will only re-run the API call
  // the id we pass in changes.
  return await getIdFromAPI(id);
}, [id]);

memo takes two arguments. The function that returns the value you want to cache and an array of dependencies. The dependencies are used to determine if the cached value should be used or if the function should be re-run. The above example will always re-run the API call if it gets called with an ID it hasn't seen before. If it gets called with a known ID it will just return the cached value.

You should use memowhenever you want to make sure a computation only runs once per design function.

Finally

If you want to dive even deeper into Mechanic we suggest checking out the GitHub page.

We are very interested in hearing feedback from you, so please let us know what you think.