Command Palette

Search for a command to run...

LAST Builder - Relottie API

Discover the last-builder package with utility functions to easily create Lottie Abstract Syntax Tree (LAST) nodes for Lottie generation or transformation.

API Reference: @lottiefiles/last-builder

The @lottiefiles/last-builder package provides a set of composable utility functions designed to simplify the process of programmatically creating LAST (Lottie Abstract Syntax Tree) nodes and structures.

This is particularly useful when:

  • Generating Lottie JSON files from scratch.

  • Performing complex transformations within plugins that require adding or replacing nodes in the LAST tree.

  • Writing Unit Tests: When testing relottie plugins, last-builder allows you to easily construct specific AST snippets or entire trees to use as input for your plugin's transformer function, isolating its logic without needing full Lottie JSON files.

Installation

Install the package alongside @lottiefiles/last or @lottiefiles/relottie:

npm install @lottiefiles/last-builder
# or
yarn add @lottiefiles/last-builder

Core Concept

Instead of manually constructing complex LAST node objects with all their required properties (type, title, key, children, etc.), last-builder offers short, named functions that create these nodes for you. You compose these functions together to build up the desired tree structure.

Builder Functions

The source documentation highlights the following core builder functions through its example:

  • rt(children, hasExpressions = false): Creates a Root node. children is an array of top-level Attribute, Element, or Collection nodes. hasExpressions defaults to false.

  • at(key, title, valueNode): Creates an Attribute node.

    • key: The JSON key (string, e.g., "v").

    • title: The semantic Lottie title from TITLES (e.g., TITLES.string.version).

    • valueNode: The child Primitive node (created using pt). Can also be an empty array [] if the attribute has no explicit value in some contexts.

  • el(key, title, valueNode): Creates an Element node.

    • key: The JSON key (string, e.g., "meta").

    • title: The semantic Lottie title (e.g., TITLES.element.metadata).

    • valueNode: The child ObjectNode (created using ob). Can also be an empty array [].

  • ob(title, children): Creates an ObjectNode.

    • title: The semantic Lottie title for the object itself (e.g., TITLES.object.animationSettings, or TITLES.custom for generic objects).

    • children: An array of Member nodes (Attribute, Element, Collection) within the object.

  • cl(key, title, valueNode): Creates a Collection node (example, consult package for exact name if different, e.g., ct or col).

    • key: The JSON key (string, e.g., "layers").

    • title: The semantic Lottie title (e.g., TITLES.collection.compositionChildren).

    • valueNode: The child ArrayNode (created using ar). Can also be an empty array [].

  • ar(title, children): Creates an ArrayNode (example, consult package for exact name if different).

    • title: The semantic Lottie title for the array (e.g., TITLES.array.array).

    • children: An array of Primitive, ObjectNode, or other ArrayNode instances.

  • pt(value, valueType): Creates a Primitive node.

    • value: The primitive value (string, number, boolean, null).

    • valueType (optional): Explicitly sets the valueType ('string', 'number', 'boolean', 'null'). If omitted, it's often inferred, but good practice to include for clarity.

  • kn(value): Creates a KeyNode (example, consult package for exact name if different). Used if you need to represent the key itself as a separate node object, though string keys are often accepted by at, el, cl.

(Note: The exact names for all builder functions, like for Collection (cl), ArrayNode (ar), or KeyNode (kn), should be verified against the @lottiefiles/last-builder package's exports if not explicitly shown in examples. The TITLES object should be imported from @lottiefiles/last to provide the correct semantic titles.)

Usage Example

This example demonstrates how to build a simple LAST tree representing basic Lottie metadata using the builder functions:

import { at, el, ob, pt, rt } from "@lottiefiles/last-builder";
import { TITLES } from "@lottiefiles/last"; // Import TITLES for semantic meaning
import type { Root } from "@lottiefiles/last"; // For type checking

// Build the LAST tree
const tree: Root = rt([
  // Create Root, default hasExpressions: false
  // Add version Attribute
  at("v", TITLES.string.version, pt("5.0.0", TITLES.string.version.startsWith("version") ? "string" : undefined)), // Example of providing valueType

  // Add metadata Element
  el(
    "meta", // key
    TITLES.element.metadata, // title
    // Create the child ObjectNode for the meta element
    ob(TITLES.object.custom, [
      // title for the ObjectNode (using 'custom' for a generic object container)
      // Attributes within the meta object
      at("a", TITLES.string.author, pt("LottieFiles")), // title from TITLES.string
      at("d", TITLES.string.description, pt("A lottie animation.")),
      at("g", TITLES.string.generator, pt("last-builder example")),
      at("data", TITLES.custom, pt(2)), // Example custom numeric data, using TITLES.custom for its title
    ])
  ),
]);

// Output the resulting LAST structure (for inspection)
// console.log(JSON.stringify(tree, null, 2));

This generated tree object can then be used within a relottie plugin (e.g., as input to a stringifier or as a tree to be modified) or stringified using @lottiefiles/relottie-stringify.

Using last-builder for Unit Testing Plugins

When writing unit tests for your relottie plugins, last-builder is invaluable for creating precise AST inputs without needing to write full Lottie JSON strings and parse them.

Suppose you have a plugin that modifies the framerate of a Lottie animation:

// --- your-plugin.ts (simplified example) ---
import type { Plugin, Transformer } from "unified";
import type { Root, Attribute, Primitive } from "@lottiefiles/last";
import { TITLES } from "@lottiefiles/last";
import { visit, EXIT } from "unist-util-visit";

export const setFrameratePlugin: Plugin<[{ fr: number }], Root> = (options) => {
  const transformer: Transformer<Root, Root> = (tree) => {
    visit(tree, "attribute", (node: Attribute) => {
      if (node.title === TITLES.number.framerate) {
        const valNode = node.children[0] as Primitive;
        if (valNode) valNode.value = options!.fr;
        return EXIT;
      }
    });
  };
  return transformer;
};
// --- end of your-plugin.ts ---

// --- your-plugin.test.ts ---
import { rt, at, pt, ob, el } from "@lottiefiles/last-builder";
import { TITLES } from "@lottiefiles/last";
import type { Root } from "@lottiefiles/last";
import { setFrameratePlugin } from "./your-plugin"; // Adjust path

// Mock or minimal unified processor for testing the transformer directly
import { unified } from "unified";

describe("setFrameratePlugin", () => {
  it("should update the framerate value", () => {
    // 1. Build a minimal AST with a framerate attribute using last-builder
    const initialTree: Root = rt([
      at(TITLES.number.framerate.slice(0, 2), TITLES.number.framerate, pt(30)), // key 'fr', title 'framerate', value 30
    ]);

    // 2. Create a unified processor and use the plugin with options
    const processor = unified().use(setFrameratePlugin, { fr: 60 });

    // 3. Run the transformer part of the plugin directly on the tree
    // For a full pipeline test, you might use processor.runSync(initialTree) or similar
    // but here we focus on the transformer's effect.
    // Note: unified().run() or .process() expects VFile or string, not direct tree for full flow.
    // To test transformer directly, you can extract it or use a simple runner.
    // For simplicity, we assume direct runnable access or a mock setup here.
    // A more robust way is to get the transformer function and call it:
    // const transformer = setFrameratePlugin({ fr: 60 });
    // if (typeof transformer === 'function') { // Check if it's a direct transformer or a plugin returning one
    //   transformer(initialTree, new VFile()); // VFile might be needed depending on plugin
    // }
    // Or let unified run it (more integrated test):
    const processedTree = processor.runSync(initialTree);

    // 4. Assert the change
    // We need to find the framerate attribute in the processedTree
    let newFramerateValue: number | undefined;
    visit(processedTree, "attribute", (node: Attribute) => {
      if (node.title === TITLES.number.framerate) {
        newFramerateValue = (node.children[0] as Primitive)?.value as number;
        return EXIT;
      }
    });

    expect(newFramerateValue).toBe(60);
  });
});

This approach allows for focused testing of a plugin's tree manipulation logic.

Note on position Property

The last-builder functions are primarily concerned with constructing the logical structure of the AST (nodes, types, titles, children, values). They do not automatically add the position property (which details a node's location in a source file) to the nodes they create.

The position property is typically added by a parser (like @lottiefiles/relottie-parse) when it converts a Lottie JSON string into a LAST tree, as it has access to the source text and can calculate line, column, and offset information.

If you need position information on nodes created with last-builder (e.g., for highly specific testing scenarios or if generating a tree that should mimic a parsed one), you would have to manually construct the Position and Point objects (as defined by unist and exported by @lottiefiles/last) and assign them to the position field of your programmatically created nodes. This is generally not a primary use case for the builders themselves.

Next Steps

Last updated: April 10, 2026 at 9:12 AMEdit this page