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-builderallows 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-builderCore 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 aRootnode.childrenis an array of top-levelAttribute,Element, orCollectionnodes.hasExpressionsdefaults tofalse.at(key, title, valueNode): Creates anAttributenode.key: The JSON key (string, e.g.,"v").title: The semantic Lottie title fromTITLES(e.g.,TITLES.string.version).valueNode: The childPrimitivenode (created usingpt). Can also be an empty array[]if the attribute has no explicit value in some contexts.
el(key, title, valueNode): Creates anElementnode.key: The JSON key (string, e.g.,"meta").title: The semantic Lottie title (e.g.,TITLES.element.metadata).valueNode: The childObjectNode(created usingob). Can also be an empty array[].
ob(title, children): Creates anObjectNode.title: The semantic Lottie title for the object itself (e.g.,TITLES.object.animationSettings, orTITLES.customfor generic objects).children: An array ofMembernodes (Attribute,Element,Collection) within the object.
cl(key, title, valueNode): Creates aCollectionnode (example, consult package for exact name if different, e.g.,ctorcol).key: The JSON key (string, e.g.,"layers").title: The semantic Lottie title (e.g.,TITLES.collection.compositionChildren).valueNode: The childArrayNode(created usingar). Can also be an empty array[].
ar(title, children): Creates anArrayNode(example, consult package for exact name if different).title: The semantic Lottie title for the array (e.g.,TITLES.array.array).children: An array ofPrimitive,ObjectNode, or otherArrayNodeinstances.
pt(value, valueType): Creates aPrimitivenode.value: The primitive value (string,number,boolean,null).valueType(optional): Explicitly sets thevalueType('string','number','boolean','null'). If omitted, it's often inferred, but good practice to include for clarity.
kn(value): Creates aKeyNode(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 byat,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
Refer to the LAST Specification for details on the nodes being created.
See the Modifying Lottie Files Guide and Creating Plugins Guide for scenarios where you might use this builder.