Soft Bun JS: A Journey into Reactivity

Nima Habibkhoda
CodeX
Published in
6 min readJul 17, 2023

Soft Bun is a lightweight and framework-agnostic reactivity system implemented in TypeScript. Soft Bun allows you to define reactive dependencies within your state objects by using the unique identifier $ to indicate variables derived from other variables in the state. By leveraging Proxies, Soft Bun tracks changes to the state and automatically recalculates dependent properties, enabling dynamic updates and simplifying complex computations. With its focus on simplicity and flexibility, Soft Bun empowers JavaScript/TypeScript developers to incorporate reactivity into their projects with ease.

https://github.com/nimahkh/soft_bun

The Story Behind Soft Bun: A Journey into Reactivity

Every great project has a story behind it, and Soft Bun is no exception. The creation of this reactivity system was driven by a personal passion for baking bread and a desire to explore the world of reactivity in JavaScript and TypeScript.

The inspiration for Soft Bun came from an unlikely source: Rich Harris’s presentation on Svelte and its reactivity system. Rich Harris’s insights and demonstrations showcased the power and elegance of reactive programming, and it left a lasting impression on me.

As I started exploring the world of reactivity, I realized that many existing reactivity systems were tightly coupled with specific frameworks or libraries such as React, Vue, and Svelte. While these systems worked well within their respective ecosystems, they often lacked flexibility and were challenging to integrate into projects that didn’t align with those frameworks.

Motivated by this observation, I set out to create a reactivity system that was framework-agnostic and could be easily implemented in any JavaScript or TypeScript project. This system would empower developers to leverage the power of reactivity without being tied to a specific framework or library.

I chose to name the project “Soft Bun” as an homage to my love for baking bread. Soft bun represents a perfect blend of simplicity and delight, characteristics I sought to embody in the reactivity system I was building.

With a clear vision in mind, I began developing Soft Bun as a pure TypeScript core that could be seamlessly integrated into any project. I focused on making the code concise, yet robust, leveraging TypeScript’s type system to ensure type safety and enhance developer productivity.

Throughout the development process, I experimented with different approaches, learned from various reactivity systems, and refined the implementation. I aimed to strike a balance between simplicity and functionality, providing a lightweight yet powerful reactivity solution.

Soft Bun is now an open-source project, and I encourage developers to explore its code, share their thoughts, and contribute to its growth. I believe that by collaborating and sharing knowledge, we can improve Soft Bun and empower developers around the world to build more reactive and dynamic applications.

I am going to introduce the Soft Bun reactivity system, explain its code and ideology, and provide examples of how to use it.

Understanding Reactivity

Reactivity refers to the ability of a system to automatically track dependencies between variables and expressions and update them when their dependencies change. In traditional JavaScript, assigning a variable to another variable creates a static relationship, meaning changes to the original variable won’t affect the assigned variable. Soft Bun introduces reactivity to enable dynamic relationships between variables.

For example, let’s consider the following code snippet:

var a = 2;
var b = 1 + a;
a = 3;
console.log(b) //3

In traditional JavaScript, we would expect b to retain its initial value of 3 because it was assigned the value of 1 + a at the time of assignment. However, in a reactive system, we would expect b to automatically update to reflect the new value of a, resulting in b being 4 after a is changed to 3.

Soft Bun provides a solution to handle such reactive relationships between variables and expressions, allowing for automatic updates when dependencies change.

You can read more about Reactive Programming here.

How Soft Bun is solving the issue?

To demonstrate the usage of Soft Bun, let’s explore a couple of examples:

import SoftBun from 'soft_bun';

const data = { a: 1, b: 1, c: { d: 2, k: 4 } };
const softbun = new SoftBun(data);

softbun.reactive('c.d', '2 + $b');
softbun.reactive('c.k', '5 * $a * $b');

console.time('Task');
softbun.state.a = 3;
softbun.state.b = 4;
for (let i = 0; i < 100000; i++) {
softbun.state.b += 4;
}
for (let i = 0; i < 100000; i++) {
softbun.state.a += 4;
}
console.timeEnd('Task');
console.log(softbun.state);

In this example, we create a reactive object r based on the initial data object. We define two reactive dependencies using the reactive method: c.d depends on b, and c.k depends on a and b. The dependencies are expressed using expressions with variables prefixed by $.

We then modify the values of a and b and perform some calculations. The console.log(r.state) statement outputs the final state object, which reflects the automatic updates based on the reactive dependencies.

Example 2: Reactive Concatenation

import SoftBun from 'soft_bun';

interface IAddress {
postal: string;
city: string;
house_number: number;
}

interface IPerson {
name: string;
family: string;
address: IAddress;
age: number;
}

const data2: IPerson = {
name: 'Nima',
family: 'HKH',
age: 30,
address: {
postal: '2A',
city: 'Amsterdam',
house_number: 3,
},
};

const person = new SoftBun(data2);

person.reactive('full_name', '$name $family');
console.log(person.state);
person.state.name = 'John';
person.state.family = 'Doe';
console.log(person.state);

In this example, we define a more complex state object representing a person’s information. We create a reactive object person based on the initial data object. We then define a reactive dependency full_name, which concatenates the name and family properties using the expression $name $family.

After modifying the name and family properties, we observe the updated person.state object, which reflects the changes propagated through the reactive dependency.

Deeper in Soft Bun

The system leverages the use of a unique identifier, the $ symbol, to indicate variables that are derived from other variables within the state object.

When you call the reactive method, you pass in a key and an expression. The key represents the target variable that will be reactive, and the expression defines how that variable is computed based on other variables within the state object.

For example, let’s consider the following code snippet:

import SoftBun from 'soft_bun';

const data = { a: 1, b: 1, c: { d: 2, k: 4 } };
const r = new SoftBun(data);

r.reactive('c.d', '2 + $b');
r.reactive('c.k', '5 * $a * $b');

In this example, we have a state object data with properties a, b, and c. By calling the reactive method, we define reactive dependencies for c.d and c.k based on other variables ($b, $a, and $b, respectively) within the state object.

Soft Bun uses a Proxy object to intercept property accesses and mutations within the state object. When you access a reactive property (e.g., r.state.c.d), the get handler of the Proxy intercepts the access and returns a new Proxy object for any nested objects within the state. This allows Soft Bun to track nested reactive dependencies as well.

When you mutate a reactive property (e.g., r.state.a = 3), the set handler of the Proxy is triggered. Soft Bun updates the value of the property and then triggers an update process. It identifies all the dependent properties that were defined through the reactive method and recalculates their values based on the updated state.

Soft Bun achieves this by maintaining a data structure (reactive_keys) that keeps track of the reactive dependencies and their expressions. When a reactive property is mutated, Soft Bun identifies the dependencies associated with that property and reevaluates their expressions using the current state values. It then updates the corresponding properties accordingly.

This reevaluation and update process happens recursively, ensuring that any nested reactive dependencies are also recalculated and updated.

The combination of the unique identifier $ to indicate reactive dependencies, the use of Proxy to intercept property accesses and mutations, and the recursive update mechanism allows Soft Bun to provide a reactive system that dynamically updates dependent properties based on changes in the state.

By understanding and utilizing Soft Bun’s reactivity system, you can build applications that respond to changes in state and automatically recalculate derived values, simplifying complex computations and enabling dynamic UI updates.

Soft Bun aims to provide a flexible and framework-agnostic solution for reactivity in JavaScript and TypeScript projects, allowing developers to harness the power of reactivity without being tied to a specific framework’s implementation.

Conclusion

Soft Bun provides a simple yet powerful reactivity system for JavaScript and TypeScript projects. By allowing the definition of reactive dependencies, Soft Bun enables automatic updates and recalculations, similar to popular frameworks like React or Vue.js.

In this article, we explored the Soft Bun code, its ideology, and how to use it in your projects.

Give Soft Bun a try in your projects and experience the convenience of reactive programming firsthand. Happy coding!

https://github.com/nimahkh/soft_bun

--

--